diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index c0d7219f1..0384fed8c 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -73,6 +73,13 @@ column="9"/> + + + + + android:exported="false" + tools:node="merge"> + + + + + - diff --git a/app/src/main/java/app/pachli/PachliApplication.kt b/app/src/main/java/app/pachli/PachliApplication.kt index a3749e720..3320e7fbc 100644 --- a/app/src/main/java/app/pachli/PachliApplication.kt +++ b/app/src/main/java/app/pachli/PachliApplication.kt @@ -19,14 +19,11 @@ package app.pachli import android.app.Application import android.content.Context -import androidx.hilt.work.HiltWorkerFactory import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import app.pachli.components.notifications.createWorkerNotificationChannel -import app.pachli.core.activity.LogEntryTree -import app.pachli.core.activity.TreeRing import app.pachli.core.activity.initCrashReporter import app.pachli.core.preferences.AppTheme import app.pachli.core.preferences.NEW_INSTALL_SCHEMA_VERSION @@ -50,18 +47,12 @@ import timber.log.Timber @HiltAndroidApp class PachliApplication : Application() { - @Inject - lateinit var workerFactory: HiltWorkerFactory - @Inject lateinit var localeManager: LocaleManager @Inject lateinit var sharedPreferencesRepository: SharedPreferencesRepository - @Inject - lateinit var logEntryTree: LogEntryTree - override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) @@ -83,12 +74,6 @@ class PachliApplication : Application() { Security.insertProviderAt(Conscrypt.newProvider(), 1) - when { - BuildConfig.DEBUG -> Timber.plant(Timber.DebugTree()) - BuildConfig.FLAVOR_color == "orange" -> Timber.plant(TreeRing) - } - Timber.plant(logEntryTree) - // Migrate shared preference keys and defaults from version to version. val oldVersion = sharedPreferencesRepository.getInt(PrefKeys.SCHEMA_VERSION, NEW_INSTALL_SCHEMA_VERSION) if (oldVersion != SCHEMA_VERSION) { @@ -108,12 +93,8 @@ class PachliApplication : Application() { createWorkerNotificationChannel(this) - WorkManager.initialize( - this, - androidx.work.Configuration.Builder().setWorkerFactory(workerFactory).build(), - ) - val workManager = WorkManager.getInstance(this) + // Prune the database every ~ 12 hours when the device is idle. val pruneCacheWorker = PeriodicWorkRequestBuilder(12, TimeUnit.HOURS) .setConstraints(Constraints.Builder().setRequiresDeviceIdle(true).build()) diff --git a/app/src/main/java/app/pachli/di/InitializerEntryPoint.kt b/app/src/main/java/app/pachli/di/InitializerEntryPoint.kt new file mode 100644 index 000000000..0a353b329 --- /dev/null +++ b/app/src/main/java/app/pachli/di/InitializerEntryPoint.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.di + +import android.content.Context +import app.pachli.initializer.TimberInitializer +import app.pachli.initializer.WorkManagerInitializer +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent + +/** + * Entry point that allows [androidx.startup.Initializer] implementations + * to inject their dependencies. + */ +@EntryPoint +@InstallIn(SingletonComponent::class) +interface InitializerEntryPoint { + fun inject(timberInitializer: TimberInitializer) + fun inject(workManagerInitializer: WorkManagerInitializer) + + companion object { + fun resolve(context: Context): InitializerEntryPoint { + val appContext = context.applicationContext ?: throw IllegalStateException() + return EntryPointAccessors.fromApplication( + appContext, + InitializerEntryPoint::class.java, + ) + } + } +} diff --git a/app/src/main/java/app/pachli/initializer/DependencyGraphInitializer.kt b/app/src/main/java/app/pachli/initializer/DependencyGraphInitializer.kt new file mode 100644 index 000000000..1307f56ee --- /dev/null +++ b/app/src/main/java/app/pachli/initializer/DependencyGraphInitializer.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.initializer + +import android.content.Context +import androidx.startup.Initializer +import app.pachli.di.InitializerEntryPoint + +/** Empty root initializer other initialisers can depend on. */ +class DependencyGraphInitializer : Initializer { + override fun create(context: Context) { + InitializerEntryPoint.resolve(context) + } + + override fun dependencies(): List>> { + return emptyList() + } +} diff --git a/app/src/main/java/app/pachli/initializer/TimberInitializer.kt b/app/src/main/java/app/pachli/initializer/TimberInitializer.kt new file mode 100644 index 000000000..2cc7ce619 --- /dev/null +++ b/app/src/main/java/app/pachli/initializer/TimberInitializer.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.initializer + +import android.content.Context +import androidx.startup.Initializer +import app.pachli.BuildConfig +import app.pachli.core.activity.LogEntryTree +import app.pachli.core.activity.TreeRing +import app.pachli.di.InitializerEntryPoint +import javax.inject.Inject +import timber.log.Timber + +/** Initialise [Timber]. */ +class TimberInitializer : Initializer { + @Inject + lateinit var logEntryTree: LogEntryTree + + override fun create(context: Context) { + InitializerEntryPoint.resolve(context).inject(this) + + when { + BuildConfig.DEBUG -> Timber.plant(Timber.DebugTree()) + BuildConfig.FLAVOR_color == "orange" -> Timber.plant(TreeRing) + } + Timber.plant(logEntryTree) + } + + override fun dependencies(): List>> { + return listOf(DependencyGraphInitializer::class.java) + } +} diff --git a/app/src/main/java/app/pachli/initializer/WorkManagerInitializer.kt b/app/src/main/java/app/pachli/initializer/WorkManagerInitializer.kt new file mode 100644 index 000000000..efba654b7 --- /dev/null +++ b/app/src/main/java/app/pachli/initializer/WorkManagerInitializer.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.initializer + +import android.content.Context +import androidx.hilt.work.HiltWorkerFactory +import androidx.startup.Initializer +import androidx.work.Configuration +import androidx.work.WorkManager +import app.pachli.di.InitializerEntryPoint +import javax.inject.Inject + +/** Initialise [WorkManager] with a [HiltWorkerFactory]. */ +class WorkManagerInitializer : Initializer, Configuration.Provider { + @Inject + lateinit var workerFactory: HiltWorkerFactory + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder().setWorkerFactory(workerFactory).build() + + override fun create(context: Context): WorkManager { + InitializerEntryPoint.resolve(context).inject(this) + + WorkManager.initialize(context, workManagerConfiguration) + return WorkManager.getInstance(context) + } + + override fun dependencies(): List>> { + return listOf( + DependencyGraphInitializer::class.java, + TimberInitializer::class.java, + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5fc9ef450..f687548f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ androidx-preference = "1.2.1" androidx-recyclerview = "1.3.2" androidx-sharetarget = "1.2.0" androidx-splashscreen = "1.0.1" +androidx-startup = "1.1.1" androidx-swiperefresh-layout = "1.1.0" androidx-testing = "2.2.0" androidx-test-core-ktx = "1.5.0" @@ -143,6 +144,7 @@ androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "androidx-room" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidx-recyclerview" } androidx-sharetarget = { module = "androidx.sharetarget:sharetarget", version.ref = "androidx-sharetarget" } +androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" } androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "androidx-swiperefresh-layout" } androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core-ktx" } androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } @@ -235,8 +237,7 @@ androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx", "androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx", "androidx-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-exoplayer-dash", "androidx-media3-exoplayer-hls", "androidx-media3-exoplayer-rtsp", "androidx-media3-datasource-okhttp", "androidx-media3-ui", - "androidx-transition", - "android-material"] + "androidx-transition", "android-material", "androidx-startup"] filemojicompat = ["filemojicompat-core", "filemojicompat-ui", "filemojicompat-defaults"] glide = ["glide-core", "glide-okhttp3-integration", "glide-animation-plugin"] lint-api = ["kotlin-stdlib", "lint-api", "lint-checks"]