From b646259f5d5dfa1f56f038da25341b20312519fb Mon Sep 17 00:00:00 2001 From: kitsunyan Date: Thu, 25 Jun 2020 23:02:28 +0300 Subject: [PATCH] Drop AppCompat --- build.gradle | 4 +- .../foxydroid/content/Preferences.kt | 3 +- .../screen/EditRepositoryFragment.kt | 4 +- .../foxydroid/screen/MessageDialog.kt | 2 +- .../foxydroid/screen/PreferencesFragment.kt | 279 ++++++++++++------ .../foxydroid/screen/ProductAdapter.kt | 49 +-- .../foxydroid/screen/ProductFragment.kt | 2 +- .../foxydroid/screen/ProductsAdapter.kt | 5 +- .../foxydroid/screen/ProductsFragment.kt | 2 +- .../foxydroid/screen/RepositoriesAdapter.kt | 4 +- .../foxydroid/screen/RepositoriesFragment.kt | 2 +- .../foxydroid/screen/RepositoryFragment.kt | 2 +- .../foxydroid/screen/ScreenActivity.kt | 8 +- .../foxydroid/screen/ScreenshotsFragment.kt | 5 +- .../foxydroid/screen/TabsFragment.kt | 173 +++++++---- .../foxydroid/service/ConnectionService.kt | 2 +- .../foxydroid/service/DownloadService.kt | 8 +- .../foxydroid/service/SyncService.kt | 8 +- .../nya/kitsunyan/foxydroid/utility/Utils.kt | 2 +- .../foxydroid/widget/RecyclerFastScroller.kt | 5 +- .../nya/kitsunyan/foxydroid/widget/Toolbar.kt | 33 +++ src/main/res/layout/edit_repository.xml | 7 +- src/main/res/layout/fragment.xml | 14 +- src/main/res/layout/link_item.xml | 3 +- src/main/res/layout/permissions_item.xml | 2 +- src/main/res/layout/preference_item.xml | 42 +++ src/main/res/layout/product_header_item.xml | 4 +- src/main/res/layout/product_item.xml | 2 +- src/main/res/layout/release_item.xml | 2 +- src/main/res/layout/repository_item.xml | 4 +- src/main/res/layout/section_item.xml | 3 +- src/main/res/layout/switch_item.xml | 4 +- src/main/res/layout/tabs_toolbar.xml | 10 +- src/main/res/values/attrs.xml | 6 + src/main/res/values/ids.xml | 1 + src/main/res/values/styles.xml | 30 +- 36 files changed, 496 insertions(+), 240 deletions(-) create mode 100644 src/main/kotlin/nya/kitsunyan/foxydroid/widget/Toolbar.kt create mode 100644 src/main/res/layout/preference_item.xml create mode 100644 src/main/res/values/attrs.xml diff --git a/build.gradle b/build.gradle index e87df3d..d1ba72a 100644 --- a/build.gradle +++ b/build.gradle @@ -116,8 +116,8 @@ repositories { dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin - implementation 'androidx.preference:preference:1.1.1' - implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.fragment:fragment:1.2.5' + implementation 'androidx.viewpager2:viewpager2:1.0.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' diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt index d839e44..1256617 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/content/Preferences.kt @@ -2,7 +2,6 @@ package nya.kitsunyan.foxydroid.content import android.content.Context import android.content.SharedPreferences -import androidx.preference.PreferenceManager import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.PublishSubject import nya.kitsunyan.foxydroid.R @@ -17,7 +16,7 @@ object Preferences { Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap() fun init(context: Context) { - preferences = PreferenceManager.getDefaultSharedPreferences(context) + preferences = context.getSharedPreferences("${context.packageName}_preferences", Context.MODE_PRIVATE) preferences.registerOnSharedPreferenceChangeListener { _, keyString -> keys[keyString]?.let(subject::onNext) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt index 5b2a08e..acf78f8 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/EditRepositoryFragment.kt @@ -1,5 +1,6 @@ package nya.kitsunyan.foxydroid.screen +import android.app.AlertDialog import android.content.ClipboardManager import android.content.Context import android.graphics.PorterDuff @@ -17,8 +18,7 @@ import android.view.ViewGroup import android.widget.EditText import android.widget.FrameLayout import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.widget.Toolbar +import android.widget.Toolbar import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt index f77c2dc..32e03bc 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/MessageDialog.kt @@ -1,11 +1,11 @@ package nya.kitsunyan.foxydroid.screen +import android.app.AlertDialog import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Parcel -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import nya.kitsunyan.foxydroid.R diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt index 86516ca..c295747 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/PreferencesFragment.kt @@ -1,38 +1,56 @@ package nya.kitsunyan.foxydroid.screen +import android.app.AlertDialog +import android.app.Dialog +import android.content.Context import android.os.Bundle import android.text.InputFilter import android.text.InputType import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.WindowManager +import android.widget.EditText import android.widget.FrameLayout -import androidx.appcompat.widget.Toolbar -import androidx.preference.EditTextPreference -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceGroup -import androidx.preference.SwitchPreference +import android.widget.LinearLayout +import android.widget.ScrollView +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.disposables.Disposable import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.content.Preferences +import nya.kitsunyan.foxydroid.utility.extension.resources.* -class PreferencesFragment: PreferenceFragmentCompat() { +class PreferencesFragment: Fragment() { + private val preferences = mutableMapOf, Preference<*>>() private var disposable: Disposable? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val view = inflater.inflate(R.layout.fragment, container, false) - val content = view.findViewById(R.id.fragment_content) - val child = super.onCreateView(LayoutInflater.from(content.context), content, savedInstanceState) - content.addView(child, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - return view + return inflater.inflate(R.layout.fragment, container, false) } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) - preferenceScreen.addCategory(getString(R.string.updates)).apply { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val toolbar = view.findViewById(R.id.toolbar) + screenActivity.onFragmentViewCreated(toolbar) + toolbar.setTitle(R.string.preferences) + + val content = view.findViewById(R.id.fragment_content) + val scroll = ScrollView(content.context) + scroll.id = R.id.preferences_list + scroll.isFillViewport = true + 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) + + 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) @@ -45,7 +63,7 @@ class PreferencesFragment: PreferenceFragmentCompat() { addSwitch(Preferences.Key.UpdateUnstable, getString(R.string.unstable_updates), getString(R.string.unstable_updates_summary)) } - preferenceScreen.addCategory(getString(R.string.proxy)).apply { + 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) @@ -56,7 +74,7 @@ class PreferencesFragment: PreferenceFragmentCompat() { addEditString(Preferences.Key.ProxyHost, getString(R.string.proxy_host)) addEditInt(Preferences.Key.ProxyPort, getString(R.string.proxy_port), 1 .. 65535) } - preferenceScreen.addCategory(getString(R.string.other)).apply { + preferences.addCategory(getString(R.string.other)) { addEnumeration(Preferences.Key.Theme, getString(R.string.theme)) { when (it) { is Preferences.Theme.Light -> getString(R.string.light) @@ -66,14 +84,6 @@ class PreferencesFragment: PreferenceFragmentCompat() { addSwitch(Preferences.Key.IncompatibleVersions, getString(R.string.incompatible_versions), getString(R.string.incompatible_versions_summary)) } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val toolbar = view.findViewById(R.id.toolbar) - screenActivity.onFragmentViewCreated(toolbar) - toolbar.setTitle(R.string.preferences) disposable = Preferences.observable.subscribe(this::updatePreference) updatePreference(null) @@ -82,75 +92,107 @@ class PreferencesFragment: PreferenceFragmentCompat() { override fun onDestroyView() { super.onDestroyView() + preferences.clear() disposable?.dispose() disposable = 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 } - findPreference(Preferences.Key.ProxyHost.name)!!.isEnabled = enabled - findPreference(Preferences.Key.ProxyPort.name)!!.isEnabled = enabled + preferences[Preferences.Key.ProxyHost]?.setEnabled(enabled) + preferences[Preferences.Key.ProxyPort]?.setEnabled(enabled) } if (key == Preferences.Key.Theme) { requireActivity().recreate() } } - private fun PreferenceGroup.addCategory(title: String): PreferenceCategory { - val preference = PreferenceCategory(context) - preference.isIconSpaceReserved = false - preference.title = title - addPreference(preference) + private fun LinearLayout.addDivider(full: Boolean): View { + val divider = View(context) + divider.background = context.getDrawableFromAttr(android.R.attr.listDivider) + addView(divider, LinearLayout.LayoutParams.MATCH_PARENT, divider.background.intrinsicHeight) + if (!full) { + (divider.layoutParams as LinearLayout.LayoutParams).apply { + marginStart = divider.resources.sizeScaled(16) + } + } + return divider + } + + private fun LinearLayout.addCategory(title: String, callback: LinearLayout.() -> Unit) { + val text = TextView(context) + text.typeface = TypefaceExtra.medium + text.setTextSizeScaled(14) + text.setTextColor(text.context.getColorFromAttr(android.R.attr.colorAccent)) + text.text = title + resources.sizeScaled(16).let { text.setPadding(it, it, it, 0) } + addView(text, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + callback() + val divider = addDivider(true) + // Negative margin for last 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) { + addDivider(false) + } + val preference = Preference(key, this@PreferencesFragment, this, title, summaryProvider, dialogProvider) + preferences[key] = preference return preference } - private fun PreferenceGroup.addSwitch(key: Preferences.Key, title: String, summary: String?) { - val preference = SwitchPreference(context) - preference.isIconSpaceReserved = false - preference.title = title - preference.summary = summary - preference.key = key.name - preference.setDefaultValue(key.default.value) - addPreference(preference) + private fun LinearLayout.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 PreferenceGroup.addEditString(key: Preferences.Key, title: String) { - val preference = EditTextPreference(context) - preference.isIconSpaceReserved = false - preference.title = title - preference.dialogTitle = title - preference.summaryProvider = Preference.SummaryProvider { it.text } - preference.key = key.name - preference.setDefaultValue(key.default.value) - addPreference(preference) - } - - private fun PreferenceGroup.addEditInt(key: Preferences.Key, title: String, range: IntRange?) { - val preference = object: EditTextPreference(context) { - override fun persistString(value: String?): Boolean { - val intValue = value.orEmpty().toIntOrNull() ?: key.default.value - val result = persistInt(intValue) - if (intValue.toString() != value) { - text = intValue.toString() + 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) + scroll.resources.sizeScaled(20).let { scroll.setPadding(it, 0, it, 0) } + val edit = EditText(it) + configureEdit(edit) + edit.id = android.R.id.edit + edit.setTextSizeScaled(16) + edit.resources.sizeScaled(16).let { edit.setPadding(edit.paddingLeft, it, edit.paddingRight, it) } + edit.setText(valueToString(Preferences[key])) + edit.hint = edit.text.toString() + edit.setSelection(edit.text.length) + edit.requestFocus() + scroll.addView(edit, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + AlertDialog.Builder(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) } - return result - } - - override fun onSetInitialValue(defaultValue: Any?) { - text = getPersistedInt((defaultValue as? Int) ?: key.default.value).toString() - } } - preference.isIconSpaceReserved = false - preference.title = title - preference.dialogTitle = title - preference.summaryProvider = Preference.SummaryProvider { it.text } - preference.key = key.name - preference.setDefaultValue(key.default.value) - preference.setOnBindEditTextListener { + } + + private fun LinearLayout.addEditString(key: Preferences.Key, title: String) { + addEdit(key, title, { it }, { it }, { }) + } + + 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 if (range != null) { it.filters = arrayOf(InputFilter { source, start, end, dest, dstart, dend -> @@ -160,23 +202,84 @@ class PreferencesFragment: PreferenceFragmentCompat() { }) } } - addPreference(preference) } - private fun > PreferenceGroup - .addEnumeration(key: Preferences.Key, title: String, valueString: (T) -> String) { - val preference = ListPreference(context) - preference.isIconSpaceReserved = false - preference.title = title - preference.dialogTitle = title - preference.summaryProvider = Preference.SummaryProvider { p -> - val index = p.entryValues.indexOfFirst { it == p.value } - if (index >= 0) p.entries[index] else valueString(key.default.value) + private fun > LinearLayout + .addEnumeration(key: Preferences.Key, title: String, valueToString: (T) -> String) { + addPreference(key, title, { valueToString(Preferences[key]) }) { + val values = key.default.value.values + AlertDialog.Builder(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 PreferencesFragment).preferences + val key = requireArguments().getString(EXTRA_KEY)!! + .let { name -> preferences.keys.find { it.name == name }!! } + val preference = preferences[key]!! + return preference.createDialog(requireContext()) } - preference.key = key.name - preference.setDefaultValue(key.default.value.valueString) - preference.entryValues = key.default.value.values.map { it.valueString }.toTypedArray() - preference.entries = key.default.value.values.map(valueString).toTypedArray() - addPreference(preference) } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt index bb56d8c..1a16f14 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductAdapter.kt @@ -33,11 +33,9 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.ProgressBar +import android.widget.Switch import android.widget.TextView import android.widget.Toast -import androidx.appcompat.widget.AppCompatImageView -import androidx.appcompat.widget.AppCompatTextView -import androidx.appcompat.widget.SwitchCompat import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.text.util.LinkifyCompat @@ -106,12 +104,12 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) private enum class SectionType(val titleResId: Int, val colorAttrResId: Int) { ANTI_FEATURES(R.string.anti_features, R.attr.colorError), - WHATS_NEW(R.string.whats_new, R.attr.colorAccent), - LINKS(R.string.links, R.attr.colorAccent), - DONATE(R.string.donate, R.attr.colorAccent), - PERMISSIONS(R.string.permissions, R.attr.colorAccent), - SCREENSHOTS(R.string.screenshots, R.attr.colorAccent), - RELEASES(R.string.releases, R.attr.colorAccent) + WHATS_NEW(R.string.whats_new, android.R.attr.colorAccent), + LINKS(R.string.links, android.R.attr.colorAccent), + DONATE(R.string.donate, android.R.attr.colorAccent), + PERMISSIONS(R.string.permissions, android.R.attr.colorAccent), + SCREENSHOTS(R.string.screenshots, android.R.attr.colorAccent), + RELEASES(R.string.releases, android.R.attr.colorAccent) } internal enum class ExpandType { NOTHING, DESCRIPTION, WHATS_NEW, @@ -300,10 +298,13 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) val progressIcon: Drawable val defaultIcon: Drawable - val actionTintNormal = action.context.getColorFromAttr(R.attr.colorAccent) + val actionTintNormal = action.context.getColorFromAttr(android.R.attr.colorAccent) val actionTintCancel = action.context.getColorFromAttr(R.attr.colorError) init { + if (Android.sdk(22)) { + action.setTextColor(action.context.getColorFromAttr(android.R.attr.colorBackground)) + } val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(icon.context) this.progressIcon = progressIcon this.defaultIcon = defaultIcon @@ -312,7 +313,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) private class SwitchViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { val title = itemView.findViewById(R.id.title)!! - val enabled = itemView.findViewById(R.id.enabled)!! + val enabled = itemView.findViewById(R.id.enabled)!! val statefulViews: Sequence get() = sequenceOf(itemView, title, enabled) @@ -323,7 +324,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) val icon = itemView.findViewById(R.id.icon)!! } - private class ExpandViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatTextView(context)) { + private class ExpandViewHolder(context: Context): RecyclerView.ViewHolder(TextView(context)) { val text: TextView get() = itemView as TextView @@ -331,8 +332,8 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) itemView as TextView itemView.typeface = TypefaceExtra.medium itemView.setTextSizeScaled(14) - itemView.setTextColor(itemView.context.getColorFromAttr(R.attr.colorAccent)) - itemView.background = itemView.context.getDrawableFromAttr(R.attr.selectableItemBackground) + itemView.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.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, @@ -340,7 +341,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) } } - private class TextViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatTextView(context)) { + private class TextViewHolder(context: Context): RecyclerView.ViewHolder(TextView(context)) { val text: TextView get() = itemView as TextView @@ -423,12 +424,12 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { itemView as FrameLayout - itemView.foreground = itemView.context.getDrawableFromAttr(R.attr.selectableItemBackground) + itemView.foreground = itemView.context.getDrawableFromAttr(android.R.attr.selectableItemBackground) val backgroundColor = itemView.context.getColorFromAttr(android.R.attr.colorBackground).defaultColor - val accentColor = itemView.context.getColorFromAttr(R.attr.colorAccent).defaultColor + val accentColor = itemView.context.getColorFromAttr(android.R.attr.colorAccent).defaultColor val primaryColor = itemView.context.getColorFromAttr(android.R.attr.textColorPrimary).defaultColor - image = object: AppCompatImageView(context) { + image = object: ImageView(context) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) setMeasuredDimension(measuredWidth, measuredWidth) @@ -465,7 +466,7 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) init { status.background = GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, null).apply { setStatusActive = { active -> color = itemView.context.getColorFromAttr(if (active) - R.attr.colorAccent else android.R.attr.textColorSecondary) } + android.R.attr.colorAccent else android.R.attr.textColorSecondary) } cornerRadius = itemView.resources.sizeScaled(2).toFloat() } } @@ -479,14 +480,14 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) itemView.orientation = LinearLayout.VERTICAL itemView.gravity = Gravity.CENTER itemView.resources.sizeScaled(20).let { itemView.setPadding(it, it, it, it) } - val title = AppCompatTextView(itemView.context) + val title = TextView(itemView.context) title.gravity = Gravity.CENTER title.typeface = TypefaceExtra.light 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) - val packageName = AppCompatTextView(itemView.context) + val packageName = TextView(itemView.context) packageName.gravity = Gravity.CENTER packageName.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) packageName.setTextSizeScaled(16) @@ -974,8 +975,10 @@ class ProductAdapter(private val callbacks: Callbacks, private val columns: Int) if (action != null) { holder.action.setText(action.titleResId) } - holder.action.backgroundTintList = if (action == Action.CANCEL) - holder.actionTintCancel else holder.actionTintNormal + if (Android.sdk(22)) { + holder.action.backgroundTintList = if (action == Action.CANCEL) + holder.actionTintCancel else holder.actionTintNormal + } holder.statusLayout.visibility = if (status != null) View.VISIBLE else View.GONE if (status != null) { when (status) { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt index 2dbf6a3..a3bf67c 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductFragment.kt @@ -12,7 +12,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.appcompat.widget.Toolbar +import android.widget.Toolbar import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt index 3ab8f9b..6d9e1fb 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsAdapter.kt @@ -10,7 +10,6 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView -import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import coil.api.clear import coil.api.load @@ -55,7 +54,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): } } - private class EmptyViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatTextView(context)) { + private class EmptyViewHolder(context: Context): RecyclerView.ViewHolder(TextView(context)) { val text: TextView get() = itemView as TextView @@ -143,7 +142,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit): resources.sizeScaled(4).let { setPadding(it, 0, it, 0) } setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorBackground)) background = GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, null).apply { - color = holder.status.context.getColorFromAttr(R.attr.colorAccent) + color = holder.status.context.getColorFromAttr(android.R.attr.colorAccent) cornerRadius = holder.status.resources.sizeScaled(2).toFloat() } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt index 811fb16..e75c37c 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ProductsFragment.kt @@ -73,7 +73,7 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return RecyclerView(container!!.context).apply { + return RecyclerView(requireContext()).apply { id = android.R.id.list layoutManager = LinearLayoutManager(context) isMotionEventSplittingEnabled = false diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt index f52cd9f..bec39b1 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesAdapter.kt @@ -2,8 +2,8 @@ package nya.kitsunyan.foxydroid.screen import android.view.View import android.view.ViewGroup +import android.widget.Switch import android.widget.TextView -import androidx.appcompat.widget.SwitchCompat import androidx.recyclerview.widget.RecyclerView import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.database.Database @@ -18,7 +18,7 @@ class RepositoriesAdapter(private val onClick: (Repository) -> Unit, private class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { val name = itemView.findViewById(R.id.name)!! - val enabled = itemView.findViewById(R.id.enabled)!! + val enabled = itemView.findViewById(R.id.enabled)!! var listenSwitch = true } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesFragment.kt index 8c72597..d130649 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoriesFragment.kt @@ -7,7 +7,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.appcompat.widget.Toolbar +import android.widget.Toolbar import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt index 104fc9e..c03739a 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/RepositoryFragment.kt @@ -13,7 +13,7 @@ import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.ScrollView import android.widget.TextView -import androidx.appcompat.widget.Toolbar +import android.widget.Toolbar import androidx.fragment.app.Fragment import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt index 4ee87a3..739e7d4 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenActivity.kt @@ -9,9 +9,9 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar +import android.widget.Toolbar import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import nya.kitsunyan.foxydroid.R import nya.kitsunyan.foxydroid.content.Cache import nya.kitsunyan.foxydroid.content.Preferences @@ -23,7 +23,7 @@ import nya.kitsunyan.foxydroid.utility.extension.resources.* import nya.kitsunyan.foxydroid.utility.extension.text.* import java.lang.ref.WeakReference -abstract class ScreenActivity: AppCompatActivity() { +abstract class ScreenActivity: FragmentActivity() { companion object { private const val STATE_FRAGMENT_STACK = "fragmentStack" } @@ -162,7 +162,7 @@ abstract class ScreenActivity: AppCompatActivity() { internal fun onFragmentViewCreated(toolbar: Toolbar?) { this.toolbar = toolbar?.let(::WeakReference) if (fragmentStack.isNotEmpty() && toolbar != null) { - toolbar.navigationIcon = toolbar.context.getDrawableFromAttr(R.attr.homeAsUpIndicator) + toolbar.navigationIcon = toolbar.context.getDrawableFromAttr(android.R.attr.homeAsUpIndicator) toolbar.setNavigationOnClickListener { onBackPressed() } } } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt index 9d0e7f1..6c457de 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/ScreenshotsFragment.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.ImageView -import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.fragment.app.DialogFragment @@ -75,7 +74,7 @@ class ScreenshotsFragment(): DialogFragment() { format = PixelFormat.TRANSLUCENT windowAnimations = run { val typedArray = dialog.context.obtainStyledAttributes(null, - intArrayOf(android.R.attr.windowAnimationStyle), R.attr.dialogTheme, 0) + intArrayOf(android.R.attr.windowAnimationStyle), android.R.attr.dialogTheme, 0) try { typedArray.getResourceId(0, 0) } finally { @@ -156,7 +155,7 @@ class ScreenshotsFragment(): DialogFragment() { StableRecyclerAdapter() { enum class ViewType { SCREENSHOT } - private class ViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatImageView(context)) { + private class ViewHolder(context: Context): RecyclerView.ViewHolder(ImageView(context)) { val image: ImageView get() = itemView as ImageView diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt index 355f009..08a743f 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/screen/TabsFragment.kt @@ -2,6 +2,12 @@ package nya.kitsunyan.foxydroid.screen import android.animation.ValueAnimator import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable import android.os.Bundle import android.view.Gravity import android.view.LayoutInflater @@ -12,17 +18,15 @@ import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.FrameLayout import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.SearchView import android.widget.TextView -import androidx.appcompat.widget.AppCompatTextView -import androidx.appcompat.widget.SearchView -import androidx.appcompat.widget.Toolbar -import androidx.core.view.MenuCompat +import android.widget.Toolbar import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentStatePagerAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager.widget.ViewPager -import com.google.android.material.tabs.TabLayout +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -34,6 +38,7 @@ import nya.kitsunyan.foxydroid.service.Connection import nya.kitsunyan.foxydroid.service.SyncService import nya.kitsunyan.foxydroid.utility.RxUtils import nya.kitsunyan.foxydroid.utility.Utils +import nya.kitsunyan.foxydroid.utility.extension.android.* import nya.kitsunyan.foxydroid.utility.extension.resources.* import nya.kitsunyan.foxydroid.utility.extension.text.* import nya.kitsunyan.foxydroid.widget.EnumRecyclerAdapter @@ -49,7 +54,7 @@ class TabsFragment: Fragment() { } private class Layout(view: View) { - val tabLayout = view.findViewById(R.id.tabs)!! + val tabs = view.findViewById(R.id.tabs)!! val categoryLayout = view.findViewById(R.id.category_layout)!! val categoryChange = view.findViewById(R.id.category_change)!! val categoryName = view.findViewById(R.id.category_name)!! @@ -57,17 +62,18 @@ class TabsFragment: Fragment() { } private var sortOrderMenu: Pair>? = null + private var syncRepositoriesMenuItem: MenuItem? = null private var layout: Layout? = null private var categoriesList: RecyclerView? = null - private var viewPager: ViewPager? = null + private var viewPager: ViewPager2? = null private var showCategories = false set(value) { if (field != value) { field = value val layout = layout - layout?.tabLayout?.let { (0 until it.tabCount) - .forEach { index -> it.getTabAt(index)!!.view.isEnabled = !value } } + layout?.tabs?.let { (0 until it.childCount) + .forEach { index -> it.getChildAt(index)!!.isEnabled = !value } } layout?.categoryIcon?.scaleY = if (value) -1f else 1f if ((categoriesList?.parent as? View)?.height ?: 0 > 0) { animateCategoriesList() @@ -127,7 +133,9 @@ class TabsFragment: Fragment() { }) toolbar.menu.apply { - MenuCompat.setGroupDividerEnabled(this, true) + if (Android.sdk(28)) { + setGroupDividerEnabled(true) + } add(0, R.id.toolbar_search, 0, R.string.search) .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_search)) @@ -138,21 +146,22 @@ class TabsFragment: Fragment() { .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_sort)) .let { menu -> menu.item.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS) - val items = ProductItem.Order.values().map { order -> menu - .add(order.titleResId) - .setOnMenuItemClickListener { item -> - this@TabsFragment.order = order - item.isChecked = true - productFragments.forEach { it.setOrder(order) } - true - } } + val items = ProductItem.Order.values().map { order -> + menu + .add(order.titleResId) + .setOnMenuItemClickListener { item -> + this@TabsFragment.order = order + item.isChecked = true + productFragments.forEach { it.setOrder(order) } + true + } + } menu.setGroupCheckable(0, true, true) Pair(menu.item, items) } - add(0, 0, 0, R.string.sync_repositories) + syncRepositoriesMenuItem = add(0, 0, 0, R.string.sync_repositories) .setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_sync)) - .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM) .setOnMenuItemClickListener { syncConnection.binder?.sync(SyncService.SyncRequest.MANUAL) true @@ -179,16 +188,27 @@ class TabsFragment: Fragment() { val layout = Layout(view) this.layout = layout - ProductsFragment.Source.values().forEach { layout.tabLayout - .addTab(layout.tabLayout.newTab().setText(it.titleResId)) } - layout.tabLayout.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - viewPager!!.currentItem = tab.position + layout.tabs.background = TabsBackgroundDrawable(layout.tabs.context, + layout.tabs.layoutDirection == View.LAYOUT_DIRECTION_RTL) + ProductsFragment.Source.values().forEach { + 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 + tab.gravity = Gravity.CENTER + tab.typeface = TypefaceExtra.medium + tab.setTextColor(ColorStateList(arrayOf(intArrayOf(android.R.attr.state_selected), intArrayOf()), + intArrayOf(selectedColor, normalColor))) + tab.setTextSizeScaled(14) + tab.isAllCaps = true + tab.text = getString(it.titleResId) + tab.background = tab.context.getDrawableFromAttr(android.R.attr.selectableItemBackground) + tab.setOnClickListener { _ -> + setSelectedTab(it) + viewPager!!.currentItem = it.ordinal } - - override fun onTabUnselected(tab: TabLayout.Tab) = Unit - override fun onTabReselected(tab: TabLayout.Tab) = Unit - }) + layout.tabs.addView(tab, 0, LinearLayout.LayoutParams.MATCH_PARENT) + (tab.layoutParams as LinearLayout.LayoutParams).weight = 1f + } showCategories = savedInstanceState?.getByte(STATE_SHOW_CATEGORIES)?.toInt() ?: 0 != 0 categories = savedInstanceState?.getStringArrayList(STATE_CATEGORIES).orEmpty() @@ -201,14 +221,16 @@ class TabsFragment: Fragment() { val content = view.findViewById(R.id.fragment_content) - viewPager = ViewPager(content.context).apply { + viewPager = ViewPager2(content.context).apply { id = R.id.fragment_pager - adapter = object: FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getItem(position: Int): Fragment = ProductsFragment(ProductsFragment.Source.values()[position]) - override fun getCount(): Int = ProductsFragment.Source.values().size + adapter = object: FragmentStateAdapter(this@TabsFragment) { + override fun getItemCount(): Int = ProductsFragment.Source.values().size + override fun createFragment(position: Int): Fragment = ProductsFragment(ProductsFragment + .Source.values()[position]) } content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - addOnPageChangeListener(pageChangeListener) + registerOnPageChangeCallback(pageChangeCallback) + offscreenPageLimit = 1 } categoriesDisposable = Observable.just(Unit) @@ -238,7 +260,7 @@ class TabsFragment: Fragment() { updateCategory() } } - setBackgroundColor(context.getColorFromAttr(R.attr.colorPrimaryDark).defaultColor) + setBackgroundColor(context.getColorFromAttr(android.R.attr.colorPrimaryDark).defaultColor) elevation = resources.sizeScaled(4).toFloat() content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, 0) visibility = View.GONE @@ -268,6 +290,7 @@ class TabsFragment: Fragment() { super.onDestroyView() sortOrderMenu = null + syncRepositoriesMenuItem = null layout = null categoriesList = null viewPager = null @@ -294,7 +317,7 @@ class TabsFragment: Fragment() { if (needSelectUpdates) { needSelectUpdates = false - selectUpdates() + selectUpdatesInternal(false) } } @@ -308,11 +331,19 @@ class TabsFragment: Fragment() { } } - internal fun selectUpdates() { - if (view == null) { - needSelectUpdates = true + private fun setSelectedTab(source: ProductsFragment.Source) { + val layout = layout!! + (0 until layout.tabs.childCount).forEach { layout.tabs.getChildAt(it).isSelected = it == source.ordinal } + } + + internal fun selectUpdates() = selectUpdatesInternal(true) + + private fun selectUpdatesInternal(allowSmooth: Boolean) { + if (view != null) { + val viewPager = viewPager + viewPager?.setCurrentItem(ProductsFragment.Source.UPDATES.ordinal, allowSmooth && viewPager.isLaidOut) } else { - layout?.tabLayout?.getTabAt(ProductsFragment.Source.UPDATES.ordinal)!!.select() + needSelectUpdates = true } } @@ -369,7 +400,7 @@ class TabsFragment: Fragment() { } } - private val pageChangeListener = object: ViewPager.OnPageChangeListener { + private val pageChangeCallback = object: ViewPager2.OnPageChangeCallback() { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { val layout = layout!! val fromCategories = ProductsFragment.Source.values()[position].categories @@ -380,14 +411,12 @@ class TabsFragment: Fragment() { } else { if (fromCategories) 1f else 0f } - if (layout.categoryLayout.childCount != 1) { - throw RuntimeException() - } + (layout.tabs.background as TabsBackgroundDrawable) + .update(position + positionOffset, layout.tabs.childCount) + assert(layout.categoryLayout.childCount == 1) val child = layout.categoryLayout.getChildAt(0) val height = child.layoutParams.height - if (height <= 0) { - throw RuntimeException() - } + assert(height > 0) val currentHeight = (offset * height).roundToInt() if (layout.categoryLayout.layoutParams.height != currentHeight) { layout.categoryLayout.layoutParams.height = currentHeight @@ -396,27 +425,63 @@ class TabsFragment: Fragment() { } override fun onPageSelected(position: Int) { - val layout = layout!! val source = ProductsFragment.Source.values()[position] updateUpdateNotificationBlocker(source) sortOrderMenu!!.first.isVisible = source.order - layout.tabLayout.selectTab(layout.tabLayout.getTabAt(source.ordinal)) + syncRepositoriesMenuItem!!.setShowAsActionFlags(if (!source.order || + resources.configuration.screenWidthDp >= 480) MenuItem.SHOW_AS_ACTION_ALWAYS else 0) + setSelectedTab(source) if (showCategories && !source.categories) { showCategories = false } } override fun onPageScrollStateChanged(state: Int) { - layout!!.categoryChange.isEnabled = state != ViewPager.SCROLL_STATE_DRAGGING && + layout!!.categoryChange.isEnabled = state != ViewPager2.SCROLL_STATE_DRAGGING && ProductsFragment.Source.values()[viewPager!!.currentItem].categories } } + private class TabsBackgroundDrawable(context: Context, private val rtl: Boolean): Drawable() { + private val height = context.resources.sizeScaled(2) + private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getColorFromAttr(android.R.attr.colorAccent).defaultColor + } + + private var position = 0f + private var total = 0 + + fun update(position: Float, total: Int) { + this.position = position + this.total = total + invalidateSelf() + } + + override fun draw(canvas: Canvas) { + if (total > 0) { + val bounds = bounds + val width = bounds.width() / total.toFloat() + val x = width * position + if (rtl) { + canvas.drawRect(bounds.right - width - x, (bounds.bottom - height).toFloat(), + bounds.right - x, bounds.bottom.toFloat(), paint) + } else { + canvas.drawRect(bounds.left + x, (bounds.bottom - height).toFloat(), + bounds.left + x + width, bounds.bottom.toFloat(), paint) + } + } + } + + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun getOpacity(): Int = PixelFormat.TRANSLUCENT + } + private class CategoriesAdapter(private val categories: () -> List, private val onClick: (String) -> Unit): EnumRecyclerAdapter() { enum class ViewType { CATEGORY } - private class CategoryViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatTextView(context)) { + private class CategoryViewHolder(context: Context): RecyclerView.ViewHolder(TextView(context)) { val title: TextView get() = itemView as TextView @@ -426,7 +491,7 @@ class TabsFragment: Fragment() { itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) } itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary)) itemView.setTextSizeScaled(16) - itemView.background = context.getDrawableFromAttr(R.attr.selectableItemBackground) + itemView.background = context.getDrawableFromAttr(android.R.attr.selectableItemBackground) itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, itemView.resources.sizeScaled(48)) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/service/ConnectionService.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/service/ConnectionService.kt index f3b35a3..75b1412 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/service/ConnectionService.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/service/ConnectionService.kt @@ -3,7 +3,7 @@ package nya.kitsunyan.foxydroid.service import android.app.Service import android.content.Intent import android.os.IBinder -import nya.kitsunyan.foxydroid.utility.extension.android.Android +import nya.kitsunyan.foxydroid.utility.extension.android.* abstract class ConnectionService: Service() { abstract override fun onBind(intent: Intent): T diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt index a4fdfeb..3214bcb 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/service/DownloadService.kt @@ -7,7 +7,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.net.Uri -import androidx.appcompat.view.ContextThemeWrapper +import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable @@ -188,7 +188,7 @@ class DownloadService: ConnectionService() { .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .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)) .apply { @@ -223,7 +223,7 @@ class DownloadService: ConnectionService() { .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_download_done) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .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)) @@ -286,7 +286,7 @@ class DownloadService: ConnectionService() { .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) .setSmallIcon(android.R.drawable.stat_sys_download) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .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)) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt index 8f01391..138d5bc 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/service/SyncService.kt @@ -9,7 +9,7 @@ import android.content.Intent import android.graphics.Color import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan -import androidx.appcompat.view.ContextThemeWrapper +import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -200,7 +200,7 @@ class SyncService: ConnectionService() { .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .getColorFromAttr(android.R.attr.colorAccent).defaultColor) .setContentTitle(getString(R.string.error_syncing_format, repository.name)) .setContentText(getString(when (exception) { is RepositoryUpdater.UpdateException -> when (exception.errorType) { @@ -218,7 +218,7 @@ class SyncService: ConnectionService() { .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(R.drawable.ic_sync) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .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)) } @@ -353,7 +353,7 @@ class SyncService: ConnectionService() { .setContentText(resources.getQuantityString(R.plurals.new_updates_description_format, productItems.size, productItems.size)) .setColor(ContextThemeWrapper(this, R.style.Theme_Main_Light) - .getColorFromAttr(R.attr.colorAccent).defaultColor) + .getColorFromAttr(android.R.attr.colorAccent).defaultColor) .setContentIntent(PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java) .setAction(MainActivity.ACTION_UPDATES), PendingIntent.FLAG_UPDATE_CURRENT)) .setStyle(NotificationCompat.InboxStyle().applyHack { diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt index 6e2f7be..958c944 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/utility/Utils.kt @@ -24,7 +24,7 @@ object Utils { fun getDefaultApplicationIcons(context: Context): Pair { val progressIcon: Drawable = createDefaultApplicationIcon(context, android.R.attr.textColorSecondary) - val defaultIcon: Drawable = createDefaultApplicationIcon(context, R.attr.colorAccent) + val defaultIcon: Drawable = createDefaultApplicationIcon(context, android.R.attr.colorAccent) return Pair(progressIcon, defaultIcon) } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt index 4e7bffd..a84dd97 100644 --- a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/RecyclerFastScroller.kt @@ -5,6 +5,7 @@ import android.graphics.Canvas import android.graphics.Rect import android.os.SystemClock import android.view.MotionEvent +import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import nya.kitsunyan.foxydroid.utility.extension.resources.* @@ -23,7 +24,7 @@ class RecyclerFastScroller(private val recyclerView: RecyclerView) { private val thumbDrawable = recyclerView.context.getDrawableFromAttr(android.R.attr.fastScrollThumbDrawable) private val trackDrawable = recyclerView.context.getDrawableFromAttr(android.R.attr.fastScrollTrackDrawable) - private val minTrackSize = recyclerView.resources.sizeScaled(32) + private val minTrackSize = recyclerView.resources.sizeScaled(16) private data class FastScrolling(val startAtThumbOffset: Float?, val startY: Float, val currentY: Float) @@ -134,6 +135,7 @@ class RecyclerFastScroller(private val recyclerView: RecyclerView) { val atThumbVertical = if (rtl) event.x <= trackWidth else event.x >= recyclerView.width - trackWidth atThumbVertical && run { withScroll { itemHeight, thumbHeight, range -> + (recyclerView.parent as? ViewGroup)?.requestDisallowInterceptTouchEvent(true) val offset = currentOffset(itemHeight, range) val thumbY = ((recyclerView.height - thumbHeight) * offset).roundToInt() val atThumb = event.y >= thumbY && event.y <= thumbY + thumbHeight @@ -153,6 +155,7 @@ class RecyclerFastScroller(private val recyclerView: RecyclerView) { } val cancel = event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL if (!success || cancel) { + (recyclerView.parent as? ViewGroup)?.requestDisallowInterceptTouchEvent(false) updateState(scrolling, null) recyclerView.invalidate() } diff --git a/src/main/kotlin/nya/kitsunyan/foxydroid/widget/Toolbar.kt b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/Toolbar.kt new file mode 100644 index 0000000..3ea53fe --- /dev/null +++ b/src/main/kotlin/nya/kitsunyan/foxydroid/widget/Toolbar.kt @@ -0,0 +1,33 @@ +package nya.kitsunyan.foxydroid.widget + +import android.content.Context +import android.util.AttributeSet +import android.widget.Toolbar + +class Toolbar: Toolbar { + constructor(context: Context): super(context) + constructor(context: Context, attrs: AttributeSet?): super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, + defStyleRes: Int): super(context, attrs, defStyleAttr, defStyleRes) + + private var initalized = false + private var layoutDirectionChanged: Int? = null + + init { + initalized = true + val layoutDirection = layoutDirectionChanged + layoutDirectionChanged = null + if (layoutDirection != null) { + onRtlPropertiesChanged(layoutDirection) + } + } + + override fun onRtlPropertiesChanged(layoutDirection: Int) { + if (initalized) { + super.onRtlPropertiesChanged(layoutDirection) + } else { + layoutDirectionChanged = layoutDirection + } + } +} diff --git a/src/main/res/layout/edit_repository.xml b/src/main/res/layout/edit_repository.xml index 92037d4..3a7f41b 100644 --- a/src/main/res/layout/edit_repository.xml +++ b/src/main/res/layout/edit_repository.xml @@ -55,7 +55,8 @@ android:scaleType="center" android:src="@drawable/ic_arrow_down" android:tint="?android:attr/textColorSecondary" - android:background="?attr/actionBarItemBackground" + android:tintMode="src_in" + android:background="?android:attr/actionBarItemBackground" android:visibility="gone" tools:ignore="ContentDescription" /> @@ -91,7 +92,7 @@ android:paddingTop="16dp" android:paddingBottom="16dp" android:gravity="top" - android:typeface="monospace" + android:fontFamily="monospace" android:textSize="16sp" android:inputType="textNoSuggestions|textMultiLine" tools:ignore="Autofill,LabelFor" /> @@ -208,7 +209,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" - style="@style/Widget.AppCompat.Button.Borderless.Colored" + style="@android:style/Widget.Material.Button.Borderless.Colored" android:text="@string/skip" /> diff --git a/src/main/res/layout/fragment.xml b/src/main/res/layout/fragment.xml index 441c2b6..34bf5fd 100644 --- a/src/main/res/layout/fragment.xml +++ b/src/main/res/layout/fragment.xml @@ -1,28 +1,30 @@ - + android:orientation="vertical" + android:theme="?android:attr/actionBarTheme" + android:background="?android:attr/colorPrimary" + android:elevation="4dp"> - + android:popupTheme="@style/Theme.Toolbar.Popup" /> - + + android:background="?android:attr/selectableItemBackground"> + android:background="?android:attr/selectableItemBackground"> + + + + + + + + + + + + + diff --git a/src/main/res/layout/product_header_item.xml b/src/main/res/layout/product_header_item.xml index d6c4953..d1c1bbb 100644 --- a/src/main/res/layout/product_header_item.xml +++ b/src/main/res/layout/product_header_item.xml @@ -66,7 +66,7 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_gravity="bottom" - style="@style/Widget.AppCompat.Button.Colored" /> + style="@android:style/Widget.Material.Button" /> @@ -94,7 +94,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="2dp" - style="@style/Widget.AppCompat.ProgressBar.Horizontal" /> + style="@android:style/Widget.Material.ProgressBar.Horizontal" /> diff --git a/src/main/res/layout/product_item.xml b/src/main/res/layout/product_item.xml index 86670f7..e445002 100644 --- a/src/main/res/layout/product_item.xml +++ b/src/main/res/layout/product_item.xml @@ -8,7 +8,7 @@ android:paddingStart="14dp" android:paddingEnd="16dp" android:gravity="center_vertical" - android:background="?attr/selectableItemBackground"> + android:background="?android:attr/selectableItemBackground"> + android:background="?android:attr/selectableItemBackground"> + android:background="?android:attr/selectableItemBackground"> - + android:background="?android:attr/selectableItemBackground"> diff --git a/src/main/res/layout/switch_item.xml b/src/main/res/layout/switch_item.xml index bbbee5a..b799dc3 100644 --- a/src/main/res/layout/switch_item.xml +++ b/src/main/res/layout/switch_item.xml @@ -5,7 +5,7 @@ android:layout_height="48dp" android:orientation="horizontal" android:gravity="center_vertical" - android:background="?attr/selectableItemBackground"> + android:background="?android:attr/selectableItemBackground"> - - + android:layout_height="48dp" + android:orientation="horizontal" /> + + + + + diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml index 9320cb4..156269c 100644 --- a/src/main/res/values/ids.xml +++ b/src/main/res/values/ids.xml @@ -5,6 +5,7 @@ + diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index e0e8eb0..eb5ed2a 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -1,30 +1,30 @@ - -