diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2625571dd..d67fac232 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.looker.android.application) alias(libs.plugins.looker.hilt.work) + alias(libs.plugins.looker.lint) } android { diff --git a/build-logic/structure/src/main/kotlin/AndroidApplicationPlugin.kt b/build-logic/structure/src/main/kotlin/AndroidApplicationPlugin.kt index a16b6cbd8..476e0e364 100644 --- a/build-logic/structure/src/main/kotlin/AndroidApplicationPlugin.kt +++ b/build-logic/structure/src/main/kotlin/AndroidApplicationPlugin.kt @@ -12,7 +12,6 @@ class AndroidApplicationPlugin : Plugin { with(pluginManager) { apply("com.android.application") apply("org.jetbrains.kotlin.android") - apply("looker.lint") } extensions.configure { diff --git a/build-logic/structure/src/main/kotlin/AndroidLibraryPlugin.kt b/build-logic/structure/src/main/kotlin/AndroidLibraryPlugin.kt index da3645585..43dbd05c7 100644 --- a/build-logic/structure/src/main/kotlin/AndroidLibraryPlugin.kt +++ b/build-logic/structure/src/main/kotlin/AndroidLibraryPlugin.kt @@ -13,7 +13,6 @@ class AndroidLibraryPlugin : Plugin { with(pluginManager) { apply("com.android.library") apply("org.jetbrains.kotlin.android") - apply("looker.lint") } extensions.configure { diff --git a/build-logic/structure/src/main/kotlin/AndroidLintPlugin.kt b/build-logic/structure/src/main/kotlin/AndroidLintPlugin.kt index 737a4af4e..9ce958c82 100644 --- a/build-logic/structure/src/main/kotlin/AndroidLintPlugin.kt +++ b/build-logic/structure/src/main/kotlin/AndroidLintPlugin.kt @@ -14,7 +14,7 @@ class AndroidLintPlugin : Plugin { tasks.getByPath("preBuild").dependsOn("ktlintFormat") extensions.configure { android.set(true) - ignoreFailures.set(false) + ignoreFailures.set(true) debug.set(true) reporters { reporter(ReporterType.HTML) diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 5ad6c5936..9c7871a7f 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -2,6 +2,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { alias(libs.plugins.looker.android.library) + alias(libs.plugins.looker.lint) } android { diff --git a/core/common/src/main/java/com/looker/core/common/Text.kt b/core/common/src/main/java/com/looker/core/common/Text.kt index 1a29b8704..1adbced07 100644 --- a/core/common/src/main/java/com/looker/core/common/Text.kt +++ b/core/common/src/main/java/com/looker/core/common/Text.kt @@ -17,9 +17,9 @@ fun T.nullIfEmpty(): T? { fun String.stripBetween(prefix: String, suffix: String = prefix): String { val prefixIndex = indexOf(prefix) val suffixIndex = lastIndexOf(suffix) - val isRangeValid = prefixIndex != -1 - && suffixIndex != -1 - && prefixIndex != suffixIndex + val isRangeValid = prefixIndex != -1 && + suffixIndex != -1 && + prefixIndex != suffixIndex return if (isRangeValid) { substring(0, prefixIndex + 1) + substring(suffixIndex + 1) } else { diff --git a/core/common/src/main/java/com/looker/core/common/extension/Service.kt b/core/common/src/main/java/com/looker/core/common/extension/Service.kt index d5587cc14..5b0dcecc5 100644 --- a/core/common/src/main/java/com/looker/core/common/extension/Service.kt +++ b/core/common/src/main/java/com/looker/core/common/extension/Service.kt @@ -17,8 +17,11 @@ fun Service.stopForegroundCompat(removeNotification: Boolean = true) { @Suppress("DEPRECATION") if (SdkCheck.isNougat) { stopForeground( - if (removeNotification) Service.STOP_FOREGROUND_REMOVE - else Service.STOP_FOREGROUND_DETACH + if (removeNotification) { + Service.STOP_FOREGROUND_REMOVE + } else { + Service.STOP_FOREGROUND_DETACH + } ) } else { stopForeground(removeNotification) diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index c2759a357..72221618d 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.looker.android.library) alias(libs.plugins.looker.hilt.work) + alias(libs.plugins.looker.lint) } android { diff --git a/core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt b/core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt index 0736a20f9..d0b6078fd 100644 --- a/core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt +++ b/core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt @@ -48,8 +48,8 @@ fun PackageV2.toEntity( ?: emptyMap(), tenInchScreenshots = metadata.screenshots?.tenInch?.mapValues { it.value.map { it.name } } ?: emptyMap(), - sevenInchScreenshots = metadata.screenshots?.sevenInch?.mapValues { it.value.map { it.name } } - ?: emptyMap(), + sevenInchScreenshots = metadata.screenshots?.sevenInch + ?.mapValues { it.value.map { it.name } } ?: emptyMap(), tvScreenshots = metadata.screenshots?.tv?.mapValues { it.value.map { it.name } } ?: emptyMap(), wearScreenshots = metadata.screenshots?.wear?.mapValues { it.value.map { it.name } } diff --git a/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexDownloaderImpl.kt b/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexDownloaderImpl.kt index b88825479..e9d94ed8f 100644 --- a/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexDownloaderImpl.kt +++ b/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexDownloaderImpl.kt @@ -43,7 +43,11 @@ class IndexDownloaderImpl @Inject constructor( fileIndex = index } val (_, response) = downloadIndexFile(repo, INDEX_V1_FILE_NAME, validator) - if (repoFingerprint == null || fileIndex == null || repoFingerprint?.isBlank() == true || response is NetworkResponse.Error) + val isFingerprintAndIndexValid = repoFingerprint == null + || fileIndex == null + || repoFingerprint?.isBlank() == true + || response is NetworkResponse.Error + if (isFingerprintAndIndexValid) throw IllegalStateException("Fingerprint: $repoFingerprint, Index: $fileIndex") IndexDownloadResponse( index = fileIndex!!, @@ -78,7 +82,11 @@ class IndexDownloaderImpl @Inject constructor( fileEntry = entry } val (_, response) = downloadIndexFile(repo, ENTRY_FILE_NAME, validator) - if (repoFingerprint == null || fileEntry == null || repoFingerprint?.isBlank() == true || response is NetworkResponse.Error.Validation) + val isFingerprintAndIndexValid = repoFingerprint == null + || fileEntry == null + || repoFingerprint?.isBlank() == true + || response is NetworkResponse.Error.Validation + if (isFingerprintAndIndexValid) throw IllegalStateException("Empty Fingerprint") IndexDownloadResponse( index = fileEntry!!, diff --git a/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexManager.kt b/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexManager.kt index f5b35f964..c2a1bc250 100644 --- a/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexManager.kt +++ b/core/data/src/main/java/com/looker/core/data/fdroid/sync/IndexManager.kt @@ -33,7 +33,9 @@ class IndexManager( timestamp = response.lastModified, etag = response.etag ) - if (response.lastModified == repo.versionInfo.timestamp) return@associate updatedRepo to null + if (response.lastModified == repo.versionInfo.timestamp) { + return@associate updatedRepo to null + } val diff = response.index.getDiff(repo.versionInfo.timestamp) updatedRepo to downloadIndexBasedOnDiff(repo, diff) } diff --git a/core/database/src/main/java/com/looker/core/database/Converters.kt b/core/database/src/main/java/com/looker/core/database/Converters.kt index f031d8eb4..4f517ded5 100644 --- a/core/database/src/main/java/com/looker/core/database/Converters.kt +++ b/core/database/src/main/java/com/looker/core/database/Converters.kt @@ -34,7 +34,6 @@ class CollectionConverter { @TypeConverter fun stringToList(byteArray: ByteArray): List = String(byteArray).split(STRING_DELIMITER) - } class LocalizedConverter { @@ -54,7 +53,6 @@ class LocalizedConverter { @TypeConverter fun jsonToLocalizedList(jsonObject: String): LocalizedList = json.decodeFromString(localizedListSerializer, jsonObject) - } class PackageEntityConverter { @@ -74,7 +72,6 @@ class PackageEntityConverter { @TypeConverter fun stringToPackageList(jsonString: String): List = json.decodeFromString(packageListSerializer, jsonString) - } class RepoConverter { @@ -94,5 +91,4 @@ class RepoConverter { @TypeConverter fun stringToCategory(string: String): Map = json.decodeFromString(categorySerializer, string) - } diff --git a/core/database/src/main/java/com/looker/core/database/DroidifyDatabase.kt b/core/database/src/main/java/com/looker/core/database/DroidifyDatabase.kt index a887a5238..cbead264c 100644 --- a/core/database/src/main/java/com/looker/core/database/DroidifyDatabase.kt +++ b/core/database/src/main/java/com/looker/core/database/DroidifyDatabase.kt @@ -31,5 +31,4 @@ abstract class DroidifyDatabase : RoomDatabase() { abstract fun repoDao(): RepoDao abstract fun installedDao(): InstalledDao - } diff --git a/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt b/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt index adce21978..0184e57b8 100644 --- a/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt +++ b/core/database/src/main/java/com/looker/core/database/dao/AppDao.kt @@ -47,5 +47,4 @@ interface AppDao { """ ) suspend fun deleteApps(repoId: Long) - } diff --git a/core/database/src/main/java/com/looker/core/database/dao/InstalledDao.kt b/core/database/src/main/java/com/looker/core/database/dao/InstalledDao.kt index 66be2b474..bb48a0b71 100644 --- a/core/database/src/main/java/com/looker/core/database/dao/InstalledDao.kt +++ b/core/database/src/main/java/com/looker/core/database/dao/InstalledDao.kt @@ -15,5 +15,4 @@ interface InstalledDao { @Delete suspend fun deleteInstalled(installedEntity: InstalledEntity) - } diff --git a/core/database/src/main/java/com/looker/core/database/dao/RepoDao.kt b/core/database/src/main/java/com/looker/core/database/dao/RepoDao.kt index 1bd9ff0e5..8731f4aed 100644 --- a/core/database/src/main/java/com/looker/core/database/dao/RepoDao.kt +++ b/core/database/src/main/java/com/looker/core/database/dao/RepoDao.kt @@ -25,5 +25,4 @@ interface RepoDao { """ ) suspend fun deleteRepo(id: Long) - } diff --git a/core/database/src/main/java/com/looker/core/database/di/DaoModule.kt b/core/database/src/main/java/com/looker/core/database/di/DaoModule.kt index 672d55b95..95eafd63b 100644 --- a/core/database/src/main/java/com/looker/core/database/di/DaoModule.kt +++ b/core/database/src/main/java/com/looker/core/database/di/DaoModule.kt @@ -31,5 +31,4 @@ object DaoModule { fun provideInstalledDao( database: DroidifyDatabase ): InstalledDao = database.installedDao() - } diff --git a/core/database/src/main/java/com/looker/core/database/di/DatabaseModule.kt b/core/database/src/main/java/com/looker/core/database/di/DatabaseModule.kt index 5aad00ec4..dd12bc301 100644 --- a/core/database/src/main/java/com/looker/core/database/di/DatabaseModule.kt +++ b/core/database/src/main/java/com/looker/core/database/di/DatabaseModule.kt @@ -23,5 +23,4 @@ object DatabaseModule { DroidifyDatabase::class.java, "droidify-database" ).createFromAsset("repo.db").build() - } diff --git a/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt b/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt index 9563610ce..248a3e8bf 100644 --- a/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/AppEntity.kt @@ -82,7 +82,7 @@ fun List.toExternal( private fun AppEntity.author(): Author = Author( name = authorName, email = authorEmail, - web = authorWebSite, + web = authorWebSite ) private fun AppEntity.donations(): Donation = Donation( @@ -92,7 +92,7 @@ private fun AppEntity.donations(): Donation = Donation( liteCoinAddress = litecoin.nullIfEmpty(), openCollectiveId = openCollective.nullIfEmpty(), librePayId = liberapayID.nullIfEmpty(), - librePayAddress = liberapay.nullIfEmpty(), + librePayAddress = liberapay.nullIfEmpty() ) private fun AppEntity.graphics(locale: String): Graphics = Graphics( diff --git a/core/database/src/main/java/com/looker/core/database/model/InstalledEntity.kt b/core/database/src/main/java/com/looker/core/database/model/InstalledEntity.kt index 5af2c731c..ef899d096 100644 --- a/core/database/src/main/java/com/looker/core/database/model/InstalledEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/InstalledEntity.kt @@ -9,4 +9,4 @@ data class InstalledEntity( val packageName: String, val versionCode: Long, val signature: String -) \ No newline at end of file +) diff --git a/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt b/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt index a18e7bbb2..afd5fb8e2 100644 --- a/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt +++ b/core/database/src/main/java/com/looker/core/database/model/PackageEntity.kt @@ -51,7 +51,7 @@ fun PackageEntity.toExternal(locale: String, installed: Boolean): Package = Pack versionName = versionName, usesSDKs = SDKs(minSdkVersion, targetSdkVersion), signer = setOf(signer), - permissions = usesPermission.map(PermissionEntity::toExternalModel), + permissions = usesPermission.map(PermissionEntity::toExternalModel) ), platforms = Platforms(nativeCode), features = features, diff --git a/core/database/src/main/java/com/looker/core/database/utils/Localization.kt b/core/database/src/main/java/com/looker/core/database/utils/Localization.kt index b8780651f..067e94d07 100644 --- a/core/database/src/main/java/com/looker/core/database/utils/Localization.kt +++ b/core/database/src/main/java/com/looker/core/database/utils/Localization.kt @@ -4,7 +4,8 @@ import androidx.core.os.LocaleListCompat import com.looker.core.common.stripBetween import java.util.Locale -internal fun localeListCompat(tag: String): LocaleListCompat = LocaleListCompat.forLanguageTags(tag) +internal fun localeListCompat(tag: String): LocaleListCompat = + LocaleListCompat.forLanguageTags(tag) /** * Find the Localized value from [Map] using [locale] @@ -27,7 +28,6 @@ fun Map?.localizedValue(locale: String): T? { * * Returns null if none found */ -@OptIn(ExperimentalStdlibApi::class) internal fun LocaleListCompat.suitableLocale(keys: Set): String? = (0.. = emptySet(), + val homeScreenSwiping: Boolean = true +) + +@OptIn(ExperimentalSerializationApi::class) +object SettingsSerializer : Serializer { + + private val json = Json { encodeDefaults = true } + + override val defaultValue: Settings = Settings() + + override suspend fun readFrom(input: InputStream): Settings { + return try { + json.decodeFromStream(input) + } catch (e: SerializationException) { + e.printStackTrace() + defaultValue + } + } + + override suspend fun writeTo(t: Settings, output: OutputStream) { + try { + json.encodeToStream(t, output) + } catch (e: SerializationException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + } +} diff --git a/core/datastore/src/main/java/com/looker/core/datastore/SettingsRepository.kt b/core/datastore/src/main/java/com/looker/core/datastore/SettingsRepository.kt index fb86c8981..c8da0f462 100644 --- a/core/datastore/src/main/java/com/looker/core/datastore/SettingsRepository.kt +++ b/core/datastore/src/main/java/com/looker/core/datastore/SettingsRepository.kt @@ -2,70 +2,24 @@ package com.looker.core.datastore import android.util.Log import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.intPreferencesKey -import androidx.datastore.preferences.core.longPreferencesKey -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.core.stringSetPreferencesKey import com.looker.core.common.extension.updateAsMutable import com.looker.core.datastore.model.AutoSync import com.looker.core.datastore.model.InstallerType -import com.looker.core.datastore.model.ProxyPreference import com.looker.core.datastore.model.ProxyType import com.looker.core.datastore.model.SortOrder import com.looker.core.datastore.model.Theme -import java.io.IOException -import kotlin.time.Duration -import kotlin.time.Duration.Companion.hours import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.datetime.Clock -import kotlinx.datetime.Instant - -data class Settings( - val language: String, - val incompatibleVersions: Boolean, - val notifyUpdate: Boolean, - val unstableUpdate: Boolean, - val theme: Theme, - val dynamicTheme: Boolean, - val installerType: InstallerType, - val autoUpdate: Boolean, - val autoSync: AutoSync, - val sortOrder: SortOrder, - val proxy: ProxyPreference, - val cleanUpInterval: Duration, - val lastCleanup: Instant?, - val favouriteApps: Set, - val homeScreenSwiping: Boolean -) - -class SettingsRepository(private val dataStore: DataStore) { - private companion object PreferencesKeys { - const val TAG: String = "SettingsRepository" +import java.io.IOException +import kotlin.time.Duration - val LANGUAGE = stringPreferencesKey("key_language") - val INCOMPATIBLE_VERSIONS = booleanPreferencesKey("key_incompatible_versions") - val NOTIFY_UPDATES = booleanPreferencesKey("key_notify_updates") - val UNSTABLE_UPDATES = booleanPreferencesKey("key_unstable_updates") - val THEME = stringPreferencesKey("key_theme") - val DYNAMIC_THEME = booleanPreferencesKey("key_dynamic_theme") - val INSTALLER_TYPE = stringPreferencesKey("key_installer_type") - val AUTO_UPDATE = booleanPreferencesKey("key_auto_updates") - val AUTO_SYNC = stringPreferencesKey("key_auto_sync") - val SORT_ORDER = stringPreferencesKey("key_sort_order") - val PROXY_TYPE = stringPreferencesKey("key_proxy_type") - val PROXY_HOST = stringPreferencesKey("key_proxy_host") - val PROXY_PORT = intPreferencesKey("key_proxy_port") - val CLEAN_UP_INTERVAL = longPreferencesKey("clean_up_interval") - val LAST_CLEAN_UP = longPreferencesKey("last_clean_up_time") - val FAVOURITE_APPS = stringSetPreferencesKey("favourite_apps") - val HOME_SCREEN_SWIPING = booleanPreferencesKey("home_swiping") +class SettingsRepository(private val dataStore: DataStore) { + private companion object { + const val TAG: String = "SettingsRepository" } val settingsFlow: Flow = dataStore.data @@ -75,117 +29,116 @@ class SettingsRepository(private val dataStore: DataStore) { } else { throw exception } - }.map(::mapSettings) + } inline fun get(crossinline block: suspend Settings.() -> T): Flow = settingsFlow.map(block).distinctUntilChanged() - suspend fun setLanguage(language: String) = - LANGUAGE.update(language) - - suspend fun enableIncompatibleVersion(enable: Boolean) = - INCOMPATIBLE_VERSIONS.update(enable) + suspend fun fetchInitialPreferences() = + dataStore.data.first() - suspend fun enableNotifyUpdates(enable: Boolean) = - NOTIFY_UPDATES.update(enable) + suspend fun setLanguage(language: String) { + dataStore.updateData { settings -> + settings.copy(language = language) + } + } - suspend fun enableUnstableUpdates(enable: Boolean) = - UNSTABLE_UPDATES.update(enable) + suspend fun enableIncompatibleVersion(enable: Boolean) { + dataStore.updateData { settings -> + settings.copy(incompatibleVersions = enable) + } + } - suspend fun setTheme(theme: Theme) = - THEME.update(theme.name) + suspend fun enableNotifyUpdates(enable: Boolean) { + dataStore.updateData { settings -> + settings.copy(notifyUpdate = enable) + } + } - suspend fun setDynamicTheme(enable: Boolean) = - DYNAMIC_THEME.update(enable) + suspend fun enableUnstableUpdates(enable: Boolean) { + dataStore.updateData { settings -> + settings.copy(unstableUpdate = enable) + } + } - suspend fun setInstallerType(installerType: InstallerType) = - INSTALLER_TYPE.update(installerType.name) + suspend fun setTheme(theme: Theme) { + dataStore.updateData { settings -> + settings.copy(theme = theme) + } + } - suspend fun setAutoUpdate(allow: Boolean) = - AUTO_UPDATE.update(allow) + suspend fun setDynamicTheme(enable: Boolean) { + dataStore.updateData { settings -> + settings.copy(dynamicTheme = enable) + } + } - suspend fun setAutoSync(autoSync: AutoSync) = - AUTO_SYNC.update(autoSync.name) + suspend fun setInstallerType(installerType: InstallerType) { + dataStore.updateData { settings -> + settings.copy(installerType = installerType) + } + } - suspend fun setSortOrder(sortOrder: SortOrder) = - SORT_ORDER.update(sortOrder.name) + suspend fun setAutoUpdate(allow: Boolean) { + dataStore.updateData { settings -> + settings.copy(autoUpdate = allow) + } + } - suspend fun setProxyType(proxyType: ProxyType) = - PROXY_TYPE.update(proxyType.name) + suspend fun setAutoSync(autoSync: AutoSync) { + dataStore.updateData { settings -> + settings.copy(autoSync = autoSync) + } + } - suspend fun setProxyHost(proxyHost: String) = - PROXY_HOST.update(proxyHost) + suspend fun setSortOrder(sortOrder: SortOrder) { + dataStore.updateData { settings -> + settings.copy(sortOrder = sortOrder) + } + } - suspend fun setProxyPort(proxyPort: Int) = - PROXY_PORT.update(proxyPort) + suspend fun setProxyType(proxyType: ProxyType) { + dataStore.updateData { settings -> + settings.copy(proxy = settings.proxy.update(newType = proxyType)) + } + } - suspend fun setCleanUpInterval(interval: Duration) = - CLEAN_UP_INTERVAL.update(interval.inWholeHours) + suspend fun setProxyHost(proxyHost: String) { + dataStore.updateData { settings -> + settings.copy(proxy = settings.proxy.update(newHost = proxyHost)) + } + } - suspend fun setCleanupInstant() = - LAST_CLEAN_UP.update(Clock.System.now().toEpochMilliseconds()) + suspend fun setProxyPort(proxyPort: Int) { + dataStore.updateData { settings -> + settings.copy(proxy = settings.proxy.update(newPort = proxyPort)) + } + } - suspend fun setHomeScreenSwiping(value: Boolean) = - HOME_SCREEN_SWIPING.update(value) + suspend fun setCleanUpInterval(interval: Duration) { + dataStore.updateData { settings -> + settings.copy(cleanUpInterval = interval) + } + } - suspend fun toggleFavourites(packageName: String) { - dataStore.edit { preference -> - val currentSet = preference[FAVOURITE_APPS] ?: emptySet() - val newSet = currentSet.updateAsMutable { - if (!add(packageName)) remove(packageName) - } - preference[FAVOURITE_APPS] = newSet + suspend fun setCleanupInstant() { + dataStore.updateData { settings -> + settings.copy(lastCleanup = Clock.System.now()) } } - private suspend inline fun Preferences.Key.update(newValue: T) { - dataStore.edit { preferences -> - preferences[this] = newValue + suspend fun setHomeScreenSwiping(value: Boolean) { + dataStore.updateData { settings -> + settings.copy(homeScreenSwiping = value) } } - suspend fun fetchInitialPreferences() = - mapSettings(dataStore.data.first().toPreferences()) - - private fun mapSettings(preferences: Preferences): Settings { - val defaultInstallerName = (InstallerType.Default).name - - val language = preferences[LANGUAGE] ?: "system" - val incompatibleVersions = preferences[INCOMPATIBLE_VERSIONS] ?: false - val notifyUpdate = preferences[NOTIFY_UPDATES] ?: true - val unstableUpdate = preferences[UNSTABLE_UPDATES] ?: false - val theme = Theme.valueOf(preferences[THEME] ?: Theme.SYSTEM.name) - val dynamicTheme = preferences[DYNAMIC_THEME] ?: false - val installerType = - InstallerType.valueOf(preferences[INSTALLER_TYPE] ?: defaultInstallerName) - val autoUpdate = preferences[AUTO_UPDATE] ?: false - val autoSync = AutoSync.valueOf(preferences[AUTO_SYNC] ?: AutoSync.WIFI_ONLY.name) - val sortOrder = SortOrder.valueOf(preferences[SORT_ORDER] ?: SortOrder.UPDATED.name) - val type = ProxyType.valueOf(preferences[PROXY_TYPE] ?: ProxyType.DIRECT.name) - val host = preferences[PROXY_HOST] ?: "localhost" - val port = preferences[PROXY_PORT] ?: 9050 - val proxy = ProxyPreference(type = type, host = host, port = port) - val cleanUpInterval = preferences[CLEAN_UP_INTERVAL]?.hours ?: 12L.hours - val lastCleanup = preferences[LAST_CLEAN_UP]?.let { Instant.fromEpochMilliseconds(it) } - val favouriteApps = preferences[FAVOURITE_APPS] ?: emptySet() - val homeScreenSwiping = preferences[HOME_SCREEN_SWIPING] ?: true - - return Settings( - language = language, - incompatibleVersions = incompatibleVersions, - notifyUpdate = notifyUpdate, - unstableUpdate = unstableUpdate, - theme = theme, - dynamicTheme = dynamicTheme, - installerType = installerType, - autoUpdate = autoUpdate, - autoSync = autoSync, - sortOrder = sortOrder, - proxy = proxy, - cleanUpInterval = cleanUpInterval, - lastCleanup = lastCleanup, - favouriteApps = favouriteApps, - homeScreenSwiping = homeScreenSwiping - ) + suspend fun toggleFavourites(packageName: String) { + dataStore.updateData { settings -> + val newSet = settings.favouriteApps.updateAsMutable { + if (!add(packageName)) remove(packageName) + } + settings.copy(favouriteApps = newSet) + } } } diff --git a/core/datastore/src/main/java/com/looker/core/datastore/di/DatastoreModule.kt b/core/datastore/src/main/java/com/looker/core/datastore/di/DatastoreModule.kt index 0a2da5b9e..f4bc22869 100644 --- a/core/datastore/src/main/java/com/looker/core/datastore/di/DatastoreModule.kt +++ b/core/datastore/src/main/java/com/looker/core/datastore/di/DatastoreModule.kt @@ -2,10 +2,15 @@ package com.looker.core.datastore.di import android.content.Context import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStoreFile +import com.looker.core.datastore.Settings import com.looker.core.datastore.SettingsRepository +import com.looker.core.datastore.SettingsSerializer +import com.looker.core.datastore.migration.ProtoDataStoreMigration import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -13,7 +18,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton -private const val PREFERENCES = "preferences_file" +private const val OLD_PREFERENCES = "preferences_file" +private const val PREFERENCES = "settings_file" @Module @InstallIn(SingletonComponent::class) @@ -24,12 +30,26 @@ object DatastoreModule { fun provideDatastore( @ApplicationContext context: Context ): DataStore = PreferenceDataStoreFactory.create { - context.preferencesDataStoreFile(PREFERENCES) + context.preferencesDataStoreFile(OLD_PREFERENCES) + } + + @Singleton + @Provides + fun provideProtoDatastore( + @ApplicationContext context: Context, + oldDataStore: DataStore + ): DataStore = DataStoreFactory.create( + serializer = SettingsSerializer, + migrations = listOf( + ProtoDataStoreMigration(oldDataStore) + ) + ) { + context.dataStoreFile(PREFERENCES) } @Singleton @Provides fun provideSettingsRepository( - dataStore: DataStore + dataStore: DataStore ): SettingsRepository = SettingsRepository(dataStore) } diff --git a/core/datastore/src/main/java/com/looker/core/datastore/migration/ProtoDataStoreMigration.kt b/core/datastore/src/main/java/com/looker/core/datastore/migration/ProtoDataStoreMigration.kt new file mode 100644 index 000000000..3621ad625 --- /dev/null +++ b/core/datastore/src/main/java/com/looker/core/datastore/migration/ProtoDataStoreMigration.kt @@ -0,0 +1,100 @@ +package com.looker.core.datastore.migration + +import androidx.datastore.core.DataMigration +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.core.stringSetPreferencesKey +import com.looker.core.datastore.Settings +import com.looker.core.datastore.model.AutoSync +import com.looker.core.datastore.model.InstallerType +import com.looker.core.datastore.model.ProxyPreference +import com.looker.core.datastore.model.ProxyType +import com.looker.core.datastore.model.SortOrder +import com.looker.core.datastore.model.Theme +import kotlinx.coroutines.flow.first +import kotlinx.datetime.Instant +import kotlin.time.Duration.Companion.hours + +class ProtoDataStoreMigration( + private val oldDataStore: DataStore +) : DataMigration { + override suspend fun cleanUp() { + oldDataStore.edit { it.clear() } + } + + override suspend fun shouldMigrate(currentData: Settings): Boolean = + oldDataStore.data.first().asMap().isNotEmpty() + + override suspend fun migrate(currentData: Settings): Settings { + return oldDataStore.data.first().mapSettings() + } + + // TODO: Remove after next update + private companion object { + val LANGUAGE = stringPreferencesKey("key_language") + val INCOMPATIBLE_VERSIONS = booleanPreferencesKey("key_incompatible_versions") + val NOTIFY_UPDATES = booleanPreferencesKey("key_notify_updates") + val UNSTABLE_UPDATES = booleanPreferencesKey("key_unstable_updates") + val THEME = stringPreferencesKey("key_theme") + val DYNAMIC_THEME = booleanPreferencesKey("key_dynamic_theme") + val INSTALLER_TYPE = stringPreferencesKey("key_installer_type") + val AUTO_UPDATE = booleanPreferencesKey("key_auto_updates") + val AUTO_SYNC = stringPreferencesKey("key_auto_sync") + val SORT_ORDER = stringPreferencesKey("key_sort_order") + val PROXY_TYPE = stringPreferencesKey("key_proxy_type") + val PROXY_HOST = stringPreferencesKey("key_proxy_host") + val PROXY_PORT = intPreferencesKey("key_proxy_port") + val CLEAN_UP_INTERVAL = longPreferencesKey("clean_up_interval") + val LAST_CLEAN_UP = longPreferencesKey("last_clean_up_time") + val FAVOURITE_APPS = stringSetPreferencesKey("favourite_apps") + val HOME_SCREEN_SWIPING = booleanPreferencesKey("home_swiping") + + private fun Preferences.mapSettings(): Settings { + val defaultSetting = Settings() + + val language = this[LANGUAGE] ?: defaultSetting.language + val incompatibleVersions = this[INCOMPATIBLE_VERSIONS] + ?: defaultSetting.incompatibleVersions + val notifyUpdate = this[NOTIFY_UPDATES] ?: defaultSetting.notifyUpdate + val unstableUpdate = this[UNSTABLE_UPDATES] ?: defaultSetting.unstableUpdate + val theme = Theme.valueOf(this[THEME] ?: Theme.SYSTEM.name) + val dynamicTheme = this[DYNAMIC_THEME] ?: defaultSetting.dynamicTheme + val installerType = + InstallerType.valueOf(this[INSTALLER_TYPE] ?: defaultSetting.installerType.name) + val autoUpdate = this[AUTO_UPDATE] ?: false + val autoSync = AutoSync.valueOf(this[AUTO_SYNC] ?: defaultSetting.autoSync.name) + val sortOrder = SortOrder.valueOf(this[SORT_ORDER] ?: defaultSetting.sortOrder.name) + val type = ProxyType.valueOf(this[PROXY_TYPE] ?: defaultSetting.proxy.type.name) + val host = this[PROXY_HOST] ?: defaultSetting.proxy.host + val port = this[PROXY_PORT] ?: defaultSetting.proxy.port + val proxy = ProxyPreference(type = type, host = host, port = port) + val cleanUpInterval = this[CLEAN_UP_INTERVAL]?.hours ?: defaultSetting.cleanUpInterval + val lastCleanup = this[LAST_CLEAN_UP]?.let { Instant.fromEpochMilliseconds(it) } + val favouriteApps = this[FAVOURITE_APPS] ?: defaultSetting.favouriteApps + val homeScreenSwiping = this[HOME_SCREEN_SWIPING] ?: defaultSetting.homeScreenSwiping + + return Settings( + language = language, + incompatibleVersions = incompatibleVersions, + notifyUpdate = notifyUpdate, + unstableUpdate = unstableUpdate, + theme = theme, + dynamicTheme = dynamicTheme, + installerType = installerType, + autoUpdate = autoUpdate, + autoSync = autoSync, + sortOrder = sortOrder, + proxy = proxy, + cleanUpInterval = cleanUpInterval, + lastCleanup = lastCleanup, + favouriteApps = favouriteApps, + homeScreenSwiping = homeScreenSwiping + ) + } + } +} diff --git a/core/datastore/src/main/java/com/looker/core/datastore/model/ProxyPreference.kt b/core/datastore/src/main/java/com/looker/core/datastore/model/ProxyPreference.kt index 6d27e5bfd..63228a8f1 100644 --- a/core/datastore/src/main/java/com/looker/core/datastore/model/ProxyPreference.kt +++ b/core/datastore/src/main/java/com/looker/core/datastore/model/ProxyPreference.kt @@ -1,7 +1,20 @@ package com.looker.core.datastore.model +import kotlinx.serialization.Serializable + +@Serializable data class ProxyPreference( - val type: ProxyType, - val host: String, - val port: Int -) + val type: ProxyType = ProxyType.DIRECT, + val host: String = "localhost", + val port: Int = 9050 +) { + fun update( + newType: ProxyType? = null, + newHost: String? = null, + newPort: Int? = null + ): ProxyPreference = copy( + type = newType ?: type, + host = newHost ?: host, + port = newPort ?: port + ) +} diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index dbe7dfb77..25d498eb5 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.looker.android.library) + alias(libs.plugins.looker.lint) } android { diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 02a281cdc..1d0c43232 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.looker.android.library) alias(libs.plugins.looker.hilt) + alias(libs.plugins.looker.lint) } android { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3286d6801..c2a44b786 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,7 @@ androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", ve androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } androidx-dataStore-core = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidxDataStore" } +androidx-dataStore-proto = { group = "androidx.datastore", name = "datastore", version.ref = "androidxDataStore" } androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "androidxLifecycle" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" } androidx-lifecycle-viewModel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } @@ -105,5 +106,5 @@ looker-android-application = { id = "looker.android.application", version = "uns looker-android-library = { id = "looker.android.library", version = "unspecified" } looker-hilt = { id = "looker.hilt", version = "unspecified" } looker-hilt-work = { id = "looker.hilt.work", version = "unspecified" } +looker-lint = { id = "looker.lint", version = "unspecified" } looker-room = { id = "looker.room", version = "unspecified" } - diff --git a/installer/build.gradle.kts b/installer/build.gradle.kts index 62d7db026..c3cef99f6 100644 --- a/installer/build.gradle.kts +++ b/installer/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.looker.android.library) alias(libs.plugins.looker.hilt) + alias(libs.plugins.looker.lint) } android {