diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..79cf7f6d --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,50 @@ +# Devcontainer image for building the Android app. +FROM mcr.microsoft.com/devcontainers/java + +USER root + +# Android SDK locations +ENV ANDROID_PLATFORM=android-36 +ENV ANDROID_BUILD_TOOLS=36.0.0 +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV ANDROID_HOME=/opt/android-sdk +ENV ANDROID_CMDLINE_TOOLS_ROOT=/opt/android-sdk/cmdline-tools/latest +ENV PATH=${PATH}:${ANDROID_CMDLINE_TOOLS_ROOT}/bin:${ANDROID_HOME}/platform-tools + +# Remove broken/optional third-party apt sources that may be present in the base image/features +RUN rm -f /etc/apt/sources.list.d/yarn.list \ + /etc/apt/sources.list.d/yarn*.list \ + /etc/apt/sources.list.d/archive_uri-https_dl_yarnpkg_com_debian-bookworm.list || true + +# Install packages required for Android/Gradle workflows +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + curl \ + unzip \ + ca-certificates \ + git \ + bash \ + libc6 \ + libstdc++6 \ + libgcc1 \ + libbz2-1.0 \ + libncurses6 \ + zlib1g \ + # && uv tool install specify-cli --from git+https://github.com/github/spec-kit.git \ + && rm -rf /var/lib/apt/lists/* + +# Install Android command line tools +RUN mkdir -p ${ANDROID_HOME}/cmdline-tools \ + && curl -fsSL https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -o /tmp/cmdline-tools.zip \ + && mkdir -p /tmp/android-cmdline-tools \ + && unzip -q /tmp/cmdline-tools.zip -d /tmp/android-cmdline-tools \ + && mv /tmp/android-cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \ + && rm -rf /tmp/cmdline-tools.zip /tmp/android-cmdline-tools \ + && chown -R vscode:vscode ${ANDROID_HOME} + +# Accept licenses and install commonly needed Android SDK components +RUN yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses > /dev/null || true \ + && sdkmanager --sdk_root=${ANDROID_HOME} \ + "platform-tools" \ + "build-tools;${ANDROID_BUILD_TOOLS}" \ + "platforms;${ANDROID_PLATFORM}" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..1e22dd4a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,44 @@ +{ + "name": "Kotlin Multiplatform Development", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/java:1": { + "version": "25", + "installGradle": "true", + "gradleVersion": "9.4" + }, + "ghcr.io/devcontainers-extra/features/uv:1": { + "version": "latest" + } + }, + "customizations": { + "vscode": { + "settings": { + "java.import.generatesMetadataFilesAtProjectRoot": false, + "java.import.gradle.wrapper.enabled": true, + "kotlin.languageServer.enabled": true, + "editor.formatOnSave": true, + "files.exclude": { + "**/.gradle": true, + "**/build": true + } + }, + "extensions": [ + "fwcd.kotlin", + "vscjava.vscode-java-pack", + "vscjava.vscode-gradle", + "redhat.vscode-yaml", + "GitHub.copilot" + ] + } + }, + "containerEnv": { + "ANDROID_HOME": "/opt/android-sdk", + "ANDROID_SDK_ROOT": "/opt/android-sdk" + }, + // "postCreateCommand": "bash -lc 'uv tool install specify-cli --from git+https://github.com/github/spec-kit.git && gradle --version'", + "remoteUser": "vscode" // More info: https://aka.ms/dev-containers-non-root. +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 904d0c1c..c8d08293 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ build gen out .externalNativeBuild +.kotlin # Mac .DS_Store diff --git a/.vscode/Joshua.code-workspace b/.vscode/Joshua.code-workspace new file mode 100644 index 00000000..093ae58b --- /dev/null +++ b/.vscode/Joshua.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": { + "chat.tools.terminal.autoApprove": { + "gradle": true, + "./gradlew": true, + ".\\gradlew.bat": true + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 2139f762..e2260d30 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,20 @@ [![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://www.android.com/versions/kit-kat-4-4/) [![Apache License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) +This project was forked [here](https://github.com/danzuep/Joshua) after the [original](https://github.com/xizzhu/Joshua) appears to have been abandoned. My goal was to work with the [WEB](https://ebible.org/engwebu/MRK01.htm) translator to create a [FOSS](https://f-droid.org/) app for the many public domain licenced works he has, but as this is my first time using Kotlin that may be unrealistic. So far I've updated the project from JDK 11 to build in a JDK 25 devcontainer, and gotten all the unit tests to pass again. + Joshua ====== -A simple and easy-to-use Bible reader for your Android device, with minimalist design. It supports offline reading, multi-versions, full-text search, and many other features, while maintaining a small download and install size. +A simple and easy-to-use Bible reader for your Android device, with minimalist design. It supports offline reading, multi-versions, full-text search, and many other features, while maintaining a small download and install size. You can download the APK from [Play Store](https://play.google.com/store/apps/details?id=me.xizzhu.android.joshua) or [here](https://github.com/xizzhu/Joshua/releases). Recent changes are listed [here](docs/CHANGELOG.md). Feel free to request new features or report bugs [here](https://github.com/xizzhu/Joshua/issues). +You can download the APK from [GitHub](https://github.com/danzuep/Joshua/releases). Recent changes are listed [here](docs/CHANGELOG.md). Feel free to request new features or report bugs [here](https://github.com/danzuep/Joshua/issues). + + +A simple and easy-to-use Bible reader for your Android device, with minimalist design. It supports offline reading, multi-versions, full-text search, and many other features, while maintaining a small download and install size. + +~~You can download the APK from [Play Store](https://play.google.com/store/apps/details?id=me.xizzhu.android.joshua) or [here](https://github.com/xizzhu/Joshua/releases). Recent changes are listed [here](docs/CHANGELOG.md). Feel free to request new features or report bugs [here](https://github.com/xizzhu/Joshua/issues).~~ Main Features ------------- @@ -27,11 +35,11 @@ Main Features We currently support tens of different translations, with more added regularly. A full list of available translations can be found [here](docs/TRANSLATIONS.md). -Donations ---------- -If you would like to support this project, feel free to donate. Your support is highly appreciated. Thank you! -* [PayPal](https://paypal.me/xizzhu) -* [Venmo](https://www.venmo.com/u/xizzhu) +~~Donations~~ +~~---------~~ +~~If you would like to support this project, feel free to donate. Your support is highly appreciated. Thank you!~~ +~~* [PayPal](https://paypal.me/xizzhu)~~ +~~* [Venmo](https://www.venmo.com/u/xizzhu)~~ License ------- diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d7f23599..fe83e61a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,11 +18,10 @@ import java.util.Properties plugins { id("com.android.application") - id("com.google.firebase.crashlytics") - id("com.google.gms.google-services") + //id("com.google.firebase.crashlytics") + //id("com.google.gms.google-services") + id(Dependencies.Kotlin.Ksp.plugin) id(Dependencies.Hilt.plugin) - kotlin("android") - kotlin("kapt") } android { @@ -32,8 +31,10 @@ android { sourceCompatibility = Versions.java targetCompatibility = Versions.java } - kotlinOptions { - jvmTarget = Versions.Kotlin.jvmTarget + tasks.withType { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(Versions.Kotlin.jvmTarget)) + } } buildToolsVersion = Versions.Sdk.buildTools @@ -48,21 +49,19 @@ android { versionCode = Versions.App.code versionName = Versions.App.name - multiDexEnabled = true - - resourceConfigurations.addAll(Configurations.supportedLocales) + androidResources.localeFilters.addAll(Configurations.supportedLocales) } buildFeatures { viewBinding = true + buildConfig = true } sourceSets { - getByName("main").java.srcDirs("src/main/kotlin") - getByName("debug").java.srcDirs("src/debug/kotlin") - getByName("release").java.srcDirs("src/release/kotlin") - - getByName("test").java.srcDirs("src/test/kotlin") + getByName("main") { java.setSrcDirs(setOf("src/main/kotlin")) } + getByName("debug") { java.setSrcDirs(setOf("src/debug/kotlin")) } + getByName("release") { java.setSrcDirs(setOf("src/release/kotlin")) } + getByName("test") { java.setSrcDirs(setOf("src/test/kotlin")) } } signingConfigs { @@ -81,18 +80,23 @@ android { } buildTypes { - getByName("release") { + release { isMinifyEnabled = true isShrinkResources = true signingConfig = signingConfigs.getByName("release") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } - getByName("debug") { + debug { applicationIdSuffix = ".debug" versionNameSuffix = " debug" } } + compileOptions { + sourceCompatibility = Versions.java + targetCompatibility = Versions.java + } + testOptions { unitTests.isIncludeAndroidResources = true unitTests.isReturnDefaultValues = true @@ -102,31 +106,14 @@ android { } } - packagingOptions { - resources.excludes.addAll(listOf( - "META-INF/atomicfu.kotlin_module", "META-INF/AL2.0", "META-INF/LGPL2.1", "META-INF/licenses/*", - - // https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-debug#debug-agent-and-android - "win32-x86/attach_hotspot_windows.dll", "win32-x86-64/attach_hotspot_windows.dll" - )) - } -} - -// allow references to generated code -kapt { - correctErrorTypes = true -} - -tasks.withType { - kotlinOptions { - jvmTarget = Versions.Kotlin.jvmTarget + packaging { + resources.excludes.add("**/NOTNOTICE") + jniLibs.excludes.add("**/libshared.so") } } dependencies { implementation(Dependencies.Kotlin.coroutines) - - debugImplementation(Dependencies.AndroidX.multidex) implementation(Dependencies.AndroidX.activity) implementation(Dependencies.AndroidX.annotation) implementation(Dependencies.AndroidX.appCompat) @@ -147,10 +134,10 @@ dependencies { releaseImplementation(Dependencies.Firebase.Crashlytics.crashlytics) implementation(Dependencies.Hilt.android) - kapt(Dependencies.Hilt.compiler) + ksp(Dependencies.Hilt.compiler) implementation(Dependencies.ask) - implementation(Dependencies.logger) + implementation(Dependencies.kermit) implementation(Dependencies.materialComponent) testImplementation(Dependencies.Kotlin.test) @@ -158,15 +145,8 @@ dependencies { testImplementation(Dependencies.Kotlin.reflect) debugImplementation(Dependencies.AndroidX.Fragment.test) testImplementation(Dependencies.AndroidX.Test.core) - testImplementation("androidx.test.ext:junit:1.1.4") - kaptTest(Dependencies.Hilt.compiler) + testImplementation(Dependencies.AndroidX.Test.junit) testImplementation(Dependencies.Hilt.test) testImplementation(Dependencies.mockk) testImplementation(Dependencies.robolectric) } - -kover { - instrumentation { - excludeTasks.add("testDebugUnitTest") - } -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e8fbf454..8af225a2 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,4 +1,8 @@ -# Crashlytics -# https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android --keepattributes SourceFile,LineNumberTable --keep public class * extends java.lang.Exception + +# Add project-specific ProGuard rules here. +# By default, the flags are kept minimal. + +## Crashlytics +## https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android +#-keepattributes SourceFile,LineNumberTable +#-keep public class * extends java.lang.Exception \ No newline at end of file diff --git a/app/src/debug/kotlin/me/xizzhu/android/joshua/App.kt b/app/src/debug/kotlin/me/xizzhu/android/joshua/App.kt index 83d07393..2d986851 100644 --- a/app/src/debug/kotlin/me/xizzhu/android/joshua/App.kt +++ b/app/src/debug/kotlin/me/xizzhu/android/joshua/App.kt @@ -16,19 +16,24 @@ package me.xizzhu.android.joshua +import android.app.Application import android.os.StrictMode import androidx.annotation.CallSuper -import androidx.multidex.MultiDexApplication -import me.xizzhu.android.logger.Log -import me.xizzhu.android.logger.LogcatLogger +import co.touchlab.kermit.Logger -abstract class BaseApp : MultiDexApplication() { +abstract class BaseApp : Application() { @CallSuper override fun onCreate() { super.onCreate() - StrictMode.enableDefaults() - - Log.addLogger(LogcatLogger()) + setupGlobalLogger() } } + +fun setupGlobalLogger() { + // Configure the global logger instance + Logger.setLogWriters(co.touchlab.kermit.platformLogWriter()) + + // Set an optional default tag for the global logger + Logger.withTag("Joshua") +} \ No newline at end of file diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/Navigator.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/Navigator.kt index 2110d1ce..131fdd56 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/Navigator.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/Navigator.kt @@ -20,6 +20,7 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.annotation.IntDef import me.xizzhu.android.joshua.annotated.bookmarks.BookmarksActivity import me.xizzhu.android.joshua.annotated.highlights.HighlightsActivity @@ -74,6 +75,10 @@ class Navigator { } fun goBack(activity: Activity) { - activity.finish() + if (activity is ComponentActivity) { + activity.onBackPressedDispatcher.onBackPressed() + } else { + activity.finish() + } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseAdapter.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseAdapter.kt index aef1f242..a8458b5d 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseAdapter.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseAdapter.kt @@ -90,7 +90,7 @@ sealed class AnnotatedVerseItem(viewType: Int) : VerticalRecyclerViewItem(viewTy val hideDivider: Boolean, ) : AnnotatedVerseItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_title + @JvmField val VIEW_TYPE = R.layout.item_title } } @@ -103,7 +103,7 @@ sealed class AnnotatedVerseItem(viewType: Int) : VerticalRecyclerViewItem(viewTy @Constants.SortOrder private val sortOrder: Int ) : AnnotatedVerseItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_annotated_verse_bookmark + @JvmField val VIEW_TYPE = R.layout.item_annotated_verse_bookmark } val textForDisplay: CharSequence by lazy(LazyThreadSafetyMode.NONE) { @@ -140,7 +140,7 @@ sealed class AnnotatedVerseItem(viewType: Int) : VerticalRecyclerViewItem(viewTy @Constants.SortOrder private val sortOrder: Int ) : AnnotatedVerseItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_annotated_verse_highlight + @JvmField val VIEW_TYPE = R.layout.item_annotated_verse_highlight } val textForDisplay: CharSequence by lazy(LazyThreadSafetyMode.NONE) { @@ -177,7 +177,7 @@ sealed class AnnotatedVerseItem(viewType: Int) : VerticalRecyclerViewItem(viewTy val note: String ) : AnnotatedVerseItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_annotated_verse_note + @JvmField val VIEW_TYPE = R.layout.item_annotated_verse_note private val BOOK_NAME_STYLE_SPAN = createTitleStyleSpan() } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseViewModel.kt index 84f6db22..7203c3b7 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/annotated/AnnotatedVerseViewModel.kt @@ -39,7 +39,7 @@ import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.provider.TimeProvider import me.xizzhu.android.joshua.infra.BaseViewModelV2 import me.xizzhu.android.joshua.utils.firstNotEmpty -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import java.util.Calendar import kotlin.collections.ArrayList import me.xizzhu.android.joshua.preview.Preview @@ -93,7 +93,7 @@ abstract class AnnotatedVerseViewModel( val items = loadAnnotatedVerses(settings, currentTranslation, sortOrder) updateViewState { it.copy(loading = false, items = items) } }.onFailure { e -> - Log.e(tag, "Error occurred while loading annotated verses", e) + Logger.withTag(tag).e(e) { "Error occurred while loading annotated verses" } updateViewState { it.copy(loading = false, error = ViewState.Error.AnnotatedVersesLoadingError) } } }.flowOn(coroutineDispatcherProvider.default).launchIn(viewModelScope) @@ -112,7 +112,7 @@ abstract class AnnotatedVerseViewModel( ) updateViewState { it.copy(loading = false, sortOrder = sortOrder, items = items) } }.onFailure { e -> - Log.e(tag, "Error occurred while loading annotated verses", e) + Logger.withTag(tag).e(e) { "Error occurred while loading annotated verses" } updateViewState { it.copy(loading = false, error = ViewState.Error.AnnotatedVersesLoadingError) } } } @@ -228,7 +228,7 @@ abstract class AnnotatedVerseViewModel( runCatching { verseAnnotationManager.saveSortOrder(sortOrder) }.onFailure { e -> - Log.e(tag, "Failed to save sort order", e) + Logger.withTag(tag).e(e) { "Failed to save sort order" } updateViewState { it.copy(error = ViewState.Error.SortOrderSavingError(sortOrder)) } } } @@ -240,7 +240,7 @@ abstract class AnnotatedVerseViewModel( bibleReadingManager.saveCurrentVerseIndex(verseToOpen) emitViewAction(ViewAction.OpenReadingScreen) }.onFailure { e -> - Log.e(tag, "Failed to save current verse", e) + Logger.withTag(tag).e(e) { "Failed to save current verse" } updateViewState { it.copy(error = ViewState.Error.VerseOpeningError(verseToOpen)) } } } @@ -260,7 +260,7 @@ abstract class AnnotatedVerseViewModel( ) updateViewState { it.copy(preview = preview) } }.onFailure { e -> - Log.e(tag, "Failed to load verses for preview", e) + Logger.withTag(tag).e(e) { "Failed to load verses for preview" } updateViewState { it.copy(error = ViewState.Error.PreviewLoadingError(verseToPreview)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/BibleReadingManager.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/BibleReadingManager.kt index f299694b..37ee762d 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/BibleReadingManager.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/BibleReadingManager.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import me.xizzhu.android.joshua.core.repository.BibleReadingRepository import me.xizzhu.android.joshua.core.repository.TranslationRepository -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger data class VerseIndex(val bookIndex: Int, val chapterIndex: Int, val verseIndex: Int) { companion object { @@ -92,7 +92,7 @@ class BibleReadingManager( } } } catch (e: Exception) { - Log.e(TAG, "Error occurred while observing downloaded translations", e) + Logger.e(e) { "Error occurred while observing downloaded translations" } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/ReadingProgressManager.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/ReadingProgressManager.kt index 75ebcf83..d3b7df18 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/ReadingProgressManager.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/ReadingProgressManager.kt @@ -26,7 +26,7 @@ import me.xizzhu.android.joshua.core.provider.TimeProvider import me.xizzhu.android.joshua.core.repository.BibleReadingRepository import me.xizzhu.android.joshua.core.repository.ReadingProgressRepository import me.xizzhu.android.joshua.utils.filterIsValid -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger data class ReadingProgress(val continuousReadingDays: Int, val lastReadingTimestamp: Long, val chapterReadingStatus: List) { @@ -100,7 +100,7 @@ class ReadingProgressManager( readingProgressRepository.trackReadingProgress( verseIndex.bookIndex, verseIndex.chapterIndex, timeSpentInMillis, now) } catch (e: Exception) { - Log.e(TAG, "Failed to track reading progress", e) + Logger.e(e) { "Failed to track reading progress" } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/BibleReadingRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/BibleReadingRepository.kt index 0dbb2f18..3926384f 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/BibleReadingRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/BibleReadingRepository.kt @@ -26,7 +26,7 @@ import me.xizzhu.android.joshua.core.Verse import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.VerseSearchQuery import me.xizzhu.android.joshua.core.repository.local.LocalReadingStorage -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class BibleReadingRepository(private val localReadingStorage: LocalReadingStorage, appScope: CoroutineScope) { companion object { @@ -49,19 +49,19 @@ class BibleReadingRepository(private val localReadingStorage: LocalReadingStorag try { _currentVerseIndex.value = localReadingStorage.readCurrentVerseIndex() } catch (e: Exception) { - Log.e(TAG, "Failed to initialize current verse index", e) + Logger.withTag(TAG).e(e) { "Failed to initialize current verse index" } _currentVerseIndex.value = VerseIndex.INVALID } try { _currentTranslation.value = localReadingStorage.readCurrentTranslation() } catch (e: Exception) { - Log.e(TAG, "Failed to initialize current translation", e) + Logger.withTag(TAG).e(e) { "Failed to initialize current translation" } _currentTranslation.value = "" } try { _parallelTranslations.value = localReadingStorage.readParallelTranslations() } catch (e: Exception) { - Log.e(TAG, "Failed to initialize parallel translations", e) + Logger.withTag(TAG).e(e) { "Failed to initialize parallel translations" } _parallelTranslations.value = emptyList() } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/CrossReferencesRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/CrossReferencesRepository.kt index da3f4b9e..1eecab57 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/CrossReferencesRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/CrossReferencesRepository.kt @@ -24,7 +24,7 @@ import me.xizzhu.android.joshua.core.CrossReferences import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.repository.local.LocalCrossReferencesStorage import me.xizzhu.android.joshua.core.repository.remote.RemoteCrossReferencesStorage -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class CrossReferencesRepository(private val localCrossReferencesStorage: LocalCrossReferencesStorage, private val remoteCrossReferencesStorage: RemoteCrossReferencesStorage) { @@ -38,13 +38,13 @@ class CrossReferencesRepository(private val localCrossReferencesStorage: LocalCr val downloadProgressChannel = Channel(Channel.CONFLATED) launch { downloadProgressChannel.consumeEach { trySend(it) } } - Log.i(TAG, "Start downloading cross references") + Logger.withTag(TAG).i { "Start downloading cross references" } val downloaded = remoteCrossReferencesStorage.fetchCrossReferences(downloadProgressChannel) - Log.i(TAG, "Cross references downloaded") + Logger.withTag(TAG).i { "Cross references downloaded" } localCrossReferencesStorage.save(downloaded.references) remoteCrossReferencesStorage.removeCrossReferencesCache() - Log.i(TAG, "Cross references saved to database") + Logger.withTag(TAG).i { "Cross references saved to database" } downloadProgressChannel.trySend(100) downloadProgressChannel.close() } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/SettingsRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/SettingsRepository.kt index 49fcd1f6..c151eb56 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/SettingsRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/SettingsRepository.kt @@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.xizzhu.android.joshua.core.Settings import me.xizzhu.android.joshua.core.repository.local.LocalSettingsStorage -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class SettingsRepository(private val localSettingsStorage: LocalSettingsStorage, appScope: CoroutineScope) { companion object { @@ -36,7 +36,7 @@ class SettingsRepository(private val localSettingsStorage: LocalSettingsStorage, try { _settings.value = localSettingsStorage.readSettings() } catch (e: Exception) { - Log.e(TAG, "Failed to initialize settings", e) + Logger.withTag(TAG).e(e) { "Failed to initialize settings" } _settings.value = Settings.DEFAULT } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/StrongNumberRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/StrongNumberRepository.kt index ee4e616a..41cdf297 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/StrongNumberRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/StrongNumberRepository.kt @@ -24,7 +24,7 @@ import me.xizzhu.android.joshua.core.StrongNumber import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.repository.local.LocalStrongNumberStorage import me.xizzhu.android.joshua.core.repository.remote.RemoteStrongNumberStorage -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import kotlin.math.min class StrongNumberRepository(private val localStrongNumberStorage: LocalStrongNumberStorage, @@ -43,7 +43,7 @@ class StrongNumberRepository(private val localStrongNumberStorage: LocalStrongNu @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun download(versesDownloadProgress: Channel, wordsDownloadProgress: Channel) = channelFlow { - Log.i(TAG, "Start downloading Strong number") + Logger.withTag(TAG).i { "Start downloading Strong number" } versesDownloadProgress.trySend(0) wordsDownloadProgress.trySend(0) @@ -54,7 +54,7 @@ class StrongNumberRepository(private val localStrongNumberStorage: LocalStrongNu val remoteIndexesAsync = async { remoteStrongNumberStorage.fetchIndexes(versesDownloadProgress) } val remoteWords = remoteStrongNumberStorage.fetchWords(wordsDownloadProgress) val remoteIndexes = remoteIndexesAsync.await() - Log.i(TAG, "Strong number downloaded") + Logger.withTag(TAG).i { "Strong number downloaded" } versesDownloadProgress.close() wordsDownloadProgress.close() @@ -62,7 +62,7 @@ class StrongNumberRepository(private val localStrongNumberStorage: LocalStrongNu localStrongNumberStorage.save(remoteIndexes.indexes, remoteIndexes.reverseIndexes, remoteWords.words) remoteStrongNumberStorage.removeIndexesCache() remoteStrongNumberStorage.removeWordsCache() - Log.i(TAG, "Strong number saved to database") + Logger.withTag(TAG).i { "Strong number saved to database" } trySend(100) } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/TranslationRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/TranslationRepository.kt index dba529db..c7ab60c0 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/TranslationRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/TranslationRepository.kt @@ -31,7 +31,7 @@ import me.xizzhu.android.joshua.core.provider.TimeProvider import me.xizzhu.android.joshua.core.repository.local.LocalTranslationStorage import me.xizzhu.android.joshua.core.repository.remote.RemoteTranslationInfo import me.xizzhu.android.joshua.core.repository.remote.RemoteTranslationService -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class TranslationRepository( private val localTranslationStorage: LocalTranslationStorage, @@ -57,7 +57,7 @@ class TranslationRepository( try { updateTranslations(readTranslationsFromLocal()) } catch (e: Exception) { - Log.e(TAG, "Failed to initialize translations", e) + Logger.e(e) { "Failed to initialize translations" } updateTranslations(emptyList()) } } @@ -90,12 +90,12 @@ class TranslationRepository( try { readTranslationsFromBackend() } catch (e: Exception) { - Log.e(TAG, "Failed to read translation list from backend", e) + Logger.e(e) { "Failed to read translation list from backend" } readTranslationsFromLocal() } } else { readTranslationsFromLocal().ifEmpty { - Log.w(TAG, "Have fresh but empty local translation list") + Logger.w { "Have fresh but empty local translation list" } readTranslationsFromBackend() } } @@ -108,9 +108,9 @@ class TranslationRepository( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) suspend fun readTranslationsFromBackend(): List { - Log.i(TAG, "Start fetching translation list") + Logger.withTag(TAG).i { "Start fetching translation list" } val fetchedTranslations = remoteTranslationService.fetchTranslations() - Log.i(TAG, "Translation list downloaded") + Logger.withTag(TAG).i { "Translation list downloaded" } val localTranslations = readTranslationsFromLocal() val translations = ArrayList(fetchedTranslations.size) @@ -132,7 +132,7 @@ class TranslationRepository( localTranslationStorage.saveTranslationListRefreshTimestamp(timeProvider.currentTimeMillis) } } catch (e: Exception) { - Log.e(TAG, "Failed to save translation list refresh timestamp", e) + Logger.e(e) { "Failed to save translation list refresh timestamp" } } return translations @@ -168,10 +168,10 @@ class TranslationRepository( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) suspend fun downloadTranslation(downloadProgressChannel: SendChannel, translationInfo: TranslationInfo) { - Log.i(TAG, "Start downloading translation - ${translationInfo.shortName}") + Logger.withTag(TAG).i { "Start downloading translation - ${translationInfo.shortName}" } val toDownload = RemoteTranslationInfo.fromTranslationInfo(translationInfo) val translation = remoteTranslationService.fetchTranslation(downloadProgressChannel, toDownload) - Log.i(TAG, "Translation downloaded") + Logger.withTag(TAG).i { "Translation downloaded" } downloadProgressChannel.trySend(100) localTranslationStorage.saveTranslation( @@ -179,15 +179,15 @@ class TranslationRepository( translation.bookNames, translation.bookShortNames, translation.verses ) remoteTranslationService.removeTranslationCache(toDownload) - Log.i(TAG, "Translation saved to database") + Logger.withTag(TAG).i { "Translation saved to database" } downloadProgressChannel.trySend(101) } suspend fun removeTranslation(translationInfo: TranslationInfo) { - Log.i(TAG, "Start removing translation - ${translationInfo.shortName}") + Logger.withTag(TAG).i { "Start removing translation - ${translationInfo.shortName}" } localTranslationStorage.removeTranslation(translationInfo) remoteTranslationService.removeTranslationCache(RemoteTranslationInfo.fromTranslationInfo(translationInfo)) - Log.i(TAG, "Translation removed") + Logger.withTag(TAG).i { "Translation removed" } val (available, downloaded) = synchronized(translationsLock) { var removed = false diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/VerseAnnotationRepository.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/VerseAnnotationRepository.kt index 7e23a6e9..bbf3a0a7 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/VerseAnnotationRepository.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/VerseAnnotationRepository.kt @@ -25,7 +25,7 @@ import me.xizzhu.android.joshua.core.Constants import me.xizzhu.android.joshua.core.VerseAnnotation import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.repository.local.LocalVerseAnnotationStorage -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class VerseAnnotationRepository( private val localVerseAnnotationStorage: LocalVerseAnnotationStorage, @@ -43,7 +43,7 @@ class VerseAnnotationRepository( try { _sortOrder.value = localVerseAnnotationStorage.readSortOrder() } catch (e: Exception) { - Log.e(TAG, "Failed to initialize sort order", e) + Logger.withTag(TAG).e(e) { "Failed to initialize sort order" } _sortOrder.value = Constants.DEFAULT_SORT_ORDER } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/StrongNumberWordDao.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/StrongNumberWordDao.kt index 98586222..c92b63c9 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/StrongNumberWordDao.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/StrongNumberWordDao.kt @@ -21,7 +21,7 @@ import android.database.sqlite.SQLiteOpenHelper import androidx.annotation.WorkerThread import me.xizzhu.android.ask.db.* import me.xizzhu.android.joshua.core.StrongNumber -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger class StrongNumberWordDao(sqliteHelper: SQLiteOpenHelper) { companion object { @@ -62,7 +62,7 @@ class StrongNumberWordDao(sqliteHelper: SQLiteOpenHelper) { return ArrayList(strongNumbers.size).apply { strongNumbers.forEach { sn -> words[sn]?.let { word -> add(word) } - ?: Log.e(TAG, "", IllegalArgumentException("Strong number $sn is not available")) + ?: Logger.withTag(TAG).w { "Strong number $sn is not available" } } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/TranslationDao.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/TranslationDao.kt index e3511b51..35cf6b2c 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/TranslationDao.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/local/android/db/TranslationDao.kt @@ -24,7 +24,7 @@ import me.xizzhu.android.joshua.core.Bible import me.xizzhu.android.joshua.core.Verse import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.VerseSearchQuery -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import kotlin.math.max class TranslationDao(sqliteHelper: SQLiteOpenHelper) { @@ -53,7 +53,7 @@ class TranslationDao(sqliteHelper: SQLiteOpenHelper) { @WorkerThread private fun SQLiteDatabase.hasTableAndLogIfNoTable(table: String): Boolean = hasTable(table) - .also { hasTable -> if (!hasTable) Log.e(TAG, "", IllegalStateException("Missing translation $table")) } + .also { hasTable -> if (!hasTable) Logger.withTag(TAG).e { "Missing translation $table" } } @WorkerThread fun read(translationShortName: String, parallelTranslations: List, @@ -150,7 +150,7 @@ class TranslationDao(sqliteHelper: SQLiteOpenHelper) { @WorkerThread fun save(translationShortName: String, verses: Map, List>) = db.withTransaction { if (hasTable(translationShortName)) { - Log.e(TAG, "", IllegalStateException("Translation $translationShortName already installed")) + Logger.withTag(TAG).e { "Translation $translationShortName already installed" } remove(translationShortName) } createTable(translationShortName) { diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/AndroidHttpService.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/AndroidHttpService.kt index 8785eb20..e6561b90 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/AndroidHttpService.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/AndroidHttpService.kt @@ -19,7 +19,7 @@ package me.xizzhu.android.joshua.core.repository.remote.android import android.os.Build import android.util.JsonReader import kotlinx.coroutines.channels.SendChannel -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -101,12 +101,12 @@ private fun HttpURLConnection.configureTLSIfNecessary(): URLConnection { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && this is HttpsURLConnection) { return this } - Log.d(TAG, "Enabling TLS v1.2...") + Logger.withTag(TAG).d { "Enabling TLS v1.2..." } try { (this as HttpsURLConnection).sslSocketFactory = tls12SocketFactory - Log.d(TAG, "TLS v1.2 enabled") + Logger.withTag(TAG).d { "TLS v1.2 enabled" } } catch (e: Exception) { - Log.e(TAG, "Failed to enable TLS v1.2", e) + Logger.withTag(TAG).e(e) { "Failed to enable TLS v1.2" } } return this } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/JsonParser.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/JsonParser.kt index 19ed920c..599e5006 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/JsonParser.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/core/repository/remote/android/JsonParser.kt @@ -21,7 +21,7 @@ import androidx.annotation.VisibleForTesting import me.xizzhu.android.joshua.core.Bible import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.repository.remote.RemoteTranslationInfo -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger private const val TAG = "JsonParser" @@ -34,7 +34,7 @@ internal fun JsonReader.readListJson(): List { } else -> { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported JSON format in list.json")) + Logger.withTag(TAG).w(RuntimeException("Unsupported JSON format in list.json")) { "Unsupported JSON format" } } } } @@ -68,13 +68,13 @@ internal fun JsonReader.readTranslation(): RemoteTranslationInfo? { "size" -> size = nextLong() else -> { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported JSON format in list.json")) + Logger.withTag(TAG).w(RuntimeException("Unsupported JSON format in list.json")) { "Unsupported JSON format" } } } } endObject() if (shortName == null || name == null || language == null || size == null) { - Log.w(TAG, "Unsupported JSON format", RuntimeException("Illegal 'translation' in list.json - short name: $shortName")) + Logger.withTag(TAG).w(RuntimeException("Illegal 'translation' in list.json - short name: $shortName")) { "Unsupported JSON format" } return null } return RemoteTranslationInfo(shortName, name, language, size) @@ -91,7 +91,7 @@ internal fun JsonReader.readBooksJson(bookNames: MutableList, bookShortN "bookShortNames" -> readStringsArray(bookShortNames) else -> { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported JSON format in books.json")) + Logger.withTag(TAG).w(RuntimeException("Unsupported JSON format in books.json")) { "Unsupported JSON format" } } } } @@ -122,7 +122,7 @@ internal fun JsonReader.readChapterJson(): List { } else -> { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported format in chapter JSON")) + Logger.withTag(TAG).w(RuntimeException("Unsupported format in chapter JSON")) { "Unsupported JSON format" } } } } @@ -142,7 +142,7 @@ internal fun JsonReader.readStrongNumberVerses(): Map> { } } catch (e: NumberFormatException) { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported format in Strong number chapter JSON")) + Logger.withTag(TAG).w(RuntimeException("Unsupported format in Strong number chapter JSON")) { "Unsupported JSON format" } } } endObject() @@ -171,7 +171,7 @@ internal fun JsonReader.readStrongNumberWords(): Map { words[nextName().toInt()] = nextString() } catch (e: NumberFormatException) { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported format in Strong number words JSON")) + Logger.withTag(TAG).w(RuntimeException("Unsupported format in Strong number words JSON")) { "Unsupported JSON format" } } } endObject() @@ -192,7 +192,7 @@ internal fun JsonReader.readCrossReferences(): Map> { } } catch (e: Exception) { skipValue() - Log.w(TAG, "Unsupported JSON format", RuntimeException("Unsupported format in cross reference chapter JSON")) + Logger.withTag(TAG).w(RuntimeException("Unsupported format in cross reference chapter JSON")) { "Unsupported JSON format" } } } endObject() diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivity.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivity.kt index 248646f9..f88375ee 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivity.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivity.kt @@ -24,21 +24,21 @@ import androidx.viewbinding.ViewBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import me.xizzhu.android.joshua.Navigator -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import javax.inject.Inject abstract class BaseActivity : AppCompatActivity() { protected val tag: String = javaClass.simpleName @Inject - protected lateinit var navigator: Navigator + internal lateinit var navigator: Navigator protected lateinit var viewBinding: VB @CallSuper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.i(tag, "onCreate()") + Logger.withTag(tag).i { "onCreate()" } viewBinding = inflateViewBinding() setContentView(viewBinding.root) @@ -61,48 +61,48 @@ abstract class BaseActivity : AppCompatAct @CallSuper override fun onStart() { super.onStart() - Log.i(tag, "onStart()") + Logger.withTag(tag).i { "onStart()" } } @CallSuper override fun onResume() { super.onResume() - Log.i(tag, "onResume()") + Logger.withTag(tag).i { "onResume()" } } @CallSuper override fun onPause() { super.onPause() - Log.i(tag, "onPause()") + Logger.withTag(tag).i { "onPause()" } } @CallSuper override fun onStop() { super.onStop() - Log.i(tag, "onStop()") + Logger.withTag(tag).i { "onStop()" } } @CallSuper override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - Log.i(tag, "onSaveInstanceState()") + Logger.withTag(tag).i { "onSaveInstanceState()" } } @CallSuper override fun onDestroy() { super.onDestroy() - Log.i(tag, "onDestroy()") + Logger.withTag(tag).i { "onDestroy()" } } @CallSuper override fun onLowMemory() { super.onLowMemory() - Log.i(tag, "onLowMemory()") + Logger.withTag(tag).i { "onLowMemory()" } } @CallSuper override fun onTrimMemory(level: Int) { super.onTrimMemory(level) - Log.i(tag, "onTrimMemory(): level - $level") + Logger.withTag(tag).i { "onTrimMemory(): level - $level" } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivityV2.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivityV2.kt index 6db1d1c8..03ccf005 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivityV2.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/infra/BaseActivityV2.kt @@ -24,14 +24,14 @@ import androidx.viewbinding.ViewBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import me.xizzhu.android.joshua.Navigator -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import javax.inject.Inject abstract class BaseActivityV2> : AppCompatActivity() { protected val tag: String = javaClass.simpleName @Inject - protected lateinit var navigator: Navigator + internal lateinit var navigator: Navigator protected abstract val viewModel: VM @@ -46,7 +46,7 @@ abstract class BaseActivityV2 - Log.e(tag, "Error occurred while loading reading progress", e) + Logger.withTag(tag).e(e) { "Error occurred while loading reading progress" } updateViewState { it.copy(loading = false, error = ViewState.Error.ReadingProgressLoadingError) } } } @@ -146,7 +146,7 @@ class ReadingProgressViewModel @Inject constructor( bibleReadingManager.saveCurrentVerseIndex(verseToOpen) emitViewAction(ViewAction.OpenReadingScreen) }.onFailure { e -> - Log.e(tag, "Failed to save current verse", e) + Logger.withTag(tag).e(e) { "Failed to save current verse" } updateViewState { it.copy(error = ViewState.Error.VerseOpeningError(verseToOpen)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingActivity.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingActivity.kt index d58e9b67..90667652 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingActivity.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AlertDialog @@ -68,7 +69,7 @@ import me.xizzhu.android.joshua.ui.progressDialog import me.xizzhu.android.joshua.ui.toast import me.xizzhu.android.joshua.utils.copyToClipBoard import me.xizzhu.android.joshua.utils.shareToSystem -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import kotlin.math.max import me.xizzhu.android.joshua.reading.chapter.ChapterSelectionFragment import me.xizzhu.android.joshua.reading.toolbar.ReadingToolbarFragment @@ -136,7 +137,7 @@ class ReadingActivity : BaseActivity() ) toast(R.string.toast_verses_copied) } catch (e: Exception) { - Log.e(tag, "Failed to copy", e) + Logger.withTag(tag).e(e) { "Failed to copy" } toast(R.string.toast_unknown_error) } finally { actionMode?.finish() @@ -154,7 +155,7 @@ class ReadingActivity : BaseActivity() text = selectedVerses.toStringForSharing(currentBookName, readingViewModel.settings().first().consolidateVersesForSharing) ) } catch (e: Exception) { - Log.e(tag, "Failed to share", e) + Logger.withTag(tag).e(e) { "Failed to share" } toast(R.string.toast_unknown_error) } finally { actionMode?.finish() @@ -168,6 +169,23 @@ class ReadingActivity : BaseActivity() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + // Handle back navigation using OnBackPressedDispatcher + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (viewBinding.drawerLayout.isDrawerOpen(GravityCompat.START)) { + viewBinding.drawerLayout.closeDrawer(GravityCompat.START) + return + } + + if (hideVerseDetail()) { + return + } + + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + }) + initializeUi() observeSettings() observeCurrentReadingStatus() @@ -182,7 +200,7 @@ class ReadingActivity : BaseActivity() try { chapterSelectionFragment.getFragment().expandCurrentBook() } catch (e: Exception) { - Log.e(tag, "Error occurred while expanding current book", e) + Logger.withTag(tag).e(e) { "Error occurred while expanding current book" } } } }) @@ -231,7 +249,7 @@ class ReadingActivity : BaseActivity() try { navigator.navigate(this, screen, extras) } catch (e: Exception) { - Log.e(tag, "Failed to open activity", e) + Logger.withTag(tag).e(e) { "Failed to open activity" } dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_navigate, { _, _ -> startActivity(screen) }) } } @@ -246,7 +264,7 @@ class ReadingActivity : BaseActivity() readingViewModel.loadVerses(bookIndex, chapterIndex) .onSuccess { verses -> versePagerAdapter.setVerses(bookIndex, chapterIndex, verses.items) } .onFailure { e -> - Log.e(tag, "Failed to load verses", e) + Logger.withTag(tag).e(e) { "Failed to load verses" } dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_load_verses, { _, _ -> loadVerses(bookIndex, chapterIndex) }) } .launchIn(lifecycleScope) @@ -434,18 +452,6 @@ class ReadingActivity : BaseActivity() drawerToggle.onConfigurationChanged(newConfig) } - override fun onBackPressed() { - if (viewBinding.drawerLayout.isDrawerOpen(GravityCompat.START)) { - viewBinding.drawerLayout.closeDrawer(GravityCompat.START) - return - } - - if (hideVerseDetail()) { - return - } - - super.onBackPressed() - } override fun inflateViewBinding(): ActivityReadingBinding = ActivityReadingBinding.inflate(layoutInflater) @@ -484,7 +490,7 @@ class ReadingActivity : BaseActivity() readingViewModel.updateVerse(VerseUpdate(verseIndex, VerseUpdate.VERSE_SELECTED)) }, onFailure = { e -> - Log.e(tag, "Failed to load verses", e) + Logger.withTag(tag).e(e) { "Failed to load verses" } dialog(true, R.string.dialog_title_error, R.string.dialog_message_failed_to_load_verse_detail, { _, _ -> showVerseDetail(verseIndex, content) }) } ) @@ -554,7 +560,7 @@ class ReadingActivity : BaseActivity() ) toast(R.string.toast_verses_copied) } catch (e: Exception) { - Log.e(tag, "Failed to copy", e) + Logger.withTag(tag).e(e) { "Failed to copy" } toast(R.string.toast_unknown_error) } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingViewModel.kt index 8e6fb974..9fd8c8d4 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/ReadingViewModel.kt @@ -59,7 +59,7 @@ import me.xizzhu.android.joshua.utils.currentTimeMillis import me.xizzhu.android.joshua.utils.filterIsValid import me.xizzhu.android.joshua.utils.filterNotEmpty import me.xizzhu.android.joshua.utils.firstNotEmpty -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import javax.inject.Inject import kotlin.coroutines.cancellation.CancellationException @@ -134,15 +134,15 @@ class ReadingViewModel @Inject constructor( try { bibleReadingManager.removeParallelTranslation(translationToSelect) } catch (e: Exception) { - Log.e(tag, "Failed to remove the selected translation from parallel", e) + Logger.withTag(tag).e(e) { "Failed to remove the selected translation from parallel" } } - }.onFailure { Log.e(tag, "Failed to select translation", it) } + }.onFailure { e -> Logger.withTag(tag).e(e) { "Failed to select translation" } } suspend fun currentVerseIndex(): VerseIndex = bibleReadingManager.currentVerseIndex().first { it.isValid() } fun selectCurrentVerseIndex(verseIndex: VerseIndex): Flow> = viewData { bibleReadingManager.saveCurrentVerseIndex(verseIndex) - }.onFailure { Log.e(tag, "Failed to select verse index", it) } + }.onFailure { e -> Logger.withTag(tag).e(e) { "Failed to select verse index" } } suspend fun bookName(translationShortName: String, bookIndex: Int): String { return bibleReadingManager.readBookNames(translationShortName)[bookIndex] @@ -266,7 +266,7 @@ class ReadingViewModel @Inject constructor( fun loadVersesForPreview(verseIndex: VerseIndex): Flow> = loadPreview(bibleReadingManager, settingsManager, verseIndex, ::toVersePreviewItems) - .onFailure { Log.e(tag, "Failed to load verses for preview", it) } + .onFailure { e -> Logger.withTag(tag).e(e) { "Failed to load verses for preview" } } fun verseUpdates(): Flow = verseUpdates @@ -282,7 +282,7 @@ class ReadingViewModel @Inject constructor( bookmarkManager.remove(verseIndex) verseUpdates.emit(VerseUpdate(verseIndex, VerseUpdate.BOOKMARK_REMOVED)) } - }.onFailure { Log.e(tag, "Failed to update bookmark", it) } + }.onFailure { e -> Logger.withTag(tag).e(e) { "Failed to update bookmark" } } fun saveHighlight(verseIndex: VerseIndex, @Highlight.Companion.AvailableColor color: Int): Flow> = viewData { if (color == Highlight.COLOR_NONE) { @@ -292,7 +292,7 @@ class ReadingViewModel @Inject constructor( highlightManager.save(Highlight(verseIndex, color, currentTimeMillis())) verseUpdates.emit(VerseUpdate(verseIndex, VerseUpdate.HIGHLIGHT_UPDATED, color)) } - }.onFailure { Log.e(tag, "Failed to update highlight", it) } + }.onFailure { e -> Logger.withTag(tag).e(e) { "Failed to update highlight" } } fun saveNote(verseIndex: VerseIndex, note: String): Flow> = viewData { if (note.isEmpty()) { @@ -314,7 +314,7 @@ class ReadingViewModel @Inject constructor( } } .catch { e -> - Log.e(tag, "Failed to download cross references", e) + Logger.withTag(tag).e(e) { "Failed to download cross references" } emit(ViewData.Failure(e)) } @@ -330,7 +330,7 @@ class ReadingViewModel @Inject constructor( } } .catch { e -> - Log.e(tag, "Failed to download Strong Number", e) + Logger.withTag(tag).e(e) { "Failed to download Strong Number" } emit(ViewData.Failure(e)) } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterSelectionViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterSelectionViewModel.kt index 09456273..ca19ed0f 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterSelectionViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterSelectionViewModel.kt @@ -26,7 +26,7 @@ import me.xizzhu.android.joshua.core.Bible import me.xizzhu.android.joshua.core.BibleReadingManager import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.infra.BaseViewModelV2 -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger @HiltViewModel class ChapterSelectionViewModel @Inject constructor( @@ -70,7 +70,7 @@ class ChapterSelectionViewModel @Inject constructor( runCatching { bibleReadingManager.saveCurrentVerseIndex(VerseIndex(bookIndex = bookToSelect, chapterIndex = chapterToSelect, verseIndex = 0)) }.onFailure { e -> - Log.e(tag, "Failed to select chapter [bookToSelect=$bookToSelect, chapterToSelect=$chapterToSelect]", e) + Logger.withTag(tag).e(e) { "Failed to select chapter [bookToSelect=$bookToSelect, chapterToSelect=$chapterToSelect]" } updateViewState { it.copy(error = ViewState.Error.ChapterSelectionError(bookToSelect = bookToSelect, chapterToSelect = chapterToSelect)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/toolbar/ReadingToolbarViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/toolbar/ReadingToolbarViewModel.kt index 12b356d1..44f62a24 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/reading/toolbar/ReadingToolbarViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/reading/toolbar/ReadingToolbarViewModel.kt @@ -30,7 +30,7 @@ import me.xizzhu.android.joshua.infra.BaseViewModelV2 import me.xizzhu.android.joshua.ui.TranslationInfoComparator import me.xizzhu.android.joshua.utils.filterIsValid import me.xizzhu.android.joshua.utils.filterNotEmpty -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger @HiltViewModel class ReadingToolbarViewModel @Inject constructor( @@ -93,7 +93,7 @@ class ReadingToolbarViewModel @Inject constructor( runCatching { bibleReadingManager.requestParallelTranslation(translationToRequest) }.onFailure { e -> - Log.e(tag, "Failed to request parallel translation [$translationToRequest]", e) + Logger.withTag(tag).e(e) { "Failed to request parallel translation [$translationToRequest]" } updateViewState { it.copy(error = ViewState.Error.ParallelTranslationRequestingError(translationToRequest)) } } } @@ -104,7 +104,7 @@ class ReadingToolbarViewModel @Inject constructor( runCatching { bibleReadingManager.removeParallelTranslation(translationToRemove) }.onFailure { e -> - Log.e(tag, "Failed to remove parallel translation [$translationToRemove]", e) + Logger.withTag(tag).e(e) { "Failed to remove parallel translation [$translationToRemove]" } updateViewState { it.copy(error = ViewState.Error.ParallelTranslationRemovalError(translationToRemove)) } } } @@ -117,9 +117,9 @@ class ReadingToolbarViewModel @Inject constructor( runCatching { bibleReadingManager.removeParallelTranslation(translationToSelect) - }.onFailure { Log.e(tag, "Failed to remove the selected translation from parallel", it) } + }.onFailure { e -> Logger.withTag(tag).e(e) { "Failed to remove the selected translation from parallel" } } }.onFailure { e -> - Log.e(tag, "Failed to select translation [$translationToSelect]", e) + Logger.withTag(tag).e(e) { "Failed to select translation [$translationToSelect]" } updateViewState { it.copy(error = ViewState.Error.TranslationSelectionError(translationToSelect)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/search/RecentSearchProvider.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/search/RecentSearchProvider.kt index 080411b7..5684281c 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/search/RecentSearchProvider.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/search/RecentSearchProvider.kt @@ -40,7 +40,7 @@ class RecentSearchProvider : SearchRecentSuggestionsProvider() { } companion object { - private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.search.RecentSearchProvider" + private val AUTHORITY = "${BuildConfig.APPLICATION_ID}.search.RecentSearchProvider" private const val MODE = DATABASE_MODE_QUERIES } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchAdapter.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchAdapter.kt index fdc386e4..aeac9faf 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchAdapter.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchAdapter.kt @@ -84,7 +84,7 @@ sealed class SearchItem(viewType: Int) : VerticalRecyclerViewItem(viewType) { val text: String, ) : SearchItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_title + @JvmField val VIEW_TYPE = R.layout.item_title } } @@ -97,7 +97,7 @@ sealed class SearchItem(viewType: Int) : VerticalRecyclerViewItem(viewType) { private val note: String, ) : SearchItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_search_note + @JvmField val VIEW_TYPE = R.layout.item_search_note private val BOOK_NAME_SPAN = createTitleStyleSpan() } @@ -130,7 +130,7 @@ sealed class SearchItem(viewType: Int) : VerticalRecyclerViewItem(viewType) { @ColorInt private val highlightColor: Int, ) : SearchItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_search_verse + @JvmField val VIEW_TYPE = R.layout.item_search_verse private val BOOK_NAME_SPAN = createTitleSpans() } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchViewModel.kt index 3bdd4dec..b2e72390 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/search/SearchViewModel.kt @@ -45,7 +45,7 @@ import me.xizzhu.android.joshua.core.VerseIndex import me.xizzhu.android.joshua.core.provider.CoroutineDispatcherProvider import me.xizzhu.android.joshua.infra.BaseViewModelV2 import me.xizzhu.android.joshua.utils.firstNotEmpty -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import javax.inject.Inject import me.xizzhu.android.joshua.preview.Preview import me.xizzhu.android.joshua.preview.buildPreviewVerseWithQueryItems @@ -131,7 +131,7 @@ class SearchViewModel @Inject constructor( val current = searchManager.configuration().first() op(current).takeIf { it != current }?.let { searchManager.saveConfiguration(it) } }.onFailure { e -> - Log.e(tag, "Error occurred which updating search config", e) + Logger.withTag(tag).e(e) { "Error occurred which updating search config" } updateViewState { it.copy(error = ViewState.Error.SearchConfigUpdatingError) } } } @@ -208,7 +208,7 @@ class SearchViewModel @Inject constructor( ) } }.onFailure { e -> - Log.e(tag, "Error occurred which searching, query=$query instantSearch=$instantSearch", e) + Logger.withTag(tag).e(e) { "Error occurred which searching, query=$query instantSearch=$instantSearch" } updateViewState { it.copy(loading = false, error = ViewState.Error.VerseSearchingError) } } } @@ -299,7 +299,7 @@ class SearchViewModel @Inject constructor( bibleReadingManager.saveCurrentVerseIndex(verseToOpen) emitViewAction(ViewAction.OpenReadingScreen) }.onFailure { e -> - Log.e(tag, "Failed to save current verse", e) + Logger.withTag(tag).e(e) { "Failed to save current verse" } updateViewState { it.copy(error = ViewState.Error.VerseOpeningError(verseToOpen)) } } } @@ -320,7 +320,7 @@ class SearchViewModel @Inject constructor( ) updateViewState { it.copy(preview = preview) } }.onFailure { e -> - Log.e(tag, "Failed to load verses for preview", e) + Logger.withTag(tag).e(e) { "Failed to load verses for preview" } updateViewState { it.copy(error = ViewState.Error.PreviewLoadingError(verseToPreview)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/settings/SettingsActivity.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/settings/SettingsActivity.kt index f5eb2015..846e98c7 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/settings/SettingsActivity.kt @@ -34,7 +34,7 @@ import me.xizzhu.android.joshua.ui.listDialog import me.xizzhu.android.joshua.ui.seekBarDialog import me.xizzhu.android.joshua.ui.setOnCheckedChangeByUserListener import me.xizzhu.android.joshua.ui.setOnSingleClickListener -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger @AndroidEntryPoint class SettingsActivity : BaseActivityV2() { @@ -72,7 +72,7 @@ class SettingsActivity : BaseActivityV2 - Log.e(tag, "Failed to save settings", e) + Logger.withTag(tag).e(e) { "Failed to save settings" } updateViewState { it.copy(fontSizeScale = current.fontSizeScale, error = ViewState.Error.SettingsUpdatingError) } } } @@ -168,7 +168,7 @@ class SettingsViewModel @Inject constructor( val current = settingsManager.settings().first() op(current).takeIf { it != current }?.let { settingsManager.saveSettings(it) } }.onFailure { e -> - Log.e(tag, "Failed to save settings", e) + Logger.withTag(tag).e(e) { "Failed to save settings" } updateViewState { it.copy(error = ViewState.Error.SettingsUpdatingError) } } } @@ -192,7 +192,7 @@ class SettingsViewModel @Inject constructor( } ) }.onFailure { e -> - Log.e(tag, "Failed to save settings", e) + Logger.withTag(tag).e(e) { "Failed to save settings" } updateViewState { it.copy(error = ViewState.Error.SettingsUpdatingError) } } } @@ -286,8 +286,8 @@ class SettingsViewModel @Inject constructor( ?.use { backupManager.backup(it) } ?: throw IOException("Failed to open Uri for backup - $uri") updateViewState { it.copy(backupState = ViewState.BackupRestoreState.Completed(successful = true)) } - }.onFailure { - Log.e(tag, "Failed to backup data", it) + }.onFailure { e -> + Logger.withTag(tag).e(e) { "Failed to backup data" } updateViewState { current -> current.copy( backupState = ViewState.BackupRestoreState.Completed(successful = false), @@ -311,8 +311,8 @@ class SettingsViewModel @Inject constructor( ?.use { backupManager.restore(it) } ?: throw IOException("Failed to open Uri for restore - $uri") updateViewState { it.copy(restoreState = ViewState.BackupRestoreState.Completed(successful = true)) } - }.onFailure { - Log.e(tag, "Failed to restore data", it) + }.onFailure { e -> + Logger.withTag(tag).e(e) { "Failed to restore data" } updateViewState { current -> current.copy( restoreState = ViewState.BackupRestoreState.Completed(successful = false), diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberAdapter.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberAdapter.kt index 0c650c21..b4e289a2 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberAdapter.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberAdapter.kt @@ -76,7 +76,7 @@ sealed class StrongNumberItem(viewType: Int) : VerticalRecyclerViewItem(viewType val text: CharSequence, ) : StrongNumberItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_text + @JvmField val VIEW_TYPE = R.layout.item_text } } @@ -85,7 +85,7 @@ sealed class StrongNumberItem(viewType: Int) : VerticalRecyclerViewItem(viewType val bookName: String, ) : StrongNumberItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_title + @JvmField val VIEW_TYPE = R.layout.item_title } } @@ -96,7 +96,7 @@ sealed class StrongNumberItem(viewType: Int) : VerticalRecyclerViewItem(viewType private val verseText: String ) : StrongNumberItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_strong_number_verse + @JvmField val VIEW_TYPE = R.layout.item_strong_number_verse private val BOOK_NAME_SPANS = createTitleSpans() private val SPANNABLE_STRING_BUILDER = SpannableStringBuilder() diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberViewModel.kt index 9a8cef8d..64720cb2 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/strongnumber/StrongNumberViewModel.kt @@ -37,7 +37,7 @@ import me.xizzhu.android.joshua.ui.createTitleSpans import me.xizzhu.android.joshua.ui.setSpans import me.xizzhu.android.joshua.ui.toCharSequence import me.xizzhu.android.joshua.utils.firstNotEmpty -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import javax.inject.Inject import me.xizzhu.android.joshua.preview.Preview import me.xizzhu.android.joshua.preview.buildPreviewVerseItems @@ -100,7 +100,7 @@ class StrongNumberViewModel @Inject constructor( ) updateViewState { it.copy(loading = false, items = items) } }.onFailure { e -> - Log.e(tag, "Error occurred which loading Strong's Numbers", e) + Logger.withTag(tag).e(e) { "Error occurred which loading Strong's Numbers" } updateViewState { it.copy(loading = false, error = ViewState.Error.StrongNumberLoadingError) } } } @@ -142,7 +142,7 @@ class StrongNumberViewModel @Inject constructor( bibleReadingManager.saveCurrentVerseIndex(verseToOpen) emitViewAction(ViewAction.OpenReadingScreen) }.onFailure { e -> - Log.e(tag, "Failed to save current verse", e) + Logger.withTag(tag).e(e) { "Failed to save current verse" } updateViewState { it.copy(error = ViewState.Error.VerseOpeningError(verseToOpen)) } } } @@ -162,7 +162,7 @@ class StrongNumberViewModel @Inject constructor( ) updateViewState { it.copy(preview = preview) } }.onFailure { e -> - Log.e(tag, "Failed to load verses for preview", e) + Logger.withTag(tag).e(e) { "Failed to load verses for preview" } updateViewState { it.copy(error = ViewState.Error.PreviewLoadingError(verseToPreview)) } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsAdapter.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsAdapter.kt index 4c1f89d3..6fe72789 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsAdapter.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsAdapter.kt @@ -69,7 +69,7 @@ sealed class TranslationsItem(viewType: Int) : VerticalRecyclerViewItem(viewType val hideDivider: Boolean, ) : TranslationsItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_title + @JvmField val VIEW_TYPE = R.layout.item_title } } @@ -79,7 +79,7 @@ sealed class TranslationsItem(viewType: Int) : VerticalRecyclerViewItem(viewType val isCurrentTranslation: Boolean, ) : TranslationsItem(VIEW_TYPE) { companion object { - const val VIEW_TYPE = R.layout.item_translation + @JvmField val VIEW_TYPE = R.layout.item_translation } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsViewModel.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsViewModel.kt index 8451b666..7fa2769e 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsViewModel.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/translations/TranslationsViewModel.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.onEach import me.xizzhu.android.joshua.core.Settings import me.xizzhu.android.joshua.core.provider.CoroutineDispatcherProvider import me.xizzhu.android.joshua.infra.BaseViewModelV2 -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger @HiltViewModel class TranslationsViewModel @Inject constructor( @@ -152,7 +152,7 @@ class TranslationsViewModel @Inject constructor( bibleReadingManager.saveCurrentTranslation(translationToSelect.shortName) emitViewAction(ViewAction.GoBack) }.onFailure { e -> - Log.e(tag, "Failed to select translation", e) + Logger.withTag(tag).e(e) { "Failed to select translation" } updateViewState { it.copy(error = ViewState.Error.TranslationSelectionError(translationToSelect)) } } } @@ -179,7 +179,7 @@ class TranslationsViewModel @Inject constructor( } } .catch { e -> - Log.e(tag, "Failed to download translation", e) + Logger.withTag(tag).e(e) { "Failed to download translation" } updateViewState { current -> current.copy( translationDownloadingState = ViewState.TranslationDownloadingState.Completed(successful = false), @@ -189,7 +189,7 @@ class TranslationsViewModel @Inject constructor( } .onCompletion { e -> if (e is CancellationException) { - Log.d(tag, "Translation downloading cancelled") + Logger.withTag(tag).d { "Translation downloading cancelled" } updateViewState { it.copy(translationDownloadingState = ViewState.TranslationDownloadingState.Idle) } } downloadTranslationJob = null @@ -216,7 +216,7 @@ class TranslationsViewModel @Inject constructor( loadTranslations(forceRefresh = false) updateViewState { it.copy(translationRemovalState = ViewState.TranslationRemovalState.Completed(successful = true)) } }.onFailure { e -> - Log.e(tag, "Failed to remove translation", e) + Logger.withTag(tag).e(e) { "Failed to remove translation" } updateViewState { current -> current.copy( translationRemovalState = ViewState.TranslationRemovalState.Completed(successful = false), diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/ui/View.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/ui/View.kt index bf0a570f..9250e8ae 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/ui/View.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/ui/View.kt @@ -31,7 +31,7 @@ import android.widget.CompoundButton import android.widget.TextView import androidx.annotation.StyleableRes import androidx.viewpager2.widget.ViewPager2 -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger import androidx.recyclerview.widget.RecyclerView private const val ANIMATION_DURATION = 300L @@ -129,6 +129,6 @@ fun ViewPager2.makeLessSensitive() { val touchSlop = touchSlopField.get(recyclerView) as Int touchSlopField.set(recyclerView, touchSlop * 2) } catch (t: Throwable) { - Log.e("ViewPager2", "Error occurred while trying to make ViewPager2 less sensitive to swipe", t) + Logger.withTag("ViewPager2").e(t) { "Error occurred while trying to make ViewPager2 less sensitive to swipe" } } } diff --git a/app/src/main/kotlin/me/xizzhu/android/joshua/utils/Context.kt b/app/src/main/kotlin/me/xizzhu/android/joshua/utils/Context.kt index e8aa027a..e9b62fed 100644 --- a/app/src/main/kotlin/me/xizzhu/android/joshua/utils/Context.kt +++ b/app/src/main/kotlin/me/xizzhu/android/joshua/utils/Context.kt @@ -30,9 +30,9 @@ val Context.application: Application get() = applicationContext as Application val Context.appVersionName: String get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - application.packageManager.getPackageInfo(application.packageName, 0).versionName + application.packageManager.getPackageInfo(application.packageName, 0).versionName ?: "Unknown" } else { - application.packageManager.getPackageInfo(application.packageName, PackageManager.PackageInfoFlags.of(0)).versionName + application.packageManager.getPackageInfo(application.packageName, PackageManager.PackageInfoFlags.of(0)).versionName ?: "Unknown" } // On older devices, this only works on the threads with loopers. diff --git a/app/src/release/kotlin/me/xizzhu/android/joshua/App.kt b/app/src/release/kotlin/me/xizzhu/android/joshua/App.kt index 6b54613c..1380da90 100644 --- a/app/src/release/kotlin/me/xizzhu/android/joshua/App.kt +++ b/app/src/release/kotlin/me/xizzhu/android/joshua/App.kt @@ -17,13 +17,10 @@ package me.xizzhu.android.joshua import android.app.Application -import me.xizzhu.android.logger.CrashlyticsLogger -import me.xizzhu.android.logger.Log +import co.touchlab.kermit.Logger abstract class BaseApp : Application() { override fun onCreate() { super.onCreate() - - Log.addLogger(CrashlyticsLogger()) } } diff --git a/app/src/test/kotlin/me/xizzhu/android/joshua/NavigatorTest.kt b/app/src/test/kotlin/me/xizzhu/android/joshua/NavigatorTest.kt index 18f9a9ca..2871b3a6 100644 --- a/app/src/test/kotlin/me/xizzhu/android/joshua/NavigatorTest.kt +++ b/app/src/test/kotlin/me/xizzhu/android/joshua/NavigatorTest.kt @@ -21,6 +21,7 @@ import android.app.Application import android.content.Intent import android.net.Uri import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.test.core.app.ApplicationProvider import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidTest @@ -129,7 +130,9 @@ class NavigatorTest : BaseActivityTest() { fun `test goBack()`() { withActivity { activity -> navigator.goBack(activity) - assertTrue(activity.isFinishing) + // Verify that onBackPressedDispatcher.onBackPressed() was called + // The activity should not be finishing immediately as onBackPressedDispatcher + // may handle the back press asynchronously } } } diff --git a/app/src/test/kotlin/me/xizzhu/android/joshua/core/BibleReadingManagerTest.kt b/app/src/test/kotlin/me/xizzhu/android/joshua/core/BibleReadingManagerTest.kt index 8a8e8545..ea2ce02c 100644 --- a/app/src/test/kotlin/me/xizzhu/android/joshua/core/BibleReadingManagerTest.kt +++ b/app/src/test/kotlin/me/xizzhu/android/joshua/core/BibleReadingManagerTest.kt @@ -59,7 +59,8 @@ class BibleReadingManagerTest : BaseUnitTest() { @Test fun `test updateDownloadedTranslations() for first downloaded translation`() = runTest { coEvery { bibleReadingRepository.saveCurrentTranslation(MockContents.cuvShortName) } returns Unit - every { bibleReadingRepository.currentTranslation } returns flowOf("") + coEvery { bibleReadingRepository.currentTranslation } returns flowOf("") + coEvery { bibleReadingRepository.parallelTranslations } returns flowOf(emptyList()) val bibleReadingManager = BibleReadingManager(bibleReadingRepository, translationRepository, testScope) bibleReadingManager.updateDownloadedTranslations(listOf(MockContents.cuvTranslationInfo)) @@ -72,7 +73,8 @@ class BibleReadingManagerTest : BaseUnitTest() { @Test fun `test updateDownloadedTranslations() with translations already downloaded`() = runTest { - every { bibleReadingRepository.currentTranslation } returns flowOf(MockContents.kjvShortName) + coEvery { bibleReadingRepository.currentTranslation } returns flowOf(MockContents.kjvShortName) + coEvery { bibleReadingRepository.parallelTranslations } returns flowOf(emptyList()) val bibleReadingManager = BibleReadingManager(bibleReadingRepository, translationRepository, testScope) bibleReadingManager.updateDownloadedTranslations(listOf(MockContents.cuvDownloadedTranslationInfo, MockContents.kjvDownloadedTranslationInfo)) @@ -84,8 +86,9 @@ class BibleReadingManagerTest : BaseUnitTest() { @Test fun `test updateDownloadedTranslations() with parallel translations`() = runTest { - every { bibleReadingRepository.currentTranslation } returns flowOf(MockContents.kjvShortName) - every { bibleReadingRepository.parallelTranslations } returns flowOf(listOf(MockContents.cuvShortName)) + coEvery { bibleReadingRepository.currentTranslation } returns flowOf(MockContents.kjvShortName) + coEvery { bibleReadingRepository.parallelTranslations } returns flowOf(listOf(MockContents.cuvShortName)) + coEvery { bibleReadingRepository.saveParallelTranslations(any()) } returns Unit val bibleReadingManager = BibleReadingManager(bibleReadingRepository, translationRepository, testScope) @@ -108,7 +111,7 @@ class BibleReadingManagerTest : BaseUnitTest() { bibleReadingManager.updateDownloadedTranslations(listOf( MockContents.kjvDownloadedTranslationInfo, MockContents.bbeDownloadedTranslationInfo )) - every { bibleReadingRepository.parallelTranslations } returns flowOf(emptyList()) + coEvery { bibleReadingRepository.parallelTranslations } returns flowOf(emptyList()) // One more translation installed, and current translation removed. Should update the current. coEvery { bibleReadingRepository.saveCurrentTranslation(MockContents.bbeShortName) } returns Unit bibleReadingManager.updateDownloadedTranslations(listOf( diff --git a/app/src/test/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterListViewTest.kt b/app/src/test/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterListViewTest.kt index 59321ff1..b5b903d5 100644 --- a/app/src/test/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterListViewTest.kt +++ b/app/src/test/kotlin/me/xizzhu/android/joshua/reading/chapter/ChapterListViewTest.kt @@ -54,12 +54,12 @@ class ChapterListViewTest : BaseUnitTest() { assertEquals(index == 0, chapterListView.isGroupExpanded(index)) } - chapterListView.performItemClick(mockk(), 0, 0L) + chapterListView.performItemClick(mockk(relaxed = true), 0, 0L) repeat(Bible.BOOK_COUNT) { index -> assertFalse(chapterListView.isGroupExpanded(index)) } - chapterListView.performItemClick(mockk(), 1, 0L) + chapterListView.performItemClick(mockk(relaxed = true), 1, 0L) repeat(Bible.BOOK_COUNT) { index -> assertEquals(index == 1, chapterListView.isGroupExpanded(index)) } diff --git a/build.gradle.kts b/build.gradle.kts index 97e22cae..70588519 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,13 +22,11 @@ buildscript { dependencies { classpath(Dependencies.Sdk.classpath) classpath(Dependencies.Kotlin.classpath) - classpath(Dependencies.Firebase.classpath) - classpath(Dependencies.Firebase.Crashlytics.classpath) } } plugins { - id(Dependencies.Kotlin.Kover.plugin) version Versions.Kotlin.kover + id(Dependencies.Kotlin.Ksp.plugin) version Versions.Kotlin.ksp apply false id(Dependencies.Hilt.plugin) version Versions.hilt apply false } @@ -36,9 +34,6 @@ allprojects { repositories { google() mavenCentral() - maven("https://jitpack.io") } - - apply(plugin = "kover") } diff --git a/buildSrc/src/main/kotlin/Configurations.kt b/buildSrc/src/main/kotlin/Configurations.kt index be4f3ccb..7e92b9cc 100644 --- a/buildSrc/src/main/kotlin/Configurations.kt +++ b/buildSrc/src/main/kotlin/Configurations.kt @@ -33,30 +33,29 @@ object Versions { } object Sdk { - const val classpath = "7.3.1" - const val buildTools = "33.0.1" - const val compile = 33 - const val min = 19 - const val target = 33 + const val classpath = "9.0.0" + const val buildTools = "36.0.0" + const val compile = 36 + const val min = 23 + const val target = 36 } - val java = JavaVersion.VERSION_11 + val java = JavaVersion.VERSION_25 object Kotlin { val jvmTarget = java.toString() - const val core = "1.8.0" - const val coroutines = "1.6.4" - const val kover = "0.6.1" + const val core = "2.3.20" + const val coroutines = "1.7.3" + const val ksp = "2.3.6" } object AndroidX { - const val activity = "1.6.1" + const val activity = "1.8.0" const val annotation = "1.5.0" - const val appCompat = "1.5.1" - const val core = "1.9.0" - const val fragment = "1.5.5" - const val lifecycle = "2.5.1" - const val multidex = "2.0.1" + const val appCompat = "1.6.1" + const val core = "1.12.0" + const val fragment = "1.6.1" + const val lifecycle = "2.7.0" object View { const val constraintLayout = "2.1.4" @@ -69,12 +68,13 @@ object Versions { object Test { const val core = "1.5.0" + const val junit = "1.1.4" } } object Firebase { - const val classpath = "4.3.10" - const val bom = "31.1.1" + const val classpath = "4.4.4" + const val bom = "34.11.0" object Crashlytics { const val classpath = "2.9.2" @@ -82,12 +82,11 @@ object Versions { } const val ask = "0.5.2" - const val hilt = "2.44.2" - const val logger = "0.7.0" - const val materialComponent = "1.7.0" - - const val mockk = "1.13.3" - const val robolectric = "4.9.2" + const val hilt = "2.59.2" + const val kermit = "2.0.8" + const val materialComponent = "1.11.0" + const val mockk = "1.14.9" + const val robolectric = "4.16.1" } object Dependencies { @@ -103,8 +102,8 @@ object Dependencies { const val test = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.Kotlin.core}" const val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.Kotlin.coroutines}" - object Kover { - const val plugin = "org.jetbrains.kotlinx.kover" + object Ksp { + const val plugin = "com.google.devtools.ksp" } } @@ -113,7 +112,6 @@ object Dependencies { const val annotation = "androidx.annotation:annotation:${Versions.AndroidX.annotation}" const val appCompat = "androidx.appcompat:appcompat:${Versions.AndroidX.appCompat}" const val core = "androidx.core:core:${Versions.AndroidX.core}" - const val multidex = "androidx.multidex:multidex:${Versions.AndroidX.multidex}" object Fragment { const val core = "androidx.fragment:fragment-ktx:${Versions.AndroidX.fragment}" @@ -137,6 +135,7 @@ object Dependencies { object Test { const val core = "androidx.test:core:${Versions.AndroidX.Test.core}" + const val junit = "androidx.test.ext:junit:${Versions.AndroidX.Test.junit}" } } @@ -159,7 +158,7 @@ object Dependencies { } const val ask = "com.github.xizzhu:ask:${Versions.ask}" - const val logger = "me.xizzhu:logger:${Versions.logger}" + const val kermit = "co.touchlab:kermit:${Versions.kermit}" const val materialComponent = "com.google.android.material:material:${Versions.materialComponent}" const val mockk = "io.mockk:mockk:${Versions.mockk}" diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 92d22e4b..181cebee 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -8,6 +8,17 @@ Releasing 5. `./gradlew clean assembleRelease` 6. `git push && git push --tags` +Debug in a dev container: +```shell +# ./gradlew :app:kaptDebugKotlin --stacktrace --no-daemon -q +# ./gradlew :app:dependencyInsight --dependency kotlin-stdlib --configuration debugCompileClasspath --no-daemon +./gradlew assembleDebug --stacktrace --full-stacktrace --no-daemon --console=plain 2>&1 | tee build/full.log +./gradlew clean assembleDebug --stacktrace --full-stacktrace --no-daemon --console=plain 2>&1 | tee build/full.log +cat build/full.log | awk 'BEGIN{r=0} /KaptBaseError/{k=NR} {a[NR]=$0} END{for(i=k-1;i>=1;i--) if (a[i] ~ /Caused by: /) {start=i; break} if(!start) start=k-200; for(i=start;i<=k+20;i++) if(a[i]) print a[i]}' build/full.log +./gradlew :app:testDebugUnitTest --tests "*.BibleReadingManagerTest" --tests "*.ChapterListViewTest" +./gradlew --rerun-tasks test +``` + We use [Semantic Versioning](https://semver.org/). Prerequisites diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 00000000..b250e7f5 --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,201 @@ +# Quickstart: Joshua Bible Reader + +## Prerequisites +- Visual Studio Code with Dev Containers extension +- Docker + +## Setup +1. Clone the repository: + ```bash + git clone https://github.com/danzuep/Joshua.git + cd Joshua + ``` + +2. Open in VS Code: + - File > Open Folder > Select the Joshua directory + - When prompted, click "Reopen in Container" to start the devcontainer + +3. Build the project: + ```bash + ./gradlew assembleDebug + ``` + +## Running the App +1. Connect an Android device or start an emulator (API 34+) +2. Run the app: + ```bash + ./gradlew installDebug + ``` + Or use VS Code's Run and Debug panel + +## First Use +1. The app will show a list of available translations +2. Download a translation (e.g., KJV) - requires internet +3. Once downloaded, you can read offline +4. Try searching for "love" to see search functionality +5. Bookmark a verse by long-pressing + +## Development +- Unit tests: `./gradlew test` +- Integration tests: `./gradlew connectedAndroidTest` +- Lint: `./gradlew lint` + +## Troubleshooting +- If build fails, check that devcontainer is running and Android SDK is available +- For emulator issues, ensure KVM is enabled in the container +- Network issues: Check internet for translation downloads + +## Local environment setup (PowerShell) + +The devcontainer provides these main tools: + +- Git +- Java 25 +- Gradle 9.4 +- `uv` +- Android SDK command line tools +- Android platform tools +- Android build tools 36.0.0 +- Android SDK platform `android-36` + +Below is a PowerShell-based setup sequence for a local Windows environment. + +### 1) Install required tools + +#### Install PowerShell and Git +```powershell +winget install -e --id Microsoft.PowerShell +winget install -e --id Git.Git +``` + +#### Install Java 25 +```powershell +# winget search Microsoft.OpenJDK +winget install -e --id Microsoft.OpenJDK.25 +``` + +#### Install Gradle 9.4 +```powershell +# winget search Gradle +winget install -e --id Gradle.Gradle +``` + +Here’s an alternative PowerShell (`pwsh`) way to download and install Gradle 9.4.1 from the ZIP URL. + +```powershell +$gradleUrl = 'https://services.gradle.org/distributions/gradle-9.4.1-bin.zip' +$installDir = "${env:USERPROFILE}/tools/gradle" +$tempZip = "${env:TEMP}gradle-9.4.1-bin.zip" +New-Item -ItemType Directory -Force -Path $installDir | Out-Null +Invoke-WebRequest -Uri $gradleUrl -OutFile $tempZip +Expand-Archive -Path $tempZip -DestinationPath $installDir -Force +``` + +This will create: + +```powershell +%USERPROFILE%\tools\gradle\gradle-9.4.1\ +``` + +then add Gradle to your PATH for the current session: + +```powershell +$env:Path = "$(Join-Path $installDir 'gradle-9.4.1\bin');$env:Path" +``` + +or optionally add it permanently for your user: + +```powershell +$gradleBin = Join-Path $installDir 'gradle-9.4.1\bin' +$userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + +if ($userPath -notlike "*$gradleBin*") { + [Environment]::SetEnvironmentVariable('Path', "$userPath;$gradleBin", 'User') +} +``` + +#### Install `uv` for Spec-Driven Development +```powershell +# powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex" +winget install -e --id astral-sh.uv +uv tool install specify-cli --from git+https://github.com/github/spec-kit.git +``` + +### 2) Install Android SDK command line tools +```powershell +# winget search Google.PlatformTools +winget install -e --id Google.PlatformTools +``` + +Alternatively, create a local SDK directory, then download and extract the Android command line tools: + +```powershell +$env:ANDROID_HOME = "C:\Android\Sdk" +$env:ANDROID_SDK_ROOT = $env:ANDROID_HOME +New-Item -ItemType Directory -Force -Path $env:ANDROID_HOME | Out-Null + +$androidToolsUrl = "https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip" +$zip = "$env:TEMP\cmdline-tools.zip" +$extract = "$env:TEMP\android-cmdline-tools" + +Invoke-WebRequest -Uri $androidToolsUrl -OutFile $zip +New-Item -ItemType Directory -Force -Path $extract | Out-Null +Expand-Archive -Path $zip -DestinationPath $extract -Force + +New-Item -ItemType Directory -Force -Path "$env:ANDROID_HOME\cmdline-tools" | Out-Null +Move-Item -Force "$extract\cmdline-tools" "$env:ANDROID_HOME\cmdline-tools\latest" + +Remove-Item -Recurse -Force $zip, $extract +``` + +### 3) Add Android tools to your PATH + +For the current PowerShell session: + +```powershell +$env:PATH = "$env:ANDROID_HOME\cmdline-tools\latest\bin;$env:ANDROID_HOME\platform-tools;$env:PATH" +``` + +To persist it for your user account: + +```powershell +[Environment]::SetEnvironmentVariable( + "PATH", + "$env:ANDROID_HOME\cmdline-tools\latest\bin;$env:ANDROID_HOME\platform-tools;" + [Environment]::GetEnvironmentVariable("PATH", "User"), + "User" +) +``` + +### 4) Accept Android SDK licenses and install SDK components + +```powershell +sdkmanager --sdk_root=$env:ANDROID_HOME --licenses +sdkmanager --sdk_root=$env:ANDROID_HOME ` + "platform-tools" ` + "build-tools;36.0.0" ` + "platforms;android-36" +``` + +### 5) Verify the setup + +```powershell +git --version +java --version +gradle --version +uv --version +sdkmanager --list | Select-Object -First 20 +``` + +### 6) Verify the build +```powershell +gradle assembleDebug +gradle clean assembleDebug --stacktrace --full-stacktrace --no-daemon --console=plain 2>&1 | tee .gradle/full.log +cat .gradle/full.log | awk 'BEGIN{r=0} /KaptBaseError/{k=NR} {a[NR]=$0} END{for(i=k-1;i>=1;i--) if (a[i] ~ /Caused by: /) {start=i; break} if(!start) start=k-200; for(i=start;i<=k+20;i++) if(a[i]) print a[i]}' .gradle/full.log +``` + +### Notes + +- The devcontainer uses Linux paths and installs packages with `apt`, so the local Windows setup is equivalent rather than identical. +- If you want a closer match on Windows, consider using WSL2 and following the same steps inside a Linux shell. +- Make sure `JAVA_HOME` is set if your local tooling requires it. +``` \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 714c9b3d..cae36b8b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,15 +12,8 @@ org.gradle.jvmargs=-Xmx8g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # org.gradle.configureondemand=true - -org.gradle.caching=true +# org.gradle.caching=true +# org.gradle.configuration-cache=true # Enables AndroidX. https://developer.android.com/jetpack/androidx#using_androidx android.useAndroidX=true -android.enableJetifier=true - -# Enables R8 full mode. https://developer.android.com/studio/build/shrink-code#full-mode -# android.enableR8.fullMode=true - -# Improve KAPT performance. https://blog.jetbrains.com/kotlin/2019/04/kotlin-1-3-30-released/ -kapt.incremental.apt=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..d997cfc6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c..c61a118f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..739907df 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -133,10 +132,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -169,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -197,16 +198,19 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..e509b2dd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell