From d5789bb1246cf6e4229988c1ba9e61a01f626d26 Mon Sep 17 00:00:00 2001 From: Michatec Date: Thu, 5 Mar 2026 18:25:52 +0100 Subject: [PATCH] -- BIG Update -- --- .gitignore | 1 + README.md | 7 +- build.gradle | 88 ++--- extra/launcher.svg | 356 +++++------------- gradle/wrapper/gradle-wrapper.properties | 2 +- proguard.pro | 1 - settings.gradle | 2 + src/main/AndroidManifest.xml | 36 +- src/main/ic_launcher-playstore.png | Bin 0 -> 51071 bytes .../kitsunyan/foxydroid/MainApplication.kt | 21 +- .../nya/kitsunyan/foxydroid/content/Cache.kt | 12 +- .../foxydroid/content/Preferences.kt | 15 +- .../foxydroid/content/ProductPreferences.kt | 18 +- .../foxydroid/database/CursorOwner.kt | 44 ++- .../kitsunyan/foxydroid/database/Database.kt | 272 +++++++------ .../foxydroid/database/ObservableCursor.kt | 36 +- .../foxydroid/database/QueryBuilder.kt | 7 +- .../nya/kitsunyan/foxydroid/entity/Product.kt | 6 +- .../nya/kitsunyan/foxydroid/entity/Release.kt | 8 +- .../kitsunyan/foxydroid/entity/Repository.kt | 37 +- .../foxydroid/graphics/DrawableWrapper.kt | 28 +- .../kitsunyan/foxydroid/index/IndexHandler.kt | 2 +- .../kitsunyan/foxydroid/index/IndexMerger.kt | 30 +- .../foxydroid/index/IndexV1Parser.kt | 12 +- .../foxydroid/index/RepositoryUpdater.kt | 52 ++- .../kitsunyan/foxydroid/network/Downloader.kt | 8 +- .../foxydroid/network/PicassoDownloader.kt | 2 +- .../screen/EditRepositoryFragment.kt | 146 +++---- .../foxydroid/screen/MessageDialog.kt | 11 +- .../foxydroid/screen/PreferencesFragment.kt | 52 +-- .../foxydroid/screen/ProductAdapter.kt | 236 +++++++----- .../foxydroid/screen/ProductFragment.kt | 81 ++-- .../foxydroid/screen/ProductsAdapter.kt | 59 ++- .../foxydroid/screen/ProductsFragment.kt | 13 +- .../foxydroid/screen/RepositoriesAdapter.kt | 32 +- .../foxydroid/screen/RepositoryFragment.kt | 26 +- .../foxydroid/screen/ScreenActivity.kt | 58 +-- .../foxydroid/screen/ScreenshotsFragment.kt | 67 ++-- .../foxydroid/screen/TabsFragment.kt | 167 ++++---- .../foxydroid/service/DownloadService.kt | 34 +- .../foxydroid/service/SyncService.kt | 31 +- .../foxydroid/utility/PackageItemResolver.kt | 2 +- .../kitsunyan/foxydroid/utility/RxUtils.kt | 88 ++--- .../nya/kitsunyan/foxydroid/utility/Utils.kt | 13 +- .../foxydroid/utility/extension/Android.kt | 20 +- .../foxydroid/utility/extension/Json.kt | 2 +- .../foxydroid/utility/extension/Resources.kt | 7 +- .../foxydroid/widget/CursorRecyclerAdapter.kt | 51 ++- .../foxydroid/widget/RecyclerFastScroller.kt | 2 - .../drawable-hdpi/ic_launcher_foreground.png | Bin 3808 -> 0 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 2396 -> 0 bytes .../drawable-xhdpi/ic_launcher_foreground.png | Bin 5407 -> 0 bytes .../ic_launcher_foreground.png | Bin 8820 -> 0 bytes .../ic_launcher_foreground.png | Bin 12664 -> 0 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 11 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3503 -> 0 bytes src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 2640 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 3920 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 4754 bytes src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2233 -> 0 bytes src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1784 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 2634 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 3028 bytes src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4884 -> 0 bytes src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 3524 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 5178 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 6348 bytes src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7718 -> 0 bytes src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 5138 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 8048 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 9548 bytes src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 10904 -> 0 bytes src/main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 7256 bytes .../ic_launcher_foreground.webp | Bin 0 -> 11640 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 13226 bytes .../res/values/ic_launcher_background.xml | 4 + src/main/res/values/strings.xml | 1 + 78 files changed, 1190 insertions(+), 1132 deletions(-) create mode 100644 settings.gradle create mode 100644 src/main/ic_launcher-playstore.png delete mode 100644 src/main/res/drawable-hdpi/ic_launcher_foreground.png delete mode 100644 src/main/res/drawable-mdpi/ic_launcher_foreground.png delete mode 100644 src/main/res/drawable-xhdpi/ic_launcher_foreground.png delete mode 100644 src/main/res/drawable-xxhdpi/ic_launcher_foreground.png delete mode 100644 src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png create mode 100644 src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 src/main/res/values/ic_launcher_background.xml diff --git a/.gitignore b/.gitignore index 4aea7f9..569393f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /* !/.gitignore !/build.gradle +!/settings.gradle !/COPYING !/extra !/gradle diff --git a/README.md b/README.md index 1eda0de..cdddf61 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# Foxy Droid +# Michas Droid Yet another F-Droid client. -[![Release](https://img.shields.io/github/v/release/kitsunyan/foxy-droid)](https://github.com/kitsunyan/foxy-droid/releases) -[![F-Droid](https://img.shields.io/f-droid/v/nya.kitsunyan.foxydroid)](https://f-droid.org/packages/nya.kitsunyan.foxydroid/) +[![Release](https://img.shields.io/github/v/release/michatec/michas-droid)](https://github.com/michatec/michas-droid/releases/latest) ## Description @@ -48,4 +47,4 @@ Run `./gradlew assembleRelease` to build the package, which can be installed usi ## License -Foxy Droid is available under the terms of the GNU General Public License v3 or later. Copyright © 2020 kitsunyan. +Michas Droid is available under the terms of the GNU General Public License v3 or later. Copyright © 2026 Michatec. diff --git a/build.gradle b/build.gradle index 4c8edca..8974baa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,52 +1,46 @@ buildscript { - ext.versions = [ - android: '3.4.1', - kotlin: '1.3.72' - ] - repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:' + versions.android - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:' + versions.kotlin + classpath 'com.android.tools.build:gradle:9.0.1' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10' } } apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' android { - compileSdkVersion 29 - buildToolsVersion '29.0.3' + namespace 'nya.kitsunyan.foxydroid' + compileSdk 36 defaultConfig { - archivesBaseName = 'foxy-droid' applicationId 'nya.kitsunyan.foxydroid' - minSdkVersion 21 - targetSdkVersion 29 - versionCode 4 - versionName '1.3' + minSdk 30 + targetSdk 36 + versionCode 15 + versionName '1.5' def languages = [ 'en' ] buildConfigField 'String[]', 'LANGUAGES', '{ "' + languages.join('", "') + '" }' resConfigs languages } - sourceSets.all { - def javaDir = it.java.srcDirs.find { it.name == 'java' } - it.java.srcDirs += new File(javaDir.parentFile, 'kotlin') + buildFeatures { + buildConfig = true + } + + sourceSets { + main { + java.srcDirs += 'src/main/kotlin' + } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = compileOptions.sourceCompatibility.toString() + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } buildTypes { @@ -58,25 +52,6 @@ android { minifyEnabled true shrinkResources false } - all { - crunchPngs false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.pro' - } - } - - lintOptions { - warning 'InvalidPackage' - ignore 'InvalidVectorPath' - } - - packagingOptions { - exclude '/DebugProbesKt.bin' - exclude '/kotlin/**.kotlin_builtins' - exclude '/kotlin/**.kotlin_metadata' - exclude '/META-INF/**.kotlin_module' - exclude '/META-INF/**.pro' - exclude '/META-INF/**.version' - exclude '/okhttp3/internal/publicsuffix/*' } def keystorePropertiesFile = rootProject.file('keystore.properties') @@ -98,31 +73,26 @@ android { storePassword signing.storePassword keyAlias signing.keyAlias keyPassword signing.keyPassword - v2SigningEnabled false + enableV2Signing false } } - - buildTypes { - debug.signingConfig signingConfigs.primary - release.signingConfig signingConfigs.primary - } } } } repositories { google() - jcenter() + mavenCentral() } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin - implementation 'androidx.fragment:fragment:1.2.5' - implementation 'androidx.viewpager2:viewpager2:1.0.0' - implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'com.squareup.okhttp3:okhttp:4.7.2' - implementation 'io.reactivex.rxjava3:rxjava:3.0.4' - implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' - implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.3.10' + implementation 'androidx.fragment:fragment-ktx:1.8.9' + implementation 'androidx.viewpager2:viewpager2:1.1.0' + implementation 'androidx.vectordrawable:vectordrawable:1.2.0' + implementation 'com.squareup.okhttp3:okhttp:5.3.2' + implementation 'io.reactivex.rxjava3:rxjava:3.1.12' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' + implementation 'com.fasterxml.jackson.core:jackson-core:2.21.1' implementation 'com.squareup.picasso:picasso:2.71828' } diff --git a/extra/launcher.svg b/extra/launcher.svg index e9f1150..6f4bd8b 100644 --- a/extra/launcher.svg +++ b/extra/launcher.svg @@ -1,273 +1,87 @@ - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 558870d..d706aba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/proguard.pro b/proguard.pro index c4b4024..b11478f 100644 --- a/proguard.pro +++ b/proguard.pro @@ -1,4 +1,3 @@ --dontobfuscate # Disable ServiceLoader reproducibility-breaking optimizations -keep class kotlinx.coroutines.CoroutineExceptionHandler diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9d8d6b8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "michas-droid" +include ':' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 11237ad..190ee09 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,26 +1,33 @@ - + - + + + + + + + + + + + + + android:theme="@style/Theme.Main.Light"> + android:name=".MainApplication$BootReceiver" + android:exported="true"> @@ -30,7 +37,8 @@ @@ -52,7 +60,7 @@ - + @@ -69,7 +77,8 @@ + android:name=".service.SyncService" + android:foregroundServiceType="dataSync" /> + android:name=".service.DownloadService" + android:foregroundServiceType="dataSync" /> diff --git a/src/main/ic_launcher-playstore.png b/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..76ded4cf3e3a8b9bd39c2bc36ca99bdca0b692c3 GIT binary patch literal 51071 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 Q0sPa|Hawqy&i={&17KclW&i*H literal 0 HcmV?d00001 diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt index 4a2de84..fbbd8b5 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/MainApplication.kt @@ -12,6 +12,7 @@ import android.content.IntentFilter import android.content.pm.PackageInfo import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso +import io.reactivex.rxjava3.disposables.Disposable import nya.kitsunyan.foxydroid.content.Cache import nya.kitsunyan.foxydroid.content.Preferences import nya.kitsunyan.foxydroid.content.ProductPreferences @@ -27,7 +28,6 @@ import nya.kitsunyan.foxydroid.utility.extension.android.* import java.net.InetSocketAddress import java.net.Proxy -@Suppress("unused") class MainApplication: Application() { private fun PackageInfo.toInstalledItem(): InstalledItem { val signatureString = singleSignature?.let(Utils::calculateHash).orEmpty() @@ -38,13 +38,15 @@ class MainApplication: Application() { super.attachBaseContext(Utils.configureLocale(base)) } + private var preferencesDisposable: Disposable? = null + override fun onCreate() { super.onCreate() val databaseUpdated = Database.init(this) Preferences.init(this) ProductPreferences.init(this) - RepositoryUpdater.init(this) + RepositoryUpdater.init() listenApplications() listenPreferences() @@ -69,7 +71,7 @@ class MainApplication: Application() { Intent.ACTION_PACKAGE_REMOVED -> { val packageInfo = try { packageManager.getPackageInfo(packageName, Android.PackageManager.signaturesFlag) - } catch (e: Exception) { + } catch (_: Exception) { null } if (packageInfo != null) { @@ -93,22 +95,21 @@ class MainApplication: Application() { private fun listenPreferences() { updateProxy() - var lastAutoSync = Preferences[Preferences.Key.AutoSync] - var lastUpdateUnstable = Preferences[Preferences.Key.UpdateUnstable] - Preferences.observable.subscribe { + val lastAutoSync = Preferences[Preferences.Key.AutoSync] + val lastUpdateUnstable = Preferences[Preferences.Key.UpdateUnstable] + preferencesDisposable?.dispose() + preferencesDisposable = Preferences.observable.subscribe { if (it == Preferences.Key.ProxyType || it == Preferences.Key.ProxyHost || it == Preferences.Key.ProxyPort) { updateProxy() } else if (it == Preferences.Key.AutoSync) { val autoSync = Preferences[Preferences.Key.AutoSync] if (lastAutoSync != autoSync) { - lastAutoSync = autoSync - updateSyncJob(true) + updateSyncJob(true) } } else if (it == Preferences.Key.UpdateUnstable) { val updateUnstable = Preferences[Preferences.Key.UpdateUnstable] if (lastUpdateUnstable != updateUnstable) { - lastUpdateUnstable = updateUnstable - forceSyncAll() + forceSyncAll() } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Cache.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Cache.kt index 3fa94eb..b35dd65 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Cache.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Cache.kt @@ -61,7 +61,7 @@ object Cache { fun getReleaseUri(context: Context, cacheFileName: String): Uri { val file = getReleaseFile(context, cacheFileName) val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PROVIDERS) - val authority = packageInfo.providers.find { it.name == Provider::class.java.name }!!.authority + val authority = packageInfo.providers?.find { it.name == Provider::class.java.name }!!.authority return Uri.Builder().scheme("content").authority(authority) .encodedPath(subPath(context.cacheDir, file)).build() } @@ -106,8 +106,8 @@ object Cache { try { val stat = Os.lstat(it.path) stat.st_atime < olderThan - } catch (e: Exception) { - false + } catch (_: Exception) { + false } } if (older) { @@ -138,7 +138,7 @@ object Cache { override fun onCreate(): Boolean = true override fun query(uri: Uri, projection: Array?, - selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? { + selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor { val file = getFileAndTypeForUri(uri).first val columns = (projection ?: defaultColumns).mapNotNull { when (it) { @@ -150,12 +150,12 @@ object Cache { return MatrixCursor(columns.first.toTypedArray()).apply { addRow(columns.second.toTypedArray()) } } - override fun getType(uri: Uri): String? = getFileAndTypeForUri(uri).second + override fun getType(uri: Uri): String = getFileAndTypeForUri(uri).second private val unsupported: Nothing get() = throw UnsupportedOperationException() - override fun insert(uri: Uri, contentValues: ContentValues?): Uri? = unsupported + override fun insert(uri: Uri, contentValues: ContentValues?): Uri = unsupported override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = unsupported override fun update(uri: Uri, contentValues: ContentValues?, selection: String?, selectionArgs: Array?): Int = unsupported diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt index 470fe68..cc37017 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt @@ -9,6 +9,7 @@ import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.entity.ProductItem import nya.kitsunyan.foxydroid.utility.extension.android.* import java.net.Proxy +import androidx.core.content.edit object Preferences { private lateinit var preferences: SharedPreferences @@ -18,9 +19,13 @@ object Preferences { private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType, Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap() + private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, keyString -> + keys[keyString]?.let(subject::onNext) + } + fun init(context: Context) { preferences = context.getSharedPreferences("${context.packageName}_preferences", Context.MODE_PRIVATE) - preferences.registerOnSharedPreferenceChangeListener { _, keyString -> keys[keyString]?.let(subject::onNext) } + preferences.registerOnSharedPreferenceChangeListener(listener) } val observable: Observable> @@ -38,7 +43,7 @@ object Preferences { } override fun set(preferences: SharedPreferences, key: String, value: Boolean) { - preferences.edit().putBoolean(key, value).apply() + preferences.edit(commit = true) { putBoolean(key, value) } } } @@ -48,7 +53,7 @@ object Preferences { } override fun set(preferences: SharedPreferences, key: String, value: Int) { - preferences.edit().putInt(key, value).apply() + preferences.edit(commit = true) { putInt(key, value) } } } @@ -58,7 +63,7 @@ object Preferences { } override fun set(preferences: SharedPreferences, key: String, value: String) { - preferences.edit().putString(key, value).apply() + preferences.edit(commit = true) { putString(key, value) } } } @@ -69,7 +74,7 @@ object Preferences { } override fun set(preferences: SharedPreferences, key: String, value: T) { - preferences.edit().putString(key, value.valueString).apply() + preferences.edit(commit = true) { putString(key, value.valueString) } } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/content/ProductPreferences.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/content/ProductPreferences.kt index 4dcf578..21a1ec8 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/content/ProductPreferences.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/content/ProductPreferences.kt @@ -2,6 +2,7 @@ package nya.kitsunyan.foxydroid.content import android.content.Context import android.content.SharedPreferences +import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.PublishSubject import nya.kitsunyan.foxydroid.database.Database @@ -9,17 +10,20 @@ import nya.kitsunyan.foxydroid.entity.ProductPreference import nya.kitsunyan.foxydroid.utility.extension.json.* import java.io.ByteArrayOutputStream import java.nio.charset.Charset +import androidx.core.content.edit object ProductPreferences { private val defaultProductPreference = ProductPreference(false, 0L) private lateinit var preferences: SharedPreferences private val subject = PublishSubject.create>() + private var disposable: Disposable? = null fun init(context: Context) { preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE) Database.LockAdapter.putAll(preferences.all.keys .mapNotNull { packageName -> this[packageName].databaseVersionCode?.let { Pair(packageName, it) } }) - subject + disposable?.dispose() + disposable = subject .observeOn(Schedulers.io()) .subscribe { (packageName, versionCode) -> if (versionCode != null) { @@ -53,9 +57,15 @@ object ProductPreferences { operator fun set(packageName: String, productPreference: ProductPreference) { val oldProductPreference = this[packageName] - preferences.edit().putString(packageName, ByteArrayOutputStream() - .apply { Json.factory.createGenerator(this).use { it.writeDictionary(productPreference::serialize) } } - .toByteArray().toString(Charset.defaultCharset())).apply() + preferences.edit { + putString( + packageName, ByteArrayOutputStream() + .apply { + Json.factory.createGenerator(this) + .use { it.writeDictionary(productPreference::serialize) } + } + .toByteArray().toString(Charset.defaultCharset())) + } if (oldProductPreference.ignoreUpdates != productPreference.ignoreUpdates || oldProductPreference.ignoreVersionCode != productPreference.ignoreVersionCode) { subject.onNext(Pair(packageName, productPreference.databaseVersionCode)) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/database/CursorOwner.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/database/CursorOwner.kt index 7a6dc2c..c6172c3 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/database/CursorOwner.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/database/CursorOwner.kt @@ -3,6 +3,8 @@ package nya.kitsunyan.foxydroid.database import android.database.Cursor import android.os.Bundle import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import nya.kitsunyan.foxydroid.entity.ProductItem @@ -39,13 +41,20 @@ class CursorOwner: Fragment(), LoaderManager.LoaderCallbacks { fun onCursorData(request: Request, cursor: Cursor?) } - private data class ActiveRequest(val request: Request, val callback: Callback?, val cursor: Cursor?) + data class ActiveRequest(val request: Request, val callback: Callback?, val cursor: Cursor?) - init { - retainInstance = true + class CursorViewModel : ViewModel() { + internal val activeRequests = mutableMapOf() + + override fun onCleared() { + activeRequests.values.forEach { it.cursor?.close() } + activeRequests.clear() + } } - private val activeRequests = mutableMapOf() + private val viewModel by lazy { ViewModelProvider(this)[CursorViewModel::class.java] } + private val activeRequests: MutableMap + get() = viewModel.activeRequests fun attach(callback: Callback, request: Request) { val oldActiveRequest = activeRequests[request.id] @@ -79,11 +88,32 @@ class CursorOwner: Fragment(), LoaderManager.LoaderCallbacks { return QueryLoader(requireContext()) { when (request) { is Request.ProductsAvailable -> Database.ProductAdapter - .query(false, false, request.searchQuery, request.section, request.order, it) + .query( + installed = false, + updates = false, + searchQuery = request.searchQuery, + section = request.section, + order = request.order, + signal = it + ) is Request.ProductsInstalled -> Database.ProductAdapter - .query(true, false, request.searchQuery, request.section, request.order, it) + .query( + installed = true, + updates = false, + searchQuery = request.searchQuery, + section = request.section, + order = request.order, + signal = it + ) is Request.ProductsUpdates -> Database.ProductAdapter - .query(true, true, request.searchQuery, request.section, request.order, it) + .query( + installed = true, + updates = true, + searchQuery = request.searchQuery, + section = request.section, + order = request.order, + signal = it + ) is Request.Repositories -> Database.RepositoryAdapter.query(it) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt index 7b7a24d..268a8ab 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/database/Database.kt @@ -16,6 +16,7 @@ import nya.kitsunyan.foxydroid.entity.Repository import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.utility.extension.json.* import java.io.ByteArrayOutputStream +import androidx.core.database.sqlite.transaction object Database { fun init(context: Context): Boolean { @@ -170,6 +171,7 @@ object Database { } override fun onOpen(db: SQLiteDatabase) { + db.enableWriteAheadLogging() val create = handleTables(db, false, Schema.Repository) val updated = handleTables(db, create, Schema.Product, Schema.Category) db.execSQL("ATTACH DATABASE ':memory:' AS memory") @@ -182,9 +184,11 @@ object Database { } private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean { - val shouldRecreate = recreate || tables.any { - val sql = db.query("${it.databasePrefix}sqlite_master", columns = arrayOf("sql"), - selection = Pair("type = ? AND name = ?", arrayOf("table", it.innerName))) + val shouldRecreate = recreate || tables.any { it -> + val sql = db.query( + "${it.databasePrefix}sqlite_master", columns = arrayOf("sql"), + selection = Pair("type = ? AND name = ?", arrayOf("table", it.innerName)) + ) .use { it.firstOrNull()?.getString(0) }.orEmpty() it.formatCreateTable(it.innerName) != sql } @@ -202,10 +206,12 @@ object Database { } private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) { - val shouldVacuum = tables.map { - val sqls = db.query("${it.databasePrefix}sqlite_master", columns = arrayOf("name", "sql"), - selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", it.innerName))) - .use { it.asSequence().mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } }.toList() } + val shouldVacuum = tables.map { it -> + val sqls = db.query( + "${it.databasePrefix}sqlite_master", columns = arrayOf("name", "sql"), + selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", it.innerName)) + ) + .use { it -> it.asSequence().mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } }.toList() } .filter { !it.first.startsWith("sqlite_") } val createIndexes = it.createIndexPairFormatted?.let { listOf(it) }.orEmpty() createIndexes.map { it.first } != sqls.map { it.second } && run { @@ -224,11 +230,13 @@ object Database { } private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) { - val tables = db.query("sqlite_master", columns = arrayOf("name"), - selection = Pair("type = ?", arrayOf("table"))) - .use { it.asSequence().mapNotNull { it.getString(0) }.toList() } + val tables = db.query( + "sqlite_master", columns = arrayOf("name"), + selection = Pair("type = ?", arrayOf("table")) + ) + .use { it -> it.asSequence().mapNotNull { it.getString(0) }.toList() } .filter { !it.startsWith("sqlite_") && !it.startsWith("android_") } - .toSet() - neededTables.mapNotNull { if (it.memory) null else it.name } + .toSet() - neededTables.mapNotNull { if (it.memory) null else it.name }.toSet() if (tables.isNotEmpty()) { for (table in tables) { db.execSQL("DROP TABLE IF EXISTS $table") @@ -281,9 +289,11 @@ object Database { return if (replace) replace(table, null, contentValues) else insert(table, null, contentValues) } - private fun SQLiteDatabase.query(table: String, columns: Array? = null, - selection: Pair>? = null, orderBy: String? = null, - signal: CancellationSignal? = null): Cursor { + private fun SQLiteDatabase.query( + table: String, columns: Array? = null, + selection: Pair>? = null, orderBy: String? = null, + signal: CancellationSignal? = null + ): Cursor { return query(false, table, columns, selection?.first, selection?.second, null, null, orderBy, null, signal) } @@ -313,33 +323,40 @@ object Database { }) } - fun put(repository: Repository): Repository { + fun put(repository: Repository): Long { val shouldReplace = repository.id >= 0L val newId = putWithoutNotification(repository, shouldReplace) val id = if (shouldReplace) repository.id else newId notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - return if (newId != repository.id) repository.copy(id = newId) else repository + return id } fun get(id: Long): Repository? { - return db.query(Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0", - arrayOf(id.toString()))) + return db.query( + Schema.Repository.name, + selection = Pair("${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0", + arrayOf(id.toString())) + ) .use { it.firstOrNull()?.let(::transform) } } fun getAll(signal: CancellationSignal?): List { - return db.query(Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal).use { it.asSequence().map(::transform).toList() } + return db.query( + Schema.Repository.name, + selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), + signal = signal + ).use { it.asSequence().map(::transform).toList() } } fun getAllDisabledDeleted(signal: CancellationSignal?): Set> { - return db.query(Schema.Repository.name, - columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DELETED), - selection = Pair("${Schema.Repository.ROW_ENABLED} == 0 OR ${Schema.Repository.ROW_DELETED} != 0", emptyArray()), - signal = signal).use { it.asSequence().map { Pair(it.getLong(it.getColumnIndex(Schema.Repository.ROW_ID)), - it.getInt(it.getColumnIndex(Schema.Repository.ROW_DELETED)) != 0) }.toSet() } + return db.query( + Schema.Repository.name, + columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DELETED), + selection = Pair("${Schema.Repository.ROW_ENABLED} == 0 OR ${Schema.Repository.ROW_DELETED} != 0", emptyArray()), + signal = signal + ).use { it -> + it.asSequence().map { Pair(it.getLong(it.getColumnIndexOrThrow(Schema.Repository.ROW_ID)), + it.getInt(it.getColumnIndexOrThrow(Schema.Repository.ROW_DELETED)) != 0) }.toSet() } } fun markAsDeleted(id: Long) { @@ -350,8 +367,8 @@ object Database { } fun cleanup(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val idsString = it.joinToString(separator = ", ") { it.first.toString() } + val result = pairs.windowed(10, 10, true).map { it -> + val idsString = it.joinToString(separator = ", ") { it.first.toString() } val productsCount = db.delete(Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} IN ($idsString)", null) val categoriesCount = db.delete(Schema.Category.name, @@ -369,28 +386,34 @@ object Database { } fun query(signal: CancellationSignal?): Cursor { - return db.query(Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal).observable(Subject.Repositories) + return db.query( + Schema.Repository.name, + selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), + signal = signal + ).observable(Subject.Repositories) } fun transform(cursor: Cursor): Repository { - return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) - .jsonParse { Repository.deserialize(cursor.getLong(cursor.getColumnIndex(Schema.Repository.ROW_ID)), it) } + return cursor.getBlob(cursor.getColumnIndexOrThrow(Schema.Repository.ROW_DATA)) + .jsonParse { Repository.deserialize(cursor.getLong(cursor.getColumnIndexOrThrow(Schema.Repository.ROW_ID)), it) } } } object ProductAdapter { fun get(packageName: String, signal: CancellationSignal?): List { - return db.query(Schema.Product.name, - columns = arrayOf(Schema.Product.ROW_REPOSITORY_ID, Schema.Product.ROW_DESCRIPTION, Schema.Product.ROW_DATA), - selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal).use { it.asSequence().map(::transform).toList() } + return db.query( + Schema.Product.name, + columns = arrayOf(Schema.Product.ROW_REPOSITORY_ID, Schema.Product.ROW_DESCRIPTION, Schema.Product.ROW_DATA), + selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), + signal = signal + ).use { it.asSequence().map(::transform).toList() } } fun getCount(repositoryId: Long): Int { - return db.query(Schema.Product.name, columns = arrayOf("COUNT (*)"), - selection = Pair("${Schema.Product.ROW_REPOSITORY_ID} = ?", arrayOf(repositoryId.toString()))) + return db.query( + Schema.Product.name, columns = arrayOf("COUNT (*)"), + selection = Pair("${Schema.Product.ROW_REPOSITORY_ID} = ?", arrayOf(repositoryId.toString())) + ) .use { it.firstOrNull()?.getInt(0) ?: 0 } } @@ -464,28 +487,28 @@ object Database { ProductItem.Order.NAME -> Unit ProductItem.Order.DATE_ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC," ProductItem.Order.LAST_UPDATE -> builder += "product.${Schema.Product.ROW_UPDATED} DESC," - }::class + } builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC" return builder.query(db, signal).observable(Subject.Products) } private fun transform(cursor: Cursor): Product { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) - .jsonParse { Product.deserialize(cursor.getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)), - cursor.getString(cursor.getColumnIndex(Schema.Product.ROW_DESCRIPTION)), it) } + return cursor.getBlob(cursor.getColumnIndexOrThrow(Schema.Product.ROW_DATA)) + .jsonParse { Product.deserialize(cursor.getLong(cursor.getColumnIndexOrThrow(Schema.Product.ROW_REPOSITORY_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Product.ROW_DESCRIPTION)), it) } } fun transformItem(cursor: Cursor): ProductItem { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) - .jsonParse { ProductItem.deserialize(cursor.getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)), - cursor.getString(cursor.getColumnIndex(Schema.Product.ROW_PACKAGE_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Product.ROW_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Product.ROW_SUMMARY)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)).orEmpty(), - cursor.getInt(cursor.getColumnIndex(Schema.Product.ROW_COMPATIBLE)) != 0, - cursor.getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_CAN_UPDATE)) != 0, - cursor.getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_MATCH_RANK)), it) } + return cursor.getBlob(cursor.getColumnIndexOrThrow(Schema.Product.ROW_DATA_ITEM)) + .jsonParse { ProductItem.deserialize(cursor.getLong(cursor.getColumnIndexOrThrow(Schema.Product.ROW_REPOSITORY_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Product.ROW_PACKAGE_NAME)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Product.ROW_NAME)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Product.ROW_SUMMARY)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_VERSION)).orEmpty(), + cursor.getInt(cursor.getColumnIndexOrThrow(Schema.Product.ROW_COMPATIBLE)) != 0, + cursor.getInt(cursor.getColumnIndexOrThrow(Schema.Synthetic.ROW_CAN_UPDATE)) != 0, + cursor.getInt(cursor.getColumnIndexOrThrow(Schema.Synthetic.ROW_MATCH_RANK)), it) } } } @@ -500,18 +523,21 @@ object Database { WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND repository.${Schema.Repository.ROW_DELETED} == 0""" - return builder.query(db, signal).use { it.asSequence() - .map { it.getString(it.getColumnIndex(Schema.Category.ROW_NAME)) }.toSet() } + return builder.query(db, signal).use { it -> + it.asSequence() + .map { it.getString(it.getColumnIndexOrThrow(Schema.Category.ROW_NAME)) }.toSet() } } } object InstalledAdapter { fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { - return db.query(Schema.Installed.name, - columns = arrayOf(Schema.Installed.ROW_PACKAGE_NAME, Schema.Installed.ROW_VERSION, - Schema.Installed.ROW_VERSION_CODE, Schema.Installed.ROW_SIGNATURE), - selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal).use { it.firstOrNull()?.let(::transform) } + return db.query( + Schema.Installed.name, + columns = arrayOf(Schema.Installed.ROW_PACKAGE_NAME, Schema.Installed.ROW_VERSION, + Schema.Installed.ROW_VERSION_CODE, Schema.Installed.ROW_SIGNATURE), + selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), + signal = signal + ).use { it.firstOrNull()?.let(::transform) } } private fun put(installedItem: InstalledItem, notify: Boolean) { @@ -529,13 +555,12 @@ object Database { fun put(installedItem: InstalledItem) = put(installedItem, true) fun putAll(installedItems: List) { - db.beginTransaction() - try { - db.delete(Schema.Installed.name, null, null) - installedItems.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.transaction { + try { + delete(Schema.Installed.name, null, null) + installedItems.forEach { put(it, false) } + } finally { + } } } @@ -547,10 +572,10 @@ object Database { } private fun transform(cursor: Cursor): InstalledItem { - return InstalledItem(cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_PACKAGE_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)), - cursor.getLong(cursor.getColumnIndex(Schema.Installed.ROW_VERSION_CODE)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_SIGNATURE))) + return InstalledItem(cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_PACKAGE_NAME)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_VERSION)), + cursor.getLong(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_VERSION_CODE)), + cursor.getString(cursor.getColumnIndexOrThrow(Schema.Installed.ROW_SIGNATURE))) } } @@ -568,13 +593,12 @@ object Database { fun put(lock: Pair) = put(lock, true) fun putAll(locks: List>) { - db.beginTransaction() - try { - db.delete(Schema.Lock.name, null, null) - locks.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.transaction { + try { + delete(Schema.Lock.name, null, null) + locks.forEach { put(it, false) } + } finally { + } } } @@ -596,60 +620,60 @@ object Database { } fun putTemporary(products: List) { - db.beginTransaction() - try { - for (product in products) { - // Format signatures like ".signature1.signature2." for easier select - val signatures = product.signatures.joinToString { ".$it" } - .let { if (it.isNotEmpty()) "$it." else "" } - db.insertOrReplace(true, Schema.Product.temporaryName, ContentValues().apply { - put(Schema.Product.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Product.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Product.ROW_NAME, product.name) - put(Schema.Product.ROW_SUMMARY, product.summary) - put(Schema.Product.ROW_DESCRIPTION, product.description) - put(Schema.Product.ROW_ADDED, product.added) - put(Schema.Product.ROW_UPDATED, product.updated) - put(Schema.Product.ROW_VERSION_CODE, product.versionCode) - put(Schema.Product.ROW_SIGNATURES, signatures) - put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0) - put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize)) - put(Schema.Product.ROW_DATA_ITEM, jsonGenerate(product.item()::serialize)) - }) - for (category in product.categories) { - db.insertOrReplace(true, Schema.Category.temporaryName, ContentValues().apply { - put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Category.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Category.ROW_NAME, category) - }) + db.transaction { + try { + for (product in products) { + // Format signatures like ".signature1.signature2." for easier select + val signatures = product.signatures.joinToString { ".$it" } + .let { if (it.isNotEmpty()) "$it." else "" } + insertOrReplace(true, Schema.Product.temporaryName, ContentValues().apply { + put(Schema.Product.ROW_REPOSITORY_ID, product.repositoryId) + put(Schema.Product.ROW_PACKAGE_NAME, product.packageName) + put(Schema.Product.ROW_NAME, product.name) + put(Schema.Product.ROW_SUMMARY, product.summary) + put(Schema.Product.ROW_DESCRIPTION, product.description) + put(Schema.Product.ROW_ADDED, product.added) + put(Schema.Product.ROW_UPDATED, product.updated) + put(Schema.Product.ROW_VERSION_CODE, product.versionCode) + put(Schema.Product.ROW_SIGNATURES, signatures) + put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0) + put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize)) + put(Schema.Product.ROW_DATA_ITEM, jsonGenerate(product.item()::serialize)) + }) + for (category in product.categories) { + insertOrReplace(true, Schema.Category.temporaryName, ContentValues().apply { + put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId) + put(Schema.Category.ROW_PACKAGE_NAME, product.packageName) + put(Schema.Category.ROW_NAME, category) + }) + } + } + } finally { } - } - db.setTransactionSuccessful() - } finally { - db.endTransaction() } } fun finishTemporary(repository: Repository, success: Boolean) { if (success) { - db.beginTransaction() - try { - db.delete(Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString())) - db.delete(Schema.Category.name, "${Schema.Category.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString())) - db.execSQL("INSERT INTO ${Schema.Product.name} SELECT * FROM ${Schema.Product.temporaryName}") - db.execSQL("INSERT INTO ${Schema.Category.name} SELECT * FROM ${Schema.Category.temporaryName}") - RepositoryAdapter.putWithoutNotification(repository, true) - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.setTransactionSuccessful() - } finally { - db.endTransaction() + db.transaction { + try { + delete( + Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} = ?", + arrayOf(repository.id.toString()) + ) + delete( + Schema.Category.name, "${Schema.Category.ROW_REPOSITORY_ID} = ?", + arrayOf(repository.id.toString()) + ) + execSQL("INSERT INTO ${Schema.Product.name} SELECT * FROM ${Schema.Product.temporaryName}") + execSQL("INSERT INTO ${Schema.Category.name} SELECT * FROM ${Schema.Category.temporaryName}") + RepositoryAdapter.putWithoutNotification(repository, true) + execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") + execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") + } finally { + } } - if (success) { notifyChanged(Subject.Repositories, Subject.Repository(repository.id), Subject.Products) - } } else { db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/database/ObservableCursor.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/database/ObservableCursor.kt index db3bf18..79a630f 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/database/ObservableCursor.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/database/ObservableCursor.kt @@ -1,57 +1,37 @@ package nya.kitsunyan.foxydroid.database -import android.database.ContentObservable import android.database.ContentObserver import android.database.Cursor import android.database.CursorWrapper +import java.util.concurrent.CopyOnWriteArraySet class ObservableCursor(cursor: Cursor, private val observable: (register: Boolean, observer: () -> Unit) -> Unit): CursorWrapper(cursor) { - private var registered = false - private val contentObservable = ContentObservable() + private val observers = CopyOnWriteArraySet() private val onChange: () -> Unit = { - contentObservable.dispatchChange(false, null) + for (observer in observers) { + observer.dispatchChange(false, null) + } } init { observable(true, onChange) - registered = true } override fun registerContentObserver(observer: ContentObserver) { super.registerContentObserver(observer) - contentObservable.registerObserver(observer) + observers.add(observer) } override fun unregisterContentObserver(observer: ContentObserver) { super.unregisterContentObserver(observer) - contentObservable.unregisterObserver(observer) - } - - @Suppress("DEPRECATION") - override fun requery(): Boolean { - if (!registered) { - observable(true, onChange) - registered = true - } - return super.requery() - } - - @Suppress("DEPRECATION") - override fun deactivate() { - super.deactivate() - deactivateOrClose() + observers.remove(observer) } override fun close() { super.close() - contentObservable.unregisterAll() - deactivateOrClose() - } - - private fun deactivateOrClose() { + observers.clear() observable(false, onChange) - registered = false } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/database/QueryBuilder.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/database/QueryBuilder.kt index b3c7ce6..584fd45 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/database/QueryBuilder.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/database/QueryBuilder.kt @@ -32,14 +32,15 @@ class QueryBuilder { this.arguments += arguments } - fun query(db: SQLiteDatabase, signal: CancellationSignal?): Cursor { + fun query(db: SQLiteDatabase, signal: CancellationSignal? = null): Cursor { val query = builder.toString() val arguments = arguments.toTypedArray() if (BuildConfig.DEBUG) { synchronized(QueryBuilder::class.java) { debug(query) - db.rawQuery("EXPLAIN QUERY PLAN $query", arguments).use { it.asSequence() - .forEach { debug(":: ${it.getString(it.getColumnIndex("detail"))}") } } + db.rawQuery("EXPLAIN QUERY PLAN $query", arguments).use { it -> + it.asSequence() + .forEach { debug(":: ${it.getString(it.getColumnIndexOrThrow("detail"))}") } } } } return db.rawQuery(query, arguments, signal) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt index 133e9b5..e36ff6a 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Product.kt @@ -156,8 +156,8 @@ data class Product(val repositoryId: Long, val packageName: String, val name: St var donates = emptyList() var screenshots = emptyList() var releases = emptyList() - parser.forEachKey { - when { + parser.forEachKey { it -> + when { it.string("packageName") -> packageName = valueAsString it.string("name") -> name = valueAsString it.string("summary") -> summary = valueAsString @@ -213,7 +213,7 @@ data class Product(val repositoryId: Long, val packageName: String, val name: St else -> skipChildren() } } - Screenshot.Type.values().find { it.jsonName == type }?.let { Screenshot(locale, it, path) } + Screenshot.Type.entries.find { it.jsonName == type }?.let { Screenshot(locale, it, path) } } it.array("releases") -> releases = collectNotNull(JsonToken.START_OBJECT, Release.Companion::deserialize) else -> skipChildren() diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Release.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Release.kt index c6930a9..1c85f49 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Release.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Release.kt @@ -1,10 +1,10 @@ package nya.kitsunyan.foxydroid.entity -import android.net.Uri import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonToken import nya.kitsunyan.foxydroid.utility.extension.json.* +import androidx.core.net.toUri data class Release(val selected: Boolean, val version: String, val versionCode: Long, val added: Long, val size: Long, val minSdkVersion: Int, val targetSdkVersion: Int, val maxSdkVersion: Int, @@ -24,7 +24,7 @@ data class Release(val selected: Boolean, val version: String, val versionCode: get() = "$versionCode.$hash" fun getDownloadUrl(repository: Repository): String { - return Uri.parse(repository.address).buildUpon().appendPath(release).build().toString() + return repository.address.toUri().buildUpon().appendPath(release).build().toString() } val cacheFileName: String @@ -102,8 +102,8 @@ data class Release(val selected: Boolean, val version: String, val versionCode: var features = emptyList() var platforms = emptyList() var incompatibilities = emptyList() - parser.forEachKey { - when { + parser.forEachKey { it -> + when { it.boolean("selected") -> selected = valueAsBoolean it.string("version") -> version = valueAsString it.number("versionCode") -> versionCode = valueAsLong diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt index 6debcf0..9292a80 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/entity/Repository.kt @@ -3,20 +3,13 @@ package nya.kitsunyan.foxydroid.entity import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser import nya.kitsunyan.foxydroid.utility.extension.json.* -import java.net.URL - -data class Repository(val id: Long, val address: String, val mirrors: List, - val name: String, val description: String, val version: Int, val enabled: Boolean, - val fingerprint: String, val lastModified: String, val entityTag: String, - val updated: Long, val timestamp: Long, val authentication: String) { - fun edit(address: String, fingerprint: String, authentication: String): Repository { - val addressChanged = this.address != address - val fingerprintChanged = this.fingerprint != fingerprint - val changed = addressChanged || fingerprintChanged - return copy(address = address, fingerprint = fingerprint, lastModified = if (changed) "" else lastModified, - entityTag = if (changed) "" else entityTag, authentication = authentication) - } +data class Repository( + val id: Long, val address: String, val mirrors: List, + val name: String, val description: String, val version: Int, val enabled: Boolean, + val fingerprint: String, val lastModified: String, val entityTag: String, + val updated: Long, val timestamp: Long, val authentication: String +) { fun update(mirrors: List, name: String, description: String, version: Int, lastModified: String, entityTag: String, timestamp: Long): Repository { return copy(mirrors = mirrors, name = name, description = description, @@ -79,25 +72,21 @@ data class Repository(val id: Long, val address: String, val mirrors: List, Int) -> Unit) { closeTransaction() - db.rawQuery("""SELECT product.description, product.data AS pd, releases.data AS rd FROM product - LEFT JOIN releases ON product.package_name = releases.package_name""", null) - ?.use { it.asSequence().map { - val description = it.getString(0) - val product = Json.factory.createParser(it.getBlob(1)).use { - it.nextToken() - Product.deserialize(repositoryId, description, it) - } - val releases = it.getBlob(2)?.let { Json.factory.createParser(it).use { - it.nextToken() - it.collectNotNull(JsonToken.START_OBJECT, Release.Companion::deserialize) - } }.orEmpty() - product.copy(releases = releases) - }.windowed(windowSize, windowSize, true).forEach { products -> callback(products, it.count) } } + db.rawQuery("""SELECT product.description, product.data AS pd, releases.data AS rd FROM product + LEFT JOIN releases ON product.package_name = releases.package_name""", null) + .use { it -> + it.asSequence().map { it -> + val description = it.getString(0) + val product = Json.factory.createParser(it.getBlob(1)).use { + it.nextToken() + Product.deserialize(repositoryId, description, it) + } + val releases = it.getBlob(2)?.let { it -> + Json.factory.createParser(it).use { + it.nextToken() + it.collectNotNull(JsonToken.START_OBJECT, Release.Companion::deserialize) + } }.orEmpty() + product.copy(releases = releases) + }.windowed(windowSize, windowSize, true).forEach { products -> callback(products, it.count) } } } override fun close() { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/index/IndexV1Parser.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/index/IndexV1Parser.kt index 00c5e0a..c5441ff 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/index/IndexV1Parser.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/index/IndexV1Parser.kt @@ -37,8 +37,8 @@ object IndexV1Parser { if (jsonParser.nextToken() != JsonToken.START_OBJECT) { jsonParser.illegal() } else { - jsonParser.forEachKey { - when { + jsonParser.forEachKey { it -> + when { it.dictionary("repo") -> { var address = "" var mirrors = emptyList() @@ -100,8 +100,8 @@ object IndexV1Parser { val licenses = mutableListOf() val donates = mutableListOf() val localizedMap = mutableMapOf() - forEachKey { - when { + forEachKey { it -> + when { it.string("packageName") -> packageName = valueAsString it.string("name") -> nameFallback = valueAsString it.string("summary") -> summaryFallback = valueAsString @@ -125,8 +125,8 @@ object IndexV1Parser { it.string("flattrID") -> donates += Product.Donate.Flattr(valueAsString) it.string("liberapayID") -> donates += Product.Donate.Liberapay(valueAsString) it.string("openCollective") -> donates += Product.Donate.OpenCollective(valueAsString) - it.dictionary("localized") -> forEachKey { - if (it.token == JsonToken.START_OBJECT) { + it.dictionary("localized") -> forEachKey { it -> + if (it.token == JsonToken.START_OBJECT) { val locale = it.key var name = "" var summary = "" diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/index/RepositoryUpdater.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/index/RepositoryUpdater.kt index 4ceff7e..1545470 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/index/RepositoryUpdater.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/index/RepositoryUpdater.kt @@ -1,9 +1,9 @@ package nya.kitsunyan.foxydroid.index import android.content.Context -import android.net.Uri import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers import nya.kitsunyan.foxydroid.content.Cache import nya.kitsunyan.foxydroid.database.Database @@ -23,6 +23,7 @@ import java.util.Locale import java.util.jar.JarEntry import java.util.jar.JarFile import javax.xml.parsers.SAXParserFactory +import androidx.core.net.toUri object RepositoryUpdater { enum class Stage { @@ -50,23 +51,19 @@ object RepositoryUpdater { } } - private lateinit var context: Context private val updaterLock = Any() private val cleanupLock = Any() - fun init(context: Context) { - this.context = context - - var lastDisabled = setOf() - Observable.just(Unit) + fun init(): Disposable { + val lastDisabled = setOf() + return Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Repositories)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAllDisabledDeleted(it) } } - .forEach { - val newDisabled = it.asSequence().filter { !it.second }.map { it.first }.toSet() + .flatMapSingle { RxUtils.querySingle { signal -> Database.RepositoryAdapter.getAllDisabledDeleted(signal) } } + .subscribe { result -> + val newDisabled = result.asSequence().filter { !it.second }.map { it.first }.toSet() val disabled = newDisabled - lastDisabled - lastDisabled = newDisabled - val deleted = it.asSequence().filter { it.second }.map { it.first }.toSet() + val deleted = result.asSequence().filter { it.second }.map { it.first }.toSet() if (disabled.isNotEmpty() || deleted.isNotEmpty()) { val pairs = (disabled.asSequence().map { Pair(it, false) } + deleted.asSequence().map { Pair(it, true) }).toSet() @@ -79,15 +76,15 @@ object RepositoryUpdater { synchronized(updaterLock) { } } - fun update(repository: Repository, unstable: Boolean, + fun update(context: Context, repository: Repository, unstable: Boolean, callback: (Stage, Long, Long?) -> Unit): Single { - return update(repository, listOf(IndexType.INDEX_V1, IndexType.INDEX), unstable, callback) + return update(context, repository, listOf(IndexType.INDEX_V1, IndexType.INDEX), unstable, callback) } - private fun update(repository: Repository, indexTypes: List, unstable: Boolean, + private fun update(context: Context, repository: Repository, indexTypes: List, unstable: Boolean, callback: (Stage, Long, Long?) -> Unit): Single { val indexType = indexTypes[0] - return downloadIndex(repository, indexType, callback) + return downloadIndex(context, repository, indexType, callback) .flatMap { (result, file) -> when { result.isNotChanged -> { @@ -96,26 +93,27 @@ object RepositoryUpdater { } !result.success -> { file.delete() - if (result.code == 404 && indexTypes.isNotEmpty()) { - update(repository, indexTypes.subList(1, indexTypes.size), unstable, callback) + if (result.code == 404 && indexTypes.size > 1) { + update(context, repository, indexTypes.subList(1, indexTypes.size), unstable, callback) } else { Single.error(UpdateException(ErrorType.HTTP, "Invalid response: HTTP ${result.code}")) } } else -> { - RxUtils.managedSingle { processFile(repository, indexType, unstable, + RxUtils.managedSingle { processFile(context, repository, indexType, unstable, file, result.lastModified, result.entityTag, callback) } } } } } - private fun downloadIndex(repository: Repository, indexType: IndexType, + private fun downloadIndex(context: Context, repository: Repository, indexType: IndexType, callback: (Stage, Long, Long?) -> Unit): Single> { return Single.just(Unit) .map { Cache.getTemporaryFile(context) } .flatMap { file -> Downloader - .download(Uri.parse(repository.address).buildUpon() + .download( + repository.address.toUri().buildUpon() .appendPath(indexType.jarName).build().toString(), file, repository.lastModified, repository.entityTag, repository.authentication) { read, total -> callback(Stage.DOWNLOAD, read, total) } .subscribeOn(Schedulers.io()) @@ -130,7 +128,7 @@ object RepositoryUpdater { } } } - private fun processFile(repository: Repository, indexType: IndexType, unstable: Boolean, + private fun processFile(context: Context, repository: Repository, indexType: IndexType, unstable: Boolean, file: File, lastModified: String, entityTag: String, callback: (Stage, Long, Long?) -> Unit): Boolean { var rollback = true return synchronized(updaterLock) { @@ -157,7 +155,7 @@ object RepositoryUpdater { certificate: String, version: Int, timestamp: Long) { changedRepository = repository.update(mirrors, name, description, version, lastModified, entityTag, timestamp) - certificateFromIndex = certificate.toLowerCase(Locale.US) + certificateFromIndex = certificate.lowercase(Locale.US) } override fun onProduct(product: Product) { @@ -191,8 +189,8 @@ object RepositoryUpdater { val unmergedProducts = mutableListOf() val unmergedReleases = mutableListOf>>() IndexMerger(mergerFile).use { indexMerger -> - ProgressInputStream(jarFile.getInputStream(indexEntry)) { callback(Stage.PROCESS, it, total) }.use { - IndexV1Parser.parse(repository.id, it, object: IndexV1Parser.Callback { + ProgressInputStream(jarFile.getInputStream(indexEntry)) { callback(Stage.PROCESS, it, total) }.use { it -> + IndexV1Parser.parse(repository.id, it, object: IndexV1Parser.Callback { override fun onRepository(mirrors: List, name: String, description: String, version: Int, timestamp: Long) { changedRepository = repository.update(mirrors, name, description, version, @@ -315,8 +313,8 @@ object RepositoryUpdater { } private fun transformProduct(product: Product, features: Set, unstable: Boolean): Product { - val releasePairs = product.releases.distinctBy { it.identifier }.sortedByDescending { it.versionCode }.map { - val incompatibilities = mutableListOf() + val releasePairs = product.releases.distinctBy { it.identifier }.sortedByDescending { it.versionCode }.map { it -> + val incompatibilities = mutableListOf() if (it.minSdkVersion > 0 && Android.sdk < it.minSdkVersion) { incompatibilities += Release.Incompatibility.MinSdk } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/network/Downloader.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/network/Downloader.kt index 12cd0ab..97c4d70 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/network/Downloader.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/network/Downloader.kt @@ -85,13 +85,13 @@ object Downloader { .callSingle { createCall(request, authentication, null) } .subscribeOn(Schedulers.io()) .flatMap { result -> RxUtils - .managedSingle { result.use { - if (result.code == 304) { + .managedSingle { result.use { it -> + if (result.code == 304) { Result(it.code, lastModified, entityTag) } else { - val body = it.body!! + val body = it.body val append = start != null && it.header("Content-Range") != null - val progressStart = if (append && start != null) start else 0L + val progressStart = if (append) start else 0L val progressTotal = body.contentLength().let { if (it >= 0L) it else null } ?.let { progressStart + it } val inputStream = ProgressInputStream(body.byteStream()) { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/network/PicassoDownloader.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/network/PicassoDownloader.kt index c55e1d8..9bfc413 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/network/PicassoDownloader.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/network/PicassoDownloader.kt @@ -65,7 +65,7 @@ object PicassoDownloader { } else { Downloader.createCall(request.newBuilder().url(address.toHttpUrl() .newBuilder().addPathSegment(packageName.orEmpty()).addPathSegment(locale.orEmpty()) - .addPathSegment(device.orEmpty()).addPathSegment(screenshot.orEmpty()).build()), + .addPathSegment(device.orEmpty()).addPathSegment(screenshot).build()), authentication.orEmpty(), cache) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt index 1729aeb..3137d0f 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt @@ -5,7 +5,6 @@ import android.content.ClipboardManager import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter -import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.Selection @@ -42,6 +41,7 @@ import java.net.URL import java.nio.charset.Charset import java.util.Locale import kotlin.math.* +import androidx.core.net.toUri class EditRepositoryFragment(): ScreenFragment() { companion object { @@ -149,7 +149,7 @@ class EditRepositoryFragment(): ScreenFragment() { override fun afterTextChanged(s: Editable) { val inputString = s.toString() - val outputString = inputString.toUpperCase(Locale.US) + val outputString = inputString.uppercase(Locale.US) .filter(validChar).windowed(2, 2, true).take(32).joinToString(separator = " ") if (inputString != outputString) { val inputStart = logicalPosition(inputString, Selection.getSelectionStart(s)) @@ -161,19 +161,19 @@ class EditRepositoryFragment(): ScreenFragment() { }) if (savedInstanceState == null) { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) + val repository = repositoryId?.let { Database.RepositoryAdapter.get(it) } if (repository == null) { val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val text = clipboardManager.primaryClip ?.let { if (it.itemCount > 0) it else null } ?.getItemAt(0)?.text?.toString().orEmpty() val (addressText, fingerprintText) = try { - val uri = Uri.parse(URL(text).toString()) + val uri = URL(text).toString().toUri() val fingerprintText = uri.getQueryParameter("fingerprint")?.nullIfEmpty() ?: uri.getQueryParameter("FINGERPRINT")?.nullIfEmpty() Pair(uri.buildUpon().path(uri.path?.pathCropped) .query(null).fragment(null).build().toString(), fingerprintText) - } catch (e: Exception) { + } catch (_: Exception) { Pair(null, null) } layout.address.setText(addressText?.nullIfEmpty() ?: layout.address.hint) @@ -209,10 +209,26 @@ class EditRepositoryFragment(): ScreenFragment() { } } - layout.address.addTextChangedListener(SimpleTextWatcher { invalidateAddress() }) - layout.fingerprint.addTextChangedListener(SimpleTextWatcher { invalidateFingerprint() }) - layout.username.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() }) - layout.password.addTextChangedListener(SimpleTextWatcher { invalidateUsernamePassword() }) + layout.address.addTextChangedListener(object: TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun afterTextChanged(s: Editable) = invalidateAddress() + }) + layout.fingerprint.addTextChangedListener(object: TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun afterTextChanged(s: Editable) = invalidateFingerprint() + }) + layout.username.addTextChangedListener(object: TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun afterTextChanged(s: Editable) = invalidateUsernamePassword() + }) + layout.password.addTextChangedListener(object: TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit + override fun afterTextChanged(s: Editable) = invalidateUsernamePassword() + }) (layout.overlay.parent as ViewGroup).layoutTransition?.setDuration(200L) layout.overlay.background!!.apply { @@ -230,14 +246,18 @@ class EditRepositoryFragment(): ScreenFragment() { repositoriesDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Repositories)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - takenAddresses = it.asSequence().filter { it.id != repositoryId } + .subscribe { result -> + takenAddresses = result.asSequence().filter { it.id != repositoryId } .flatMap { (it.mirrors + it.address).asSequence() } .map { it.withoutKnownPath }.toSet() invalidateAddress() } + + invalidateAddress() + invalidateFingerprint() + invalidateUsernamePassword() } override fun onDestroyView() { @@ -253,14 +273,6 @@ class EditRepositoryFragment(): ScreenFragment() { checkDisposable = null } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - invalidateAddress() - invalidateFingerprint() - invalidateUsernamePassword() - } - private var addressError = false private var fingerprintError = false private var usernamePasswordError = false @@ -328,7 +340,7 @@ class EditRepositoryFragment(): ScreenFragment() { private fun invalidateState() { val layout = layout!! - saveMenuItem!!.isEnabled = !addressError && !fingerprintError && + saveMenuItem?.isEnabled = !addressError && !fingerprintError && !usernamePasswordError && checkDisposable == null layout.apply { sequenceOf(address, addressMirror, fingerprint, username, password) .forEach { it.isEnabled = checkDisposable == null } } @@ -346,21 +358,21 @@ class EditRepositoryFragment(): ScreenFragment() { val cropped = pathCropped val endsWith = checkPaths.asSequence().filter { it.isNotEmpty() } .sortedByDescending { it.length }.find { cropped.endsWith("/$it") } - return if (endsWith != null) cropped.substring(0, cropped.length - endsWith.length - 1) else cropped + return if (endsWith != null) cropped.take(cropped.length - endsWith.length - 1) else cropped } private fun normalizeAddress(address: String): String? { val uri = try { val uri = URI(address) if (uri.isAbsolute) uri.normalize() else null - } catch (e: Exception) { + } catch (_: Exception) { null } val path = uri?.path?.pathCropped return if (uri != null && path != null) { try { URI(uri.scheme, uri.userInfo, uri.host, uri.port, path, uri.query, uri.fragment).toString() - } catch (e: Exception) { + } catch (_: Exception) { null } } else { @@ -394,7 +406,7 @@ class EditRepositoryFragment(): ScreenFragment() { .fold(Single.just("")) { oldAddressSingle, checkPath -> oldAddressSingle .flatMap { oldAddress -> if (oldAddress.isEmpty()) { - val builder = Uri.parse(address).buildUpon() + val builder = address.toUri().buildUpon() .let { if (checkPath.isEmpty()) it else it.appendEncodedPath(checkPath) } val newAddress = builder.build() val indexAddress = builder.appendPath("index.jar").build() @@ -413,71 +425,67 @@ class EditRepositoryFragment(): ScreenFragment() { .subscribe { result, throwable -> checkDisposable = null throwable?.printStackTrace() - val resultAddress = result?.let { if (it.isEmpty()) null else it } ?: address - val allow = resultAddress == address || run { - layout.address.setText(resultAddress) - invalidateAddress(resultAddress) - !addressError - } - if (allow) { - onSaveRepositoryProceedInvalidate(resultAddress, fingerprint, authentication) + val resultAddress = result?.let { it.ifEmpty { null } } ?: address + val allow = resultAddress == address || resultAddress == "$address/" + if (!allow) { + AlertDialog.Builder(requireContext()) + .setMessage(getString(R.string.address_redirect_FORMAT, resultAddress)) + .setPositiveButton(android.R.string.ok) { _, _ -> saveRepository(resultAddress, fingerprint, authentication) } + .setNegativeButton(android.R.string.cancel, null) + .show() } else { - invalidateState() + saveRepository(resultAddress, fingerprint, authentication) } } - invalidateState() } else { - onSaveRepositoryProceedInvalidate(address, fingerprint, authentication) + saveRepository(address, fingerprint, authentication) } - } - } - - private fun onSaveRepositoryProceedInvalidate(address: String, fingerprint: String, authentication: String) { - val binder = syncConnection.binder - if (binder != null) { - val repositoryId = repositoryId - if (repositoryId != null && binder.isCurrentlySyncing(repositoryId)) { - MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager) - invalidateState() - } else { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) - ?.edit(address, fingerprint, authentication) - ?: Repository.newRepository(address, fingerprint, authentication) - val changedRepository = Database.RepositoryAdapter.put(repository) - if (repositoryId == null && changedRepository.enabled) { - binder.sync(changedRepository) - } - requireActivity().onBackPressed() - } - } else { invalidateState() } } - private class SimpleTextWatcher(private val callback: (Editable) -> Unit): TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit - override fun onTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit - override fun afterTextChanged(s: Editable) = callback(s) + private fun saveRepository(address: String, fingerprint: String, authentication: String) { + val repository = repositoryId?.let { Database.RepositoryAdapter.get(it) } + if (repository != null) { + Database.RepositoryAdapter.put(repository.copy(address = address, fingerprint = fingerprint, + authentication = authentication)) + } else { + val id = Database.RepositoryAdapter.put(Repository( + id = 0, + address = address, + mirrors = emptyList(), + name = "", + description = "", + version = 0, + enabled = true, + fingerprint = fingerprint, + lastModified = "", + entityTag = "", + updated = 0L, + timestamp = 0L, + authentication = authentication + )) + Database.RepositoryAdapter.get(id)?.let { syncConnection.binder?.sync(it) } + } + screenActivity.onBackPressedDispatcher.onBackPressed() } class SelectMirrorDialog(): DialogFragment() { - companion object { - private const val EXTRA_MIRRORS = "mirrors" - } + private val mirrors: List + get() = requireArguments().getStringArrayList("mirrors")!! constructor(mirrors: List): this() { arguments = Bundle().apply { - putStringArrayList(EXTRA_MIRRORS, ArrayList(mirrors)) + putStringArrayList("mirrors", ArrayList(mirrors)) } } override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog { - val mirrors = requireArguments().getStringArrayList(EXTRA_MIRRORS)!! return AlertDialog.Builder(requireContext()) .setTitle(R.string.select_mirror) - .setItems(mirrors.toTypedArray()) { _, position -> (parentFragment as EditRepositoryFragment) - .setMirror(mirrors[position]) } - .setNegativeButton(R.string.cancel, null) + .setItems(mirrors.toTypedArray()) { _, which -> + (parentFragment as EditRepositoryFragment).setMirror(mirrors[which]) + } .create() } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt index 1ac9a61..c482ab0 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Parcel +import androidx.core.os.BundleCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import nya.kitsunyan.foxydroid.R @@ -14,6 +15,7 @@ import nya.kitsunyan.foxydroid.utility.KParcelable import nya.kitsunyan.foxydroid.utility.PackageItemResolver import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.utility.extension.text.* +import androidx.core.net.toUri class MessageDialog(): DialogFragment() { companion object { @@ -36,7 +38,7 @@ class MessageDialog(): DialogFragment() { companion object { @Suppress("unused") @JvmField val CREATOR = KParcelable.creator { - val uri = Uri.parse(it.readString()!!) + val uri = it.readString()!!.toUri() Link(uri) } } @@ -124,7 +126,8 @@ class MessageDialog(): DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog { val dialog = AlertDialog.Builder(requireContext()) - when (val message = requireArguments().getParcelable(EXTRA_MESSAGE)!!) { + val message = BundleCompat.getParcelable(requireArguments(), EXTRA_MESSAGE, Message::class.java)!! + when (message) { is Message.DeleteRepositoryConfirm -> { dialog.setTitle(R.string.confirmation) dialog.setMessage(R.string.delete_repository_DESC) @@ -157,7 +160,7 @@ class MessageDialog(): DialogFragment() { val permissionGroupInfo = packageManager.getPermissionGroupInfo(message.group, 0) PackageItemResolver.loadLabel(requireContext(), localCache, permissionGroupInfo) ?.nullIfEmpty()?.let { if (it == message.group) null else it } - } catch (e: Exception) { + } catch (_: Exception) { null } name ?: getString(R.string.unknown) @@ -169,7 +172,7 @@ class MessageDialog(): DialogFragment() { val permissionInfo = packageManager.getPermissionInfo(permission, 0) PackageItemResolver.loadDescription(requireContext(), localCache, permissionInfo) ?.nullIfEmpty()?.let { if (it == permission) null else it } - } catch (e: Exception) { + } catch (_: Exception) { null } description?.let { builder.append(it).append("\n\n") } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt index 383f753..01a0693 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt @@ -18,11 +18,12 @@ import android.widget.Switch import android.widget.TextView import android.widget.Toolbar import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.content.Preferences import nya.kitsunyan.foxydroid.utility.extension.resources.* +import androidx.core.view.isNotEmpty class PreferencesFragment: ScreenFragment() { private val preferences = mutableMapOf, Preference<*>>() @@ -46,11 +47,11 @@ class PreferencesFragment: ScreenFragment() { content.addView(scroll, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) val scrollLayout = FrameLayout(content.context) scroll.addView(scrollLayout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - val preferences = LinearLayout(scrollLayout.context) - preferences.orientation = LinearLayout.VERTICAL - scrollLayout.addView(preferences, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val preferencesLayout = LinearLayout(scrollLayout.context) + preferencesLayout.orientation = LinearLayout.VERTICAL + scrollLayout.addView(preferencesLayout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - preferences.addCategory(getString(R.string.updates)) { + preferencesLayout.addCategory(getString(R.string.updates)) { addEnumeration(Preferences.Key.AutoSync, getString(R.string.sync_repositories_automatically)) { when (it) { Preferences.AutoSync.Never -> getString(R.string.never) @@ -63,7 +64,7 @@ class PreferencesFragment: ScreenFragment() { addSwitch(Preferences.Key.UpdateUnstable, getString(R.string.unstable_updates), getString(R.string.unstable_updates_summary)) } - preferences.addCategory(getString(R.string.proxy)) { + preferencesLayout.addCategory(getString(R.string.proxy)) { addEnumeration(Preferences.Key.ProxyType, getString(R.string.proxy_type)) { when (it) { is Preferences.ProxyType.Direct -> getString(R.string.no_proxy) @@ -74,7 +75,7 @@ class PreferencesFragment: ScreenFragment() { addEditString(Preferences.Key.ProxyHost, getString(R.string.proxy_host)) addEditInt(Preferences.Key.ProxyPort, getString(R.string.proxy_port), 1 .. 65535) } - preferences.addCategory(getString(R.string.other)) { + preferencesLayout.addCategory(getString(R.string.other)) { addEnumeration(Preferences.Key.Theme, getString(R.string.theme)) { when (it) { is Preferences.Theme.System -> getString(R.string.system) @@ -86,7 +87,9 @@ class PreferencesFragment: ScreenFragment() { getString(R.string.incompatible_versions_summary)) } - disposable = Preferences.observable.subscribe(this::updatePreference) + disposable = Preferences.observable + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::updatePreference) updatePreference(null) } @@ -138,12 +141,12 @@ class PreferencesFragment: ScreenFragment() { callback() val divider = addDivider(true) // Negative margin for last divider - (layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = -divider.layoutParams.height + (divider.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = -divider.layoutParams.height } private fun LinearLayout.addPreference(key: Preferences.Key, title: String, summaryProvider: () -> String, dialogProvider: ((Context) -> AlertDialog)?): Preference { - if (childCount > 0 && getChildAt(childCount - 1) !is TextView) { + if (isNotEmpty() && getChildAt(childCount - 1) !is TextView) { addDivider(false) } val preference = Preference(key, this@PreferencesFragment, this, title, summaryProvider, dialogProvider) @@ -160,10 +163,10 @@ class PreferencesFragment: ScreenFragment() { private fun LinearLayout.addEdit(key: Preferences.Key, title: String, valueToString: (T) -> String, stringToValue: (String) -> T?, configureEdit: (EditText) -> Unit) { - addPreference(key, title, { valueToString(Preferences[key]) }) { - val scroll = ScrollView(it) + addPreference(key, title, { valueToString(Preferences[key]) }) { context -> + val scroll = ScrollView(context) scroll.resources.sizeScaled(20).let { scroll.setPadding(it, 0, it, 0) } - val edit = EditText(it) + val edit = EditText(context) configureEdit(edit) edit.id = android.R.id.edit edit.setTextSizeScaled(16) @@ -173,12 +176,12 @@ class PreferencesFragment: ScreenFragment() { edit.setSelection(edit.text.length) edit.requestFocus() scroll.addView(edit, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - AlertDialog.Builder(it) + AlertDialog.Builder(context) .setTitle(title) .setView(scroll) .setPositiveButton(R.string.ok) { _, _ -> val value = stringToValue(edit.text.toString()) ?: key.default.value - post { Preferences[key] = value } + Preferences[key] = value } .setNegativeButton(R.string.cancel, null) .create() @@ -194,11 +197,10 @@ class PreferencesFragment: ScreenFragment() { private fun LinearLayout.addEditInt(key: Preferences.Key, title: String, range: IntRange?) { addEdit(key, title, { it.toString() }, { it.toIntOrNull() }) { - it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + it.inputType = InputType.TYPE_CLASS_NUMBER if (range != null) { it.filters = arrayOf(InputFilter { source, start, end, dest, dstart, dend -> - val value = (dest.substring(0, dstart) + source.substring(start, end) + - dest.substring(dend, dest.length)).toIntOrNull() + val value = "${dest.subSequence(0, dstart)}${source.subSequence(start, end)}${dest.subSequence(dend, dest.length)}".toIntOrNull() if (value != null && value in range) null else "" }) } @@ -207,14 +209,14 @@ class PreferencesFragment: ScreenFragment() { private fun > LinearLayout .addEnumeration(key: Preferences.Key, title: String, valueToString: (T) -> String) { - addPreference(key, title, { valueToString(Preferences[key]) }) { + addPreference(key, title, { valueToString(Preferences[key]) }) { context -> val values = key.default.value.values - AlertDialog.Builder(it) + AlertDialog.Builder(context) .setTitle(title) .setSingleChoiceItems(values.map(valueToString).toTypedArray(), values.indexOf(Preferences[key])) { dialog, which -> dialog.dismiss() - post { Preferences[key] = values[which] } + Preferences[key] = values[which] } .setNegativeButton(R.string.cancel, null) .create() @@ -222,7 +224,7 @@ class PreferencesFragment: ScreenFragment() { } private class Preference(private val key: Preferences.Key, - fragment: Fragment, parent: ViewGroup, titleText: String, + private val fragment: PreferencesFragment, parent: ViewGroup, titleText: String, private val summaryProvider: () -> String, private val dialogProvider: ((Context) -> AlertDialog)?) { val view = parent.inflate(R.layout.preference_item) val title = view.findViewById(R.id.title)!! @@ -276,10 +278,10 @@ class PreferencesFragment: ScreenFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val preferences = (parentFragment as PreferencesFragment).preferences + val preferencesFragment = parentFragment as PreferencesFragment val key = requireArguments().getString(EXTRA_KEY)!! - .let { name -> preferences.keys.find { it.name == name }!! } - val preference = preferences[key]!! + .let { name -> preferencesFragment.preferences.keys.find { it.name == name }!! } + val preference = preferencesFragment.preferences[key]!! return preference.createDialog(requireContext()) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt index 42beb87..6a959ac 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt @@ -38,6 +38,7 @@ import android.widget.Toast import androidx.core.graphics.ColorUtils import androidx.core.text.HtmlCompat import androidx.core.text.util.LinkifyCompat +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.content.Preferences @@ -61,6 +62,7 @@ import nya.kitsunyan.foxydroid.widget.StableRecyclerAdapter import java.lang.ref.WeakReference import java.util.Locale import kotlin.math.* +import androidx.core.net.toUri class ProductAdapter(private val callbacks: Callbacks, private val columns: Int): StableRecyclerAdapter() { @@ -130,7 +132,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) abstract val descriptor: String abstract val viewType: ViewType - class HeaderItem(val repository: Repository, val product: Product): Item() { + data class HeaderItem(val repository: Repository, val product: Product): Item() { override val descriptor: String get() = "header" @@ -138,7 +140,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.HEADER } - class SwitchItem(val switchType: SwitchType, val packageName: String, val versionCode: Long): Item() { + data class SwitchItem(val switchType: SwitchType, val packageName: String, val versionCode: Long): Item() { override val descriptor: String get() = "switch.${switchType.name}" @@ -146,7 +148,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.SWITCH } - class SectionItem(val sectionType: SectionType, val expandType: ExpandType, + data class SectionItem(val sectionType: SectionType, val expandType: ExpandType, val items: List, val collapseCount: Int): Item() { constructor(sectionType: SectionType): this(sectionType, ExpandType.NOTHING, emptyList(), 0) @@ -157,7 +159,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.SECTION } - class ExpandItem(val expandType: ExpandType, val replace: Boolean, val items: List): Item() { + data class ExpandItem(val expandType: ExpandType, val replace: Boolean, val items: List): Item() { override val descriptor: String get() = "expand.${expandType.name}" @@ -165,7 +167,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.EXPAND } - class TextItem(val textType: TextType, val text: CharSequence): Item() { + data class TextItem(val textType: TextType, val text: CharSequence): Item() { override val descriptor: String get() = "text.${textType.name}" @@ -185,7 +187,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = uri?.schemeSpecificPart?.nullIfEmpty() ?.let { if (it.startsWith("//")) null else it } ?: uri?.toString() - class Typed(val linkType: LinkType, val text: String, override val uri: Uri?): LinkItem() { + data class Typed(val linkType: LinkType, val text: String, override val uri: Uri?): LinkItem() { override val descriptor: String get() = "link.typed.${linkType.name}" @@ -198,7 +200,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } } - class Donate(val donate: Product.Donate): LinkItem() { + data class Donate(val donate: Product.Donate): LinkItem() { override val descriptor: String get() = "link.donate.$donate" @@ -222,17 +224,17 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } override val uri: Uri? = when (donate) { - is Product.Donate.Regular -> Uri.parse(donate.url) - is Product.Donate.Bitcoin -> Uri.parse("bitcoin:${donate.address}") - is Product.Donate.Litecoin -> Uri.parse("litecoin:${donate.address}") - is Product.Donate.Flattr -> Uri.parse("https://flattr.com/thing/${donate.id}") - is Product.Donate.Liberapay -> Uri.parse("https://liberapay.com/~${donate.id}") - is Product.Donate.OpenCollective -> Uri.parse("https://opencollective.com/${donate.id}") + is Product.Donate.Regular -> donate.url.toUri() + is Product.Donate.Bitcoin -> "bitcoin:${donate.address}".toUri() + is Product.Donate.Litecoin -> "litecoin:${donate.address}".toUri() + is Product.Donate.Flattr -> "https://flattr.com/thing/${donate.id}".toUri() + is Product.Donate.Liberapay -> "https://liberapay.com/~${donate.id}".toUri() + is Product.Donate.OpenCollective -> "https://opencollective.com/${donate.id}".toUri() } } } - class PermissionsItem(val group: PermissionGroupInfo?, val permissions: List): Item() { + data class PermissionsItem(val group: PermissionGroupInfo?, val permissions: List): Item() { override val descriptor: String get() = "permissions.${group?.name}.${permissions.joinToString(separator = ".") { it.name }}" @@ -240,7 +242,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.PERMISSIONS } - class ScreenshotItem(val repository: Repository, val packageName: String, + data class ScreenshotItem(val repository: Repository, val packageName: String, val screenshot: Product.Screenshot): Item() { override val descriptor: String get() = "screenshot.${repository.id}.${screenshot.identifier}" @@ -249,7 +251,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.SCREENSHOT } - class ReleaseItem(val repository: Repository, val release: Release, val selectedRepository: Boolean, + data class ReleaseItem(val repository: Repository, val release: Release, val selectedRepository: Boolean, val showSignature: Boolean): Item() { override val descriptor: String get() = "release.${repository.id}.${release.identifier}" @@ -258,7 +260,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) get() = ViewType.RELEASE } - class EmptyItem(val packageName: String): Item() { + data class EmptyItem(val packageName: String): Item() { override val descriptor: String get() = "empty" @@ -269,7 +271,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) private class Measurement { private var density = 0f - private var scaledDensity = 0f + private var fontScale = 0f private lateinit var metric: T fun measure(view: View) { @@ -277,10 +279,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } fun invalidate(resources: Resources, callback: () -> T): T { - val (density, scaledDensity) = resources.displayMetrics.let { Pair(it.density, it.scaledDensity) } - if (this.density != density || this.scaledDensity != scaledDensity) { + val (density, fontScale) = resources.displayMetrics.density to resources.configuration.fontScale + if (this.density != density || this.fontScale != fontScale) { this.density = density - this.scaledDensity = scaledDensity + this.fontScale = fontScale metric = callback() } return metric @@ -334,12 +336,12 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { itemView as TextView - itemView.typeface = TypefaceExtra.medium - itemView.setTextSizeScaled(14) - itemView.setTextColor(itemView.context.getColorFromAttr(android.R.attr.colorAccent)) + (itemView as TextView).typeface = TypefaceExtra.medium + (itemView as TextView).setTextSizeScaled(14) + (itemView as TextView).setTextColor(itemView.context.getColorFromAttr(android.R.attr.colorAccent)) itemView.background = itemView.context.getDrawableFromAttr(android.R.attr.selectableItemBackground) - itemView.gravity = Gravity.CENTER - itemView.isAllCaps = true + (itemView as TextView).gravity = Gravity.CENTER + (itemView as TextView).isAllCaps = true itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, itemView.resources.sizeScaled(48)).apply { topMargin = -itemView.resources.sizeScaled(16) } } @@ -351,10 +353,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { itemView as TextView - itemView.setTextSizeScaled(14) - itemView.setTextColor(itemView.context.getColorFromAttr(android.R.attr.textColorPrimary)) + (itemView as TextView).setTextSizeScaled(14) + (itemView as TextView).setTextColor(itemView.context.getColorFromAttr(android.R.attr.textColorPrimary)) itemView.resources.sizeScaled(16).let { itemView.setPadding(it, it, it, it) } - itemView.movementMethod = ClickableMovementMethod + (itemView as TextView).movementMethod = ClickableMovementMethod itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) } @@ -363,11 +365,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) private open class OverlappingViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { init { // Block touch events if touched above negative margin - itemView.setOnTouchListener { _, event -> - event.action == MotionEvent.ACTION_DOWN && run { - val top = (itemView.layoutParams as ViewGroup.MarginLayoutParams).topMargin - top < 0 && event.y < -top - } + @SuppressLint("ClickableViewAccessibility") + itemView.setOnTouchListener { v, event -> + val top = (v.layoutParams as ViewGroup.MarginLayoutParams).topMargin + event.action == MotionEvent.ACTION_DOWN && top < 0 && event.y < -top } } } @@ -441,7 +442,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } image.scaleType = ImageView.ScaleType.CENTER_CROP image.setBackgroundColor(ColorUtils.blendARGB(backgroundColor, accentColor, 0.1f)) - itemView.addView(image, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT) + (itemView as ViewGroup).addView(image, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) @@ -482,8 +483,8 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { itemView as LinearLayout - itemView.orientation = LinearLayout.VERTICAL - itemView.gravity = Gravity.CENTER + (itemView as LinearLayout).orientation = LinearLayout.VERTICAL + (itemView as LinearLayout).gravity = Gravity.CENTER itemView.resources.sizeScaled(20).let { itemView.setPadding(it, it, it, it) } val title = TextView(itemView.context) title.gravity = Gravity.CENTER @@ -491,12 +492,12 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) title.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) title.setTextSizeScaled(20) title.setText(R.string.application_not_found) - itemView.addView(title, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + (itemView as ViewGroup).addView(title, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) val packageName = TextView(itemView.context) packageName.gravity = Gravity.CENTER packageName.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) packageName.setTextSizeScaled(16) - itemView.addView(packageName, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + (itemView as ViewGroup).addView(packageName, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) this.packageName = packageName @@ -529,7 +530,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { val holder = parent.getChildViewHolder(view) if (holder is ScreenshotViewHolder) { - val position = holder.adapterPosition + val position = holder.bindingAdapterPosition if (position >= 0) { val first = items.subList(0, position).indexOfLast { it !is Item.ScreenshotItem } + 1 val gridCount = items.subList(first, items.size).indexOfFirst { it !is Item.ScreenshotItem } @@ -555,21 +556,31 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) when { nextItem == null || currentItem is Item.HeaderItem || nextItem is Item.TextItem && nextItem.textType == TextType.DESCRIPTION -> { - configuration.set(true, false, 0, 0) + configuration.set(needDivider = true, toTop = false, paddingStart = 0, paddingEnd = 0) } nextItem is Item.SectionItem -> { - configuration.set(true, true, 0, 0) + configuration.set(needDivider = true, toTop = true, paddingStart = 0, paddingEnd = 0) } currentItem is Item.LinkItem && nextItem is Item.LinkItem || currentItem is Item.PermissionsItem && nextItem is Item.PermissionsItem -> { - configuration.set(true, false, context.resources.sizeScaled(72), 0) + configuration.set( + needDivider = true, + toTop = false, + paddingStart = context.resources.sizeScaled(72), + paddingEnd = 0 + ) } currentItem is Item.SwitchItem && nextItem is Item.SwitchItem || currentItem is Item.ReleaseItem && nextItem is Item.ReleaseItem -> { - configuration.set(true, false, context.resources.sizeScaled(16), 0) + configuration.set( + needDivider = true, + toTop = false, + paddingStart = context.resources.sizeScaled(16), + paddingEnd = 0 + ) } else -> { - configuration.set(false, false, 0, 0) + configuration.set(needDivider = false, toTop = false, paddingStart = 0, paddingEnd = 0) } } } @@ -579,24 +590,37 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) private var product: Product? = null private var installedItem: InstalledItem? = null + private class ItemDiffCallback(private val oldList: List, private val newList: List): DiffUtil.Callback() { + override fun getOldListSize(): Int = oldList.size + override fun getNewListSize(): Int = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition].descriptor == newList[newItemPosition].descriptor + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } + } + fun setProducts(context: Context, packageName: String, products: List>, installedItem: InstalledItem?) { val productRepository = Product.findSuggested(products, installedItem) { it.first } - items.clear() + val newItems = mutableListOf() if (productRepository != null) { - items += Item.HeaderItem(productRepository.second, productRepository.first) + newItems += Item.HeaderItem(productRepository.second, productRepository.first) if (installedItem != null) { - items.add(Item.SwitchItem(SwitchType.IGNORE_ALL_UPDATES, packageName, productRepository.first.versionCode)) + newItems.add(Item.SwitchItem(SwitchType.IGNORE_ALL_UPDATES, packageName, productRepository.first.versionCode)) if (productRepository.first.canUpdate(installedItem)) { - items.add(Item.SwitchItem(SwitchType.IGNORE_THIS_UPDATE, packageName, productRepository.first.versionCode)) + newItems.add(Item.SwitchItem(SwitchType.IGNORE_THIS_UPDATE, packageName, productRepository.first.versionCode)) } } val textViewHolder = TextViewHolder(context) - val textViewWidthSpec = context.resources.displayMetrics.widthPixels - .let { View.MeasureSpec.makeMeasureSpec(it, View.MeasureSpec.EXACTLY) } + val textViewWidthSpec = View.MeasureSpec.makeMeasureSpec( + context.resources.displayMetrics.widthPixels, View.MeasureSpec.EXACTLY) val textViewHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) fun CharSequence.lineCropped(maxLines: Int, cropLines: Int): CharSequence? { @@ -638,10 +662,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) val cropped = if (ExpandType.DESCRIPTION !in expanded) description.lineCropped(12, 10) else null val item = Item.TextItem(TextType.DESCRIPTION, description) if (cropped != null) { - items += listOf(Item.TextItem(TextType.DESCRIPTION, cropped), + newItems += listOf(Item.TextItem(TextType.DESCRIPTION, cropped), Item.ExpandItem(ExpandType.DESCRIPTION, true, listOf(item))) } else { - items += item + newItems += item } } @@ -662,20 +686,20 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } }.joinToString(separator = "\n") { "\u2022 $it" } if (antiFeatures.isNotEmpty()) { - items += Item.SectionItem(SectionType.ANTI_FEATURES) - items += Item.TextItem(TextType.ANTI_FEATURES, antiFeatures) + newItems += Item.SectionItem(SectionType.ANTI_FEATURES) + newItems += Item.TextItem(TextType.ANTI_FEATURES, antiFeatures) } val changes = formatHtml(productRepository.first.whatsNew) if (changes.isNotEmpty()) { - items += Item.SectionItem(SectionType.CHANGES) + newItems += Item.SectionItem(SectionType.CHANGES) val cropped = if (ExpandType.CHANGES !in expanded) changes.lineCropped(12, 10) else null val item = Item.TextItem(TextType.CHANGES, changes) if (cropped != null) { - items += listOf(Item.TextItem(TextType.CHANGES, cropped), + newItems += listOf(Item.TextItem(TextType.CHANGES, cropped), Item.ExpandItem(ExpandType.CHANGES, true, listOf(item))) } else { - items += item + newItems += item } } @@ -685,30 +709,32 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) linkItems += Item.LinkItem.Typed(LinkType.AUTHOR, author.name, author.web.nullIfEmpty()?.let(Uri::parse)) } author.email.nullIfEmpty()?.let { linkItems += Item.LinkItem - .Typed(LinkType.EMAIL, "", Uri.parse("mailto:$it")) } + .Typed(LinkType.EMAIL, "", "mailto:$it".toUri()) } linkItems += licenses.asSequence().map { Item.LinkItem.Typed(LinkType.LICENSE, it, - Uri.parse("https://spdx.org/licenses/$it.html")) } - source.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.SOURCE, "", Uri.parse(it)) } - tracker.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.TRACKER, "", Uri.parse(it)) } - changelog.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.CHANGELOG, "", Uri.parse(it)) } - web.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.WEB, "", Uri.parse(it)) } + "https://spdx.org/licenses/$it.html".toUri()) } + source.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.SOURCE, "", it.toUri()) } + tracker.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.TRACKER, "", + it.toUri()) } + changelog.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.CHANGELOG, "", + it.toUri()) } + web.nullIfEmpty()?.let { linkItems += Item.LinkItem.Typed(LinkType.WEB, "", it.toUri()) } } if (linkItems.isNotEmpty()) { if (ExpandType.LINKS in expanded) { - items += Item.SectionItem(SectionType.LINKS, ExpandType.LINKS, emptyList(), linkItems.size) - items += linkItems + newItems += Item.SectionItem(SectionType.LINKS, ExpandType.LINKS, emptyList(), linkItems.size) + newItems += linkItems } else { - items += Item.SectionItem(SectionType.LINKS, ExpandType.LINKS, linkItems, 0) + newItems += Item.SectionItem(SectionType.LINKS, ExpandType.LINKS, linkItems, 0) } } val donateItems = productRepository.first.donates.map(Item.LinkItem::Donate) if (donateItems.isNotEmpty()) { if (ExpandType.DONATES in expanded) { - items += Item.SectionItem(SectionType.DONATE, ExpandType.DONATES, emptyList(), donateItems.size) - items += donateItems + newItems += Item.SectionItem(SectionType.DONATE, ExpandType.DONATES, emptyList(), donateItems.size) + newItems += donateItems } else { - items += Item.SectionItem(SectionType.DONATE, ExpandType.DONATES, donateItems, 0) + newItems += Item.SectionItem(SectionType.DONATE, ExpandType.DONATES, donateItems, 0) } } @@ -719,7 +745,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) .asSequence().mapNotNull { try { packageManager.getPermissionInfo(it, 0) - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -727,7 +753,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) .asSequence().map { (group, permissionInfo) -> val permissionGroupInfo = try { group?.let { packageManager.getPermissionGroupInfo(it, 0) } - } catch (e: Exception) { + } catch (_: Exception) { null } Pair(permissionGroupInfo, permissionInfo) @@ -740,11 +766,11 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) permissions.asSequence().find { it.key == null } ?.let { permissionsItems += Item.PermissionsItem(null, it.value.flatten()) } if (ExpandType.PERMISSIONS in expanded) { - items += Item.SectionItem(SectionType.PERMISSIONS, ExpandType.PERMISSIONS, + newItems += Item.SectionItem(SectionType.PERMISSIONS, ExpandType.PERMISSIONS, emptyList(), permissionsItems.size) - items += permissionsItems + newItems += permissionsItems } else { - items += Item.SectionItem(SectionType.PERMISSIONS, ExpandType.PERMISSIONS, permissionsItems, 0) + newItems += Item.SectionItem(SectionType.PERMISSIONS, ExpandType.PERMISSIONS, permissionsItems, 0) } } } @@ -753,10 +779,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) .map { Item.ScreenshotItem(productRepository.second, packageName, it) } if (screenshotItems.isNotEmpty()) { if (ExpandType.SCREENSHOTS in expanded) { - items += Item.SectionItem(SectionType.SCREENSHOTS, ExpandType.SCREENSHOTS, emptyList(), screenshotItems.size) - items += screenshotItems + newItems += Item.SectionItem(SectionType.SCREENSHOTS, ExpandType.SCREENSHOTS, emptyList(), screenshotItems.size) + newItems += screenshotItems } else { - items += Item.SectionItem(SectionType.SCREENSHOTS, ExpandType.SCREENSHOTS, screenshotItems, 0) + newItems += Item.SectionItem(SectionType.SCREENSHOTS, ExpandType.SCREENSHOTS, screenshotItems, 0) } } } @@ -778,22 +804,26 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) .sortedByDescending { it.release.versionCode } .toList() if (releaseItems.isNotEmpty()) { - items += Item.SectionItem(SectionType.VERSIONS) + newItems += Item.SectionItem(SectionType.VERSIONS) val maxReleases = 5 if (releaseItems.size > maxReleases && ExpandType.VERSIONS !in expanded) { - items += releaseItems.take(maxReleases) - items += Item.ExpandItem(ExpandType.VERSIONS, false, releaseItems.takeLast(releaseItems.size - maxReleases)) + newItems += releaseItems.take(maxReleases) + newItems += Item.ExpandItem(ExpandType.VERSIONS, false, releaseItems.takeLast(releaseItems.size - maxReleases)) } else { - items += releaseItems + newItems += releaseItems } } - if (items.isEmpty()) { - items += Item.EmptyItem(packageName) + if (newItems.isEmpty()) { + newItems += Item.EmptyItem(packageName) } + + val diffResult = DiffUtil.calculateDiff(ItemDiffCallback(items, newItems)) + items.clear() + items.addAll(newItems) this.product = productRepository?.first this.installedItem = installedItem - notifyDataSetChanged() + diffResult.dispatchUpdatesTo(this) } private var action: Action? = null @@ -850,7 +880,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } ViewType.SWITCH -> SwitchViewHolder(parent.inflate(R.layout.switch_item)).apply { itemView.setOnClickListener { - val switchItem = items[adapterPosition] as Item.SwitchItem + val switchItem = items[bindingAdapterPosition] as Item.SwitchItem val productPreference = when (switchItem.switchType) { SwitchType.IGNORE_ALL_UPDATES -> { ProductPreferences[switchItem.packageName].let { it.copy(ignoreUpdates = !it.ignoreUpdates) } @@ -868,20 +898,20 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } ViewType.SECTION -> SectionViewHolder(parent.inflate(R.layout.section_item)).apply { itemView.setOnClickListener { - val position = adapterPosition + val position = bindingAdapterPosition val sectionItem = items[position] as Item.SectionItem if (sectionItem.items.isNotEmpty()) { expanded += sectionItem.expandType items[position] = Item.SectionItem(sectionItem.sectionType, sectionItem.expandType, emptyList(), sectionItem.items.size + sectionItem.collapseCount) - notifyItemChanged(adapterPosition, Payload.REFRESH) + notifyItemChanged(bindingAdapterPosition, Payload.REFRESH) items.addAll(position + 1, sectionItem.items) notifyItemRangeInserted(position + 1, sectionItem.items.size) } else if (sectionItem.collapseCount > 0) { expanded -= sectionItem.expandType items[position] = Item.SectionItem(sectionItem.sectionType, sectionItem.expandType, items.subList(position + 1, position + 1 + sectionItem.collapseCount).toList(), 0) - notifyItemChanged(adapterPosition, Payload.REFRESH) + notifyItemChanged(bindingAdapterPosition, Payload.REFRESH) repeat(sectionItem.collapseCount) { items.removeAt(position + 1) } notifyItemRangeRemoved(position + 1, sectionItem.collapseCount) } @@ -889,7 +919,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } ViewType.EXPAND -> ExpandViewHolder(parent.context).apply { itemView.setOnClickListener { - val position = adapterPosition + val position = bindingAdapterPosition val expandItem = items[position] as Item.ExpandItem items.removeAt(position) expanded += expandItem.expandType @@ -913,32 +943,32 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) ViewType.TEXT -> TextViewHolder(parent.context) ViewType.LINK -> LinkViewHolder(parent.inflate(R.layout.link_item)).apply { itemView.setOnClickListener { - val linkItem = items[adapterPosition] as Item.LinkItem + val linkItem = items[bindingAdapterPosition] as Item.LinkItem if (linkItem.uri?.let { callbacks.onUriClick(it, false) } != true) { linkItem.displayLink?.let { copyLinkToClipboard(itemView.context, it) } } } itemView.setOnLongClickListener { - val linkItem = items[adapterPosition] as Item.LinkItem + val linkItem = items[bindingAdapterPosition] as Item.LinkItem linkItem.displayLink?.let { copyLinkToClipboard(itemView.context, it) } true } } ViewType.PERMISSIONS -> PermissionsViewHolder(parent.inflate(R.layout.permissions_item)).apply { itemView.setOnClickListener { - val permissionsItem = items[adapterPosition] as Item.PermissionsItem + val permissionsItem = items[bindingAdapterPosition] as Item.PermissionsItem callbacks.onPermissionsClick(permissionsItem.group?.name, permissionsItem.permissions.map { it.name }) } } ViewType.SCREENSHOT -> ScreenshotViewHolder(parent.context).apply { itemView.setOnClickListener { - val screenshotItem = items[adapterPosition] as Item.ScreenshotItem + val screenshotItem = items[bindingAdapterPosition] as Item.ScreenshotItem callbacks.onScreenshotClick(screenshotItem.screenshot) } } ViewType.RELEASE -> ReleaseViewHolder(parent.inflate(R.layout.release_item)).apply { itemView.setOnClickListener { - val releaseItem = items[adapterPosition] as Item.ReleaseItem + val releaseItem = items[bindingAdapterPosition] as Item.ReleaseItem callbacks.onReleaseClick(releaseItem.release) } } @@ -989,7 +1019,6 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) holder.actionTintCancel else holder.actionTintNormal } } - if (updateAll || updateStatus) { val status = status holder.statusLayout.visibility = if (status != null) View.VISIBLE else View.GONE if (status != null) { @@ -1013,8 +1042,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } }::class } - } - Unit + Unit } ViewType.SWITCH -> { holder as SwitchViewHolder @@ -1094,10 +1122,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) val labelFromPackage = PackageItemResolver.loadLabel(context, localCache, permission) val label = labelFromPackage ?: run { val prefixes = listOf("android.permission.", "com.android.browser.permission.") - prefixes.find { permission.name.startsWith(it) }?.let { - val transform = permission.name.substring(it.length) + prefixes.find { permission.name.startsWith(it) }?.let { it -> + val transform = permission.name.substring(it.length) if (transform.matches("[A-Z_]+".toRegex())) { - transform.split("_").joinToString(separator = " ") { it.toLowerCase(Locale.US) } + transform.split("_").joinToString(separator = " ") { it.lowercase(Locale.US) } } else { null } @@ -1106,7 +1134,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) if (label == null) { Pair(false, permission.name) } else { - Pair(true, label.first().toUpperCase() + label.substring(1, label.length)) + Pair(true, label.first().uppercaseChar() + label.substring(1, label.length)) } } val builder = SpannableStringBuilder() @@ -1171,7 +1199,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) holder.signature.visibility = if (item.showSignature && item.release.signature.isNotEmpty()) View.VISIBLE else View.GONE if (item.showSignature && item.release.signature.isNotEmpty()) { - val bytes = item.release.signature.toUpperCase(Locale.US).windowed(2, 2, false).take(8) + val bytes = item.release.signature.uppercase(Locale.US).windowed(2, 2, false).take(8) val signature = bytes.joinToString(separator = " ") val builder = SpannableStringBuilder(context.getString(R.string.signature_FORMAT, signature)) val index = builder.indexOf(signature) @@ -1255,7 +1283,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) override fun onClick(view: View) { val productAdapter = productAdapterReference.get() val uri = try { - Uri.parse(url) + url.toUri() } catch (e: Exception) { e.printStackTrace() null diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt index deb28c9..7917ebb 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.net.Uri import android.os.Bundle +import android.os.Parcelable import android.provider.Settings import android.view.LayoutInflater import android.view.MenuItem @@ -14,6 +15,7 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.Toolbar +import androidx.core.os.BundleCompat import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager @@ -36,6 +38,7 @@ import nya.kitsunyan.foxydroid.utility.RxUtils import nya.kitsunyan.foxydroid.utility.Utils import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.widget.DividerItemDecoration +import androidx.core.net.toUri class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { companion object { @@ -67,7 +70,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { val packageName: String get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!! - private var layoutManagerState: LinearLayoutManager.SavedState? = null + private var layoutManagerState: Parcelable? = null private var actions = Pair(emptySet(), null as Action?) private var products = emptyList>() @@ -100,7 +103,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { this.toolbar = toolbar toolbar.menu.apply { - for (action in Action.values()) { + for (action in Action.entries) { add(0, action.id, 0, action.adapterAction.titleResId) .setIcon(Utils.getToolbarIcon(toolbar.context, action.iconResId)) .setVisible(false) @@ -130,42 +133,43 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { addOnScrollListener(scrollListener) addItemDecoration(adapter.gridItemDecoration) addItemDecoration(DividerItemDecoration(context, adapter::configureDivider)) - savedInstanceState?.getParcelable(STATE_ADAPTER)?.let(adapter::restoreState) - layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER) + savedInstanceState?.let { bundle -> + BundleCompat.getParcelable(bundle, STATE_ADAPTER, ProductAdapter.SavedState::class.java)?.let(adapter::restoreState) + layoutManagerState = BundleCompat.getParcelable(bundle, STATE_LAYOUT_MANAGER, Parcelable::class.java) + } recyclerView = this }, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - var first = true + val first = true productDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Products)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } + .flatMapSingle { RxUtils.querySingle { signal -> Database.ProductAdapter.get(packageName, signal) } } .flatMapSingle { products -> RxUtils - .querySingle { Database.RepositoryAdapter.getAll(it) } - .map { it.asSequence().map { Pair(it.id, it) }.toMap() - .let { products.mapNotNull { product -> it[product.repositoryId]?.let { Pair(product, it) } } } } } + .querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } + .map { result -> + result.asSequence().map { Pair(it.id, it) }.toMap() + .let { map -> products.mapNotNull { product -> map[product.repositoryId]?.let { Pair(product, it) } } } } } .flatMapSingle { products -> RxUtils - .querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) } - .map { Pair(products, it) } } + .querySingle { signal -> Nullable(Database.InstalledAdapter.get(packageName, signal)) } + .map { result -> Pair(products, result) } } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - val (products, installedItem) = it - val firstChanged = first - first = false - val productChanged = this.products != products + .subscribe { result -> + val (products, installedItem) = result + val productChanged = this.products != products val installedItemChanged = this.installed?.installedItem != installedItem.value - if (firstChanged || productChanged || installedItemChanged) { + if (first || productChanged || installedItemChanged) { layoutManagerState?.let { recyclerView?.layoutManager!!.onRestoreInstanceState(it) } layoutManagerState = null - if (firstChanged || productChanged) { + if (first || productChanged) { this.products = products } - if (firstChanged || installedItemChanged) { - installed = installedItem.value?.let { - val isSystem = try { + if (first || installedItemChanged) { + installed = installedItem.value?.let { it -> + val isSystem = try { ((requireContext().packageManager.getApplicationInfo(packageName, 0).flags) and ApplicationInfo.FLAG_SYSTEM) != 0 - } catch (e: Exception) { + } catch (_: Exception) { false } val launcherActivities = if (packageName == requireContext().packageName) { @@ -173,9 +177,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { emptyList() } else { val packageManager = requireContext().packageManager - packageManager + val activities = packageManager .queryIntentActivities(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 0) - .asSequence().mapNotNull { it.activityInfo }.filter { it.packageName == packageName } + activities.asSequence().mapNotNull { it.activityInfo }.filter { it.packageName == packageName } .mapNotNull { activityInfo -> val label = try { activityInfo.loadLabel(packageManager).toString() @@ -190,10 +194,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { Installed(it, isSystem, launcherActivities) } } - val recyclerView = recyclerView!! - val adapter = recyclerView.adapter as ProductAdapter - if (firstChanged || productChanged || installedItemChanged) { - adapter.setProducts(recyclerView.context, packageName, products, installedItem.value) + recyclerView?.let { + val adapter = it.adapter as ProductAdapter + adapter.setProducts(it.context, packageName, products, installedItem.value) } updateButtons() } @@ -289,7 +292,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { } val toolbar = toolbar if (toolbar != null) { - for (action in Action.values()) { + for (action in Action.entries) { toolbar.menu.findItem(action.id).isVisible = action in displayActions } } @@ -336,11 +339,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { val compatibleReleases = productRepository?.first?.selectedReleases.orEmpty() .filter { installedItem == null || installedItem.signature == it.signature } val release = if (compatibleReleases.size >= 2) { - compatibleReleases - .filter { it.platforms.contains(Android.primaryPlatform) } - .minBy { it.platforms.size } - ?: compatibleReleases.minBy { it.platforms.size } - ?: compatibleReleases.firstOrNull() + compatibleReleases + .filter { it.platforms.contains(Android.primaryPlatform) } + .minBy { it.platforms.size } } else { compatibleReleases.firstOrNull() } @@ -361,13 +362,11 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { } ProductAdapter.Action.DETAILS -> { startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.parse("package:$packageName"))) + .setData("package:$packageName".toUri())) } ProductAdapter.Action.UNINSTALL -> { - // TODO Handle deprecation - @Suppress("DEPRECATION") - startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE) - .setData(Uri.parse("package:$packageName"))) + startActivity(Intent(Intent.ACTION_DELETE) + .setData("package:$packageName".toUri())) } ProductAdapter.Action.CANCEL -> { val binder = downloadConnection.binder @@ -400,7 +399,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { override fun onScreenshotClick(screenshot: Product.Screenshot) { val pair = products.asSequence() - .map { Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) } + .map { it -> Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) } .filter { it.second != null }.firstOrNull() if (pair != null) { val (repository, identifier) = pair @@ -424,7 +423,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks { MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(childFragmentManager) } else -> { - val productRepository = products.asSequence().filter { it.first.releases.any { it === release } }.firstOrNull() + val productRepository = products.asSequence().filter { it -> it.first.releases.any { it === release } }.firstOrNull() if (productRepository != null) { downloadConnection.binder?.enqueue(packageName, productRepository.first.name, productRepository.second, release) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt index d72a115..8c4f7d7 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt @@ -1,6 +1,8 @@ package nya.kitsunyan.foxydroid.screen +import android.annotation.SuppressLint import android.content.Context +import android.database.Cursor import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.view.Gravity @@ -46,7 +48,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): init { itemView as FrameLayout val progressBar = ProgressBar(itemView.context) - itemView.addView(progressBar, FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, + (itemView as ViewGroup).addView(progressBar, FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER }) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) @@ -59,11 +61,11 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): init { itemView as TextView - itemView.gravity = Gravity.CENTER + (itemView as TextView).gravity = Gravity.CENTER itemView.resources.sizeScaled(20).let { itemView.setPadding(it, it, it, it) } - itemView.typeface = TypefaceExtra.light - itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) - itemView.setTextSizeScaled(20) + (itemView as TextView).typeface = TypefaceExtra.light + (itemView as TextView).setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) + (itemView as TextView).setTextSizeScaled(20) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) } @@ -75,18 +77,25 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): getProductItem(position + 1) else null when { currentItem != null && nextItem != null && currentItem.matchRank != nextItem.matchRank -> { - configuration.set(true, false, 0, 0) + configuration.set(needDivider = true, toTop = false, paddingStart = 0, paddingEnd = 0) } else -> { - configuration.set(true, false, context.resources.sizeScaled(72), 0) + configuration.set( + needDivider = true, + toTop = false, + paddingStart = context.resources.sizeScaled(72), + paddingEnd = 0 + ) } } } var repositories: Map = emptyMap() set(value) { - field = value - notifyDataSetChanged() + if (field != value) { + field = value + notifyItemRangeChanged(0, itemCount) + } } var emptyText: String = "" @@ -94,7 +103,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): if (field != value) { field = value if (isEmpty) { - notifyDataSetChanged() + notifyItemChanged(0) } } } @@ -123,7 +132,12 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder { return when (viewType) { ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply { - itemView.setOnClickListener { onClick(getProductItem(adapterPosition)) } + itemView.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + onClick(getProductItem(position)) + } + } } ViewType.LOADING -> LoadingViewHolder(parent.context) ViewType.EMPTY -> EmptyViewHolder(parent.context) @@ -170,7 +184,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): } } val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty() - sequenceOf(holder.name, holder.status, holder.summary).forEach { it.isEnabled = enabled } + sequenceOf(holder.name, holder.status, holder.summary).forEach { view -> view.isEnabled = enabled } } ViewType.LOADING -> { // Do nothing @@ -179,6 +193,25 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): holder as EmptyViewHolder holder.text.text = emptyText } - }::class + } + } + + @SuppressLint("NotifyDataSetChanged") + override fun onCursorChanged(oldCursor: Cursor?, newCursor: Cursor?) { + val oldSize = oldCursor?.count ?: 0 + val newSize = newCursor?.count ?: 0 + + val oldIsVirtual = oldSize == 0 + val newIsVirtual = newSize == 0 + + if (oldIsVirtual != newIsVirtual) { + notifyDataSetChanged() + } else if (oldIsVirtual) { + if (oldCursor != newCursor) { + notifyItemChanged(0) + } + } else { + super.onCursorChanged(oldCursor, newCursor) + } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt index 7af8a19..04579c4 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.os.BundleCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -90,17 +91,21 @@ class ProductsFragment(): ScreenFragment(), CursorOwner.Callback { super.onViewCreated(view, savedInstanceState) currentSearchQuery = savedInstanceState?.getString(STATE_CURRENT_SEARCH_QUERY).orEmpty() - currentSection = savedInstanceState?.getParcelable(STATE_CURRENT_SECTION) ?: ProductItem.Section.All + currentSection = savedInstanceState?.let { bundle -> + BundleCompat.getParcelable(bundle, STATE_CURRENT_SECTION, ProductItem.Section::class.java) + } ?: ProductItem.Section.All currentOrder = savedInstanceState?.getString(STATE_CURRENT_ORDER) ?.let(ProductItem.Order::valueOf) ?: ProductItem.Order.NAME - layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER) + layoutManagerState = savedInstanceState?.let { bundle -> + BundleCompat.getParcelable(bundle, STATE_LAYOUT_MANAGER, Parcelable::class.java) + } screenActivity.cursorOwner.attach(this, request) repositoriesDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Repositories)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } - .map { it.asSequence().map { Pair(it.id, it) }.toMap() } + .flatMapSingle { RxUtils.querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } } + .map { result -> result.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (recyclerView?.adapter as? ProductsAdapter)?.repositories = it } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt index bec39b1..b49720b 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt @@ -34,20 +34,28 @@ class RepositoriesAdapter(private val onClick: (Repository) -> Unit, return Database.RepositoryAdapter.transform(moveTo(position)) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder { - return ViewHolder(parent.inflate(R.layout.repository_item)).apply { - itemView.setOnClickListener { onClick(getRepository(adapterPosition)) } - enabled.setOnCheckedChangeListener { _, isChecked -> - if (listenSwitch) { - if (!onSwitch(getRepository(adapterPosition), isChecked)) { - listenSwitch = false - enabled.isChecked = !isChecked - listenSwitch = true - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder { + return ViewHolder(parent.inflate(R.layout.repository_item)).apply { + itemView.setOnClickListener { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + onClick(getRepository(position)) + } + } + enabled.setOnCheckedChangeListener { _, isChecked -> + if (listenSwitch) { + val position = bindingAdapterPosition + if (position != RecyclerView.NO_POSITION) { + if (!onSwitch(getRepository(position), isChecked)) { + listenSwitch = false + enabled.isChecked = !isChecked + listenSwitch = true + } + } + } + } } - } } - } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { holder as ViewHolder diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt index 4275b7b..33122a5 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt @@ -113,15 +113,13 @@ class RepositoryFragment(): ScreenFragment() { layout.addTitleText(R.string.name, repository.name) layout.addTitleText(R.string.description, repository.description.replace('\n', ' ')) layout.addTitleText(R.string.last_update, run { - val lastUpdated = repository.updated - if (lastUpdated > 0L) { - val date = Date(repository.updated) - val format = if (DateUtils.isToday(date.time)) DateUtils.FORMAT_SHOW_TIME else - DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE - DateUtils.formatDateTime(layout.context, date.time, format) - } else { - getString(R.string.unknown) - } + repository.updated + run { + val date = Date(repository.updated) + val format = if (DateUtils.isToday(date.time)) DateUtils.FORMAT_SHOW_TIME else + DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE + DateUtils.formatDateTime(layout.context, date.time, format) + } }) if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) { layout.addTitleText(R.string.number_of_applications, @@ -139,7 +137,7 @@ class RepositoryFragment(): ScreenFragment() { } } else { val fingerprint = SpannableStringBuilder(repository.fingerprint.windowed(2, 2, false) - .take(32).joinToString(separator = " ") { it.toUpperCase(Locale.US) }) + .take(32).joinToString(separator = " ") { it.uppercase(Locale.US) }) fingerprint.setSpan(TypefaceSpan("monospace"), 0, fingerprint.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) layout.addTitleText(R.string.fingerprint, fingerprint) @@ -158,9 +156,7 @@ class RepositoryFragment(): ScreenFragment() { } } - internal fun onDeleteConfirm() { - if (syncConnection.binder?.deleteRepository(repositoryId) == true) { - requireActivity().onBackPressed() - } - } + internal fun onDeleteConfirm() {if (syncConnection.binder?.deleteRepository(repositoryId) == true) { + screenActivity.onBackPressedDispatcher.onBackPressed() + }} } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt index 2466e1c..2bc9930 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt @@ -5,11 +5,13 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Parcel -import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout import android.widget.Toolbar +import androidx.activity.OnBackPressedCallback +import androidx.core.os.BundleCompat +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nya.kitsunyan.foxydroid.R @@ -72,8 +74,7 @@ abstract class ScreenActivity: FragmentActivity() { setTheme(Preferences[Preferences.Key.Theme].getResId(resources.configuration)) super.onCreate(savedInstanceState) - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + WindowCompat.setDecorFitsSystemWindows(window, false) addContentView(FrameLayout(this).apply { id = R.id.main_content }, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) @@ -87,14 +88,34 @@ abstract class ScreenActivity: FragmentActivity() { .findFragmentByTag(CursorOwner::class.java.name) as CursorOwner } - savedInstanceState?.getParcelableArrayList(STATE_FRAGMENT_STACK) - ?.let { fragmentStack += it } + savedInstanceState?.let { + BundleCompat.getParcelableArrayList(it, STATE_FRAGMENT_STACK, FragmentStackItem::class.java) + ?.let { list -> fragmentStack += list } + } if (savedInstanceState == null) { replaceFragment(TabsFragment(), null) if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) { handleIntent(intent) } } + + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val currentFragment = currentFragment + if (!(currentFragment is ScreenFragment && currentFragment.onBackPressed())) { + hideKeyboard() + if (!popFragment()) { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + isEnabled = true + } + } + } + }) + + supportFragmentManager.addFragmentOnAttachListener { _, _ -> + hideKeyboard() + } } override fun onSaveInstanceState(outState: Bundle) { @@ -102,16 +123,6 @@ abstract class ScreenActivity: FragmentActivity() { outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack)) } - override fun onBackPressed() { - val currentFragment = currentFragment - if (!(currentFragment is ScreenFragment && currentFragment.onBackPressed())) { - hideKeyboard() - if (!popFragment()) { - super.onBackPressed() - } - } - } - private fun replaceFragment(fragment: Fragment, open: Boolean?) { if (open != null) { currentFragment?.view?.translationZ = (if (open) Int.MIN_VALUE else Int.MAX_VALUE).toFloat() @@ -137,7 +148,7 @@ abstract class ScreenActivity: FragmentActivity() { private fun popFragment(): Boolean { return fragmentStack.isNotEmpty() && run { val stackItem = fragmentStack.removeAt(fragmentStack.size - 1) - val fragment = Class.forName(stackItem.className).newInstance() as Fragment + val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, stackItem.className) stackItem.arguments?.let(fragment::setArguments) stackItem.savedState?.let(fragment::setInitialSavedState) replaceFragment(fragment, false) @@ -150,15 +161,10 @@ abstract class ScreenActivity: FragmentActivity() { ?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0) } - override fun onAttachFragment(fragment: Fragment) { - super.onAttachFragment(fragment) - hideKeyboard() - } - internal fun onToolbarCreated(toolbar: Toolbar) { if (fragmentStack.isNotEmpty()) { toolbar.navigationIcon = toolbar.context.getDrawableFromAttr(android.R.attr.homeAsUpIndicator) - toolbar.setNavigationOnClickListener { onBackPressed() } + toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } } } @@ -235,10 +241,10 @@ abstract class ScreenActivity: FragmentActivity() { } else { Pair(Uri.fromFile(Cache.getReleaseFile(this, cacheFileName)), 0) } - // TODO Handle deprecation - @Suppress("DEPRECATION") - startActivity(Intent(Intent.ACTION_INSTALL_PACKAGE) - .setDataAndType(uri, "application/vnd.android.package-archive").setFlags(flags)) + + startActivity(Intent(Intent.ACTION_VIEW) + .setDataAndType(uri, "application/vnd.android.package-archive") + .addFlags(flags or Intent.FLAG_ACTIVITY_NEW_TASK)) } internal fun navigateProduct(packageName: String) = pushFragment(ProductFragment(packageName)) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt index 55b2b2b..e7247c2 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt @@ -5,13 +5,16 @@ import android.content.Context import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.Bundle -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.ImageView import androidx.core.graphics.ColorUtils +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.MarginPageTransformer import androidx.viewpager2.widget.ViewPager2 @@ -65,10 +68,11 @@ class ScreenshotsFragment(): DialogFragment() { val background = dialog.context.getColorFromAttr(android.R.attr.colorBackground).defaultColor decorView.setBackgroundColor(background.let { ColorUtils.blendARGB(0x00ffffff and it, it, 0.9f) }) decorView.setPadding(0, 0, 0, 0) - background.let { ColorUtils.blendARGB(0x00ffffff and it, it, 0.8f) }.let { - window.statusBarColor = it - window.navigationBarColor = it - } + + WindowCompat.setDecorFitsSystemWindows(window, false) + val insetsController = WindowCompat.getInsetsController(window, decorView) + insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + window.attributes = window.attributes.apply { title = ScreenshotsFragment::class.java.name format = PixelFormat.TRANSLUCENT @@ -86,17 +90,16 @@ class ScreenshotsFragment(): DialogFragment() { } } - val hideFlags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE - decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - val applyHide = Runnable { decorView.systemUiVisibility = decorView.systemUiVisibility or hideFlags } + val applyHide = Runnable { insetsController.hide(WindowInsetsCompat.Type.systemBars()) } val handleClick = { decorView.removeCallbacks(applyHide) - if ((decorView.systemUiVisibility and hideFlags) == hideFlags) { - decorView.systemUiVisibility = decorView.systemUiVisibility and hideFlags.inv() + val isVisible = decorView.rootWindowInsets?.let { + it.isVisible(WindowInsetsCompat.Type.statusBars()) || it.isVisible(WindowInsetsCompat.Type.navigationBars()) + } ?: true + if (isVisible) { + insetsController.hide(WindowInsetsCompat.Type.systemBars()) } else { - decorView.systemUiVisibility = decorView.systemUiVisibility or hideFlags + insetsController.show(WindowInsetsCompat.Type.systemBars()) } } decorView.postDelayed(applyHide, 2000L) @@ -112,21 +115,20 @@ class ScreenshotsFragment(): DialogFragment() { ViewGroup.LayoutParams.MATCH_PARENT)) this.viewPager = viewPager - var restored = false + val restored = false productDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Products)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } - .map { Pair(it.find { it.repositoryId == repositoryId }, Database.RepositoryAdapter.get(repositoryId)) } + .flatMapSingle { RxUtils.querySingle { signal -> Database.ProductAdapter.get(packageName, signal) } } + .map { result -> Pair(result.find { it.repositoryId == repositoryId }, Database.RepositoryAdapter.get(repositoryId)) } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - val (product, repository) = it + .subscribe { result -> + val (product, repository) = result val screenshots = product?.screenshots.orEmpty() (viewPager.adapter as Adapter).update(repository, screenshots) if (!restored) { - restored = true - val identifier = savedInstanceState?.getString(STATE_IDENTIFIER) - ?: requireArguments().getString(STATE_IDENTIFIER) + val identifier = savedInstanceState?.getString(STATE_IDENTIFIER) + ?: requireArguments().getString(EXTRA_IDENTIFIER) if (identifier != null) { val index = screenshots.indexOfFirst { it.identifier == identifier } if (index >= 0) { @@ -160,9 +162,11 @@ class ScreenshotsFragment(): DialogFragment() { private class Adapter(private val packageName: String, private val onClick: () -> Unit): StableRecyclerAdapter() { - enum class ViewType { SCREENSHOT } + enum class ViewType { + SECTION + } - private class ViewHolder(context: Context): RecyclerView.ViewHolder(ImageView(context)) { + private class ViewHolder(context: Context): RecyclerView.ViewHolder(ImageView(context)) { val image: ImageView get() = itemView as ImageView @@ -182,17 +186,28 @@ class ScreenshotsFragment(): DialogFragment() { private var repository: Repository? = null private var screenshots = emptyList() + private class ScreenshotDiffCallback(private val oldList: List, + private val newList: List): DiffUtil.Callback() { + override fun getOldListSize(): Int = oldList.size + override fun getNewListSize(): Int = newList.size + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldList[oldItemPosition].identifier == newList[newItemPosition].identifier + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldList[oldItemPosition] == newList[newItemPosition] + } + fun update(repository: Repository?, screenshots: List) { this.repository = repository + val diffResult = DiffUtil.calculateDiff(ScreenshotDiffCallback(this.screenshots, screenshots)) this.screenshots = screenshots - notifyDataSetChanged() + diffResult.dispatchUpdatesTo(this) } var size = Pair(0, 0) set(value) { if (field != value) { field = value - notifyDataSetChanged() + notifyItemRangeChanged(0, itemCount) } } @@ -206,7 +221,7 @@ class ScreenshotsFragment(): DialogFragment() { override fun getItemCount(): Int = screenshots.size override fun getItemDescriptor(position: Int): String = screenshots[position].identifier - override fun getItemEnumViewType(position: Int): ViewType = ViewType.SCREENSHOT + override fun getItemEnumViewType(position: Int): ViewType = ViewType.SECTION override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder { return ViewHolder(parent.context).apply { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt index 61a2b6a..673664d 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt @@ -22,7 +22,10 @@ import android.widget.LinearLayout import android.widget.SearchView import android.widget.TextView import android.widget.Toolbar +import androidx.core.os.BundleCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter @@ -75,10 +78,10 @@ class TabsFragment: ScreenFragment() { if (field != value) { field = value val layout = layout - layout?.tabs?.let { (0 until it.childCount) - .forEach { index -> it.getChildAt(index)!!.isEnabled = !value } } + layout?.tabs?.let { tabs -> (0 until tabs.childCount) + .forEach { index -> tabs.getChildAt(index)!!.isEnabled = !value } } layout?.sectionIcon?.scaleY = if (value) -1f else 1f - if ((sectionsList?.parent as? View)?.height ?: 0 > 0) { + if (((sectionsList?.parent as? View)?.height ?: 0) > 0) { animateSectionsList() } } @@ -89,8 +92,8 @@ class TabsFragment: ScreenFragment() { private var section: ProductItem.Section = ProductItem.Section.All private val syncConnection = Connection(SyncService::class.java, onBind = { _, _ -> - viewPager?.let { - val source = ProductsFragment.Source.values()[it.currentItem] + viewPager?.let { pager -> + val source = ProductsFragment.Source.entries[pager.currentItem] updateUpdateNotificationBlocker(source) } }) @@ -195,7 +198,7 @@ class TabsFragment: ScreenFragment() { layout.tabs.background = TabsBackgroundDrawable(layout.tabs.context, layout.tabs.layoutDirection == View.LAYOUT_DIRECTION_RTL) - ProductsFragment.Source.values().forEach { + ProductsFragment.Source.entries.forEach { source -> val tab = TextView(layout.tabs.context) val selectedColor = tab.context.getColorFromAttr(android.R.attr.textColorPrimary).defaultColor val normalColor = tab.context.getColorFromAttr(android.R.attr.textColorSecondary).defaultColor @@ -205,25 +208,25 @@ class TabsFragment: ScreenFragment() { intArrayOf(selectedColor, normalColor))) tab.setTextSizeScaled(14) tab.isAllCaps = true - tab.text = getString(it.titleResId) + tab.text = getString(source.titleResId) tab.background = tab.context.getDrawableFromAttr(android.R.attr.selectableItemBackground) - tab.setOnClickListener { _ -> - setSelectedTab(it) - viewPager!!.setCurrentItem(it.ordinal, Utils.areAnimationsEnabled(tab.context)) + tab.setOnClickListener { + setSelectedTab(source) + viewPager!!.setCurrentItem(source.ordinal, Utils.areAnimationsEnabled(tab.context)) } layout.tabs.addView(tab, 0, LinearLayout.LayoutParams.MATCH_PARENT) (tab.layoutParams as LinearLayout.LayoutParams).weight = 1f } - showSections = savedInstanceState?.getByte(STATE_SHOW_SECTIONS)?.toInt() ?: 0 != 0 - sections = savedInstanceState?.getParcelableArrayList(STATE_SECTIONS).orEmpty() - section = savedInstanceState?.getParcelable(STATE_SECTION) ?: ProductItem.Section.All + showSections = (savedInstanceState?.getByte(STATE_SHOW_SECTIONS)?.toInt() ?: 0) != 0 + sections = savedInstanceState?.let { BundleCompat.getParcelableArrayList(it, STATE_SECTIONS, ProductItem.Section::class.java) }.orEmpty() + section = savedInstanceState?.let { BundleCompat.getParcelable(it, STATE_SECTION, ProductItem.Section::class.java) } ?: ProductItem.Section.All layout.sectionChange.setOnClickListener { showSections = sections .any { it !is ProductItem.Section.All } && !showSections } updateOrder() - sortOrderDisposable = Preferences.observable.subscribe { - if (it == Preferences.Key.SortOrder) { + sortOrderDisposable = Preferences.observable.subscribe { key -> + if (key == Preferences.Key.SortOrder) { updateOrder() } } @@ -233,9 +236,8 @@ class TabsFragment: ScreenFragment() { viewPager = ViewPager2(content.context).apply { id = R.id.fragment_pager adapter = object: FragmentStateAdapter(this@TabsFragment) { - override fun getItemCount(): Int = ProductsFragment.Source.values().size - override fun createFragment(position: Int): Fragment = ProductsFragment(ProductsFragment - .Source.values()[position]) + override fun getItemCount(): Int = ProductsFragment.Source.entries.size + override fun createFragment(position: Int): Fragment = ProductsFragment(ProductsFragment.Source.entries[position]) } content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) registerOnPageChangeCallback(pageChangeCallback) @@ -245,16 +247,17 @@ class TabsFragment: ScreenFragment() { categoriesDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Products)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { signal -> Database.CategoryAdapter.getAll(signal) } } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { setSectionsAndUpdate(it.asSequence().sorted() + .subscribe { result -> setSectionsAndUpdate(result.asSequence().sorted() .map(ProductItem.Section::Category).toList(), null) } repositoriesDisposable = Observable.just(Unit) .concatWith(Database.observable(Database.Subject.Repositories)) .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { signal -> Database.RepositoryAdapter.getAll(signal) } } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { setSectionsAndUpdate(null, it.asSequence().filter { it.enabled } + .subscribe { result -> + setSectionsAndUpdate(null, result.asSequence().filter { it.enabled } .map { ProductItem.Section.Repository(it.id, it.name) }.toList()) } updateSection() @@ -264,10 +267,10 @@ class TabsFragment: ScreenFragment() { isMotionEventSplittingEnabled = false isVerticalScrollBarEnabled = false setHasFixedSize(true) - val adapter = SectionsAdapter({ sections }) { + val adapter = SectionsAdapter({ sections }) { newSection -> if (showSections) { showSections = false - section = it + section = newSection updateSection() } } @@ -280,14 +283,13 @@ class TabsFragment: ScreenFragment() { } this.sectionsList = sectionsList - var lastContentHeight = -1 + val lastContentHeight = -1 content.viewTreeObserver.addOnGlobalLayoutListener { if (this.view != null) { - val initial = lastContentHeight <= 0 + val initial = true val contentHeight = content.height if (lastContentHeight != contentHeight) { - lastContentHeight = contentHeight - if (initial) { + if (initial) { sectionsList.layoutParams.height = if (showSections) contentHeight else 0 sectionsList.visibility = if (showSections) View.VISIBLE else View.GONE sectionsList.requestLayout() @@ -340,7 +342,9 @@ class TabsFragment: ScreenFragment() { } } + @Deprecated("Deprecated in Java") override fun onAttachFragment(childFragment: Fragment) { + @Suppress("DEPRECATION") super.onAttachFragment(childFragment) if (view != null && childFragment is ProductsFragment) { @@ -368,7 +372,7 @@ class TabsFragment: ScreenFragment() { private fun setSelectedTab(source: ProductsFragment.Source) { val layout = layout!! - (0 until layout.tabs.childCount).forEach { layout.tabs.getChildAt(it).isSelected = it == source.ordinal } + (0 until layout.tabs.childCount).forEach { index -> layout.tabs.getChildAt(index).isSelected = index == source.ordinal } } internal fun selectUpdates() = selectUpdatesInternal(true) @@ -407,10 +411,20 @@ class TabsFragment: ScreenFragment() { val oldCategories = collectOldSections(categories) val oldRepositories = collectOldSections(repositories) if (oldCategories == null || oldRepositories == null) { + val oldSections = sections sections = listOf(ProductItem.Section.All) + (categories ?: oldCategories).orEmpty() + (repositories ?: oldRepositories).orEmpty() + val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int = oldSections.size + override fun getNewListSize(): Int = sections.size + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldSections[oldItemPosition] == sections[newItemPosition] + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldSections[oldItemPosition] == sections[newItemPosition] + }) updateSection() + sectionsList?.adapter?.let { adapter -> diffResult.dispatchUpdatesTo(adapter) } } } @@ -418,14 +432,19 @@ class TabsFragment: ScreenFragment() { if (section !in sections) { section = ProductItem.Section.All } - layout?.sectionName?.text = when (val section = section) { + layout?.sectionName?.text = when (val s = section) { is ProductItem.Section.All -> getString(R.string.all_applications) - is ProductItem.Section.Category -> section.name - is ProductItem.Section.Repository -> section.name + is ProductItem.Section.Category -> s.name + is ProductItem.Section.Repository -> s.name } layout?.sectionIcon?.visibility = if (sections.any { it !is ProductItem.Section.All }) View.VISIBLE else View.GONE productFragments.forEach { it.setSection(section) } - sectionsList?.adapter?.notifyDataSetChanged() + sectionsList?.adapter?.let { adapter -> + val index = sections.indexOf(section) + if (index >= 0) { + adapter.notifyItemRangeChanged(0, sections.size) + } + } } private fun animateSectionsList() { @@ -440,16 +459,16 @@ class TabsFragment: ScreenFragment() { sectionsAnimator = ValueAnimator.ofFloat(value, target).apply { duration = (250 * abs(target - value)).toLong() interpolator = if (target >= 1f) AccelerateInterpolator(2f) else DecelerateInterpolator(2f) - addUpdateListener { - val newValue = animatedValue as Float + addUpdateListener { animator -> + val newValue = animator.animatedValue as Float sectionsList.apply { - val height = ((parent as View).height * newValue).toInt() - val visible = height > 0 - if ((visibility == View.VISIBLE) != visible) { + val h = ((parent as View).height * newValue).toInt() + val visible = h > 0 + if ((isVisible) != visible) { visibility = if (visible) View.VISIBLE else View.GONE } - if (layoutParams.height != height) { - layoutParams.height = height + if (layoutParams.height != h) { + layoutParams.height = h requestLayout() } } @@ -464,30 +483,30 @@ class TabsFragment: ScreenFragment() { private val pageChangeCallback = object: ViewPager2.OnPageChangeCallback() { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - val layout = layout!! - val fromSections = ProductsFragment.Source.values()[position].sections + val l = layout!! + val fromSections = ProductsFragment.Source.entries[position].sections val toSections = if (positionOffset <= 0f) fromSections else - ProductsFragment.Source.values()[position + 1].sections + ProductsFragment.Source.entries[position + 1].sections val offset = if (fromSections != toSections) { if (fromSections) 1f - positionOffset else positionOffset } else { if (fromSections) 1f else 0f } - (layout.tabs.background as TabsBackgroundDrawable) - .update(position + positionOffset, layout.tabs.childCount) - assert(layout.sectionLayout.childCount == 1) - val child = layout.sectionLayout.getChildAt(0) - val height = child.layoutParams.height - assert(height > 0) - val currentHeight = (offset * height).roundToInt() - if (layout.sectionLayout.layoutParams.height != currentHeight) { - layout.sectionLayout.layoutParams.height = currentHeight - layout.sectionLayout.requestLayout() + (l.tabs.background as TabsBackgroundDrawable) + .update(position + positionOffset, l.tabs.childCount) + assert(l.sectionLayout.childCount == 1) + val child = l.sectionLayout.getChildAt(0) + val h = child.layoutParams.height + assert(h > 0) + val currentHeight = (offset * h).roundToInt() + if (l.sectionLayout.layoutParams.height != currentHeight) { + l.sectionLayout.layoutParams.height = currentHeight + l.sectionLayout.requestLayout() } } override fun onPageSelected(position: Int) { - val source = ProductsFragment.Source.values()[position] + val source = ProductsFragment.Source.entries[position] updateUpdateNotificationBlocker(source) sortOrderMenu!!.first.isVisible = source.order syncRepositoriesMenuItem!!.setShowAsActionFlags(if (!source.order || @@ -499,7 +518,7 @@ class TabsFragment: ScreenFragment() { } override fun onPageScrollStateChanged(state: Int) { - val source = ProductsFragment.Source.values()[viewPager!!.currentItem] + val source = ProductsFragment.Source.entries[viewPager!!.currentItem] layout!!.sectionChange.isEnabled = state != ViewPager2.SCROLL_STATE_DRAGGING && source.sections if (state == ViewPager2.SCROLL_STATE_IDLE) { // onPageSelected can be called earlier than fragments created @@ -509,7 +528,7 @@ class TabsFragment: ScreenFragment() { } private class TabsBackgroundDrawable(context: Context, private val rtl: Boolean): Drawable() { - private val height = context.resources.sizeScaled(2) + private val h = context.resources.sizeScaled(2) private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColorFromAttr(android.R.attr.colorAccent).defaultColor } @@ -525,15 +544,15 @@ class TabsFragment: ScreenFragment() { override fun draw(canvas: Canvas) { if (total > 0) { - val bounds = bounds - val width = bounds.width() / total.toFloat() - val x = width * position + val b = bounds + val w = b.width() / total.toFloat() + val x = w * position if (rtl) { - canvas.drawRect(bounds.right - width - x, (bounds.bottom - height).toFloat(), - bounds.right - x, bounds.bottom.toFloat(), paint) + canvas.drawRect(b.right - w - x, (b.bottom - h).toFloat(), + b.right - x, b.bottom.toFloat(), paint) } else { - canvas.drawRect(bounds.left + x, (bounds.bottom - height).toFloat(), - bounds.left + x + width, bounds.bottom.toFloat(), paint) + canvas.drawRect(b.left + x, (b.bottom - h).toFloat(), + b.left + x + w, b.bottom.toFloat(), paint) } } } @@ -554,10 +573,10 @@ class TabsFragment: ScreenFragment() { init { itemView as TextView - itemView.gravity = Gravity.CENTER_VERTICAL + (itemView as TextView).gravity = Gravity.CENTER_VERTICAL itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) } - itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) - itemView.setTextSizeScaled(16) + (itemView as TextView).setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) + (itemView as TextView).setTextSizeScaled(16) itemView.background = context.getDrawableFromAttr(android.R.attr.selectableItemBackground) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, itemView.resources.sizeScaled(48)) @@ -570,10 +589,15 @@ class TabsFragment: ScreenFragment() { when { nextSection != null && currentSection.javaClass != nextSection.javaClass -> { val padding = context.resources.sizeScaled(16) - configuration.set(true, false, padding, padding) + configuration.set( + needDivider = true, + toTop = false, + paddingStart = padding, + paddingEnd = padding + ) } else -> { - configuration.set(false, false, 0, 0) + configuration.set(needDivider = false, toTop = false, paddingStart = 0, paddingEnd = 0) } } } @@ -587,7 +611,12 @@ class TabsFragment: ScreenFragment() { override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder { return SectionViewHolder(parent.context).apply { - itemView.setOnClickListener { onClick(sections()[adapterPosition]) } + itemView.setOnClickListener { + val pos = bindingAdapterPosition + if (pos != RecyclerView.NO_POSITION) { + onClick(sections()[pos]) + } + } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt index 7c4c4e2..535753a 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt @@ -6,9 +6,9 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.net.Uri import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -29,6 +29,7 @@ import java.io.File import java.security.MessageDigest import java.util.concurrent.TimeUnit import kotlin.math.* +import androidx.core.net.toUri class DownloadService: ConnectionService() { companion object { @@ -47,14 +48,14 @@ class DownloadService: ConnectionService() { action.startsWith("$ACTION_OPEN.") -> { val packageName = action.substring(ACTION_OPEN.length + 1) context.startActivity(Intent(context, MainActivity::class.java) - .setAction(Intent.ACTION_VIEW).setData(Uri.parse("package:$packageName")) + .setAction(Intent.ACTION_VIEW).setData("package:$packageName".toUri()) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } action.startsWith("$ACTION_INSTALL.") -> { val packageName = action.substring(ACTION_INSTALL.length + 1) val cacheFileName = intent.getStringExtra(EXTRA_CACHE_FILE_NAME) context.startActivity(Intent(context, MainActivity::class.java) - .setAction(MainActivity.ACTION_INSTALL).setData(Uri.parse("package:$packageName")) + .setAction(MainActivity.ACTION_INSTALL).setData("package:$packageName".toUri()) .putExtra(MainActivity.EXTRA_CACHE_FILE_NAME, cacheFileName) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } @@ -189,8 +190,10 @@ class DownloadService: ConnectionService() { .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) .getColorFromAttr(android.R.attr.colorAccent).defaultColor) - .setContentIntent(PendingIntent.getBroadcast(this, 0, Intent(this, Receiver::class.java) - .setAction("$ACTION_OPEN.${task.packageName}"), PendingIntent.FLAG_UPDATE_CURRENT)) + .setContentIntent(PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java) + .setAction(Intent.ACTION_VIEW).setData("package:${task.packageName}".toUri()) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT or + Android.PendingIntent.FLAG_IMMUTABLE)) .apply { when (errorType) { is ErrorType.Network -> { @@ -223,9 +226,11 @@ class DownloadService: ConnectionService() { .setSmallIcon(android.R.drawable.stat_sys_download_done) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) .getColorFromAttr(android.R.attr.colorAccent).defaultColor) - .setContentIntent(PendingIntent.getBroadcast(this, 0, Intent(this, Receiver::class.java) - .setAction("$ACTION_INSTALL.${task.packageName}") - .putExtra(EXTRA_CACHE_FILE_NAME, task.release.cacheFileName), PendingIntent.FLAG_UPDATE_CURRENT)) + .setContentIntent(PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java) + .setAction(MainActivity.ACTION_INSTALL).setData("package:${task.packageName}".toUri()) + .putExtra(MainActivity.EXTRA_CACHE_FILE_NAME, task.release.cacheFileName) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT or + Android.PendingIntent.FLAG_IMMUTABLE)) .setContentTitle(getString(R.string.downloaded_FORMAT, task.name)) .setContentText(getString(R.string.tap_to_install_DESC)) .build()) @@ -243,12 +248,12 @@ class DownloadService: ConnectionService() { val hash = try { val hashType = task.release.hashType.nullIfEmpty() ?: "SHA256" val digest = MessageDigest.getInstance(hashType) - file.inputStream().use { - val bytes = ByteArray(8 * 1024) + file.inputStream().use { it -> + val bytes = ByteArray(8 * 1024) generateSequence { it.read(bytes) }.takeWhile { it >= 0 }.forEach { digest.update(bytes, 0, it) } digest.digest().hex() } - } catch (e: Exception) { + } catch (_: Exception) { "" } return if (hash.isEmpty() || hash != task.release.hash) { @@ -287,7 +292,8 @@ class DownloadService: ConnectionService() { .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) .getColorFromAttr(android.R.attr.colorAccent).defaultColor) .addAction(0, getString(R.string.cancel), PendingIntent.getService(this, 0, - Intent(this, this::class.java).setAction(ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)) } + Intent(this, this::class.java).setAction(ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT or + Android.PendingIntent.FLAG_IMMUTABLE)) } private fun publishForegroundState(force: Boolean, state: State) { if (force || currentTask != null) { @@ -313,7 +319,7 @@ class DownloadService: ConnectionService() { throw IllegalStateException() } }::class - }.build()) + }.build(), Android.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) stateSubject.onNext(state) } } @@ -361,7 +367,7 @@ class DownloadService: ConnectionService() { currentTask = CurrentTask(task, disposable, initialState) } else if (started) { started = false - stopForeground(true) + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) stopSelf() } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt index 037ea6a..b96562e 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt @@ -11,6 +11,7 @@ import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import androidx.fragment.app.Fragment import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable @@ -123,11 +124,7 @@ class SyncService: ConnectionService() { return true } - fun isCurrentlySyncing(repositoryId: Long): Boolean { - return currentTask?.task?.repositoryId == repositoryId - } - - fun deleteRepository(repositoryId: Long): Boolean { + fun deleteRepository(repositoryId: Long): Boolean { val repository = Database.RepositoryAdapter.get(repositoryId) return repository != null && run { setEnabled(repository, false) @@ -220,7 +217,8 @@ class SyncService: ConnectionService() { .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) .getColorFromAttr(android.R.attr.colorAccent).defaultColor) .addAction(0, getString(R.string.cancel), PendingIntent.getService(this, 0, - Intent(this, this::class.java).setAction(ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)) } + Intent(this, this::class.java).setAction(ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT or + Android.PendingIntent.FLAG_IMMUTABLE)) } private fun publishForegroundState(force: Boolean, state: State) { if (force || currentTask?.lastState != state) { @@ -267,7 +265,7 @@ class SyncService: ConnectionService() { setProgress(0, 0, true) } }::class - }.build()) + }.build(), Android.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) } } } @@ -294,7 +292,7 @@ class SyncService: ConnectionService() { val unstable = Preferences[Preferences.Key.UpdateUnstable] lateinit var disposable: Disposable disposable = RepositoryUpdater - .update(repository, unstable) { stage, progress, total -> + .update(this, repository, unstable) { stage, progress, total -> if (!disposable.isDisposed) { stateSubject.onNext(State.Syncing(repository.name, stage, progress, total)) } @@ -315,8 +313,16 @@ class SyncService: ConnectionService() { } else if (started != Started.NO) { if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) { val disposable = RxUtils - .querySingle { Database.ProductAdapter - .query(true, true, "", ProductItem.Section.All, ProductItem.Order.NAME, it) + .querySingle { it -> + Database.ProductAdapter + .query( + installed = true, + updates = true, + searchQuery = "", + section = ProductItem.Section.All, + order = ProductItem.Order.NAME, + signal = it + ) .use { it.asSequence().map(Database.ProductAdapter::transformItem).toList() } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -335,7 +341,7 @@ class SyncService: ConnectionService() { val needStop = started == Started.MANUAL started = Started.NO if (needStop) { - stopForeground(true) + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) stopSelf() } } @@ -355,7 +361,8 @@ class SyncService: ConnectionService() { .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) .getColorFromAttr(android.R.attr.colorAccent).defaultColor) .setContentIntent(PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java) - .setAction(MainActivity.ACTION_UPDATES), PendingIntent.FLAG_UPDATE_CURRENT)) + .setAction(MainActivity.ACTION_UPDATES), PendingIntent.FLAG_UPDATE_CURRENT or + Android.PendingIntent.FLAG_IMMUTABLE)) .setStyle(NotificationCompat.InboxStyle().applyHack { for (productItem in productItems.take(maxUpdates)) { val builder = SpannableStringBuilder(productItem.name) diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/PackageItemResolver.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/PackageItemResolver.kt index 5808d43..91eb5ca 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/PackageItemResolver.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/PackageItemResolver.kt @@ -40,7 +40,7 @@ object PackageItemResolver { @Suppress("DEPRECATION") resources.updateConfiguration(context.resources.configuration, null) resources - } catch (e: Exception) { + } catch (_: Exception) { null } resources?.let { localCache.resources[packageName] = it } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/RxUtils.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/RxUtils.kt index 91fd040..12afd37 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/RxUtils.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/RxUtils.kt @@ -21,63 +21,63 @@ object RxUtils { } } - private fun managedSingle(create: () -> T, cancel: (T) -> Unit, execute: (T) -> R): Single { + private fun managedSingle(create: () -> T, cancel: (T) -> Unit, execute: (T) -> R): Single { return Single.create { - val task = create() - val thread = Thread.currentThread() - val disposable = ManagedDisposable { - thread.interrupt() - cancel(task) - } - it.setDisposable(disposable) - if (!disposable.isDisposed) { - val result = try { - execute(task) - } catch (e: Throwable) { - Exceptions.throwIfFatal(e) - if (!disposable.isDisposed) { - try { - it.onError(e) - } catch (inner: Throwable) { - Exceptions.throwIfFatal(inner) - RxJavaPlugins.onError(CompositeException(e, inner)) + val task = create() + val thread = Thread.currentThread() + val disposable = ManagedDisposable { + thread.interrupt() + cancel(task) + } + it.setDisposable(disposable) + if (!disposable.isDisposed) { + val result = try { + execute(task) + } catch (e: Throwable) { + Exceptions.throwIfFatal(e) + if (!disposable.isDisposed) { + try { + it.onError(e) + } catch (inner: Throwable) { + Exceptions.throwIfFatal(inner) + RxJavaPlugins.onError(CompositeException(e, inner)) + } + } + null + } + if (result != null && !disposable.isDisposed) { + it.onSuccess(result) } - } - null } - if (result != null && !disposable.isDisposed) { - it.onSuccess(result) - } - } } } - fun managedSingle(execute: () -> R): Single { - return managedSingle({ Unit }, { }, { execute() }) - } + fun managedSingle(execute: () -> R): Single { + return managedSingle({ }, { }, { execute() }) + } fun callSingle(create: () -> Call): Single { return managedSingle(create, Call::cancel, Call::execute) } - fun querySingle(query: (CancellationSignal) -> T): Single { + fun querySingle(query: (CancellationSignal?) -> T): Single { return Single.create { - val cancellationSignal = CancellationSignal() - it.setCancellable { - try { - cancellationSignal.cancel() - } catch (e: OperationCanceledException) { - // Do nothing + val cancellationSignal = CancellationSignal() + it.setCancellable { + try { + cancellationSignal.cancel() + } catch (_: OperationCanceledException) { + // Do nothing + } + } + val result = try { + query(cancellationSignal) + } catch (_: OperationCanceledException) { + null + } + if (result != null) { + it.onSuccess(result) } - } - val result = try { - query(cancellationSignal) - } catch (e: OperationCanceledException) { - null - } - if (result != null) { - it.onSuccess(result) - } } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt index e6d72d6..ee9b91d 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt @@ -1,6 +1,7 @@ package nya.kitsunyan.foxydroid.utility import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.content.Context import android.content.pm.Signature import android.content.res.Configuration @@ -35,14 +36,14 @@ object Utils { return drawable } - fun calculateHash(signature: Signature): String? { + fun calculateHash(signature: Signature): String { return MessageDigest.getInstance("MD5").digest(signature.toCharsString().toByteArray()).hex() } fun calculateFingerprint(certificate: Certificate): String { val encoded = try { certificate.encoded - } catch (e: CertificateEncodingException) { + } catch (_: CertificateEncodingException) { null } return encoded?.let(::calculateFingerprint).orEmpty() @@ -66,6 +67,7 @@ object Utils { } } + @SuppressLint("SuspiciousIndentation") fun configureLocale(context: Context): Context { val supportedLanguages = BuildConfig.LANGUAGES.toSet() val configuration = context.resources.configuration @@ -78,15 +80,10 @@ object Utils { } val compatibleLocales = currentLocales .filter { it.language in supportedLanguages } - .let { if (it.isEmpty()) listOf(Locale.US) else it } + .let { it.ifEmpty { listOf(Locale.US) } } Locale.setDefault(compatibleLocales.first()) val newConfiguration = Configuration(configuration) - if (Android.sdk(24)) { newConfiguration.setLocales(LocaleList(*compatibleLocales.toTypedArray())) - } else { - @Suppress("DEPRECATION") - newConfiguration.locale = compatibleLocales.first() - } return context.createConfigurationContext(newConfiguration) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Android.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Android.kt index fdb7c21..d47a9a1 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Android.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Android.kt @@ -34,8 +34,7 @@ val PackageInfo.singleSignature: Signature? if (signingInfo?.hasMultipleSigners() == false) signingInfo.apkContentsSigners ?.let { if (it.size == 1) it[0] else null } else null } else { - @Suppress("DEPRECATION") - signatures?.let { if (it.size == 1) it[0] else null } + null } } @@ -55,11 +54,20 @@ object Android { return Build.VERSION.SDK_INT >= sdk } + object PendingIntent { + val FLAG_IMMUTABLE: Int + get() = if (sdk(23)) android.app.PendingIntent.FLAG_IMMUTABLE else 0 + + } + object PackageManager { - // GET_SIGNATURES should always present for getPackageArchiveInfo val signaturesFlag: Int - get() = (if (sdk(28)) android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES else 0) or - @Suppress("DEPRECATION") android.content.pm.PackageManager.GET_SIGNATURES + get() = (if (sdk(28)) android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES else 0) + } + + object ServiceInfo { + val FOREGROUND_SERVICE_TYPE_DATA_SYNC: Int + get() = if (sdk(29)) android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0 } object Device { @@ -68,7 +76,7 @@ object Android { return try { Class.forName("com.huawei.android.os.BuildEx") true - } catch (e: Exception) { + } catch (_: Exception) { false } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Json.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Json.kt index 1123d27..a3329ee 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Json.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Json.kt @@ -38,7 +38,7 @@ inline fun JsonParser.forEachKey(callback: JsonParser.(KeyToken) -> Unit) { while (true) { val token = nextToken() if (token == JsonToken.FIELD_NAME) { - passKey = currentName + passKey = currentName() passToken = nextToken() callback(keyToken) } else if (token == JsonToken.END_OBJECT) { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Resources.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Resources.kt index 142a5e3..6f7a990 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Resources.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/extension/Resources.kt @@ -31,8 +31,8 @@ fun Context.getDrawableCompat(resId: Int): Drawable { val drawable = if (!Android.sdk(24)) { val fileName = TypedValue().apply { resources.getValue(resId, this, true) }.string if (fileName.endsWith(".xml")) { - resources.getXml(resId).use { - val eventType = generateSequence { it.next() } + resources.getXml(resId).use { it -> + val eventType = generateSequence { it.next() } .find { it == XmlPullParser.START_TAG || it == XmlPullParser.END_DOCUMENT } if (eventType == XmlPullParser.START_TAG) { when (it.name) { @@ -77,8 +77,7 @@ fun Resources.sizeScaled(size: Int): Int { } fun TextView.setTextSizeScaled(size: Int) { - val realSize = (size * resources.displayMetrics.scaledDensity).roundToInt() - setTextSize(TypedValue.COMPLEX_UNIT_PX, realSize.toFloat()) + setTextSize(TypedValue.COMPLEX_UNIT_SP, size.toFloat()) } fun ViewGroup.inflate(layoutResId: Int): View { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/CursorRecyclerAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/CursorRecyclerAdapter.kt index 2ca5d4c..658325f 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/CursorRecyclerAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/CursorRecyclerAdapter.kt @@ -1,6 +1,8 @@ package nya.kitsunyan.foxydroid.widget +import android.annotation.SuppressLint import android.database.Cursor +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView abstract class CursorRecyclerAdapter, VH: RecyclerView.ViewHolder>: EnumRecyclerAdapter() { @@ -13,13 +15,58 @@ abstract class CursorRecyclerAdapter, VH: RecyclerView.ViewHolder>: var cursor: Cursor? = null set(value) { if (field != value) { - field?.close() + val oldCursor = field field = value rowIdIndex = value?.getColumnIndexOrThrow("_id") ?: 0 - notifyDataSetChanged() + onCursorChanged(oldCursor, value) } } + @SuppressLint("NotifyDataSetChanged") + protected open fun onCursorChanged(oldCursor: Cursor?, newCursor: Cursor?) { + val oldSize = oldCursor?.count ?: 0 + val newSize = newCursor?.count ?: 0 + + if (oldCursor == null || newCursor == null) { + if (oldSize > 0) notifyItemRangeRemoved(0, oldSize) + if (newSize > 0) notifyItemRangeInserted(0, newSize) + return + } + + // Further reduced threshold to 100 for DiffUtil to avoid any noticeable frame drops on the main thread. + // JSON parsing and DB access during diffing are slow. + if (oldSize > 100 || newSize > 100) { + notifyDataSetChanged() + return + } + + val oldIdIndex = oldCursor.getColumnIndexOrThrow("_id") + val newIdIndex = newCursor.getColumnIndexOrThrow("_id") + + try { + val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int = oldSize + override fun getNewListSize(): Int = newSize + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + if (!oldCursor.moveToPosition(oldItemPosition) || !newCursor.moveToPosition(newItemPosition)) return false + return oldCursor.getLong(oldIdIndex) == newCursor.getLong(newIdIndex) + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + if (!oldCursor.moveToPosition(oldItemPosition) || !newCursor.moveToPosition(newItemPosition)) return false + return areContentsTheSame(oldCursor, newCursor) + } + }) + diffResult.dispatchUpdatesTo(this) + } catch (_: Exception) { + // Fallback in case of cursor issues during diffing + notifyDataSetChanged() + } + } + + protected open fun areContentsTheSame(oldCursor: Cursor, newCursor: Cursor): Boolean = false + final override fun setHasStableIds(hasStableIds: Boolean) { throw UnsupportedOperationException() } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt index a84dd97..22725f9 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt @@ -1,6 +1,5 @@ package nya.kitsunyan.foxydroid.widget -import android.annotation.SuppressLint import android.graphics.Canvas import android.graphics.Rect import android.os.SystemClock @@ -11,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView import nya.kitsunyan.foxydroid.utility.extension.resources.* import kotlin.math.* -@SuppressLint("ClickableViewAccessibility") class RecyclerFastScroller(private val recyclerView: RecyclerView) { companion object { private const val TRANSITION_IN = 100L diff --git a/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/src/main/res/drawable-hdpi/ic_launcher_foreground.png deleted file mode 100644 index de740355516becb3061a1da4e3daecd4e28c7b9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3808 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 diff --git a/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/src/main/res/drawable-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 80eb5b234e5000118b58fceed677587d5b56d91d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2396 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 EzeE{<;Q#;t diff --git a/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/src/main/res/drawable-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index fd16125e134d01628eb023891e2ef1744b44c38d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5407 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 diff --git a/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 69b5c559c5df8208d8b80e54b29ffead5334bfa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12664 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 Wn)Exhx5s4+)K#CW6hD3y{C@z6^bte= diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 8d2856c..036d09b 100644 --- a/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,8 +1,5 @@ - - - - - - + + + + \ No newline at end of file diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/mipmap-hdpi/ic_launcher.png b/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b97e739075199437cab37ab6eac3adfda25f1bdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3503 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 diff --git a/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..6363b42af41c645546e38d171f24676bf234f882 GIT binary patch literal 2640 zcmV-W3a|B2Nk&FU3IG6CMM6+kP&iCH3IG5vN5ByfO(qOSk3fHjtU(|^k^j5HgoseswhdAm zUc$v}_E<-~Pzc;s+m3h+GjlsJL&%Nkwbj_t z|2N11{R{K=oU3wqL2um)mFd8?ZQ8c_TpO3?o~LiyW^CKa{6pLJ|0y%uw(V!z?;UIJ zH3ucz+K%nfIrp3IU9oK&E!$2Ct?IK|v#@Iv_6%&>w&Neq6%zo6r|xkGsEhytD0*=i zkTer8y(jLdYz|Ridir$K)gySuWdnxP{eRjw-7#u$GK>Q{J_mrc&n!IwrgZ+qD0PsA z$pK6R8PLpu0eP+hR9th+fiz4WS85}2EuJUI8M{0$RG0%rtt1VFg?x~2C37Gue@3vz zfgukN#FXVz?J;5z4b1_J8T6>ePgBTGYQ`-x=UhkIP3fCzd0GRoDz+H_`eB#==%Q9c z0-y-rnu1{Q{bb#Q@dT{Io4a%CX~tUxtd3_<+5ud+j=V|$>_nYM*$6DMI&azaUs2={ zdBgGb`p&R4NVzN2Wd*Tbf$>mkb{23TO8}s`&?{B_|KQ=_OhKh{;3b zJ#z8?5~Y%k7Trw^N%vi`1`A&&Ztm6!0H9BL!skIB3J?I;)9G}wC{Zd|Jh@UWnSn+q zvF0im0Dy5lv7eJ4ox@IBP&f<4TK9V(INVv2B?wbrV@fX1rph!+BYV29Yk{jmr9 z-#Owm$N=yyYT?6PvP0-ni7qUb(s;jCtJ4Usb79}Hq=P?~vGwm0Dy9RzYMvtLrGqa^ zYi$r8;9G5FR$L1KFyn0PwU=I#j2Q_ zwy^owd6JkHRab4uSv^$SXEFl-=%Ssx<>#sh{BJ(by00gwt#8X3e+(u8=!6$GAN%H= zpAGIi)Go-37zo^E>{rk4JUsTzOQ@vV{`ftzlJ=R9!N%N@rHJ%^Ny;;?r%e{_Vrd zr&Xp7Vyj*Au{v^*-O~w*t9|s@+1lGLMK%uzD@EqeK*du6_ z+dxWe?A&GCVdGP;So3#ZwMeeiptGyHZm3$*U0b=|D^_Wos=IpaB5A=&-(NnZ^U*mo z-YM?fr#_jr>ZOa6EazP{Zu!?-jQJ$nQ7H?=3LG? z0RT)%k=zWNHL3z{qZGnIS;&~4iTASh+vlNwTO406jLcNVc1#)Dx93o0&ro0nERp(W zL&w`kI1ZB4`s?F3^i1!(TPOonS^HEc|5aIng=CZtd#7;3%aSy9$pky81yg%pU!{s- zfT4Pq{)fxrj!`;V0ssT|-o47j(>i|Nq@V`(7Jt9izNEWK*Kqrh30LlPnN17a$^U(G z-|e|GpNIeZtKaUubI)oYEm6W0cN9tdptkjewI=~={oe;W|NeSm)ALR$nF6jP0N^Z;P}LBcMbti9-ROs9IPQmWmMS2 zNIEV%Hg^gr4NrxFMNm?R7&EFRlqtq&6l`)3qbkD&F9C*6s(@|GQ0hJ?0ci{k1!Gxe zm;`jfyp^H^s|5XhM@JRXD8KP<=rCs+mt=qnfrEQ2)mERd|X zpi|hefK4-+;)no)A^_N$4scKzY%nMoPOu5mCdCnjkZFo0Ch(hBV?!CrV@9Nfct>)B z!N13~J_V6xYxqkiVU;2b09ge!QYp*TYo8zc$NgyNgmn48m&vXb8xu3gp-6h(&X#|! zDee30x^>8q5HCfCWd2&2nZ9%y*-220RiD)P(V^UtFRi=eD@ef0&}owY&1n5$E}INy z2+<06Ca?9#3pxI;l_*066mX*59~0j&VJv@Wp#ETuhi@Dl!J)G(wl@{DggvO#f{DsQ z2B8t;iiGM<=;+NW_v1!XK%ufUKn9U)q=*C$2EFw@?|sc{-Xe2z;WRr0Mv6fmz=9Mw z!b3K~Fl33*1;qu>4rSMcW|Kn~hAhkwkRX6&(yR)NAwXgb4GjR0Hb$ZzVXH6CjEM)e_7M+o zzYFeHV_Xz-esppnQ(kzk6XkVb$OSC;&9Ba;v5gBZUM>uHfgm8*)H}K$6(E4p={Z|+ z8`KlYx40MpK}4z7<=ExdyK0B4Tnz&7?(wK}Q*9tv0&W~9e!-eL4NwM;KIlq?92&X> zoWR?cZMN~sS7f7)n-Yphilxv}tXK*y#gY_Dp`}={6k3Xv925u(8lRQSOW*y6;5vzh ynu}071{YRf0a`$G`W5(rTN95QNQZ1kQ-R5iIt+7M0kQ(_<_Z2h$WJ=|il?ZcQ}>AePk{bg zcuNcTAq{iQavFW3l9AmFKJ?&;$THpFja z_g_+(ip)%`Kj)Q&9U%mO%xtIMm9H~z&LRE)4h&ajS*!G^G4v)19XLM(bx;N=dbb*X zf-)%eOOp_|6e%wF>v9u zBE-`rFh@d7-j^0FvU`i#<6@{7kIn!{5danl{c(jYY}*b=lAiC+%yQSZdu-dbJ!{*x zZF`Sw+qP{RU0uV%Oifi*BED;~BL2wTD{>BJ-iV|F+qP-j>SOM6-obsgjc+3|B0Kbt z%*aN@(6(*ce7242d-q;z_|dlQ*tXl&*6n=w+5TGU)*_XF+1CnThOHD#+tLcqGTW+v zt?>2R^!mB4>yTsHwrN}a;;8`swju{|*`d2U z#utEyZUM(_{=3y}xA%G37E0F;N!yLhOWC4hh-AE)JAT0~;~0)T88Vs(Pwn2)+W`<2 zJ?1SW`S>|_I&iM=Bl!v2O3)rel`;vB1}Uurzh^(R1Q}ECUT&$AOoM+Vp0eTXmWVKT zVB`d(@$Bmqr-Q+}!EzVNX>xYlsZO@SmF$$YgOQDv$PTgxWGj52xaYmaeW?I=V9YKy zxM&W34-zbL8RtvDFlCIvx$Gjgvx^nm4$8Vg+;9C;n%<=&A)Q`l~Pv7kZZs;1+Phz>2{U*sg*<%4)zRAMB!Uus$o?A=7|N++eWZhNj^1py=}O zQ}9|5r!pMM{%j|1vdXP+nv;zL9yW@-9{4gCj_1GMz->hsxxF_D09gslLd-0qYXW4I zclI!ojR3bjrylgSo*+F4D#IBd<`|D86SDwIPRtTnfQ^huhrQr9zzA@_q%Soq1``K? z;{r&;7~I~QZUTsyF>YcUX5cXIB1?HEV@@Q(5{^#bVDWY79{8-715CqoDq^-9HyA|h zMmI5s!2wDn0YP*B!ZDLDQqC9FP|22_U8b*%UU`(+Ya~cFiUH;al1qnpW7)*3C zW8(A`=S>+P1}3-+1e=R2;3oK?sEs?uVgHU605RpY9vH!hY_5eCj3`kpmWQn1dWe95 zar%L#0k~aU&Vv`wbH!cq?q(I@b~s%J4~#pJO#-uK4<%mlFu5L>!uAAAju(f+|68#(He*fCH0bf&f4Bw5A`}J zZnD)*_pUyQ`e^E7sE&CfpzgR(fNjCjT1g*<;)z~e5vA}{3`>u<_4Kj$nI6ZoHlG^}@*6TN)k!oGbY z#~&7q$6R_vWJw=7clkAW=4PM$DpS@jq`#zg4ncnl?xNGjjZK-{l!cUg0Ds zy7LErkppDT%{aYZNT4#dB=7Lf&tBFockx){NzH@XKl^<-esbQU-jS|qZrkKsl9W98 zsvj~&9H=xm{d935fsy8|4oF9NWcQzL+vV3=<`({kL-dl-37qfE3vT$Y@jl`{{lJU< z{ONyP^VMxRCzcoThF31zbLmZSj$zl$JgHsKuVtRrGEb0(NMy{MoJSq=2YdefqIyU6 zKY!FZ0ytzp09Q9N`}YYlKl<)ZJ~r#!oHz?9PCP+Eq~R$V-m7|@)1Q1J)3VE1V`M@i zXUn9Q>)(j?UifEid2RbxC*di*-dZarPb~wNfMSlRE@Q}Z$P*-#meh0g@j2P`{mb=LQt>3=xq04wzqqK8z+W)l%2j>3`*`O5kFxdq?D8o`-99ckdUL>2 zJ1Yo)f-q-jKrk^qlXGZ{i`A3#v${1aOKEKhiaCfOP?bvy?)%B>|MJJ%FT2io&Svip zhLgm!Kmm*hQK~VXu{qSoV~xd2I=j+ZmG%a35P(I5fV9O=2lMMbb_~#V)>q76_Posl z55Zrcz@vQI|6iW-suyp2`d6Rxpz5&Ri;!zF;PKiQ2aSn+_!V{f2M19S;PvG z;divBU4zJ8Yx&5?u4jev98jTX<8-DbMAD5j?^~X50K**sqlwmu>5daU%S&1OIcxW# zu{!dGr2YVakU7K31d3`pHkux1icnN(5XNN3Bi0Rj#6QpduN%%d@CLCOq4PQ0?*FRw zAAAB}24eLEkUjt(s%Tg7w0K2Td7@V3p5%T7fUJlG27p?l?)cYbKen>>bXS zE2CRt3;^PdlQ0l-=@=HzJBUWf@awL+<3091>tKA{TR_a10Cc_b0g+VkYLO&$$a@pl zk}V2g5Cni?=D!#I%F6s}q?na`^N$;k9C$T#01%^{41~pf@5`&<^#`s#5|^D{<7 zRRLNA7uhBW%RB*u5(I!qigD9|%kmno9KgTGK8(5pXdqA<(tpw`zK}m4>jB^V+Gx4C zd0x-hTrfg_?Jvl^{(iwq>wsHMdO8l8Uf8N&hyK(XYPsWEC0NL#t8eH;gO z2aypAf*BI&V<8q9!$iIiONay!$Vhq+268bml!o~b2Ic`6SCIj@_s+@2-27*U0i+YnO=0AXr+5C$-S z0+Ku_S;j90x#TlskjK07}XUgqyxSx?E7=VL|S`60^!U#%bkk471 zNO(5{^y{hba@B}eq>o@Ww{LJp5{rW=2164-;kk#T&>LxU?(^;ICzZGHOkSoFWB{|_nTPzB2xfJKXC&{dwjvaj~H{u{|(IxLiBhHS8hg`>7SOJ zpNlC&BL>utL*b_rNm~D14!CePmoFG2fCW`qnHXBcw3srn<^_n#0F8bMx+BRd_WGWy z$&v{Wf-*3ID6Kf6pja%sJf5L341@p*kGzp2jgd_pTiA^;N(0kClrqPRMS#*2sSlWR zLsza(<0d6Q5M^LkB(MvNVQF9tm4k3N^T~gtII@1^xS=zMUQo)aGDD=QWClhbPsp9j zbD7UsfdCc>3@c)afe8(TGBg5=4p8hF{VLIDFg={5Zp>Ic@m1QS)3#9A{E-OY|I!)vVZ^q$NvDzYc_f1(cT>p-kEW%obTn$|7!&E~6kiSsW;^L^w-97!+AYM|8_K5)ZT#1Wu84ee}db zxq`qR7ujON_H_f|uA^=sQiT7;f!RCC%|wxb#J%*9ejngVkCRGn->6ZC4yeOHL>*MG e4%C6Z1@9m4_13jW!T)<1+|(`LxXpjJ7EA#AFGYa> literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..f20d2d2be2fb7bb2d22e232913275cb84ab55ed3 GIT binary patch literal 4754 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 literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-mdpi/ic_launcher.png b/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a9e37dff190f139a666e06e771f16abfbf5b81c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmV;q2uAmbP)@~8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H12sTMXK~!ko?OJPW9Mu)J{02zISGJ*ZY{6eR|iA zwRfGkuApj#iXb#8e_E=BrlD>F1%(h%kcm*BK~TU>2ruymf!fj%TB(q>i9;IEI8;T= zqZRp4O41f+>X6fOW@k71nwjfY=#P$cr1j3sz2A54cOLhyYij;~6|P#eC1`b#NaRXE zkelQE!wxd~oJytY>7M#b?f(+!x_IwkuqU&Gnp|>^=E6B&@9yqiF)#2KsEu-b@HcK>Y{9|XI1#LT1Uh?e!q1f?Xil8_ zfEpKblZ_Nl|H3_wAk_r#4ndb&g7zCHpl$urV08tRl_-Cf=El_gv$@ErF48l!YcV`; zCGeaKruF-vb<0sOZTc;AIfKe7RC|XoAeIN!0?2eNiS-U#i#oix7~nh6^Lg-j>j(_v zs)*nylTlGjTnr}%N0>-Z;9%uExme)a)<07Mo~*s^8_IpV1_82ex%V_nP%nul()YO* zQ1_(m9C(z?&6VKE+P*jr7L7Q_8>R19VraYWD){~WYdpckxkUlL{@JSFl{mQNC5!_< z*f~jeJZwQeaJgJfn%)n`2e+2Iq~z+-e61|A&Qe$R{BxS%75mN+=;XpmYSkN?Rv{A0 zjA^aHGM|{5E(hV1vS3BeSX==m-4Q67D3`mdkWvXN;4#meHoT}0UNv*%ONMbk<`a{D zAgq;nKhY?F}I}E0;z5p%Xo`TlRZz&Q{OovR{R~}a!q8G?{6XOHlEtko&S_}hau`~^^(M4z`W=!vbcu{Yq>Rk1zf#ECIAa3AaNjG0 zxtPEeLLh!ZD}QstUuPmvY(2ZrLEpn?VBO@$5Z`qMI>wF^k(sJWrqkP-TXh2&A=Y;Z za{;~6>HMtI8@*8RuqvZ2tdg?<-~afBVBRtX&by{z_DC~4&o>kZ@WHrQFC9__3U7Lm34Hp=^I#b}ta|q-jP5-TnLU?C zkkQ|nr#=FC{4WUp5}EeV?aEws1{paVzP7?P(I>`uqc*@1z1En(Q#0Va^X;5RRc)tt z{|(meEhSKF**!A_GNujBV;pod$;dA!xcv6B))S0)G#^&eDOJkU?)P;G2-}V$_`@Z^ z>AB~RB{I^p%*217|E~RD%hwj&dJy9a1e{)}%do0qj`m%@Llb=HhbMD-u96~0_o0_3 z*+>kIPczj6t5sN2H~_F%ERA*{d_jK`B4PncRRte;=nS+{n$*bgnaBPP!+RK308L1K z**K7SdB$e5mF(2&o!;;X-BnO)LeITNO28)``2b9tr*w-{_l`foItCe~?>n3;W(Gm* z6l6-HCA+lcyf6AHOHa=Y-BJMG``AA*T^=%Q7uxXt=)McOGNOBYbd)KLBG#W0=8{Iv zb!4-l*OqJ;;{*CGKtwOpqvb9hC12c{`f&P0BFnM1g;E!%CZ|LecZy>?a zBGbkrbn7!4>sQ4pb190BvD zUo&@_O1F9YQAI{oz5j##%-%q+)}r+bavtront5<`yEjQ&?K@EiM#>pe*8Yzgs5@bOgY~xy;jrEu)2H;$#N}Qv0_CB_8n99xsoU+ zf^xB7CAWogXQ6uI(mkgj9bCC`rMc=c3gflBv9YnsB}l(yIV~_IqWz;rd9A@Y_tV_v zJ+{0^(>pXnKTw!_Nz(Yt7k>R5F(pTf07UG6JYAlIO^V*2fDZf^FhS<{su zqtEmm-BX`wRNq-To8oj?{!>K-wM_MDy=u$V@0Kp~f2{l${efrSpSk2a00000NkvXX Hu0mjf7W6^M diff --git a/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..38cbb922af7f751e32b3ede818cd562dd1a59a7b GIT binary patch literal 1784 zcmVuMGv)K352%NRkxgGN@Sp+K%tu7i=O)wld*+K!k*^e<4ID zXa#8Ke*i=PGNSL3*&AeEpZ&S)C7K3_`*=3e^~+jE{r`SFE&vf=p>*4}u{}EHK6vrP zPRh1z+h!}(v(0X${)27Xwrzg4UOvw`jTd#FjH>>QKE!t3PqMXbh9tcak*cw6+gf>+ zwr;?_0UKLuH7nb;m$s+PNyjg@wr$mQ);dp;nVG`x+#y3J)Cb#bU<1yXD}a!h8TEDV zhFjaVYGq#ghPy^mrbFsfq!FdKJKQ?~1Ofk(3TatnOUhG-1dZSikx%JcQw~8zHCv_( zeMqrI#3b~9pq5k1rUAj8;I&{u)bj-PmX(-K5D7j)h>CLrlZd=ua4tnF6L|YMkDwM1 z1Z3#zN^YU>!;lq3#6aPvCq?eQrET^tD^|yBLa=}!6GVn+Ye{id#0nTnJ4~Pm&#Gc_p@;kT z;*;4$HO8ZxDYNhnm_!F$$;{q#sI|JG@{pPox1P|GYr&!@#OU?qdUS_?eb6|O3Q!FI zR8!_-oWj1Y?yrGb{nG<2`xV8{W8$d|$Opnuw7)H40YTLw0M$|gDMkVZco;^fxhThv z=M?y0fwhM2xa+X;fq=a&##qfKkdbJx0iY3OY$OnYu?qXR$Ye!xh3MPMMT~5v>f?bG zXlW@R5#pe0BV;8AXFt%bO*8%cz9tUUYS`4 z0J}aOjQb@)KzE@uH|Nl5##Y zktUF&>~VjP$e?K9!p?>eKa3S+l3bdqyH4jJ1#@GW_8k6~*G^trxUFQG;f+zMZdR=1 zT@6Sg38fJ*eS8WxApjorrAG&_;3V{qNGfcIdKe1`JlmY?0iL$>a4*9PI3;Zg1sjQ6tqpct7 ze;OHGyfFwp}0_H1(B+sV$0*@%jqLCDAYFR6u3V4IX?tAFgfk$f|Q*4F|_{F2@RjRu+;-c zfBA24ELbLmWSxd5zEyqTF47%=#qp18jX%0uqWL1fcON--%S0BLD*ZC7OgQaYFn(g#q*=~wPc%L2W+|uFvM4+xE{%KPrL>QvBQy3*` zFt2OCJJ0vnug2d7TggrJy|mtrf(VJ|V3Xgf<}4gr=dF-IXpxIM6dJb(_Gh6j!doUbJ4Y4B%v6;ZFC1Y4fdc@P2mc?$ik3S3`xSwnnD01K z{%@?cf2QA>svD*_)r-VR7GZ)wp#rWQth?u+hK4_g4aX^t*0`fn zjt!QuMiP>A1V+Sz6bKX?fl*)qTNP0YN#WOwjf+<3ECO8Qij3`God`)gAg+)T2x7rh z6jgy_0b?+@1v1PtHW;vknt`pbk$yjRTzXss08;^0NVPobyb(K)V5$e+pLB^@0l*AW z!XJN*S*Pt;PLb?{b)9Q$FSs;V$+3Z_98!K_m!8qF0E;2ok~Hv;1G)nuS*mj4S*QR0R<+ z5csL0#tvlR+10FAneqnY0`xKTHfMauWQxJuo`~imiIe*=*=OkQ4;4ifB171P$!a@v z+u_k8Nue=>NS0PQ;V}<+Cnjr^HN^me&c{qYZsGgs+SDKi5Umd;@$>Xkrf+jl?kVt3 aa6^y~_3}k1o1mTxbufg;md!8kzZ?KOPE+Ur literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..565ed83a9e73b3b9c7506e1b6ae4f6bdebe4f433 GIT binary patch literal 2634 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 WV#0?&d;4dHp_z``N3L2O(oUhXF}08Y literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 854055f7c86b0076260252f8e36fdb0f8a7f288d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4884 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 literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..c9f237a6529fca722b414c4d67831e46fd9299e2 GIT binary patch literal 6348 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 O$0SD!NL%aMGynkfnCR00 literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b10087a126e1bf637461f768ea47e3be69a3ea4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7718 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 diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..eccc1fe96894f391d925869098c57d63b6daa260 GIT binary patch literal 5138 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 literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a446d8c7c72d39a61ff7c44f7c5c296cbbe6caaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10904 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 diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..7ee7c94ad2f708686a603bb21a9ab0afc8f8329f GIT binary patch literal 7256 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 literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..4178df1d68ae300a93a3f731c9c5a002ee5de44c GIT binary patch literal 11640 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 C%QAHU literal 0 HcmV?d00001 diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..d3a913439687d615c3d4f6a49cec57142f068f27 GIT binary patch literal 13226 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 literal 0 HcmV?d00001 diff --git a/src/main/res/values/ic_launcher_background.xml b/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..e0bee58 --- /dev/null +++ b/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #00E4FF + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6dcf864..abd3e8a 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -163,5 +163,6 @@ Versions Waiting to start download Website + The repository address was redirected to %s. Do you want to use it instead?