diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d182a4f2..bf9a2f29 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,3 @@ - import org.apache.commons.io.output.ByteArrayOutputStream import java.io.FileInputStream import java.nio.charset.Charset @@ -20,9 +19,7 @@ private val gitCommitsCount: Int by lazy { } kotlin { - compilerOptions { - jvmToolchain(libs.versions.jdk.get().toInt()) - } + jvmToolchain(libs.versions.jdk.get().toInt()) } android { @@ -75,12 +72,14 @@ android { release { isDebuggable = false isMinifyEnabled = true + isShrinkResources = true signingConfig = if (signingConfigs.findByName("release") != null) signingConfigs.getByName("release") else null proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } debug { isDebuggable = true isMinifyEnabled = false + isShrinkResources = false applicationIdSuffix = ".debug" } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18e760f6..97eac933 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,10 @@ + + \ No newline at end of file diff --git a/app/src/main/kotlin/org/michaelbel/template/MainActivity.kt b/app/src/main/kotlin/org/michaelbel/template/MainActivity.kt index 7c91ef8f..b2d20c8d 100644 --- a/app/src/main/kotlin/org/michaelbel/template/MainActivity.kt +++ b/app/src/main/kotlin/org/michaelbel/template/MainActivity.kt @@ -1,12 +1,10 @@ package org.michaelbel.template import android.os.Bundle -import android.util.Log import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import org.michaelbel.core.ktx.versionCode class MainActivity: AppCompatActivity() { @@ -17,6 +15,5 @@ class MainActivity: AppCompatActivity() { setContent { MainActivityContent() } - Log.e("2", "versionCode=$versionCode") } } \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index b91077b8..bfdc14a0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -15,16 +15,24 @@ kotlin { android { namespace = "org.michaelbel.template.core" // Replace with your own namespace + compileSdk = libs.versions.compile.sdk.get().toInt() defaultConfig { - compileSdk = libs.versions.compile.sdk.get().toInt() minSdk = libs.versions.min.sdk.get().toInt() } buildFeatures { + resValues = true + shaders = false + aidl = false + renderScript = false buildConfig = true compose = true } + + kotlinOptions { + allWarningsAsErrors = false + } } dependencies { diff --git a/core/src/main/kotlin/org/michaelbel/core/placeholder/Placeholder.kt b/core/src/main/kotlin/org/michaelbel/core/placeholder/Placeholder.kt new file mode 100644 index 00000000..074a8286 --- /dev/null +++ b/core/src/main/kotlin/org/michaelbel/core/placeholder/Placeholder.kt @@ -0,0 +1,245 @@ +package org.michaelbel.core.placeholder + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.node.Ref +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.LayoutDirection + +/** + * Contains default values used by [Modifier.placeholder] and [PlaceholderHighlight]. + */ +internal object PlaceholderDefaults { + /** + * The default [InfiniteRepeatableSpec] to use for [fade]. + */ + val fadeAnimationSpec: InfiniteRepeatableSpec by lazy { + infiniteRepeatable( + animation = tween(delayMillis = 200, durationMillis = 600), + repeatMode = RepeatMode.Reverse, + ) + } + + /** + * The default [InfiniteRepeatableSpec] to use for [shimmer]. + */ + val shimmerAnimationSpec: InfiniteRepeatableSpec by lazy { + infiniteRepeatable( + animation = tween(durationMillis = 1700, delayMillis = 200), + repeatMode = RepeatMode.Restart + ) + } +} + +/** + * Draws some skeleton UI which is typically used whilst content is 'loading'. + * + * A version of this modifier which uses appropriate values for Material themed apps is available + * in the 'Placeholder Material' library. + * + * You can provide a [PlaceholderHighlight] which runs an highlight animation on the placeholder. + * The [shimmer] and [fade] implementations are provided for easy usage. + * + * A cross-fade transition will be applied to the content and placeholder UI when the [visible] + * value changes. The transition can be customized via the [contentFadeTransitionSpec] and + * [placeholderFadeTransitionSpec] parameters. + * + * You can find more information on the pattern at the Material Theming + * [Placeholder UI](https://material.io/design/communication/launch-screen.html#placeholder-ui) + * guidelines. + * + * @param visible whether the placeholder should be visible or not. + * @param color the color used to draw the placeholder UI. + * @param shape desired shape of the placeholder. Defaults to [RectangleShape]. + * @param highlight optional highlight animation. + * @param placeholderFadeTransitionSpec The transition spec to use when fading the placeholder + * on/off screen. The boolean parameter defined for the transition is [visible]. + * @param contentFadeTransitionSpec The transition spec to use when fading the content + * on/off screen. The boolean parameter defined for the transition is [visible]. + */ +fun Modifier.placeholder( + visible: Boolean, + color: Color, + shape: Shape = RectangleShape, + highlight: PlaceholderHighlight? = null, + placeholderFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, + contentFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() } +): Modifier = composed( + inspectorInfo = debugInspectorInfo { + name = "placeholder" + value = visible + properties["visible"] = visible + properties["color"] = color + properties["highlight"] = highlight + properties["shape"] = shape + } +) { + // Values used for caching purposes + val lastSize = remember { Ref() } + val lastLayoutDirection = remember { Ref() } + val lastOutline = remember { Ref() } + + // The current highlight animation progress + var highlightProgress: Float by remember { mutableFloatStateOf(0F) } + + // This is our crossfade transition + val transitionState = remember { MutableTransitionState(visible) }.apply { + targetState = visible + } + val transition = updateTransition(transitionState, "placeholder_crossfade") + + val placeholderAlpha by transition.animateFloat( + transitionSpec = placeholderFadeTransitionSpec, + label = "placeholder_fade", + targetValueByState = { placeholderVisible -> if (placeholderVisible) 1F else 0F } + ) + val contentAlpha by transition.animateFloat( + transitionSpec = contentFadeTransitionSpec, + label = "content_fade", + targetValueByState = { placeholderVisible -> if (placeholderVisible) 0F else 1F } + ) + + // Run the optional animation spec and update the progress if the placeholder is visible + val animationSpec = highlight?.animationSpec + if (animationSpec != null && (visible || placeholderAlpha >= 0.01F)) { + val infiniteTransition = rememberInfiniteTransition(label = "") + highlightProgress = infiniteTransition.animateFloat( + initialValue = 0F, + targetValue = 1F, + animationSpec = animationSpec, + label = "" + ).value + } + + val paint = remember { Paint() } + remember(color, shape, highlight) { + drawWithContent { + // Draw the composable content first + if (contentAlpha in 0.01F..0.99F) { + // If the content alpha is between 1% and 99%, draw it in a layer with + // the alpha applied + paint.alpha = contentAlpha + withLayer(paint) { + with(this@drawWithContent) { + drawContent() + } + } + } else if (contentAlpha >= 0.99F) { + // If the content alpha is > 99%, draw it with no alpha + drawContent() + } + + if (placeholderAlpha in 0.01F..0.99F) { + // If the placeholder alpha is between 1% and 99%, draw it in a layer with + // the alpha applied + paint.alpha = placeholderAlpha + withLayer(paint) { + lastOutline.value = drawPlaceholder( + shape = shape, + color = color, + highlight = highlight, + progress = highlightProgress, + lastOutline = lastOutline.value, + lastLayoutDirection = lastLayoutDirection.value, + lastSize = lastSize.value, + ) + } + } else if (placeholderAlpha >= 0.99F) { + // If the placeholder alpha is > 99%, draw it with no alpha + lastOutline.value = drawPlaceholder( + shape = shape, + color = color, + highlight = highlight, + progress = highlightProgress, + lastOutline = lastOutline.value, + lastLayoutDirection = lastLayoutDirection.value, + lastSize = lastSize.value, + ) + } + + // Keep track of the last size & layout direction + lastSize.value = size + lastLayoutDirection.value = layoutDirection + } + } +} + +private fun DrawScope.drawPlaceholder( + shape: Shape, + color: Color, + highlight: PlaceholderHighlight?, + progress: Float, + lastOutline: Outline?, + lastLayoutDirection: LayoutDirection?, + lastSize: Size?, +): Outline? { + // shortcut to avoid Outline calculation and allocation + if (shape === RectangleShape) { + // Draw the initial background color + drawRect(color = color) + + if (highlight != null) { + drawRect( + brush = highlight.brush(progress, size), + alpha = highlight.alpha(progress), + ) + } + // We didn't create an outline so return null + return null + } + + // Otherwise we need to create an outline from the shape + val outline = lastOutline.takeIf { + size == lastSize && layoutDirection == lastLayoutDirection + } ?: shape.createOutline(size, layoutDirection, this) + + // Draw the placeholder color + drawOutline(outline = outline, color = color) + + if (highlight != null) { + drawOutline( + outline = outline, + brush = highlight.brush(progress, size), + alpha = highlight.alpha(progress), + ) + } + + // Return the outline we used + return outline +} + +private inline fun DrawScope.withLayer( + paint: Paint, + drawBlock: DrawScope.() -> Unit, +) = drawIntoCanvas { canvas -> + canvas.saveLayer(size.toRect(), paint) + drawBlock() + canvas.restore() +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/core/placeholder/PlaceholderHightlight.kt b/core/src/main/kotlin/org/michaelbel/core/placeholder/PlaceholderHightlight.kt new file mode 100644 index 00000000..76b9ec56 --- /dev/null +++ b/core/src/main/kotlin/org/michaelbel/core/placeholder/PlaceholderHightlight.kt @@ -0,0 +1,130 @@ +package org.michaelbel.core.placeholder + +import androidx.annotation.FloatRange +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.runtime.Stable +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.util.lerp +import kotlin.math.max + +/** + * A class which provides a brush to paint placeholder based on progress. + */ +@Stable +interface PlaceholderHighlight { + /** + * The optional [AnimationSpec] to use when running the animation for this highlight. + */ + val animationSpec: InfiniteRepeatableSpec? + + /** + * Return a [Brush] to draw for the given [progress] and [size]. + * + * @param progress the current animated progress in the range of 0f..1f. + * @param size The size of the current layout to draw in. + */ + fun brush( + @FloatRange(from = 0.0, to = 1.0) progress: Float, + size: Size + ): Brush + + /** + * Return the desired alpha value used for drawing the [Brush] returned from [brush]. + * + * @param progress the current animated progress in the range of 0f..1f. + */ + @FloatRange(from = 0.0, to = 1.0) + fun alpha(progress: Float): Float + + companion object +} + +/** + * Creates a [Fade] brush with the given initial and target colors. + * + * @param highlightColor the color of the highlight which is faded in/out. + * @param animationSpec the [AnimationSpec] to configure the animation. + */ +internal fun PlaceholderHighlight.Companion.fade( + highlightColor: Color, + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec +): PlaceholderHighlight = Fade( + highlightColor = highlightColor, + animationSpec = animationSpec, +) + +/** + * Creates a [PlaceholderHighlight] which 'shimmers', using the given [highlightColor]. + * + * The highlight starts at the top-start, and then grows to the bottom-end during the animation. + * During that time it is also faded in, from 0f..progressForMaxAlpha, and then faded out from + * progressForMaxAlpha..1F. + * + * @param highlightColor the color of the highlight 'shimmer'. + * @param animationSpec the [AnimationSpec] to configure the animation. + * @param progressForMaxAlpha The progress where the shimmer should be at it's peak opacity. + * Defaults to 0.6f. + */ +internal fun PlaceholderHighlight.Companion.shimmer( + highlightColor: Color, + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.shimmerAnimationSpec, + @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F +): PlaceholderHighlight = Shimmer( + highlightColor = highlightColor, + animationSpec = animationSpec, + progressForMaxAlpha = progressForMaxAlpha, +) + +private data class Fade( + private val highlightColor: Color, + override val animationSpec: InfiniteRepeatableSpec, +): PlaceholderHighlight { + private val brush = SolidColor(highlightColor) + + override fun brush(progress: Float, size: Size): Brush = brush + + override fun alpha(progress: Float): Float = progress +} + +private data class Shimmer( + private val highlightColor: Color, + override val animationSpec: InfiniteRepeatableSpec, + private val progressForMaxAlpha: Float = 0.6F, +): PlaceholderHighlight { + override fun brush( + progress: Float, + size: Size, + ): Brush = Brush.radialGradient( + colors = listOf( + highlightColor.copy(alpha = 0F), + highlightColor, + highlightColor.copy(alpha = 0F), + ), + center = Offset(x = 0F, y = 0F), + radius = (max(size.width, size.height) * progress * 2).coerceAtLeast(0.01F), + ) + + override fun alpha(progress: Float): Float = when { + // From 0f...ProgressForOpaqueAlpha we animate from 0..1 + progress <= progressForMaxAlpha -> { + lerp( + start = 0F, + stop = 1F, + fraction = progress / progressForMaxAlpha + ) + } + // From ProgressForOpaqueAlpha..1f we animate from 1..0 + else -> { + lerp( + start = 1F, + stop = 0F, + fraction = (progress - progressForMaxAlpha) / (1F - progressForMaxAlpha) + ) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/Placeholder.kt b/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/Placeholder.kt new file mode 100644 index 00000000..70b87c66 --- /dev/null +++ b/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/Placeholder.kt @@ -0,0 +1,111 @@ +package org.michaelbel.core.placeholder.material3 + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.spring +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.isSpecified +import org.michaelbel.core.placeholder.PlaceholderDefaults +import org.michaelbel.core.placeholder.PlaceholderHighlight +import org.michaelbel.core.placeholder.placeholder + +/** + * Returns the value used as the the `color` parameter value on [Modifier.placeholder]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.surface`. + * @param contentColor The content color to be used on top of [backgroundColor]. + * @param contentAlpha The alpha component to set on [contentColor] when compositing the color + * on top of [backgroundColor]. Defaults to `0.1F`. + */ +@Composable +internal fun PlaceholderDefaults.color( + backgroundColor: Color = MaterialTheme.colorScheme.surface, + contentColor: Color = contentColorFor(backgroundColor), + contentAlpha: Float = 0.1F +): Color = contentColor.copy(contentAlpha).compositeOver(backgroundColor) + +/** + * Returns the value used as the the `highlightColor` parameter value of + * [PlaceholderHighlight.Companion.fade]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.surface`. + * @param alpha The alpha component to set on [backgroundColor]. Defaults to `0.3F`. + */ +@Composable +internal fun PlaceholderDefaults.fadeHighlightColor( + backgroundColor: Color = MaterialTheme.colorScheme.surface, + alpha: Float = 0.3F +): Color = backgroundColor.copy(alpha = alpha) + +/** + * Returns the value used as the the `highlightColor` parameter value of + * [PlaceholderHighlight.Companion.shimmer]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.inverseSurface`. + * @param alpha The alpha component to set on [backgroundColor]. Defaults to `0.75F`. + */ +@Composable +internal fun PlaceholderDefaults.shimmerHighlightColor( + backgroundColor: Color = MaterialTheme.colorScheme.inverseSurface, + alpha: Float = 0.75F +): Color { + return backgroundColor.copy(alpha = alpha) +} + +/** + * Draws some skeleton UI which is typically used whilst content is 'loading'. + * + * To customize the color and shape of the placeholder, you can use the foundation version of + * [Modifier.placeholder], along with the values provided by [PlaceholderDefaults]. + * + * A cross-fade transition will be applied to the content and placeholder UI when the [visible] + * value changes. The transition can be customized via the [contentFadeTransitionSpec] and + * [placeholderFadeTransitionSpec] parameters. + * + * You can provide a [PlaceholderHighlight] which runs an highlight animation on the placeholder. + * The [shimmer] and [fade] implementations are provided for easy usage. + * + * You can find more information on the pattern at the Material Theming + * [Placeholder UI](https://material.io/design/communication/launch-screen.html#placeholder-ui) + * guidelines. + * + * @sample com.google.accompanist.sample.placeholder.DocSample_Material_Placeholder + * + * @param visible whether the placeholder should be visible or not. + * @param color the color used to draw the placeholder UI. If [Color.Unspecified] is provided, + * the placeholder will use [PlaceholderDefaults.color]. + * @param shape desired shape of the placeholder. If null is provided the placeholder + * will use the small shape set in [MaterialTheme.shapes]. + * @param highlight optional highlight animation. + * @param placeholderFadeTransitionSpec The transition spec to use when fading the placeholder + * on/off screen. The boolean parameter defined for the transition is [visible]. + * @param contentFadeTransitionSpec The transition spec to use when fading the content + * on/off screen. The boolean parameter defined for the transition is [visible]. + */ +internal fun Modifier.placeholder( + visible: Boolean, + color: Color = Color.Unspecified, + shape: Shape? = null, + highlight: PlaceholderHighlight? = null, + placeholderFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, + contentFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() } +): Modifier = composed { + Modifier.placeholder( + visible = visible, + color = if (color.isSpecified) color else PlaceholderDefaults.color(), + shape = shape ?: MaterialTheme.shapes.small, + highlight = highlight, + placeholderFadeTransitionSpec = placeholderFadeTransitionSpec, + contentFadeTransitionSpec = contentFadeTransitionSpec, + ) +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/PlaceholderHighlight.kt b/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/PlaceholderHighlight.kt new file mode 100644 index 00000000..58b0ef38 --- /dev/null +++ b/core/src/main/kotlin/org/michaelbel/core/placeholder/material3/PlaceholderHighlight.kt @@ -0,0 +1,45 @@ +package org.michaelbel.core.placeholder.material3 + +import androidx.annotation.FloatRange +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.runtime.Composable +import org.michaelbel.core.placeholder.PlaceholderDefaults +import org.michaelbel.core.placeholder.PlaceholderHighlight +import org.michaelbel.core.placeholder.fade +import org.michaelbel.core.placeholder.shimmer + +/** + * Creates a [PlaceholderHighlight] which fades in an appropriate color, using the + * given [animationSpec]. + * + * @param animationSpec the [AnimationSpec] to configure the animation. + */ +@Composable +fun PlaceholderHighlight.Companion.fade( + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec +) = PlaceholderHighlight.fade( + highlightColor = PlaceholderDefaults.fadeHighlightColor(), + animationSpec = animationSpec, +) + +/** + * Creates a [PlaceholderHighlight] which 'shimmers', using a default color. + * + * The highlight starts at the top-start, and then grows to the bottom-end during the animation. + * During that time it is also faded in, from 0f..progressForMaxAlpha, and then faded out from + * progressForMaxAlpha..1F. + * + * @param animationSpec the [AnimationSpec] to configure the animation. + * @param progressForMaxAlpha The progress where the shimmer should be at it's peak opacity. + * Defaults to 0.6F. + */ +@Composable +internal fun PlaceholderHighlight.Companion.shimmer( + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.shimmerAnimationSpec, + @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F +): PlaceholderHighlight = PlaceholderHighlight.shimmer( + highlightColor = PlaceholderDefaults.shimmerHighlightColor(), + animationSpec = animationSpec, + progressForMaxAlpha = progressForMaxAlpha, +) \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/core/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/org/michaelbel/core/viewmodel/BaseViewModel.kt new file mode 100644 index 00000000..fd410a93 --- /dev/null +++ b/core/src/main/kotlin/org/michaelbel/core/viewmodel/BaseViewModel.kt @@ -0,0 +1,30 @@ +package org.michaelbel.core.viewmodel + +import androidx.annotation.CallSuper +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren +import kotlin.coroutines.CoroutineContext + +open class BaseViewModel: ViewModel(), CoroutineScope { + + private val scopeJob: Job = SupervisorJob() + + private val errorHandler = CoroutineExceptionHandler { _, throwable -> + handleError(throwable) + } + + override val coroutineContext: CoroutineContext = scopeJob + Dispatchers.Main + errorHandler + + override fun onCleared() { + coroutineContext.cancelChildren() + super.onCleared() + } + + @CallSuper + protected open fun handleError(throwable: Throwable) {} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3106d02d..10394078 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,18 @@ org.gradle.jvmargs=-Xmx8192m -Dfile.encoding=UTF-8 +org.gradle.daemon=true org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.warning.mode=all +org.gradle.logging.level=info android.useAndroidX=true android.enableJetifier=false -android.defaults.buildFeatures.resValues=false -android.defaults.buildFeatures.shaders=false \ No newline at end of file +android.enableR8=true +android.enableBuildCache=false +android.defaults.buildFeatures.resValues=true +android.defaults.buildFeatures.shaders=false +android.defaults.buildFeatures.aidl=false +android.defaults.buildFeatures.renderscript=false +android.defaults.buildFeatures.viewBinding=false +android.defaults.buildFeatures.dataBinding=false +CI=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2b5fc33..f7a9c6b9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -358,10 +358,12 @@ ktor = "3.0.0" okio = "3.9.1" okhttp = "4.12.0" retrofit = "2.11.0" +chucker = "4.0.0" moshi = "1.15.1" rxjava3 = "3.1.9" rxjava3-rxkotlin = "3.0.1" rxjava3-rxandroid = "3.0.2" +timber = "5.0.1" coil = "2.7.0" cicerone = "7.1" lottie = "6.5.2" @@ -369,9 +371,6 @@ viewbindingpropertydelegate = "1.5.9" leakcanary = "2.14" kotlin-inject = "0.7.2" koin = "3.5.6" - -chucker = "4.0.0" -timber = "5.0.1" junit = "4.13.2" robolectric = "4.13" mockito = "5.11.0" @@ -973,7 +972,6 @@ androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version androidx-work-multiprocess = { module = "androidx.work:work-multiprocess", version.ref = "androidx-work" } androidx-work-gcm = { module = "androidx.work:work-gcm", version.ref = "androidx-work" } #endregion -#region third-party ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } @@ -1008,8 +1006,6 @@ koin-androidx-workmanager = { module = "io.insert-koin:koin-androidx-workmanager koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } -#endregion - junit = { module = "junit:junit", version.ref = "junit" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } diff --git a/readme.md b/readme.md index ba76e058..36c863d9 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ Android App Template This Android app template provides a foundation for quickly starting development. It includes pre-configured dependencies, code samples, and project structure, simplifying the creation and setup of new applications. The template helps reduce configuration time and allows developers to focus on implementing functionality, ensuring a smooth start. -
+
Project Image
@@ -17,7 +17,7 @@ This Android app template provides a foundation for quickly starting development - [x] Included all Google Material dependencies - [x] Included all Firebase dependencies - [x] Included all AndroidX dependencies -- [ ] Included all popular third-party dependencies +- [x] Included all popular third-party dependencies ## Configure * In the current `readme.md` file, update the project name, badges, description, and screenshot.