From d3a5315bbb18dc621ec55aa636acd603ea5a15bc Mon Sep 17 00:00:00 2001 From: Aliaksei Ivanouski Date: Mon, 6 Jul 2026 22:44:55 +0200 Subject: [PATCH 1/3] Add optional crash reporting --- .gitignore | 5 +- PRIVACY-POLICY.md | 4 +- app/build.gradle.kts | 11 ++++ .../passnotes/injection/DebugModuleBuilder.kt | 3 +- .../passnotes/injection/DebugModuleBuilder.kt | 6 +- app/src/main/AndroidManifest.xml | 6 +- .../kotlin/com/ivanovsky/passnotes/App.kt | 18 +++++- .../data/repository/settings/Settings.kt | 1 + .../data/repository/settings/SettingsImpl.kt | 12 ++++ .../settings/app/AppSettingsInteractor.kt | 13 +++- .../CrashReporterInteractor.kt | 45 ++++++++++++++ .../FileLogger.kt | 2 +- .../FileLoggerTree.kt | 2 +- .../FileSystemFacade.kt | 2 +- .../LoggerInteractor.kt | 2 +- .../injection/DefaultModuleBuilder.kt | 8 ++- .../passnotes/injection/modules/CoreModule.kt | 60 ++++++++++--------- .../passnotes/injection/modules/UiModule.kt | 2 +- .../settings/app/AppSettingsFragment.kt | 23 ++++++- .../settings/app/AppSettingsViewModel.kt | 8 +++ app/src/main/res/values/strings.xml | 5 +- .../res/values/untranslatable-strings.xml | 3 +- app/src/main/res/xml/application_settings.xml | 9 ++- .../FileLoggerImplTest.kt | 8 +-- .../MockFileSystemFacade.kt | 2 +- .../MockLogGenerator.kt | 8 +-- gradle/libs.versions.toml | 4 +- 27 files changed, 208 insertions(+), 64 deletions(-) create mode 100644 app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/CrashReporterInteractor.kt rename app/src/main/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/FileLogger.kt (99%) rename app/src/main/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/FileLoggerTree.kt (96%) rename app/src/main/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/FileSystemFacade.kt (97%) rename app/src/main/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/LoggerInteractor.kt (98%) rename app/src/test/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/FileLoggerImplTest.kt (95%) rename app/src/test/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/MockFileSystemFacade.kt (97%) rename app/src/test/kotlin/com/ivanovsky/passnotes/domain/{logger => loggingAndReporting}/MockLogGenerator.kt (84%) diff --git a/.gitignore b/.gitignore index 8693e3e8..582e8863 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,7 @@ project.properties .lsp/ .clj-kondo/ .cpcache/ -.nrepl-port \ No newline at end of file +.nrepl-port + +keepass-rs/ +keepass-rs-android/ diff --git a/PRIVACY-POLICY.md b/PRIVACY-POLICY.md index 6a17efca..2a3f4e69 100644 --- a/PRIVACY-POLICY.md +++ b/PRIVACY-POLICY.md @@ -14,7 +14,7 @@ For a better experience, while using our Service, I may require you to provide u **Log Data** -I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. +If you enable crash reporting in the application settings, then in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. Crash reporting is disabled by default. **Cookies** @@ -49,7 +49,7 @@ I do not knowingly collect personally identifiable information from children. I I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. -This policy is effective as of 2022-10-22 +This policy is effective as of 2026-07-06 **Contact Us** diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9cb8b62..5744a512 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,6 +29,7 @@ val ciPropertiesFile = file("../ci.properties") val propertyKeystorePassword = "KEYSTORE_PASSWORD" val propertyKeystoreAlias = "KEYSTORE_ALIAS" +val propertySentryDsn = "SENTRY_DSN" val debugKeyPath = "../keys/debug.keystore" val releaseKeyPath = "../keys/release.keystore" @@ -51,6 +52,10 @@ fun getCustomProperty(key: String): String { return customProps.getProperty(key) ?: System.getenv(key).orEmpty() } +fun String.toBuildConfigString(): String { + return "\"${replace("\\", "\\\\").replace("\"", "\\\"")}\"" +} + val generateVersionPropertiesFile by tasks.registering { description = "Generates version.properties for F-Droid." @@ -91,6 +96,11 @@ android { versionCode = formattedVersionCode.toInt() versionName = formattedVersionName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField( + "String", + "SENTRY_DSN", + getCustomProperty(propertySentryDsn).toBuildConfigString() + ) vectorDrawables { useSupportLibrary = true @@ -310,6 +320,7 @@ dependencies { // Logging implementation(libs.timber) implementation(libs.treessence) + implementation(libs.sentry.android) // KeePass implementation(libs.kotpass) diff --git a/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt b/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt index 1feb89bc..ebb3cdb2 100644 --- a/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt +++ b/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt @@ -15,6 +15,7 @@ import org.koin.core.module.Module class DebugModuleBuilder( private val context: Context, private val loggerInteractor: LoggerInteractor, + private val crashReporterInteractor: CrashReporterInteractor, private val settings: Settings ) : DIModuleBuilder { @@ -25,7 +26,7 @@ class DebugModuleBuilder( val isFakeFileSystemEnabled = settings.testToggles?.isFakeFileSystemEnabled ?: false return listOf( - CoreModule.build(loggerInteractor), + CoreModule.build(loggerInteractor, crashReporterInteractor), DebugModule.build(), DebugFileSystemProvidersModule.build( isExternalStorageAccessEnabled = isExternalStorageAccessEnabled, diff --git a/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt b/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt index c25c02a0..644c450c 100644 --- a/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt +++ b/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt @@ -2,7 +2,8 @@ package com.ivanovsky.passnotes.injection import android.content.Context import com.ivanovsky.passnotes.data.repository.settings.Settings -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.injection.modules.CoreModule import com.ivanovsky.passnotes.injection.modules.UiModule import com.ivanovsky.passnotes.injection.modules.UseCaseModule @@ -15,6 +16,7 @@ import org.koin.core.module.Module class DebugModuleBuilder( private val context: Context, private val loggerInteractor: LoggerInteractor, + private val crashReporterInteractor: CrashReporterInteractor, private val settings: Settings ) : DIModuleBuilder { @@ -24,7 +26,7 @@ class DebugModuleBuilder( val isFakeFileSystemEnabled = settings.testToggles?.isFakeFileSystemEnabled ?: false return listOf( - CoreModule.build(loggerInteractor), + CoreModule.build(loggerInteractor, crashReporterInteractor), DebugModule.build(), DebugFileSystemProvidersModule.build( isExternalStorageAccessEnabled = isExternalStorageAccessEnabled, diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8ced7136..3a5f79b2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,10 @@ android:theme="@style/AppTheme" android:networkSecurityConfig="@xml/network_security_config"> + + - \ No newline at end of file + diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt index 51cb4fce..88e02dcf 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt @@ -4,7 +4,8 @@ import android.app.Application import android.content.Context import com.ivanovsky.passnotes.data.repository.settings.Settings import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.injection.DIModuleBuilder import com.ivanovsky.passnotes.injection.DefaultModuleBuilder import org.koin.android.ext.koin.androidContext @@ -21,6 +22,11 @@ open class App : Application() { super.onCreate() val settings = SettingsImpl(context = this) + val crashReporterInteractor = CrashReporterInteractor(context = this) + .apply { + initialize(settings) + } + val loggerInteractor = LoggerInteractor(context = this, settings) .apply { initialize() @@ -32,12 +38,18 @@ open class App : Application() { val constructor = type.getConstructor( Context::class.java, LoggerInteractor::class.java, + CrashReporterInteractor::class.java, Settings::class.java ) - constructor.newInstance(this, loggerInteractor, settings) as DIModuleBuilder + constructor.newInstance( + this, + loggerInteractor, + crashReporterInteractor, + settings + ) as DIModuleBuilder } else { - DefaultModuleBuilder(loggerInteractor) + DefaultModuleBuilder(loggerInteractor, crashReporterInteractor) } configureModuleBuilder(moduleBuilder) diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/Settings.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/Settings.kt index 4ac46a5b..0383e5c8 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/Settings.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/Settings.kt @@ -18,6 +18,7 @@ interface Settings { var isLockNotificationDialogEnabled: Boolean var isLockDatabaseOnBack: Boolean var isFileLogEnabled: Boolean + var isCrashReportingEnabled: Boolean var searchOptions: SearchOptions var isActivateSearchOnStart: Boolean var sortType: SortType diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/SettingsImpl.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/SettingsImpl.kt index d3a111d1..6aec4dc1 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/SettingsImpl.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/data/repository/settings/SettingsImpl.kt @@ -16,6 +16,7 @@ import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.GIT_US import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.GIT_USER_NAME import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_ACTIVATE_SEARCH_ON_START import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_BIOMETRIC_UNLOCK_ENABLED +import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_CRASH_REPORTING_ENABLED import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_EXTERNAL_STORAGE_CACHE_ENABLED import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_FILE_LOG_ENABLED import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_GROUPS_AT_START_ENABLED @@ -101,6 +102,12 @@ class SettingsImpl(private val context: Context) : Settings { putBoolean(IS_FILE_LOG_ENABLED, value) } + override var isCrashReportingEnabled: Boolean + get() = getBoolean(IS_CRASH_REPORTING_ENABLED) + set(value) { + putBoolean(IS_CRASH_REPORTING_ENABLED, value) + } + override var isBiometricUnlockEnabled: Boolean get() = getBoolean(IS_BIOMETRIC_UNLOCK_ENABLED) set(value) { @@ -318,6 +325,11 @@ class SettingsImpl(private val context: Context) : Settings { type = BOOLEAN, defaultValue = false ), + IS_CRASH_REPORTING_ENABLED( + keyId = R.string.pref_is_crash_reporting_enabled, + type = BOOLEAN, + defaultValue = false + ), IS_POSTPONED_SYNC_ENABLED( keyId = R.string.pref_is_postponed_sync_enabled, type = BOOLEAN, diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/interactor/settings/app/AppSettingsInteractor.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/interactor/settings/app/AppSettingsInteractor.kt index ad4e4c32..6b2a3210 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/interactor/settings/app/AppSettingsInteractor.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/interactor/settings/app/AppSettingsInteractor.kt @@ -6,7 +6,8 @@ import com.ivanovsky.passnotes.data.entity.OperationError.newGenericIOError import com.ivanovsky.passnotes.data.entity.OperationResult import com.ivanovsky.passnotes.domain.DispatcherProvider import com.ivanovsky.passnotes.domain.entity.exception.Stacktrace -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.domain.usecases.LockDatabaseUseCase import com.ivanovsky.passnotes.domain.usecases.RemoveBiometricDataUseCase import java.io.File @@ -16,13 +17,21 @@ class AppSettingsInteractor( private val loggerInteractor: LoggerInteractor, private val dispatchers: DispatcherProvider, private val lockUseCase: LockDatabaseUseCase, - private val removeBiometricDataUseCase: RemoveBiometricDataUseCase + private val removeBiometricDataUseCase: RemoveBiometricDataUseCase, + private val crashReporterInteractor: CrashReporterInteractor ) { + fun isCrashReportingAvailable(): Boolean = + crashReporterInteractor.isAvailable + fun reInitializeLogging() { loggerInteractor.initialize() } + fun setCrashReportingEnabled(isEnabled: Boolean) { + crashReporterInteractor.setEnabled(isEnabled) + } + fun lockDatabase() { lockUseCase.lockIfNeed() } diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/CrashReporterInteractor.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/CrashReporterInteractor.kt new file mode 100644 index 00000000..fe9522d2 --- /dev/null +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/CrashReporterInteractor.kt @@ -0,0 +1,45 @@ +package com.ivanovsky.passnotes.domain.loggingAndReporting + +import android.content.Context +import com.ivanovsky.passnotes.BuildConfig +import com.ivanovsky.passnotes.data.repository.settings.Settings +import io.sentry.Sentry +import io.sentry.android.core.SentryAndroid + +class CrashReporterInteractor( + private val context: Context +) { + + val isAvailable: Boolean = BuildConfig.SENTRY_DSN.isNotBlank() + + fun initialize(settings: Settings) { + setEnabled(settings.isCrashReportingEnabled) + } + + fun setEnabled(isEnabled: Boolean) { + if (!isEnabled || !isAvailable) { + Sentry.close() + return + } + + if (Sentry.isEnabled()) { + return + } + + SentryAndroid.init(context.applicationContext) { options -> + options.setDsn(BuildConfig.SENTRY_DSN) + options.dist = BuildConfig.VERSION_CODE.toString() + options.environment = "${BuildConfig.FLAVOR}-${BuildConfig.BUILD_TYPE}" + options.release = buildReleaseName() + } + } + + private fun buildReleaseName(): String = + buildString { + append(BuildConfig.APPLICATION_ID) + append('@') + append(BuildConfig.VERSION_NAME) + append('+') + append(BuildConfig.VERSION_CODE) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLogger.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLogger.kt similarity index 99% rename from app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLogger.kt rename to app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLogger.kt index a8bab948..a1c66c26 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLogger.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLogger.kt @@ -1,4 +1,4 @@ -package com.ivanovsky.passnotes.domain.logger +package com.ivanovsky.passnotes.domain.loggingAndReporting import android.util.Log import java.io.File diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLoggerTree.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLoggerTree.kt similarity index 96% rename from app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLoggerTree.kt rename to app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLoggerTree.kt index 7210e10f..a9747667 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileLoggerTree.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileLoggerTree.kt @@ -1,4 +1,4 @@ -package com.ivanovsky.passnotes.domain.logger +package com.ivanovsky.passnotes.domain.loggingAndReporting import android.util.Log import java.io.File diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileSystemFacade.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileSystemFacade.kt similarity index 97% rename from app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileSystemFacade.kt rename to app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileSystemFacade.kt index ea1adcea..d4429cff 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/FileSystemFacade.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/FileSystemFacade.kt @@ -1,4 +1,4 @@ -package com.ivanovsky.passnotes.domain.logger +package com.ivanovsky.passnotes.domain.loggingAndReporting import java.io.BufferedWriter import java.io.File diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/LoggerInteractor.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/LoggerInteractor.kt similarity index 98% rename from app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/LoggerInteractor.kt rename to app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/LoggerInteractor.kt index 3dbc4b65..575216f7 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/domain/logger/LoggerInteractor.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/domain/loggingAndReporting/LoggerInteractor.kt @@ -1,4 +1,4 @@ -package com.ivanovsky.passnotes.domain.logger +package com.ivanovsky.passnotes.domain.loggingAndReporting import android.content.Context import android.util.Log diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt index 3b2f54bc..ea797566 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt @@ -1,6 +1,7 @@ package com.ivanovsky.passnotes.injection -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.injection.modules.BiometricModule import com.ivanovsky.passnotes.injection.modules.CoreModule import com.ivanovsky.passnotes.injection.modules.FileSystemProvidersModule @@ -9,14 +10,15 @@ import com.ivanovsky.passnotes.injection.modules.UseCaseModule import org.koin.core.module.Module class DefaultModuleBuilder( - private val loggerInteractor: LoggerInteractor + private val loggerInteractor: LoggerInteractor, + private val crashReporterInteractor: CrashReporterInteractor ) : DIModuleBuilder { override var isExternalStorageAccessEnabled: Boolean = false override fun buildModules(): List { return listOf( - CoreModule.build(loggerInteractor), + CoreModule.build(loggerInteractor, crashReporterInteractor), FileSystemProvidersModule.build( isExternalStorageAccessEnabled = isExternalStorageAccessEnabled ), diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt index c682baa8..2a1226e9 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt @@ -21,42 +21,46 @@ import com.ivanovsky.passnotes.domain.NoteDiffer import com.ivanovsky.passnotes.domain.PermissionHelper import com.ivanovsky.passnotes.domain.ResourceProvider import com.ivanovsky.passnotes.domain.interactor.SelectionHolder -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.presentation.core.ThemeProvider import org.koin.dsl.module object CoreModule { - fun build(loggerInteractor: LoggerInteractor) = - module { - single { loggerInteractor } - single { ThemeProvider(get()) } - single { ResourceProvider(get(), get()) } - single { PermissionHelper(get()) } - single { LocaleProvider(get()) } - single { DispatcherProvider() } - single { ObserverBus() } - single { DateFormatProvider(get(), get()) } - single { DateFormatter(get()) } - single { NoteDiffer() } - single { SelectionHolder() } - single { SettingsImpl(get()) } - single { DataCipherProviderImpl(get()) } - single { FileHelper(get(), get()) } - single { SAFHelper(get()) } + fun build( + loggerInteractor: LoggerInteractor, + crashReporterInteractor: CrashReporterInteractor + ) = module { + single { loggerInteractor } + single { ThemeProvider(get()) } + single { ResourceProvider(get(), get()) } + single { PermissionHelper(get()) } + single { LocaleProvider(get()) } + single { DispatcherProvider() } + single { ObserverBus() } + single { DateFormatProvider(get(), get()) } + single { DateFormatter(get()) } + single { NoteDiffer() } + single { SelectionHolder() } + single { SettingsImpl(get()) } + single { crashReporterInteractor } + single { DataCipherProviderImpl(get()) } + single { FileHelper(get(), get()) } + single { SAFHelper(get()) } - // Database - single { AppDatabase.buildDatabase(get(), get()) } - single { provideRemoteFileRepository(get()) } - single { provideUsedFileRepository(get(), get()) } - single { provideGitRootDao(get()) } + // Database + single { AppDatabase.buildDatabase(get(), get()) } + single { provideRemoteFileRepository(get()) } + single { provideUsedFileRepository(get(), get()) } + single { provideGitRootDao(get()) } - // Files, Keepass - single { DatabaseSyncStateProvider(get(), get(), get()) } - single { - KeepassDatabaseRepository(get(), get(), get(), get()) - } + // Files, Keepass + single { DatabaseSyncStateProvider(get(), get(), get()) } + single { + KeepassDatabaseRepository(get(), get(), get(), get()) } + } private fun provideRemoteFileRepository(database: AppDatabase) = RemoteFileRepository(database.remoteFileDao) diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/UiModule.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/UiModule.kt index 58228fd3..c6cf5070 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/UiModule.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/UiModule.kt @@ -168,7 +168,7 @@ object UiModule { single { ServerLoginInteractor(get(), get(), get()) } single { MainSettingsInteractor(get()) } single { DatabaseSettingsInteractor(get(), get()) } - single { AppSettingsInteractor(get(), get(), get(), get()) } + single { AppSettingsInteractor(get(), get(), get(), get(), get()) } single { AutofillInteractor(get(), get()) } single { MainInteractor(get()) } single { LockServiceInteractor(get(), get(), get(), get(), get()) } diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsFragment.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsFragment.kt index 4afc5e09..938076ae 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsFragment.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsFragment.kt @@ -16,6 +16,7 @@ import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.AUTO_C import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.AUTO_LOCK_DELAY_IN_MS import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_ACTIVATE_SEARCH_ON_START import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_BIOMETRIC_UNLOCK_ENABLED +import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_CRASH_REPORTING_ENABLED import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_FILE_LOG_ENABLED import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_LOCK_DATABASE_ON_BACK import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl.Pref.IS_LOCK_NOTIFICATION_VISIBLE @@ -42,6 +43,7 @@ class AppSettingsFragment : BasePreferenceFragment(), PermissionRequestResultRec private val viewModel: AppSettingsViewModel by viewModel() private lateinit var isFileLogEnabledPref: SwitchPreferenceCompat + private lateinit var isCrashReportingEnabledPref: SwitchPreferenceCompat private lateinit var isPostponedSyncEnabledPref: SwitchPreferenceCompat private lateinit var isBiometricUnlockEnabledPref: SwitchPreferenceCompat private lateinit var sendLogFilePref: Preference @@ -83,6 +85,7 @@ class AppSettingsFragment : BasePreferenceFragment(), PermissionRequestResultRec AUTO_LOCK_DELAY_IN_MS, AUTO_CLEAR_CLIPBOARD_DELAY_IN_MS, IS_FILE_LOG_ENABLED, + IS_CRASH_REPORTING_ENABLED, IS_POSTPONED_SYNC_ENABLED, IS_BIOMETRIC_UNLOCK_ENABLED, IS_ACTIVATE_SEARCH_ON_START @@ -100,6 +103,11 @@ class AppSettingsFragment : BasePreferenceFragment(), PermissionRequestResultRec isFileLogEnabledPref = findPreference(getString(R.string.pref_is_file_log_enabled)) ?: throwPreferenceNotFound(R.string.pref_is_file_log_enabled) + isCrashReportingEnabledPref = findPreference( + getString(R.string.pref_is_crash_reporting_enabled) + ) + ?: throwPreferenceNotFound(R.string.pref_is_crash_reporting_enabled) + isPostponedSyncEnabledPref = findPreference( getString(R.string.pref_is_postponed_sync_enabled) ) @@ -129,14 +137,19 @@ class AppSettingsFragment : BasePreferenceFragment(), PermissionRequestResultRec categoryNotifications = findPreference(getString(R.string.pref_category_notifications)) ?: throwPreferenceNotFound(R.string.pref_category_notifications) - categoryLogging = findPreference(getString(R.string.pref_category_logging)) - ?: throwPreferenceNotFound(R.string.pref_category_logging) + categoryLogging = findPreference(getString(R.string.pref_category_reporting)) + ?: throwPreferenceNotFound(R.string.pref_category_reporting) isFileLogEnabledPref.setOnPreferenceChangeListener { _, newValue -> viewModel.onFileLogEnabledChanged(newValue as Boolean) true } + isCrashReportingEnabledPref.setOnPreferenceChangeListener { _, newValue -> + viewModel.onCrashReportingEnabledChanged(newValue as Boolean) + true + } + isPostponedSyncEnabledPref.setOnPreferenceChangeListener { _, newValue -> viewModel.onPostponedSyncEnabledChanged(newValue as Boolean) true @@ -148,6 +161,12 @@ class AppSettingsFragment : BasePreferenceFragment(), PermissionRequestResultRec } categoryBiometric.isVisible = viewModel.isBiometricUnlockAvailable() + isCrashReportingEnabledPref.isEnabled = viewModel.isCrashReportingAvailable() + if (!viewModel.isCrashReportingAvailable()) { + isCrashReportingEnabledPref.summary = getString( + R.string.pref_is_crash_reporting_enabled_not_configured_summary + ) + } enableNotificationPermissionPref.isVisible = viewModel.isEnableNotificationPermissionVisible.value ?: false } diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsViewModel.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsViewModel.kt index 6c3b4809..9238f804 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsViewModel.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/settings/app/AppSettingsViewModel.kt @@ -74,6 +74,14 @@ class AppSettingsViewModel( interactor.reInitializeLogging() } + fun isCrashReportingAvailable(): Boolean = + interactor.isCrashReportingAvailable() + + fun onCrashReportingEnabledChanged(isEnabled: Boolean) { + settings.isCrashReportingEnabled = isEnabled + interactor.setCrashReportingEnabled(isEnabled) + } + fun onPostponedSyncEnabledChanged(isEnabled: Boolean) { interactor.lockDatabase() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3bb4c62..42405c40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -364,6 +364,9 @@ Lock database when pressing \'Back\' from the root group screen Write log to file Application will write all logs to file that can be attached to issue at Github page + Send crash reports + Send crash reports to Sentry to help diagnose application failures + Crash reporting is not configured for this build Send log file Clear logs Postpone uploading @@ -382,7 +385,7 @@ Open the search field automatically after the database is opened General Notifications - Logging + Reporting Biometrics Master key Synchronization with server diff --git a/app/src/main/res/values/untranslatable-strings.xml b/app/src/main/res/values/untranslatable-strings.xml index 6be17a16..5f464556 100644 --- a/app/src/main/res/values/untranslatable-strings.xml +++ b/app/src/main/res/values/untranslatable-strings.xml @@ -12,6 +12,7 @@ isExternalStorageCacheEnabled isSslCertificateValidationEnabled isLogToFileEnabled + isCrashReportingEnabled isPostponedSyncEnabled gitUserName gitUserEmail @@ -35,7 +36,7 @@ categoryGeneral categoryBiometrics categoryNotifications - categoryLogging + categoryLogging categorySearch categorySynchronization diff --git a/app/src/main/res/xml/application_settings.xml b/app/src/main/res/xml/application_settings.xml index 21904906..d947caad 100644 --- a/app/src/main/res/xml/application_settings.xml +++ b/app/src/main/res/xml/application_settings.xml @@ -86,8 +86,13 @@ + app:key="@string/pref_category_reporting" + app:title="@string/reporting"> + + Date: Tue, 7 Jul 2026 16:17:52 +0200 Subject: [PATCH 2/3] Refactor automation build --- app/build.gradle.kts | 12 ++--- .../passnotes/injection/DebugModuleBuilder.kt | 40 ----------------- .../passnotes/injection/DebugModuleBuilder.kt | 16 +++---- .../kotlin/com/ivanovsky/passnotes/App.kt | 45 ++++++++----------- .../injection/AppStartDependencies.kt | 13 ++++++ .../injection/DefaultModuleBuilder.kt | 7 +-- .../passnotes/injection/modules/CoreModule.kt | 13 +++--- .../presentation/about/AboutScreen.kt | 2 +- .../presentation/about/AboutViewModel.kt | 2 +- 9 files changed, 52 insertions(+), 98 deletions(-) delete mode 100644 app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt create mode 100644 app/src/main/kotlin/com/ivanovsky/passnotes/injection/AppStartDependencies.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5744a512..26e3a300 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -167,9 +167,15 @@ android { productFlavors { create("fdroid") { dimension = "default" + buildConfigField("boolean", "IS_AUTOMATION_BUILD", "false") + } + create("automation") { + applicationIdSuffix = ".automation" + buildConfigField("boolean", "IS_AUTOMATION_BUILD", "true") } create("gplay") { dimension = "default" + buildConfigField("boolean", "IS_AUTOMATION_BUILD", "false") } } @@ -180,12 +186,6 @@ android { versionNameSuffix = "d" signingConfig = signingConfigs.getByName("debug") } - create("automation") { - isDebuggable = true - isMinifyEnabled = false - signingConfig = signingConfigs.getByName("debug") - applicationIdSuffix = ".automation" - } release { isDebuggable = false isMinifyEnabled = false diff --git a/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt b/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt deleted file mode 100644 index ebb3cdb2..00000000 --- a/app/src/automation/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.ivanovsky.passnotes.injection - -import android.content.Context -import com.ivanovsky.passnotes.data.repository.settings.Settings -import com.ivanovsky.passnotes.domain.logger.LoggerInteractor -import com.ivanovsky.passnotes.injection.modules.CoreModule -import com.ivanovsky.passnotes.injection.modules.UiModule -import com.ivanovsky.passnotes.injection.modules.UseCaseModule -import com.ivanovsky.passnotes.injection.modules.debug.DebugBiometricModule -import com.ivanovsky.passnotes.injection.modules.debug.DebugFileSystemProvidersModule -import com.ivanovsky.passnotes.injection.modules.debug.DebugModule -import org.koin.core.module.Module - -// Is loaded via reflection in App.kt -class DebugModuleBuilder( - private val context: Context, - private val loggerInteractor: LoggerInteractor, - private val crashReporterInteractor: CrashReporterInteractor, - private val settings: Settings -) : DIModuleBuilder { - - override var isExternalStorageAccessEnabled: Boolean = false - - override fun buildModules(): List { - val isLoadFakeBiometricModule = settings.testToggles?.isFakeBiometricEnabled ?: false - val isFakeFileSystemEnabled = settings.testToggles?.isFakeFileSystemEnabled ?: false - - return listOf( - CoreModule.build(loggerInteractor, crashReporterInteractor), - DebugModule.build(), - DebugFileSystemProvidersModule.build( - isExternalStorageAccessEnabled = isExternalStorageAccessEnabled, - isFakeFileSystemEnabled = isFakeFileSystemEnabled - ), - DebugBiometricModule.build(), - UseCaseModule.build(), - UiModule.build() - ) - } -} \ No newline at end of file diff --git a/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt b/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt index 644c450c..e2f5e8d3 100644 --- a/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt +++ b/app/src/debug/kotlin/com/ivanovsky/passnotes/injection/DebugModuleBuilder.kt @@ -1,9 +1,6 @@ package com.ivanovsky.passnotes.injection -import android.content.Context -import com.ivanovsky.passnotes.data.repository.settings.Settings -import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor -import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor +import com.ivanovsky.passnotes.BuildConfig import com.ivanovsky.passnotes.injection.modules.CoreModule import com.ivanovsky.passnotes.injection.modules.UiModule import com.ivanovsky.passnotes.injection.modules.UseCaseModule @@ -12,21 +9,18 @@ import com.ivanovsky.passnotes.injection.modules.debug.DebugFileSystemProvidersM import com.ivanovsky.passnotes.injection.modules.debug.DebugModule import org.koin.core.module.Module -// Is loaded via reflection in App.kt class DebugModuleBuilder( - private val context: Context, - private val loggerInteractor: LoggerInteractor, - private val crashReporterInteractor: CrashReporterInteractor, - private val settings: Settings + private val startDeps: AppStartDependencies ) : DIModuleBuilder { override var isExternalStorageAccessEnabled: Boolean = false override fun buildModules(): List { - val isFakeFileSystemEnabled = settings.testToggles?.isFakeFileSystemEnabled ?: false + val isFakeFileSystemEnabled = startDeps.settings.testToggles?.isFakeFileSystemEnabled + ?: BuildConfig.IS_AUTOMATION_BUILD return listOf( - CoreModule.build(loggerInteractor, crashReporterInteractor), + CoreModule.build(startDeps), DebugModule.build(), DebugFileSystemProvidersModule.build( isExternalStorageAccessEnabled = isExternalStorageAccessEnabled, diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt index 88e02dcf..119c72d2 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/App.kt @@ -1,12 +1,12 @@ package com.ivanovsky.passnotes import android.app.Application -import android.content.Context -import com.ivanovsky.passnotes.data.repository.settings.Settings import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor +import com.ivanovsky.passnotes.injection.AppStartDependencies import com.ivanovsky.passnotes.injection.DIModuleBuilder +import com.ivanovsky.passnotes.injection.DebugModuleBuilder import com.ivanovsky.passnotes.injection.DefaultModuleBuilder import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -16,40 +16,33 @@ open class App : Application() { open fun configureModuleBuilder(builder: DIModuleBuilder) { // implementation should be flavor specific + if (BuildConfig.IS_AUTOMATION_BUILD) { + builder.isExternalStorageAccessEnabled = true + } } override fun onCreate() { super.onCreate() val settings = SettingsImpl(context = this) - val crashReporterInteractor = CrashReporterInteractor(context = this) - .apply { - initialize(settings) - } - val loggerInteractor = LoggerInteractor(context = this, settings) - .apply { - initialize() - } + val deps = AppStartDependencies( + context = this, + settings = settings, + loggerInteractor = LoggerInteractor(context = this, settings) + .apply { + initialize() + }, + crashReporterInteractor = CrashReporterInteractor(context = this) + .apply { + initialize(settings) + } + ) val moduleBuilder = if (BuildConfig.DEBUG) { - val type = Class.forName("com.ivanovsky.passnotes.injection.DebugModuleBuilder") - - val constructor = type.getConstructor( - Context::class.java, - LoggerInteractor::class.java, - CrashReporterInteractor::class.java, - Settings::class.java - ) - - constructor.newInstance( - this, - loggerInteractor, - crashReporterInteractor, - settings - ) as DIModuleBuilder + DebugModuleBuilder(deps) } else { - DefaultModuleBuilder(loggerInteractor, crashReporterInteractor) + DefaultModuleBuilder(deps) } configureModuleBuilder(moduleBuilder) diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/AppStartDependencies.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/AppStartDependencies.kt new file mode 100644 index 00000000..1281f682 --- /dev/null +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/AppStartDependencies.kt @@ -0,0 +1,13 @@ +package com.ivanovsky.passnotes.injection + +import android.content.Context +import com.ivanovsky.passnotes.data.repository.settings.Settings +import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor +import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor + +data class AppStartDependencies( + val context: Context, + val loggerInteractor: LoggerInteractor, + val crashReporterInteractor: CrashReporterInteractor, + val settings: Settings +) \ No newline at end of file diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt index ea797566..28795047 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/DefaultModuleBuilder.kt @@ -1,7 +1,5 @@ package com.ivanovsky.passnotes.injection -import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor -import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor import com.ivanovsky.passnotes.injection.modules.BiometricModule import com.ivanovsky.passnotes.injection.modules.CoreModule import com.ivanovsky.passnotes.injection.modules.FileSystemProvidersModule @@ -10,15 +8,14 @@ import com.ivanovsky.passnotes.injection.modules.UseCaseModule import org.koin.core.module.Module class DefaultModuleBuilder( - private val loggerInteractor: LoggerInteractor, - private val crashReporterInteractor: CrashReporterInteractor + private val appDeps: AppStartDependencies ) : DIModuleBuilder { override var isExternalStorageAccessEnabled: Boolean = false override fun buildModules(): List { return listOf( - CoreModule.build(loggerInteractor, crashReporterInteractor), + CoreModule.build(appDeps), FileSystemProvidersModule.build( isExternalStorageAccessEnabled = isExternalStorageAccessEnabled ), diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt index 2a1226e9..750e573c 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/injection/modules/CoreModule.kt @@ -11,7 +11,6 @@ import com.ivanovsky.passnotes.data.repository.file.saf.SAFHelper import com.ivanovsky.passnotes.data.repository.keepass.DatabaseSyncStateProvider import com.ivanovsky.passnotes.data.repository.keepass.KeepassDatabaseRepository import com.ivanovsky.passnotes.data.repository.settings.Settings -import com.ivanovsky.passnotes.data.repository.settings.SettingsImpl import com.ivanovsky.passnotes.domain.DateFormatProvider import com.ivanovsky.passnotes.domain.DateFormatter import com.ivanovsky.passnotes.domain.DispatcherProvider @@ -21,18 +20,16 @@ import com.ivanovsky.passnotes.domain.NoteDiffer import com.ivanovsky.passnotes.domain.PermissionHelper import com.ivanovsky.passnotes.domain.ResourceProvider import com.ivanovsky.passnotes.domain.interactor.SelectionHolder -import com.ivanovsky.passnotes.domain.loggingAndReporting.CrashReporterInteractor -import com.ivanovsky.passnotes.domain.loggingAndReporting.LoggerInteractor +import com.ivanovsky.passnotes.injection.AppStartDependencies import com.ivanovsky.passnotes.presentation.core.ThemeProvider import org.koin.dsl.module object CoreModule { fun build( - loggerInteractor: LoggerInteractor, - crashReporterInteractor: CrashReporterInteractor + appStartDeps: AppStartDependencies ) = module { - single { loggerInteractor } + single { appStartDeps.loggerInteractor } single { ThemeProvider(get()) } single { ResourceProvider(get(), get()) } single { PermissionHelper(get()) } @@ -43,8 +40,8 @@ object CoreModule { single { DateFormatter(get()) } single { NoteDiffer() } single { SelectionHolder() } - single { SettingsImpl(get()) } - single { crashReporterInteractor } + single { appStartDeps.settings } + single { appStartDeps.crashReporterInteractor } single { DataCipherProviderImpl(get()) } single { FileHelper(get(), get()) } single { SAFHelper(get()) } diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutScreen.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutScreen.kt index 2731c566..48cbfa42 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutScreen.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutScreen.kt @@ -194,7 +194,7 @@ fun DarkPreview() { ) { AboutScreen( version = BuildConfig.VERSION_NAME, - buildType = BuildConfig.BUILD_TYPE + buildType = "${BuildConfig.BUILD_TYPE} / ${BuildConfig.FLAVOR}" ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutViewModel.kt b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutViewModel.kt index b67a6110..5fa20e2e 100644 --- a/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutViewModel.kt +++ b/app/src/main/kotlin/com/ivanovsky/passnotes/presentation/about/AboutViewModel.kt @@ -12,7 +12,7 @@ class AboutViewModel( ) : ViewModel() { val appVersion = BuildConfig.VERSION_NAME - val appBuildType = BuildConfig.BUILD_TYPE + val appBuildType = "${BuildConfig.BUILD_TYPE} / ${BuildConfig.FLAVOR}" val theme = themeFlow(themeProvider) fun onBackClicked() = router.exit() From cdf6ed4349fbf51e39e6b8e830d0e3d58f1f2156 Mon Sep 17 00:00:00 2001 From: Aliaksei Ivanouski Date: Tue, 7 Jul 2026 20:51:38 +0200 Subject: [PATCH 3/3] Update CI config --- .github/workflows/decrypt-and-validate-data.sh | 10 +++++----- .github/workflows/pipeline.yml | 10 +++------- .github/workflows/release.yml | 6 ++---- .gitignore | 5 +---- ci.properties.gpg | Bin 191 -> 362 bytes 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.github/workflows/decrypt-and-validate-data.sh b/.github/workflows/decrypt-and-validate-data.sh index c0702dbb..f4954798 100755 --- a/.github/workflows/decrypt-and-validate-data.sh +++ b/.github/workflows/decrypt-and-validate-data.sh @@ -1,15 +1,15 @@ #!/bin/bash if [ -n "$GPG_PASSPHRASE" ]; then - gpg --passphrase $GPG_PASSPHRASE --pinentry-mode loopback -o ./keys/debug.keystore -d ./keys/debug.keystore.gpg - gpg --passphrase $GPG_PASSPHRASE --pinentry-mode loopback -o ./keys/release.keystore -d ./keys/release.keystore.gpg - gpg --passphrase $GPG_PASSPHRASE --pinentry-mode loopback -o ./keys/google-cloud-key.json -d ./keys/google-cloud-key.json.gpg - gpg --passphrase $GPG_PASSPHRASE --pinentry-mode loopback -o ./ci.properties -d ./ci.properties.gpg + gpg --passphrase $GPG_PASSPHRASE --yes --pinentry-mode loopback -o ./keys/debug.keystore -d ./keys/debug.keystore.gpg + gpg --passphrase $GPG_PASSPHRASE --yes --pinentry-mode loopback -o ./keys/release.keystore -d ./keys/release.keystore.gpg + gpg --passphrase $GPG_PASSPHRASE --yes --pinentry-mode loopback -o ./keys/google-cloud-key.json -d ./keys/google-cloud-key.json.gpg + gpg --passphrase $GPG_PASSPHRASE --yes --pinentry-mode loopback -o ./ci.properties -d ./ci.properties.gpg echo "3a15a7065ed4a62a747af2e3477b0a3e1940a7bc2946df638b902bcb186998e7 *keys/debug.keystore" | shasum --algorithm 256 --check echo "36a3bb8b7bda141b414c2df7fac7dcd09a2775769c561e562d2b43f7b246bfa5 *keys/release.keystore" | shasum --algorithm 256 --check echo "eac44fec5848bb6718b23ad433e73650352611953ae7f98c8e502c0491154da5 *keys/google-cloud-key.json" | shasum --algorithm 256 --check - echo "b009011968e80d5b99eaa181141e6c94178a538c3dac0e413c43d5f95f50f557 *ci.properties" | shasum --algorithm 256 --check + echo "43278a8837886a8e485f021e5bb4f0df14a057f796d0230cf2ea0e88709c98fa *ci.properties" | shasum --algorithm 256 --check else echo "Unable to decrypt secret data" exit 1 diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 16b4591e..89c15929 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -56,10 +56,6 @@ jobs: strategy: matrix: include: - - command: assembleGplayDebug - fileName: app-gplay-debug.apk - filePath: ./app/build/outputs/apk/gplay/debug/app-gplay-debug.apk - - command: assembleGplayRelease fileName: app-gplay-release.apk filePath: ./app/build/outputs/apk/gplay/release/app-gplay-release.apk @@ -72,9 +68,9 @@ jobs: fileName: app-fdroid-release.apk filePath: ./app/build/outputs/apk/fdroid/release/app-fdroid-release.apk - - command: assembleFdroidAutomation - fileName: app-fdroid-automation.apk - filePath: ./app/build/outputs/apk/fdroid/automation/app-fdroid-automation.apk + - command: assembleAutomationDebug + fileName: app-automation-debug.apk + filePath: ./app/build/outputs/apk/automation/debug/app-automation-debug.apk steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 263522d8..4f677578 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,13 +26,12 @@ jobs: - name: Build apk's run: | - ./gradlew app:assembleFdroidDebug app:assembleFdroidRelease app:assembleFdroidAutomation + app:assembleFdroidRelease app:assembleAutomationDebug - name: Rename files run: | - cp ./app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk ./keepassvault-debug.apk cp ./app/build/outputs/apk/fdroid/release/app-fdroid-release.apk ./keepassvault-release.apk - cp ./app/build/outputs/apk/fdroid/automation/app-fdroid-automation.apk ./keepassvault-test.apk + cp ./app/build/outputs/apk/automation/debug/app-automation-debug.apk ./keepassvault-test.apk - name: Publish Github Release uses: softprops/action-gh-release@v2 @@ -40,7 +39,6 @@ jobs: generate_release_notes: true prerelease: true files: | - ./keepassvault-debug.apk ./keepassvault-release.apk ./keepassvault-test.apk diff --git a/.gitignore b/.gitignore index 582e8863..8693e3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,4 @@ project.properties .lsp/ .clj-kondo/ .cpcache/ -.nrepl-port - -keepass-rs/ -keepass-rs-android/ +.nrepl-port \ No newline at end of file diff --git a/ci.properties.gpg b/ci.properties.gpg index f7132a807580267164320a71935eb32b3d6f9a07..6afe27efc2f8a9dcc08180180ea57bdc3fcb9ad9 100644 GIT binary patch literal 362 zcmV-w0hRuY4Fm}T2vf^rmaB#AegD$Hm;pzau7+4Lo_{y--xKg4NK$IiI zeL_EkVbANU92VQ?>`*V>m>BO6$H9CvSk_1=5rhh+U<3@);>@^NhLdFoH(iSo&=?y# zGwR!o*zzp8W*m%tfGhe>FudibS)(%i<}ImGw;EUYMnT-qVC(<@