diff --git a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt b/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt deleted file mode 100644 index 51cd266c..00000000 --- a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.looker.droidify.database - -import android.database.Cursor -import android.os.Bundle -import androidx.fragment.app.Fragment -import androidx.loader.app.LoaderManager -import androidx.loader.content.Loader -import com.looker.droidify.entity.Order -import com.looker.droidify.entity.Section - -class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { - sealed class Request { - internal abstract val id: Int - - data class ProductsAvailable( - val searchQuery: String, val section: Section, - val order: Order, - ) : Request() { - override val id: Int - get() = 1 - } - - data class ProductsInstalled( - val searchQuery: String, val section: Section, - val order: Order, - ) : Request() { - override val id: Int - get() = 2 - } - - data class ProductsUpdates( - val searchQuery: String, val section: Section, - val order: Order, - ) : Request() { - override val id: Int - get() = 3 - } - - object Repositories : Request() { - override val id: Int - get() = 4 - } - } - - interface Callback { - fun onCursorData(request: Request, cursor: Cursor?) - } - - data class ActiveRequest( - val request: Request, - val callback: Callback?, - val cursor: Cursor?, - ) - - init { - retainInstance = true - } - - private val activeRequests = mutableMapOf() - - fun attach(callback: Callback, request: Request) { - val oldActiveRequest = activeRequests[request.id] - if (oldActiveRequest?.callback != null && - oldActiveRequest.callback != callback && oldActiveRequest.cursor != null - ) { - oldActiveRequest.callback.onCursorData(oldActiveRequest.request, null) - } - val cursor = if (oldActiveRequest?.request == request && oldActiveRequest.cursor != null) { - callback.onCursorData(request, oldActiveRequest.cursor) - oldActiveRequest.cursor - } else { - null - } - activeRequests[request.id] = ActiveRequest(request, callback, cursor) - if (cursor == null) { - LoaderManager.getInstance(this).restartLoader(request.id, null, this) - } - } - - fun detach(callback: Callback) { - for (id in activeRequests.keys) { - val activeRequest = activeRequests[id]!! - if (activeRequest.callback == callback) { - activeRequests[id] = activeRequest.copy(callback = null) - } - } - } - - override fun onCreateLoader(id: Int, args: Bundle?): Loader { - val request = activeRequests[id]!!.request - val db = DatabaseX.getInstance(requireContext()) - return QueryLoader(requireContext()) { - when (request) { - is Request.ProductsAvailable -> db.productDao - .query( - installed = false, - updates = false, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is Request.ProductsInstalled -> db.productDao - .query( - installed = true, - updates = false, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is Request.ProductsUpdates -> db.productDao - .query( - installed = true, - updates = true, - searchQuery = request.searchQuery, - section = request.section, - order = request.order, - signal = it - ) - is Request.Repositories -> db.repositoryDao.allCursor - } - } - } - - override fun onLoadFinished(loader: Loader, data: Cursor?) { - val activeRequest = activeRequests[loader.id] - if (activeRequest != null) { - activeRequests[loader.id] = activeRequest.copy(cursor = data) - activeRequest.callback?.onCursorData(activeRequest.request, data) - } - } - - override fun onLoaderReset(loader: Loader) = onLoadFinished(loader, null) -} diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index b6e54898..d89dfe73 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -1,7 +1,5 @@ package com.looker.droidify.database -import android.database.Cursor -import android.os.CancellationSignal import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.room.* @@ -13,7 +11,6 @@ import com.looker.droidify.entity.Order import com.looker.droidify.entity.Section import io.reactivex.rxjava3.core.Flowable - interface BaseDao { @Insert fun insert(vararg product: T) @@ -49,9 +46,6 @@ interface RepositoryDao : BaseDao { @Query("SELECT * FROM repository WHERE _id = :id") fun getLive(id: Long): LiveData - @get:Query("SELECT * FROM repository ORDER BY _id ASC") - val allCursor: Cursor - @get:Query("SELECT * FROM repository ORDER BY _id ASC") val all: List @@ -86,95 +80,6 @@ interface ProductDao : BaseDao { @Query("DELETE FROM product WHERE repository_id = :id") fun deleteById(id: Long): Int - @RawQuery - fun query( - query: SupportSQLiteQuery - ): Cursor - - // TODO Remove - @Transaction - fun query( - installed: Boolean, updates: Boolean, searchQuery: String, - section: Section, order: Order, signal: CancellationSignal? - ): Cursor { - val builder = QueryBuilder() - - val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND - product.${ROW_SIGNATURES} LIKE ('%.' || installed.${ROW_SIGNATURE} || '.%') AND - product.${ROW_SIGNATURES} != ''""" - - builder += """SELECT product.rowid AS _id, product.${ROW_REPOSITORY_ID}, - product.${ROW_PACKAGE_NAME}, product.${ROW_NAME}, - product.${ROW_SUMMARY}, installed.${ROW_VERSION}, - (COALESCE(lock.${ROW_VERSION_CODE}, -1) NOT IN (0, product.${ROW_VERSION_CODE}) AND - product.${ROW_COMPATIBLE} != 0 AND product.${ROW_VERSION_CODE} > - COALESCE(installed.${ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) - AS ${ROW_CAN_UPDATE}, product.${ROW_COMPATIBLE},""" - - if (searchQuery.isNotEmpty()) { - builder += """(((product.${ROW_NAME} LIKE ? OR - product.${ROW_SUMMARY} LIKE ?) * 7) | - ((product.${ROW_PACKAGE_NAME} LIKE ?) * 3) | - (product.${ROW_DESCRIPTION} LIKE ?)) AS ${ROW_MATCH_RANK},""" - builder %= List(4) { "%$searchQuery%" } - } else { - builder += "0 AS ${ROW_MATCH_RANK}," - } - - builder += """MAX((product.${ROW_COMPATIBLE} AND - (installed.${ROW_SIGNATURE} IS NULL OR $signatureMatches)) || - PRINTF('%016X', product.${ROW_VERSION_CODE})) FROM $ROW_PRODUCT_NAME AS product""" - builder += """JOIN $ROW_REPOSITORY_NAME AS repository - ON product.${ROW_REPOSITORY_ID} = repository.${ROW_ID}""" - builder += """LEFT JOIN $ROW_LOCK_NAME AS lock - ON product.${ROW_PACKAGE_NAME} = lock.${ROW_PACKAGE_NAME}""" - - if (!installed && !updates) { - builder += "LEFT" - } - builder += """JOIN $ROW_INSTALLED_NAME AS installed - ON product.${ROW_PACKAGE_NAME} = installed.${ROW_PACKAGE_NAME}""" - - if (section is Section.Category) { - builder += """JOIN $ROW_CATEGORY_NAME AS category - ON product.${ROW_PACKAGE_NAME} = category.${ROW_PACKAGE_NAME}""" - } - - builder += """WHERE repository.${ROW_ENABLED} != 0""" - - if (section is Section.Category) { - builder += "AND category.${ROW_NAME} = ?" - builder %= section.name - } else if (section is Section.Repository) { - builder += "AND product.${ROW_REPOSITORY_ID} = ?" - builder %= section.id.toString() - } - - if (searchQuery.isNotEmpty()) { - builder += """AND $ROW_MATCH_RANK > 0""" - } - - builder += "GROUP BY product.${ROW_PACKAGE_NAME} HAVING 1" - - if (updates) { - builder += "AND $ROW_CAN_UPDATE" - } - builder += "ORDER BY" - - if (searchQuery.isNotEmpty()) { - builder += """$ROW_MATCH_RANK DESC,""" - } - - when (order) { - Order.NAME -> Unit - Order.DATE_ADDED -> builder += "product.${ROW_ADDED} DESC," - Order.LAST_UPDATE -> builder += "product.${ROW_UPDATED} DESC," - }::class - builder += "product.${ROW_NAME} COLLATE LOCALIZED ASC" - - return query(SimpleSQLiteQuery(builder.build())) - } - @RawQuery fun queryObject(query: SupportSQLiteQuery): List diff --git a/src/main/kotlin/com/looker/droidify/entity/ProductItem.kt b/src/main/kotlin/com/looker/droidify/entity/ProductItem.kt index c7aa294c..b0311e48 100644 --- a/src/main/kotlin/com/looker/droidify/entity/ProductItem.kt +++ b/src/main/kotlin/com/looker/droidify/entity/ProductItem.kt @@ -1,9 +1,5 @@ package com.looker.droidify.entity -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.looker.droidify.utility.extension.json.forEachKey - data class ProductItem( var repositoryId: Long, var packageName: String, @@ -16,55 +12,4 @@ data class ProductItem( var compatible: Boolean, var canUpdate: Boolean, var matchRank: Int, -) { - fun serialize(generator: JsonGenerator) { - generator.writeNumberField("serialVersion", 1) - generator.writeNumberField("repositoryId", repositoryId) - generator.writeStringField("packageName", packageName) - generator.writeStringField("name", name) - generator.writeStringField("summary", summary) - generator.writeStringField("icon", icon) - generator.writeStringField("metadataIcon", metadataIcon) - generator.writeStringField("version", version) - generator.writeStringField("installedVersion", installedVersion) - generator.writeBooleanField("compatible", compatible) - generator.writeBooleanField("canUpdate", canUpdate) - generator.writeNumberField("matchRank", matchRank) - } - - companion object { - fun deserialize(parser: JsonParser): ProductItem { - var repositoryId = 0L - var packageName = "" - var name = "" - var summary = "" - var icon = "" - var metadataIcon = "" - var version = "" - var installedVersion = "" - var compatible = false - var canUpdate = false - var matchRank = 0 - parser.forEachKey { - when { - it.number("repositoryId") -> repositoryId = valueAsLong - it.string("packageName") -> packageName = valueAsString - it.string("name") -> name = valueAsString - it.string("summary") -> summary = valueAsString - it.string("icon") -> icon = valueAsString - it.string("metadataIcon") -> metadataIcon = valueAsString - it.string("version") -> version = valueAsString - it.string("installedVersion") -> installedVersion = valueAsString - it.boolean("compatible") -> compatible = valueAsBoolean - it.boolean("canUpdate") -> canUpdate = valueAsBoolean - it.number("matchRank") -> matchRank = valueAsInt - else -> skipChildren() - } - } - return ProductItem( - repositoryId, packageName, name, summary, icon, metadataIcon, - version, installedVersion, compatible, canUpdate, matchRank - ) - } - } -} +) diff --git a/src/main/kotlin/com/looker/droidify/screen/BaseFragment.kt b/src/main/kotlin/com/looker/droidify/screen/BaseFragment.kt deleted file mode 100644 index e0ca5456..00000000 --- a/src/main/kotlin/com/looker/droidify/screen/BaseFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.looker.droidify.screen - -import androidx.fragment.app.Fragment - -open class BaseFragment : Fragment() { - val screenActivity: ScreenActivity - get() = requireActivity() as ScreenActivity - - open fun onBackPressed(): Boolean = false -} diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt deleted file mode 100644 index c9dc2e66..00000000 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt +++ /dev/null @@ -1,264 +0,0 @@ -package com.looker.droidify.screen - -import android.content.Intent -import android.content.pm.PackageInstaller -import android.os.Bundle -import android.os.Parcel -import android.view.ViewGroup -import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import com.google.android.material.circularreveal.CircularRevealFrameLayout -import com.looker.droidify.MainApplication -import com.looker.droidify.R -import com.looker.droidify.content.Preferences -import com.looker.droidify.database.CursorOwner -import com.looker.droidify.installer.InstallerService -import com.looker.droidify.ui.fragments.AppDetailFragment -import com.looker.droidify.utility.KParcelable -import com.looker.droidify.utility.extension.resources.getDrawableFromAttr -import com.looker.droidify.utility.extension.text.nullIfEmpty -import kotlinx.coroutines.launch - -abstract class ScreenActivity : AppCompatActivity() { - companion object { - private const val STATE_FRAGMENT_STACK = "fragmentStack" - } - - val db - get() = (application as MainApplication).db - - sealed class SpecialIntent { - object Updates : SpecialIntent() - class Install(val packageName: String?, val status: Int?, val promptIntent: Intent?) : - SpecialIntent() - } - - private class FragmentStackItem( - val className: String, val arguments: Bundle?, - val savedState: Fragment.SavedState?, - ) : KParcelable { - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(className) - dest.writeByte(if (arguments != null) 1 else 0) - arguments?.writeToParcel(dest, flags) - dest.writeByte(if (savedState != null) 1 else 0) - savedState?.writeToParcel(dest, flags) - } - - companion object { - @Suppress("unused") - @JvmField - val CREATOR = KParcelable.creator { - val className = it.readString()!! - val arguments = - if (it.readByte().toInt() == 0) null else Bundle.CREATOR.createFromParcel(it) - arguments?.classLoader = ScreenActivity::class.java.classLoader - val savedState = if (it.readByte() - .toInt() == 0 - ) null else Fragment.SavedState.CREATOR.createFromParcel(it) - FragmentStackItem(className, arguments, savedState) - } - } - } - - lateinit var cursorOwner: CursorOwner - private set - - private val fragmentStack = mutableListOf() - - private val currentFragment: Fragment? - get() { - supportFragmentManager.executePendingTransactions() - return supportFragmentManager.findFragmentById(R.id.main_content) - } - - override fun onCreate(savedInstanceState: Bundle?) { - setTheme(Preferences[Preferences.Key.Theme].getResId(resources.configuration)) - super.onCreate(savedInstanceState) - - addContentView( - CircularRevealFrameLayout(this).apply { id = R.id.main_content }, - ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) - - if (savedInstanceState == null) { - cursorOwner = CursorOwner() - supportFragmentManager.beginTransaction() - .add(cursorOwner, CursorOwner::class.java.name) - .commit() - } else { - cursorOwner = supportFragmentManager - .findFragmentByTag(CursorOwner::class.java.name) as CursorOwner - } - - savedInstanceState?.getParcelableArrayList(STATE_FRAGMENT_STACK) - ?.let { fragmentStack += it } - if (savedInstanceState == null) { - replaceFragment(TabsFragment(), null) - if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) { - handleIntent(intent) - } - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - 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() - } - supportFragmentManager - .beginTransaction() - .apply { - if (open != null) { - setCustomAnimations( - if (open) R.animator.slide_in else 0, - if (open) R.animator.slide_in_keep else R.animator.slide_out - ) - } - } - .replace(R.id.main_content, fragment) - .commit() - } - - private fun pushFragment(fragment: Fragment) { - currentFragment?.let { - fragmentStack.add( - FragmentStackItem( - it::class.java.name, it.arguments, - supportFragmentManager.saveFragmentInstanceState(it) - ) - ) - } - replaceFragment(fragment, true) - } - - private fun popFragment(): Boolean { - return fragmentStack.isNotEmpty() && run { - val stackItem = fragmentStack.removeAt(fragmentStack.size - 1) - val fragment = Class.forName(stackItem.className).newInstance() as Fragment - stackItem.arguments?.let(fragment::setArguments) - stackItem.savedState?.let(fragment::setInitialSavedState) - replaceFragment(fragment, false) - true - } - } - - private fun hideKeyboard() { - (getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager) - ?.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() } - } - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - handleIntent(intent) - } - - protected val Intent.packageName: String? - get() { - val uri = data - return when { - uri?.scheme == "package" || uri?.scheme == "fdroid.app" -> { - uri.schemeSpecificPart?.nullIfEmpty() - } - uri?.scheme == "market" && uri.host == "details" -> { - uri.getQueryParameter("id")?.nullIfEmpty() - } - uri != null && uri.scheme in setOf("http", "https") -> { - val host = uri.host.orEmpty() - if (host == "f-droid.org" || host.endsWith(".f-droid.org")) { - uri.lastPathSegment?.nullIfEmpty() - } else { - null - } - } - else -> { - null - } - } - } - - protected fun handleSpecialIntent(specialIntent: SpecialIntent) { - when (specialIntent) { - is SpecialIntent.Updates -> { - if (currentFragment !is TabsFragment) { - fragmentStack.clear() - replaceFragment(TabsFragment(), true) - } - val tabsFragment = currentFragment as TabsFragment - tabsFragment.selectUpdates() - } - is SpecialIntent.Install -> { - val packageName = specialIntent.packageName - val status = specialIntent.status - val promptIntent = specialIntent.promptIntent - if (!packageName.isNullOrEmpty() && status != null && promptIntent != null) { - lifecycleScope.launch { - startService( - Intent(baseContext, InstallerService::class.java) - .putExtra(PackageInstaller.EXTRA_STATUS, status) - .putExtra( - PackageInstaller.EXTRA_PACKAGE_NAME, - packageName - ) - .putExtra(Intent.EXTRA_INTENT, promptIntent) - ) - } - } else { - throw IllegalArgumentException("Missing parameters needed to relaunch InstallerService and trigger prompt.") - } - Unit - } - }::class - } - - open fun handleIntent(intent: Intent?) { - when (intent?.action) { - Intent.ACTION_VIEW -> { - val packageName = intent.packageName - if (!packageName.isNullOrEmpty()) { - val fragment = currentFragment - if (fragment !is AppDetailFragment || fragment.packageName != packageName) { - navigateProduct(packageName) - } - } - } - } - } - - internal fun navigateProduct(packageName: String) = pushFragment(AppDetailFragment(packageName)) - internal fun navigatePreferences() = pushFragment(SettingsFragment()) -} diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenFragment.kt index 26570e5c..a482a50f 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenFragment.kt @@ -4,11 +4,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.MaterialToolbar import com.looker.droidify.databinding.FragmentBinding -open class ScreenFragment : BaseFragment() { +open class ScreenFragment : Fragment() { private var _fragmentBinding: FragmentBinding? = null val fragmentBinding get() = _fragmentBinding!! diff --git a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt deleted file mode 100644 index 21e2bbea..00000000 --- a/src/main/kotlin/com/looker/droidify/screen/SettingsFragment.kt +++ /dev/null @@ -1,433 +0,0 @@ -package com.looker.droidify.screen - -import android.app.Dialog -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.text.InputFilter -import android.text.InputType -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.widget.LinearLayoutCompat -import androidx.core.net.toUri -import androidx.core.widget.NestedScrollView -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope -import com.google.android.material.circularreveal.CircularRevealFrameLayout -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.switchmaterial.SwitchMaterial -import com.google.android.material.textfield.TextInputEditText -import com.google.android.material.textview.MaterialTextView -import com.looker.droidify.BuildConfig -import com.looker.droidify.R -import com.looker.droidify.content.Preferences -import com.looker.droidify.databinding.PreferenceItemBinding -import com.looker.droidify.utility.Utils.getLocaleOfCode -import com.looker.droidify.utility.Utils.languagesList -import com.looker.droidify.utility.Utils.translateLocale -import com.looker.droidify.utility.extension.resources.* -import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.launch - -class SettingsFragment : ScreenFragment() { - - private var preferenceBinding: PreferenceItemBinding? = null - private val preferences = mutableMapOf, Preference<*>>() - - override fun onResume() { - super.onResume() - preferences.forEach { (_, preference) -> preference.update() } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - preferenceBinding = PreferenceItemBinding.inflate(layoutInflater) - screenActivity.onToolbarCreated(toolbar) - collapsingToolbar.title = getString(R.string.settings) - - val content = fragmentBinding.fragmentContent - val scroll = NestedScrollView(content.context) - scroll.id = R.id.preferences_list - scroll.isFillViewport = true - content.addView( - scroll, - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - val scrollLayout = CircularRevealFrameLayout(content.context) - scroll.addView( - scrollLayout, - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - val preferences = LinearLayoutCompat(scrollLayout.context) - preferences.orientation = LinearLayoutCompat.VERTICAL - scrollLayout.addView( - preferences, - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - - preferences.addCategory(requireContext().getString(R.string.prefs_personalization)) { - addList( - Preferences.Key.Language, - context.getString(R.string.prefs_language_title), - languagesList - ) { translateLocale(context.getLocaleOfCode(it)) } - addEnumeration(Preferences.Key.Theme, getString(R.string.theme)) { - when (it) { - is Preferences.Theme.System -> getString(R.string.system) - is Preferences.Theme.AmoledSystem -> getString(R.string.system) + " " + getString( - R.string.amoled - ) - is Preferences.Theme.Light -> getString(R.string.light) - is Preferences.Theme.Dark -> getString(R.string.dark) - is Preferences.Theme.Amoled -> getString(R.string.amoled) - } - } - addSwitch( - Preferences.Key.ListAnimation, getString(R.string.list_animation), - getString(R.string.list_animation_description) - ) - } - preferences.addCategory(getString(R.string.updates)) { - addEnumeration( - Preferences.Key.AutoSync, - getString(R.string.sync_repositories_automatically) - ) { - when (it) { - Preferences.AutoSync.Never -> getString(R.string.never) - Preferences.AutoSync.Wifi -> getString(R.string.only_on_wifi) - Preferences.AutoSync.WifiBattery -> getString(R.string.only_on_wifi_and_battery) - Preferences.AutoSync.Always -> getString(R.string.always) - } - } - addSwitch( - Preferences.Key.InstallAfterSync, getString(R.string.install_after_sync), - getString(R.string.install_after_sync_summary) - ) - addSwitch( - Preferences.Key.UpdateNotify, getString(R.string.notify_about_updates), - getString(R.string.notify_about_updates_summary) - ) - addSwitch( - Preferences.Key.UpdateUnstable, getString(R.string.unstable_updates), - getString(R.string.unstable_updates_summary) - ) - addSwitch( - Preferences.Key.IncompatibleVersions, getString(R.string.incompatible_versions), - getString(R.string.incompatible_versions_summary) - ) - } - preferences.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) - is Preferences.ProxyType.Http -> getString(R.string.http_proxy) - is Preferences.ProxyType.Socks -> getString(R.string.socks_proxy) - } - } - 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.install_types)) { - addSwitch( - Preferences.Key.RootPermission, getString(R.string.root_permission), - getString(R.string.root_permission_description) - ) - } - preferences.addCategory(getString(R.string.credits)) { - addText( - title = "Based on Foxy-Droid", - summary = "FoxyDroid", - url = "https://github.com/kitsunyan/foxy-droid/" - ) - addText( - title = getString(R.string.application_name), - summary = "v ${BuildConfig.VERSION_NAME}", - url = "https://github.com/iamlooker/Droid-ify/" - ) - } - - lifecycleScope.launch { - Preferences.subject - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collect { updatePreference(it) } - } - updatePreference(null) - } - - private fun LinearLayoutCompat.addText(title: String, summary: String, url: String) { - val text = MaterialTextView(context) - val subText = MaterialTextView(context) - text.text = title - subText.text = summary - text.setTextSizeScaled(16) - subText.setTextSizeScaled(14) - resources.sizeScaled(16).let { - text.setPadding(it, it, 5, 5) - subText.setPadding(it, 5, 5, 25) - } - addView( - text, - LinearLayoutCompat.LayoutParams.MATCH_PARENT, - LinearLayoutCompat.LayoutParams.WRAP_CONTENT - ) - addView( - subText, - LinearLayoutCompat.LayoutParams.MATCH_PARENT, - LinearLayoutCompat.LayoutParams.WRAP_CONTENT - ) - text.setOnClickListener { openURI(url.toUri()) } - subText.setOnClickListener { openURI(url.toUri()) } - } - - private fun openURI(url: Uri) { - startActivity(Intent(Intent.ACTION_VIEW, url)) - } - - override fun onDestroyView() { - super.onDestroyView() - preferences.clear() - preferenceBinding = null - } - - private fun updatePreference(key: Preferences.Key<*>?) { - if (key != null) { - preferences[key]?.update() - } - if (key == null || key == Preferences.Key.ProxyType) { - val enabled = when (Preferences[Preferences.Key.ProxyType]) { - is Preferences.ProxyType.Direct -> false - is Preferences.ProxyType.Http, is Preferences.ProxyType.Socks -> true - } - preferences[Preferences.Key.ProxyHost]?.setEnabled(enabled) - preferences[Preferences.Key.ProxyPort]?.setEnabled(enabled) - } - if (key == Preferences.Key.RootPermission) { - preferences[Preferences.Key.RootPermission]?.setEnabled( - Shell.getCachedShell()?.isRoot - ?: Shell.getShell().isRoot - ) - } - if (key == Preferences.Key.Theme) { - requireActivity().recreate() - } - } - - private inline fun LinearLayoutCompat.addCategory( - title: String, - callback: LinearLayoutCompat.() -> Unit, - ) { - val text = MaterialTextView(context) - text.typeface = TypefaceExtra.medium - text.setTextSizeScaled(14) - text.setTextColor(text.context.getColorFromAttr(R.attr.colorPrimary)) - text.text = title - resources.sizeScaled(16).let { text.setPadding(it, it, it, 0) } - addView( - text, - LinearLayoutCompat.LayoutParams.MATCH_PARENT, - LinearLayoutCompat.LayoutParams.WRAP_CONTENT - ) - callback() - } - - private fun LinearLayoutCompat.addPreference( - key: Preferences.Key, title: String, - summaryProvider: () -> String, dialogProvider: ((Context) -> AlertDialog)?, - ): Preference { - val preference = - Preference(key, this@SettingsFragment, this, title, summaryProvider, dialogProvider) - preferences[key] = preference - return preference - } - - private fun LinearLayoutCompat.addSwitch( - key: Preferences.Key, - title: String, - summary: String, - ) { - val preference = addPreference(key, title, { summary }, null) - preference.check.visibility = View.VISIBLE - preference.view.setOnClickListener { Preferences[key] = !Preferences[key] } - preference.setCallback { preference.check.isChecked = Preferences[key] } - } - - private fun LinearLayoutCompat.addEdit( - key: Preferences.Key, title: String, valueToString: (T) -> String, - stringToValue: (String) -> T?, configureEdit: (TextInputEditText) -> Unit, - ) { - addPreference(key, title, { valueToString(Preferences[key]) }) { it -> - val scroll = NestedScrollView(it) - scroll.resources.sizeScaled(20).let { scroll.setPadding(it, 0, it, 0) } - val edit = TextInputEditText(it) - configureEdit(edit) - edit.id = android.R.id.edit - edit.resources.sizeScaled(16) - .let { edit.setPadding(edit.paddingLeft, it, edit.paddingRight, it) } - edit.setText(valueToString(Preferences[key])) - edit.hint = edit.text.toString() - edit.text?.let { editable -> edit.setSelection(editable.length) } - edit.requestFocus() - scroll.addView( - edit, - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - MaterialAlertDialogBuilder(it) - .setTitle(title) - .setView(scroll) - .setPositiveButton(R.string.ok) { _, _ -> - val value = stringToValue(edit.text.toString()) ?: key.default.value - post { Preferences[key] = value } - } - .setNegativeButton(R.string.cancel, null) - .create() - .apply { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - } - } - } - - private fun LinearLayoutCompat.addEditString(key: Preferences.Key, title: String) { - addEdit(key, title, { it }, { it }, { }) - } - - private fun LinearLayoutCompat.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 - 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() - if (value != null && value in range) null else "" - }) - } - } - - private fun > LinearLayoutCompat.addEnumeration( - key: Preferences.Key, - title: String, - valueToString: (T) -> String, - ) { - addPreference(key, title, { valueToString(Preferences[key]) }) { - val values = key.default.value.values - MaterialAlertDialogBuilder(it) - .setTitle(title) - .setSingleChoiceItems( - values.map(valueToString).toTypedArray(), - values.indexOf(Preferences[key]) - ) { dialog, which -> - dialog.dismiss() - post { Preferences[key] = values[which] } - } - .setNegativeButton(R.string.cancel, null) - .create() - } - } - - private fun LinearLayoutCompat.addList( - key: Preferences.Key, - title: String, - values: List, - valueToString: (T) -> String, - ) { - addPreference(key, title, { valueToString(Preferences[key]) }) { - MaterialAlertDialogBuilder(it) - .setTitle(title) - .setSingleChoiceItems( - values.map(valueToString).toTypedArray(), - values.indexOf(Preferences[key]) - ) { dialog, which -> - dialog.dismiss() - post { Preferences[key] = values[which] } - } - .setNegativeButton(R.string.cancel, null) - .create() - } - } - - private class Preference( - private val key: Preferences.Key, - fragment: Fragment, - 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)!! - val summary = view.findViewById(R.id.summary)!! - val check = view.findViewById(R.id.check)!! - - private var callback: (() -> Unit)? = null - - init { - title.text = titleText - parent.addView(view) - if (dialogProvider != null) { - view.setOnClickListener { - PreferenceDialog(key.name) - .show( - fragment.childFragmentManager, - "${PreferenceDialog::class.java.name}.${key.name}" - ) - } - } - update() - } - - fun setCallback(callback: () -> Unit) { - this.callback = callback - update() - } - - fun setEnabled(enabled: Boolean) { - view.isEnabled = enabled - title.isEnabled = enabled - summary.isEnabled = enabled - check.isEnabled = enabled - } - - fun update() { - summary.text = summaryProvider() - summary.visibility = if (summary.text.isNotEmpty()) View.VISIBLE else View.GONE - callback?.invoke() - } - - fun createDialog(context: Context): AlertDialog { - return dialogProvider!!(context) - } - } - - class PreferenceDialog() : DialogFragment() { - companion object { - private const val EXTRA_KEY = "key" - } - - constructor(key: String) : this() { - arguments = Bundle().apply { - putString(EXTRA_KEY, key) - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val preferences = (parentFragment as SettingsFragment).preferences - val key = requireArguments().getString(EXTRA_KEY)!! - .let { name -> preferences.keys.find { it.name == name }!! } - val preference = preferences[key]!! - return preference.createDialog(requireContext()) - } - } -} diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt deleted file mode 100644 index 44254298..00000000 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ /dev/null @@ -1,598 +0,0 @@ -package com.looker.droidify.screen - -import android.animation.ValueAnimator -import android.content.Context -import android.os.Bundle -import android.view.Gravity -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.view.animation.AccelerateInterpolator -import android.view.animation.DecelerateInterpolator -import androidx.appcompat.widget.SearchView -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager2.adapter.FragmentStateAdapter -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.tabs.TabLayoutMediator -import com.google.android.material.textview.MaterialTextView -import com.looker.droidify.R -import com.looker.droidify.content.Preferences -import com.looker.droidify.databinding.TabsToolbarBinding -import com.looker.droidify.entity.Section -import com.looker.droidify.service.Connection -import com.looker.droidify.service.SyncService -import com.looker.droidify.ui.fragments.AppListFragment -import com.looker.droidify.utility.RxUtils -import com.looker.droidify.utility.Utils -import com.looker.droidify.utility.extension.android.Android -import com.looker.droidify.utility.extension.resources.getDrawableCompat -import com.looker.droidify.utility.extension.resources.getDrawableFromAttr -import com.looker.droidify.utility.extension.resources.sizeScaled -import com.looker.droidify.widget.DividerItemDecoration -import com.looker.droidify.widget.FocusSearchView -import com.looker.droidify.widget.StableRecyclerAdapter -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.schedulers.Schedulers -import kotlinx.coroutines.launch -import kotlin.math.abs -import kotlin.math.roundToInt - -class TabsFragment : ScreenFragment() { - - private var _tabsBinding: TabsToolbarBinding? = null - private val tabsBinding get() = _tabsBinding!! - - companion object { - private const val STATE_SEARCH_FOCUSED = "searchFocused" - private const val STATE_SEARCH_QUERY = "searchQuery" - private const val STATE_SHOW_SECTIONS = "showSections" - private const val STATE_SECTIONS = "sections" - private const val STATE_SECTION = "section" - } - - private class Layout(view: TabsToolbarBinding) { - val tabs = view.tabs - val sectionLayout = view.sectionLayout - val sectionChange = view.sectionChange - val sectionName = view.sectionName - val sectionIcon = view.sectionIcon - } - - private var searchMenuItem: MenuItem? = null - private var sortOrderMenu: Pair>? = null - private var syncRepositoriesMenuItem: MenuItem? = null - private var layout: Layout? = null - private var sectionsList: RecyclerView? = null - private var viewPager: ViewPager2? = null - - private var showSections = false - set(value) { - if (field != value) { - field = value - val layout = layout - layout?.tabs?.let { - (0 until it.childCount) - .forEach { index -> it.getChildAt(index)!!.isEnabled = !value } - } - layout?.sectionIcon?.scaleY = if (value) -1f else 1f - if ((sectionsList?.parent as? View)?.height ?: 0 > 0) { - animateSectionsList() - } - } - } - - private var searchQuery = "" - private var sections = listOf
(Section.All) - private var section: Section = Section.All - - private val syncConnection = Connection(SyncService::class.java, onBind = { _, _ -> - viewPager?.let { - val source = AppListFragment.Source.values()[it.currentItem] - updateUpdateNotificationBlocker(source) - } - }) - - private var categoriesDisposable: Disposable? = null - private var repositoriesDisposable: Disposable? = null - private var sectionsAnimator: ValueAnimator? = null - - private var needSelectUpdates = false - - private val productFragments: Sequence - get() = if (host == null) emptySequence() else - childFragmentManager.fragments.asSequence().mapNotNull { it as? AppListFragment } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - _tabsBinding = TabsToolbarBinding.inflate(layoutInflater) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - syncConnection.bind(requireContext()) - - screenActivity.onToolbarCreated(toolbar) - collapsingToolbar.title = getString(R.string.application_name) - // Move focus from SearchView to Toolbar - toolbar.isFocusableInTouchMode = true - - val searchView = FocusSearchView(toolbar.context).apply { - maxWidth = Int.MAX_VALUE - queryHint = getString(R.string.search) - setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - clearFocus() - return true - } - - override fun onQueryTextChange(newText: String?): Boolean { - if (isResumed) { - searchQuery = newText.orEmpty() - productFragments.forEach { it.setSearchQuery(newText.orEmpty()) } - } - return true - } - }) - setOnSearchClickListener { fragmentBinding.appbarLayout.setExpanded(false, true) } - } - - toolbar.menu.apply { - if (Android.sdk(28) && !Android.Device.isHuaweiEmui) { - setGroupDividerEnabled(true) - } - - searchMenuItem = add(0, R.id.toolbar_search, 0, R.string.search) - .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_search)) - .setActionView(searchView) - .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) - - sortOrderMenu = addSubMenu(0, 0, 0, R.string.sorting_order) - .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_sort)) - .let { menu -> - menu.item.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS) - val items = Preferences.Key.SortOrder.default.value.values - .map { sortOrder -> - menu - .add(sortOrder.order.titleResId) - .setOnMenuItemClickListener { - Preferences[Preferences.Key.SortOrder] = sortOrder - true - } - } - menu.setGroupCheckable(0, true, true) - Pair(menu.item, items) - } - - syncRepositoriesMenuItem = add(0, 0, 0, R.string.sync_repositories) - .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_sync)) - .setOnMenuItemClickListener { - syncConnection.binder?.sync(SyncService.SyncRequest.MANUAL) - true - } - - add(1, 0, 0, R.string.settings) - .setOnMenuItemClickListener { - view.post { screenActivity.navigatePreferences() } - true - } - } - - searchQuery = savedInstanceState?.getString(STATE_SEARCH_QUERY).orEmpty() - productFragments.forEach { it.setSearchQuery(searchQuery) } - - val toolbarExtra = fragmentBinding.toolbarExtra - toolbarExtra.addView(tabsBinding.root) - val layout = Layout(tabsBinding) - this.layout = layout - - showSections = savedInstanceState?.getByte(STATE_SHOW_SECTIONS)?.toInt() ?: 0 != 0 - sections = savedInstanceState?.getParcelableArrayList
(STATE_SECTIONS) - .orEmpty() - section = savedInstanceState?.getParcelable(STATE_SECTION) ?: Section.All - layout.sectionChange.setOnClickListener { - showSections = sections - .any { it !is Section.All } && !showSections - } - - updateOrder() - lifecycleScope.launch { - Preferences.subject - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collect { - if (it == Preferences.Key.SortOrder) updateOrder() - } - } - - val content = fragmentBinding.fragmentContent - - viewPager = ViewPager2(content.context).apply { - id = R.id.fragment_pager - adapter = object : FragmentStateAdapter(this@TabsFragment) { - override fun getItemCount(): Int = AppListFragment.Source.values().size - override fun createFragment(position: Int): Fragment = AppListFragment( - AppListFragment - .Source.values()[position] - ) - } - content.addView(this) - registerOnPageChangeCallback(pageChangeCallback) - offscreenPageLimit = 1 - } - - viewPager?.let { - TabLayoutMediator(layout.tabs, it) { tab, position -> - tab.text = getString(AppListFragment.Source.values()[position].titleResId) - }.attach() - } - - categoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava - .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - setSectionsAndUpdate( - it.asSequence().sorted() - .map(Section::Category).toList(), null - ) - } - repositoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava - .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all } } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { it -> - setSectionsAndUpdate(null, it.asSequence().filter { it.enabled } - .map { Section.Repository(it.id, it.name) }.toList()) - } - updateSection() - - val sectionsList = RecyclerView(toolbar.context).apply { - id = R.id.sections_list - layoutManager = LinearLayoutManager(context) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false - setHasFixedSize(true) - val adapter = SectionsAdapter({ sections }) { - if (showSections) { - showSections = false - section = it - updateSection() - } - } - this.adapter = adapter - addItemDecoration(DividerItemDecoration(context, adapter::configureDivider)) - background = context.getDrawableCompat(R.drawable.background_border) - elevation = resources.sizeScaled(4).toFloat() - content.addView(this) - val margins = resources.sizeScaled(8) - (layoutParams as ViewGroup.MarginLayoutParams).setMargins(margins, margins, margins, 0) - visibility = View.GONE - } - this.sectionsList = sectionsList - - var lastContentHeight = -1 - content.viewTreeObserver.addOnGlobalLayoutListener { - if (this.view != null) { - val initial = lastContentHeight <= 0 - val contentHeight = content.height - if (lastContentHeight != contentHeight) { - lastContentHeight = contentHeight - if (initial) { - sectionsList.layoutParams.height = if (showSections) contentHeight else 0 - sectionsList.visibility = if (showSections) View.VISIBLE else View.GONE - sectionsList.requestLayout() - } else { - animateSectionsList() - } - } - } - } - } - - override fun onDestroyView() { - super.onDestroyView() - - searchMenuItem = null - sortOrderMenu = null - syncRepositoriesMenuItem = null - layout = null - sectionsList = null - viewPager = null - - syncConnection.unbind(requireContext()) - categoriesDisposable?.dispose() - categoriesDisposable = null - repositoriesDisposable?.dispose() - repositoriesDisposable = null - sectionsAnimator?.cancel() - sectionsAnimator = null - - _tabsBinding = null - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - - outState.putBoolean(STATE_SEARCH_FOCUSED, searchMenuItem?.actionView?.hasFocus() == true) - outState.putString(STATE_SEARCH_QUERY, searchQuery) - outState.putByte(STATE_SHOW_SECTIONS, if (showSections) 1 else 0) - outState.putParcelableArrayList(STATE_SECTIONS, ArrayList(sections)) - outState.putParcelable(STATE_SECTION, section) - } - - override fun onViewStateRestored(savedInstanceState: Bundle?) { - super.onViewStateRestored(savedInstanceState) - - (searchMenuItem?.actionView as FocusSearchView).allowFocus = true - if (needSelectUpdates) { - needSelectUpdates = false - selectUpdatesInternal(false) - } - } - - override fun onBackPressed(): Boolean { - return when { - searchMenuItem?.isActionViewExpanded == true -> { - searchMenuItem?.collapseActionView() - true - } - showSections -> { - showSections = false - true - } - else -> { - super.onBackPressed() - } - } - } - - internal fun selectUpdates() = selectUpdatesInternal(true) - - private fun selectUpdatesInternal(allowSmooth: Boolean) { - if (view != null) { - val viewPager = viewPager - viewPager?.setCurrentItem( - AppListFragment.Source.UPDATES.ordinal, - allowSmooth && viewPager.isLaidOut - ) - } else { - needSelectUpdates = true - } - } - - private fun updateUpdateNotificationBlocker(activeSource: AppListFragment.Source) { - val blockerFragment = if (activeSource == AppListFragment.Source.UPDATES) { - productFragments.find { it.source == activeSource } - } else { - null - } - syncConnection.binder?.setUpdateNotificationBlocker(blockerFragment) - } - - private fun updateOrder() { - val order = Preferences[Preferences.Key.SortOrder].order - sortOrderMenu!!.second[order.ordinal].isChecked = true - productFragments.forEach { it.setOrder(order) } - } - - private inline fun collectOldSections(list: List?): List? { - val oldList = sections.mapNotNull { it as? T } - return if (list == null || oldList == list) oldList else null - } - - private fun setSectionsAndUpdate( - categories: List?, - repositories: List?, - ) { - val oldCategories = collectOldSections(categories) - val oldRepositories = collectOldSections(repositories) - if (oldCategories == null || oldRepositories == null) { - sections = listOf(Section.All) + - (categories ?: oldCategories).orEmpty() + - (repositories ?: oldRepositories).orEmpty() - updateSection() - } - } - - private fun updateSection() { - if (section !in sections) { - section = Section.All - } - layout?.sectionName?.text = when (val section = section) { - is Section.All -> getString(R.string.all_applications) - is Section.Category -> section.name - is Section.Repository -> section.name - } - layout?.sectionIcon?.visibility = - if (sections.any { it !is Section.All }) View.VISIBLE else View.GONE - productFragments.forEach { it.setSection(section) } - sectionsList?.adapter?.notifyDataSetChanged() - } - - private fun animateSectionsList() { - val sectionsList = sectionsList!! - val value = if (sectionsList.visibility != View.VISIBLE) 0f else - sectionsList.height.toFloat() / (sectionsList.parent as View).height - val target = if (showSections) 0.98f else 0f - sectionsAnimator?.cancel() - sectionsAnimator = null - - if (value != target) { - 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 - sectionsList.apply { - val height = ((parent as View).height * newValue).toInt() - val visible = height > 0 - if ((visibility == View.VISIBLE) != visible) { - visibility = if (visible) View.VISIBLE else View.GONE - } - if (layoutParams.height != height) { - layoutParams.height = height - requestLayout() - } - } - if (target <= 0f && newValue <= 0f || target >= 1f && newValue >= 1f) { - sectionsAnimator = null - } - } - start() - } - } - } - - private val pageChangeCallback = object : ViewPager2.OnPageChangeCallback() { - override fun onPageScrolled( - position: Int, - positionOffset: Float, - positionOffsetPixels: Int, - ) { - val layout = layout!! - val fromSections = AppListFragment.Source.values()[position].sections - val toSections = if (positionOffset <= 0f) fromSections else - AppListFragment.Source.values()[position + 1].sections - val offset = if (fromSections != toSections) { - if (fromSections) 1f - positionOffset else positionOffset - } else { - if (fromSections) 1f else 0f - } - 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() - } - } - - override fun onPageSelected(position: Int) { - val source = AppListFragment.Source.values()[position] - updateUpdateNotificationBlocker(source) - sortOrderMenu!!.first.apply { - isVisible = source.order - setShowAsActionFlags( - if (!source.order || - resources.configuration.screenWidthDp >= 400 - ) MenuItem.SHOW_AS_ACTION_ALWAYS else 0 - ) - } - syncRepositoriesMenuItem!!.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS) - if (showSections && !source.sections) { - showSections = false - } - } - - override fun onPageScrollStateChanged(state: Int) { - val source = AppListFragment.Source.values()[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 - updateUpdateNotificationBlocker(source) - } - } - } - - private class SectionsAdapter( - private val sections: () -> List
, - private val onClick: (Section) -> Unit, - ) : StableRecyclerAdapter() { - enum class ViewType { SECTION } - - private class SectionViewHolder(context: Context) : - RecyclerView.ViewHolder(MaterialTextView(context)) { - val title: MaterialTextView - get() = itemView as MaterialTextView - - init { - itemView as MaterialTextView - itemView.gravity = Gravity.CENTER_VERTICAL - itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) } - itemView.background = - context.getDrawableFromAttr(android.R.attr.selectableItemBackground) - itemView.layoutParams = RecyclerView.LayoutParams( - RecyclerView.LayoutParams.MATCH_PARENT, - itemView.resources.sizeScaled(48) - ) - } - } - - fun configureDivider( - context: Context, - position: Int, - configuration: DividerItemDecoration.Configuration, - ) { - val currentSection = sections()[position] - val nextSection = sections().getOrNull(position + 1) - when { - nextSection != null && currentSection.javaClass != nextSection.javaClass -> { - val padding = context.resources.sizeScaled(16) - configuration.set( - needDivider = true, - toTop = false, - paddingStart = padding, - paddingEnd = padding - ) - } - else -> { - configuration.set( - needDivider = false, - toTop = false, - paddingStart = 0, - paddingEnd = 0 - ) - } - } - } - - override val viewTypeClass: Class - get() = ViewType::class.java - - override fun getItemCount(): Int = sections().size - override fun getItemDescriptor(position: Int): String = sections()[position].toString() - override fun getItemEnumViewType(position: Int): ViewType = ViewType.SECTION - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: ViewType, - ): RecyclerView.ViewHolder { - return SectionViewHolder(parent.context).apply { - itemView.setOnClickListener { onClick(sections()[adapterPosition]) } - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - holder as SectionViewHolder - val section = sections()[position] - val previousSection = sections().getOrNull(position - 1) - val nextSection = sections().getOrNull(position + 1) - val margin = holder.itemView.resources.sizeScaled(8) - val layoutParams = holder.itemView.layoutParams as RecyclerView.LayoutParams - layoutParams.topMargin = if (previousSection == null || - section.javaClass != previousSection.javaClass - ) margin else 0 - layoutParams.bottomMargin = if (nextSection == null || - section.javaClass != nextSection.javaClass - ) margin else 0 - holder.title.text = when (section) { - is Section.All -> holder.itemView.resources.getString(R.string.all_applications) - is Section.Category -> section.name - is Section.Repository -> section.name - } - } - } -} diff --git a/src/main/kotlin/com/looker/droidify/ui/adapters/AppListAdapter.kt b/src/main/kotlin/com/looker/droidify/ui/adapters/AppListAdapter.kt deleted file mode 100644 index 846624a5..00000000 --- a/src/main/kotlin/com/looker/droidify/ui/adapters/AppListAdapter.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.looker.droidify.ui.adapters - -import android.content.Context -import android.graphics.drawable.Drawable -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.view.animation.AnimationUtils -import androidx.core.content.res.ResourcesCompat -import androidx.recyclerview.widget.RecyclerView -import coil.load -import coil.transform.RoundedCornersTransformation -import com.google.android.material.circularreveal.CircularRevealFrameLayout -import com.google.android.material.imageview.ShapeableImageView -import com.google.android.material.progressindicator.CircularProgressIndicator -import com.google.android.material.textview.MaterialTextView -import com.looker.droidify.R -import com.looker.droidify.content.Preferences -import com.looker.droidify.database.entity.Repository -import com.looker.droidify.entity.ProductItem -import com.looker.droidify.network.CoilDownloader -import com.looker.droidify.utility.Utils -import com.looker.droidify.utility.extension.resources.* -import com.looker.droidify.utility.extension.text.nullIfEmpty -import com.looker.droidify.utility.getProduct -import com.looker.droidify.widget.CursorRecyclerAdapter - -class AppListAdapter(private val onClick: (ProductItem) -> Unit) : - CursorRecyclerAdapter() { - private var lastPosition = 0 - - enum class ViewType { PRODUCT, LOADING, EMPTY } - - private class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val name = itemView.findViewById(R.id.name)!! - val status = itemView.findViewById(R.id.status)!! - val summary = itemView.findViewById(R.id.summary)!! - val icon = itemView.findViewById(R.id.icon)!! - - val progressIcon: Drawable - val defaultIcon: Drawable - - init { - val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(icon.context) - this.progressIcon = progressIcon - this.defaultIcon = defaultIcon - } - } - - private class LoadingViewHolder(context: Context) : - RecyclerView.ViewHolder(CircularRevealFrameLayout(context)) { - init { - itemView as CircularRevealFrameLayout - val progressBar = CircularProgressIndicator(itemView.context) - itemView.addView(progressBar) - itemView.layoutParams = RecyclerView.LayoutParams( - RecyclerView.LayoutParams.MATCH_PARENT, - RecyclerView.LayoutParams.MATCH_PARENT - ) - } - } - - private class EmptyViewHolder(context: Context) : - RecyclerView.ViewHolder(MaterialTextView(context)) { - val text: MaterialTextView - get() = itemView as MaterialTextView - - init { - itemView as MaterialTextView - itemView.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.colorPrimary)) - itemView.setTextSizeScaled(20) - itemView.layoutParams = RecyclerView.LayoutParams( - RecyclerView.LayoutParams.MATCH_PARENT, - RecyclerView.LayoutParams.MATCH_PARENT - ) - } - } - - var repositories: Map = emptyMap() - set(value) { - field = value - notifyDataSetChanged() - } - - var emptyText: String = "" - set(value) { - if (field != value) { - field = value - if (isEmpty) { - notifyDataSetChanged() - } - } - } - - override val viewTypeClass: Class - get() = ViewType::class.java - - private val isEmpty: Boolean - get() = super.getItemCount() == 0 - - override fun getItemCount(): Int = if (isEmpty) 1 else super.getItemCount() - override fun getItemId(position: Int): Long = if (isEmpty) -1 else super.getItemId(position) - - override fun getItemEnumViewType(position: Int): ViewType { - return when { - !isEmpty -> ViewType.PRODUCT - cursor == null -> ViewType.LOADING - else -> ViewType.EMPTY - } - } - - private fun getProductItem(position: Int): ProductItem { - return moveTo(position).getProduct().item() - } - - 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)) } - } - ViewType.LOADING -> LoadingViewHolder(parent.context) - ViewType.EMPTY -> EmptyViewHolder(parent.context) - } - } - - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { - super.onViewDetachedFromWindow(holder) - if (Preferences[Preferences.Key.ListAnimation]) { - holder.itemView.clearAnimation() - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (getItemEnumViewType(position)) { - ViewType.PRODUCT -> { - holder as ProductViewHolder - val productItem = getProductItem(position) - holder.name.text = productItem.name - holder.summary.text = - if (productItem.name == productItem.summary) "" else productItem.summary - holder.summary.visibility = - if (holder.summary.text.isNotEmpty()) View.VISIBLE else View.GONE - val repository: Repository? = repositories[productItem.repositoryId] - holder.icon.load( - repository?.let { - CoilDownloader.createIconUri( - holder.icon, productItem.packageName, - productItem.icon, productItem.metadataIcon, it - ) - } - ) { - transformations(RoundedCornersTransformation(4.toPx)) - placeholder(holder.progressIcon) - error(holder.defaultIcon) - } - holder.status.apply { - if (productItem.canUpdate) { - text = productItem.version - if (background == null) { - background = - ResourcesCompat.getDrawable( - holder.itemView.resources, - R.drawable.background_border, - context.theme - ) - resources.sizeScaled(6).let { setPadding(it, it, it, it) } - backgroundTintList = - context.getColorFromAttr(R.attr.colorSecondaryContainer) - setTextColor(context.getColorFromAttr(R.attr.colorSecondary)) - } - } else { - text = productItem.installedVersion.nullIfEmpty() ?: productItem.version - if (background != null) { - setPadding(0, 0, 0, 0) - setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorControlNormal)) - background = null - } - } - } - val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty() - sequenceOf(holder.name, holder.status, holder.summary).forEach { - it.isEnabled = enabled - } - } - ViewType.LOADING -> { - // Do nothing - } - ViewType.EMPTY -> { - holder as EmptyViewHolder - holder.text.text = emptyText - } - }::class - if (Preferences[Preferences.Key.ListAnimation]) { - setAnimation(holder.itemView, holder.adapterPosition) - } - } - - private fun setAnimation(itemView: View, position: Int) { - val animation = AnimationUtils.loadAnimation( - itemView.context, - if (position > lastPosition) R.anim.slide_up else R.anim.slide_down - ) - itemView.startAnimation(animation) - lastPosition = position - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt index 1c6a73be..64c9877e 100644 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt +++ b/src/main/kotlin/com/looker/droidify/ui/fragments/AppDetailFragment.kt @@ -27,6 +27,7 @@ import com.looker.droidify.screen.ScreenFragment import com.looker.droidify.screen.ScreenshotsFragment import com.looker.droidify.service.Connection import com.looker.droidify.service.DownloadService +import com.looker.droidify.ui.activities.MainActivityX import com.looker.droidify.ui.adapters.AppDetailAdapter import com.looker.droidify.utility.RxUtils import com.looker.droidify.utility.Utils @@ -47,6 +48,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { + private val screenActivity: MainActivityX + get() = requireActivity() as MainActivityX + companion object { private const val EXTRA_PACKAGE_NAME = "packageName" private const val STATE_LAYOUT_MANAGER = "layoutManager" @@ -103,7 +107,6 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - screenActivity.onToolbarCreated(toolbar) toolbar.menu.apply { for (action in Action.values()) { add(0, action.id, 0, action.adapterAction.titleResId) diff --git a/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt b/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt deleted file mode 100644 index 1e2b6320..00000000 --- a/src/main/kotlin/com/looker/droidify/ui/fragments/AppListFragment.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.looker.droidify.ui.fragments - -import android.database.Cursor -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.looker.droidify.R -import com.looker.droidify.database.CursorOwner -import com.looker.droidify.entity.Order -import com.looker.droidify.entity.Section -import com.looker.droidify.screen.BaseFragment -import com.looker.droidify.ui.adapters.AppListAdapter -import com.looker.droidify.ui.viewmodels.AppListViewModel -import com.looker.droidify.utility.RxUtils -import com.looker.droidify.utility.extension.resources.getDrawableCompat -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.schedulers.Schedulers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import me.zhanghai.android.fastscroll.FastScrollerBuilder - -class AppListFragment() : BaseFragment(), CursorOwner.Callback { - - private val viewModel: AppListViewModel by viewModels() - - companion object { - private const val EXTRA_SOURCE = "source" - } - - enum class Source(val titleResId: Int, val sections: Boolean, val order: Boolean) { - AVAILABLE(R.string.available, true, true), - INSTALLED(R.string.installed, false, true), - UPDATES(R.string.updates, false, false) - } - - constructor(source: Source) : this() { - arguments = Bundle().apply { - putString(EXTRA_SOURCE, source.name) - } - } - - val source: Source - get() = requireArguments().getString(EXTRA_SOURCE)!!.let(Source::valueOf) - - private var recyclerView: RecyclerView? = null - - private var repositoriesDisposable: Disposable? = null - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - return RecyclerView(requireContext()).apply { - id = android.R.id.list - layoutManager = LinearLayoutManager(context) - isMotionEventSplittingEnabled = false - isVerticalScrollBarEnabled = false - setHasFixedSize(true) - recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30) - val adapter = AppListAdapter { screenActivity.navigateProduct(it.packageName) } - this.adapter = adapter - FastScrollerBuilder(this) - .useMd2Style() - .setThumbDrawable(this.context.getDrawableCompat(R.drawable.scrollbar_thumb)) - .build() - recyclerView = this - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - screenActivity.cursorOwner.attach(this, viewModel.request(source)) - repositoriesDisposable = Observable.just(Unit) - //.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava - .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all } } - .map { it.asSequence().map { Pair(it.id, it) }.toMap() } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { (recyclerView?.adapter as? AppListAdapter)?.repositories = it } - } - - override fun onDestroyView() { - super.onDestroyView() - - recyclerView = null - - screenActivity.cursorOwner.detach(this) - repositoriesDisposable?.dispose() - repositoriesDisposable = null - } - - override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) { - (recyclerView?.adapter as? AppListAdapter)?.apply { - this.cursor = cursor - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - emptyText = when { - cursor == null -> "" - viewModel.searchQuery.first() - .isNotEmpty() -> getString(R.string.no_matching_applications_found) - else -> when (source) { - Source.AVAILABLE -> getString(R.string.no_applications_available) - Source.INSTALLED -> getString(R.string.no_applications_installed) - Source.UPDATES -> getString(R.string.all_applications_up_to_date) - } - } - } - } - } - } - - internal fun setSearchQuery(searchQuery: String) { - viewModel.setSearchQuery(searchQuery) { - if (view != null) { - screenActivity.cursorOwner.attach(this, viewModel.request(source)) - } - } - } - - internal fun setSection(section: Section) { - viewModel.setSection(section) { - if (view != null) { - screenActivity.cursorOwner.attach(this, viewModel.request(source)) - } - } - } - - internal fun setOrder(order: Order) { - viewModel.setOrder(order) { - if (view != null) { - screenActivity.cursorOwner.attach(this, viewModel.request(source)) - } - } - } -} diff --git a/src/main/kotlin/com/looker/droidify/ui/viewmodels/AppListViewModel.kt b/src/main/kotlin/com/looker/droidify/ui/viewmodels/AppListViewModel.kt deleted file mode 100644 index 2c13442c..00000000 --- a/src/main/kotlin/com/looker/droidify/ui/viewmodels/AppListViewModel.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.looker.droidify.ui.viewmodels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.looker.droidify.content.Preferences -import com.looker.droidify.database.CursorOwner -import com.looker.droidify.entity.Order -import com.looker.droidify.entity.Section -import com.looker.droidify.ui.fragments.AppListFragment -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch - -class AppListViewModel : ViewModel() { - - private val _order = MutableStateFlow(Preferences[Preferences.Key.SortOrder].order) - private val _sections = MutableStateFlow
(Section.All) - private val _searchQuery = MutableStateFlow("") - - val order: StateFlow = _order.stateIn( - initialValue = Order.LAST_UPDATE, - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000) - ) - - val sections: StateFlow
= _sections.stateIn( - initialValue = Section.All, - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000) - ) - val searchQuery: StateFlow = _searchQuery.stateIn( - initialValue = "", - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000) - ) - - fun request(source: AppListFragment.Source): CursorOwner.Request { - var mSearchQuery = "" - var mSections: Section = Section.All - var mOrder: Order = Order.NAME - viewModelScope.launch { - launch { searchQuery.collect { if (source.sections) mSearchQuery = it } } - launch { sections.collect { if (source.sections) mSections = it } } - launch { order.collect { if (source.order) mOrder = it } } - } - return when (source) { - AppListFragment.Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable( - mSearchQuery, - mSections, - mOrder - ) - AppListFragment.Source.INSTALLED -> CursorOwner.Request.ProductsInstalled( - mSearchQuery, - mSections, - mOrder - ) - AppListFragment.Source.UPDATES -> CursorOwner.Request.ProductsUpdates( - mSearchQuery, - mSections, - mOrder - ) - } - } - - fun setSection(newSection: Section, perform: () -> Unit) { - viewModelScope.launch { - if (newSection != sections.value) { - _sections.emit(newSection) - launch(Dispatchers.Main) { perform() } - } - } - } - - fun setOrder(newOrder: Order, perform: () -> Unit) { - viewModelScope.launch { - if (newOrder != order.value) { - _order.emit(newOrder) - launch(Dispatchers.Main) { perform() } - } - } - } - - fun setSearchQuery(newSearchQuery: String, perform: () -> Unit) { - viewModelScope.launch { - if (newSearchQuery != searchQuery.value) { - _searchQuery.emit(newSearchQuery) - launch(Dispatchers.Main) { perform() } - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index b300b0d8..99649112 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -7,18 +7,18 @@ import android.content.Context import android.content.pm.PackageInfo import android.content.pm.Signature import android.content.res.Configuration -import android.database.Cursor import android.graphics.drawable.Drawable import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.core.JsonParser -import com.looker.droidify.* +import com.looker.droidify.BuildConfig +import com.looker.droidify.PREFS_LANGUAGE_DEFAULT +import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.database.entity.Installed import com.looker.droidify.database.entity.Repository import com.looker.droidify.entity.Product -import com.looker.droidify.entity.ProductItem import com.looker.droidify.service.Connection import com.looker.droidify.service.DownloadService import com.looker.droidify.utility.extension.android.Android @@ -184,39 +184,6 @@ object Utils { } -// TODO Remove -fun Cursor.getProduct(): Product = getBlob(getColumnIndex(ROW_DATA)) - .jsonParse { - Product.deserialize(it).apply { - this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) - this.description = getString(getColumnIndex(ROW_DESCRIPTION)) - } - } - -// TODO Remove -fun Cursor.getProductItem(): ProductItem = getBlob(getColumnIndex(ROW_DATA_ITEM)) - .jsonParse { - ProductItem.deserialize(it).apply { - this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) - this.packageName = getString(getColumnIndex(ROW_PACKAGE_NAME)) - this.name = getString(getColumnIndex(ROW_NAME)) - this.summary = getString(getColumnIndex(ROW_SUMMARY)) - this.installedVersion = getString(getColumnIndex(ROW_VERSION)) - .orEmpty() - this.compatible = getInt(getColumnIndex(ROW_COMPATIBLE)) != 0 - this.canUpdate = getInt(getColumnIndex(ROW_CAN_UPDATE)) != 0 - this.matchRank = getInt(getColumnIndex(ROW_MATCH_RANK)) - } - } - -// TODO Remove -fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) - .jsonParse { - Repository.deserialize(it).apply { - this.id = getLong(getColumnIndex(ROW_ID)) - } - } - fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { return Json.factory.createParser(this).use { it.parseDictionary(callback) } }