Change sorting order of installed extensions

Default is Name
Other options are Recently updated, Recently installed, and Language
This commit is contained in:
Jays2Kings 2021-08-11 22:15:52 -04:00
parent fa80fa94a0
commit 7e812f9530
12 changed files with 203 additions and 15 deletions

View File

@ -152,6 +152,8 @@ object PreferenceKeys {
const val automaticExtUpdates = "automatic_ext_updates" const val automaticExtUpdates = "automatic_ext_updates"
const val installedExtensionsOrder = "installed_extensions_order"
const val autoHideHopper = "autohide_hopper" const val autoHideHopper = "autohide_hopper"
const val hopperLongPress = "hopper_long_press" const val hopperLongPress = "hopper_long_press"

View File

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.updater.AutoUpdaterJob import eu.kanade.tachiyomi.data.updater.AutoUpdaterJob
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
import eu.kanade.tachiyomi.ui.reader.settings.PageLayout import eu.kanade.tachiyomi.ui.reader.settings.PageLayout
@ -297,6 +298,8 @@ class PreferencesHelper(val context: Context) {
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true) fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
fun installedExtensionsOrder() = flowPrefs.getInt(Keys.installedExtensionsOrder, InstalledExtensionsOrder.Name.value)
fun collapsedCategories() = rxPrefs.getStringSet("collapsed_categories", mutableSetOf()) fun collapsedCategories() = rxPrefs.getStringSet("collapsed_categories", mutableSetOf())
fun collapsedDynamicCategories() = flowPrefs.getStringSet("collapsed_dynamic_categories", mutableSetOf()) fun collapsedDynamicCategories() = flowPrefs.getStringSet("collapsed_dynamic_categories", mutableSetOf())

View File

@ -0,0 +1,18 @@
package eu.kanade.tachiyomi.extension.model
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
enum class InstalledExtensionsOrder(val value: Int, @StringRes val nameRes: Int) {
Name(0, R.string.name),
RecentlyUpdated(1, R.string.recently_updated),
RecentlyInstalled(2, R.string.recently_installed),
Language(3, R.string.language),
;
companion object {
fun fromValue(preference: Int) = values().find { it.value == preference } ?: Name
fun fromPreference(pref: PreferencesHelper) = fromValue(pref.installedExtensionsOrder().get())
}
}

View File

@ -1,8 +1,11 @@
package eu.kanade.tachiyomi.ui.extension package eu.kanade.tachiyomi.ui.extension
import android.widget.TextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.extension.ExtensionAdapter.OnButtonClickListener import eu.kanade.tachiyomi.ui.extension.ExtensionAdapter.OnButtonClickListener
import uy.kohesive.injekt.injectLazy
/** /**
* Adapter that holds the catalogue cards. * Adapter that holds the catalogue cards.
@ -12,6 +15,10 @@ import eu.kanade.tachiyomi.ui.extension.ExtensionAdapter.OnButtonClickListener
class ExtensionAdapter(val listener: OnButtonClickListener) : class ExtensionAdapter(val listener: OnButtonClickListener) :
FlexibleAdapter<IFlexible<*>>(null, listener, true) { FlexibleAdapter<IFlexible<*>>(null, listener, true) {
val preferences: PreferencesHelper by injectLazy()
var installedSortOrder = preferences.installedExtensionsOrder().get()
init { init {
setDisplayHeadersAtStartUp(true) setDisplayHeadersAtStartUp(true)
} }
@ -25,5 +32,6 @@ class ExtensionAdapter(val listener: OnButtonClickListener) :
fun onButtonClick(position: Int) fun onButtonClick(position: Int)
fun onCancelClick(position: Int) fun onCancelClick(position: Int)
fun onUpdateAllClicked(position: Int) fun onUpdateAllClicked(position: Int)
fun onExtSortClicked(view: TextView, position: Int)
} }
} }

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.ExtensionsChangedListener import eu.kanade.tachiyomi.extension.ExtensionsChangedListener
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -24,7 +25,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -181,11 +181,25 @@ class ExtensionBottomPresenter(
val items = mutableListOf<ExtensionItem>() val items = mutableListOf<ExtensionItem>()
val updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.pkgName } val updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.name }
val sortOrder = InstalledExtensionsOrder.fromPreference(preferences)
val installedSorted = installed val installedSorted = installed
.filter { !it.hasUpdate && (showNsfwExtensions || !it.isNsfw) } .filter { !it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }
.sortedWith(compareBy({ !it.isObsolete }, { it.pkgName })) .sortedWith(
val untrustedSorted = untrusted.sortedBy { it.pkgName } compareBy(
{ !it.isObsolete },
{
when (sortOrder) {
InstalledExtensionsOrder.Name -> it.name
InstalledExtensionsOrder.RecentlyUpdated -> Long.MAX_VALUE - extensionUpdateDate(it.pkgName)
InstalledExtensionsOrder.RecentlyInstalled -> Long.MAX_VALUE - extensionInstallDate(it.pkgName)
InstalledExtensionsOrder.Language -> it.lang
}
},
{ it.name }
)
)
val untrustedSorted = untrusted.sortedBy { it.name }
val availableSorted = available val availableSorted = available
// Filter out already installed extensions and disabled languages // Filter out already installed extensions and disabled languages
.filter { avail -> .filter { avail ->
@ -194,7 +208,7 @@ class ExtensionBottomPresenter(
(avail.lang in activeLangs || avail.lang == "all") && (avail.lang in activeLangs || avail.lang == "all") &&
(showNsfwExtensions || !avail.isNsfw) (showNsfwExtensions || !avail.isNsfw)
} }
.sortedBy { it.pkgName } .sortedBy { it.name }
if (updatesSorted.isNotEmpty()) { if (updatesSorted.isNotEmpty()) {
val header = ExtensionGroupItem( val header = ExtensionGroupItem(
@ -211,7 +225,7 @@ class ExtensionBottomPresenter(
} }
} }
if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) { if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) {
val header = ExtensionGroupItem(context.getString(R.string.installed), installedSorted.size + untrustedSorted.size) val header = ExtensionGroupItem(context.getString(R.string.installed), installedSorted.size + untrustedSorted.size, installedSorting = preferences.installedExtensionsOrder().get())
items += installedSorted.map { extension -> items += installedSorted.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName]) ExtensionItem(extension, header, currentDownloads[extension.pkgName])
} }
@ -237,8 +251,25 @@ class ExtensionBottomPresenter(
return items return items
} }
private fun extensionInstallDate(pkgName: String): Long {
val context = bottomSheet.context
return try {
context.packageManager.getPackageInfo(pkgName, 0).firstInstallTime
} catch (e: java.lang.Exception) {
0
}
}
private fun extensionUpdateDate(pkgName: String): Long {
val context = bottomSheet.context
return try {
context.packageManager.getPackageInfo(pkgName, 0).lastUpdateTime
} catch (e: java.lang.Exception) {
0
}
}
fun getExtensionUpdateCount(): Int = preferences.extensionUpdatesCount().getOrDefault() fun getExtensionUpdateCount(): Int = preferences.extensionUpdatesCount().getOrDefault()
fun getAutoCheckPref() = preferences.automaticExtUpdates()
@Synchronized @Synchronized
private fun updateInstallStep( private fun updateInstallStep(

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -18,6 +19,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.databinding.ExtensionsBottomSheetBinding import eu.kanade.tachiyomi.databinding.ExtensionsBottomSheetBinding
import eu.kanade.tachiyomi.databinding.RecyclerWithScrollerBinding import eu.kanade.tachiyomi.databinding.RecyclerWithScrollerBinding
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.ui.extension.details.ExtensionDetailsController import eu.kanade.tachiyomi.ui.extension.details.ExtensionDetailsController
import eu.kanade.tachiyomi.ui.migration.MangaAdapter import eu.kanade.tachiyomi.ui.migration.MangaAdapter
import eu.kanade.tachiyomi.ui.migration.MangaItem import eu.kanade.tachiyomi.ui.migration.MangaItem
@ -30,6 +32,7 @@ import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.popupMenu
import eu.kanade.tachiyomi.util.view.smoothScrollToTop import eu.kanade.tachiyomi.util.view.smoothScrollToTop
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
@ -217,6 +220,18 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
} }
} }
override fun onExtSortClicked(view: TextView, position: Int) {
view.popupMenu(
InstalledExtensionsOrder.values().map { it.value to it.nameRes },
presenter.preferences.installedExtensionsOrder().get()
) {
presenter.preferences.installedExtensionsOrder().set(itemId)
extAdapter?.installedSortOrder = itemId
view.setText(InstalledExtensionsOrder.fromValue(itemId).nameRes)
presenter.refreshExtensions()
}
}
fun updateAllExtensions(position: Int) { fun updateAllExtensions(position: Int) {
val header = (extAdapter?.getSectionHeader(position)) as? ExtensionGroupItem ?: return val header = (extAdapter?.getSectionHeader(position)) as? ExtensionGroupItem ?: return
val items = extAdapter?.getSectionItemPositions(header) val items = extAdapter?.getSectionItemPositions(header)

View File

@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.ExtensionCardHeaderBinding import eu.kanade.tachiyomi.databinding.ExtensionCardHeaderBinding
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
@ -19,6 +20,9 @@ class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<IFlexible<Recycl
binding.extButton.setOnClickListener { binding.extButton.setOnClickListener {
(adapter as? ExtensionAdapter)?.listener?.onUpdateAllClicked(bindingAdapterPosition) (adapter as? ExtensionAdapter)?.listener?.onUpdateAllClicked(bindingAdapterPosition)
} }
binding.extSort.setOnClickListener {
(adapter as? ExtensionAdapter)?.listener?.onExtSortClicked(binding.extSort, bindingAdapterPosition)
}
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -26,5 +30,9 @@ class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<IFlexible<Recycl
binding.title.text = item.name binding.title.text = item.name
binding.extButton.isVisible = item.canUpdate != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S binding.extButton.isVisible = item.canUpdate != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
binding.extButton.isEnabled = item.canUpdate == true binding.extButton.isEnabled = item.canUpdate == true
binding.extSort.isVisible = item.installedSorting != null
binding.extSort.setText(InstalledExtensionsOrder.fromValue(item.installedSorting ?: 0).nameRes)
binding.extSort.post {
}
} }
} }

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.R
* @param name The header name. * @param name The header name.
* @param size The number of items in the group. * @param size The number of items in the group.
*/ */
data class ExtensionGroupItem(val name: String, val size: Int, var canUpdate: Boolean? = null) : AbstractHeaderItem<ExtensionGroupHolder>() { data class ExtensionGroupItem(val name: String, val size: Int, var canUpdate: Boolean? = null, var installedSorting: Int? = null) : AbstractHeaderItem<ExtensionGroupHolder>() {
/** /**
* Returns the layout resource of this item. * Returns the layout resource of this item.

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.extension
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.view.View import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.clear
import coil.load import coil.load
@ -14,6 +15,8 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder
import eu.kanade.tachiyomi.util.system.timeSpanFromNow
import eu.kanade.tachiyomi.util.view.resetStrokeColor import eu.kanade.tachiyomi.util.view.resetStrokeColor
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -41,7 +44,34 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
// Set source name // Set source name
binding.extTitle.text = extension.name binding.extTitle.text = extension.name
binding.version.text = extension.versionName
val infoText = mutableListOf(extension.versionName)
if (extension is Extension.Installed) {
when (InstalledExtensionsOrder.fromValue(adapter.installedSortOrder)) {
InstalledExtensionsOrder.RecentlyUpdated -> {
binding.date.isVisible = true
binding.date.text = itemView.context.getString(
R.string.updated_,
extensionUpdateDate(extension.pkgName).timeSpanFromNow
)
infoText.add("")
}
InstalledExtensionsOrder.RecentlyInstalled -> {
binding.date.isVisible = true
binding.date.text = itemView.context.getString(
R.string.installed_,
extensionInstallDate(extension.pkgName).timeSpanFromNow
)
infoText.add("")
}
else -> binding.date.isVisible = false
}
} else {
binding.date.isVisible = false
}
binding.lang.isVisible = binding.date.isGone
binding.version.text = infoText.joinToString("")
binding.lang.text = LocaleHelper.getDisplayName(extension.lang) binding.lang.text = LocaleHelper.getDisplayName(extension.lang)
binding.warning.text = when { binding.warning.text = when {
extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted) extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted)
@ -113,4 +143,22 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
setText(R.string.install) setText(R.string.install)
} }
} }
private fun extensionInstallDate(pkgName: String): Long {
val context = itemView.context
return try {
context.packageManager.getPackageInfo(pkgName, 0).firstInstallTime
} catch (e: java.lang.Exception) {
0
}
}
private fun extensionUpdateDate(pkgName: String): Long {
val context = itemView.context
return try {
context.packageManager.getPackageInfo(pkgName, 0).lastUpdateTime
} catch (e: java.lang.Exception) {
0
}
}
} }

View File

@ -33,7 +33,7 @@
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/accent_text_btn_color_selector" android:textColor="@color/accent_text_btn_color_selector"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/title" app:layout_constraintBaseline_toBaselineOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -43,6 +43,34 @@
app:rippleColor="@color/fullRippleColor" app:rippleColor="@color/fullRippleColor"
android:text="@string/update_all" /> android:text="@string/update_all" />
<TextView
android:id="@+id/ext_sort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:background="@drawable/square_ripple"
android:clickable="true"
android:drawablePadding="6dp"
app:drawableTint="?android:textColorPrimary"
android:ellipsize="start"
android:focusable="true"
android:gravity="center|end"
android:maxLines="2"
android:padding="6dp"
android:textAlignment="textEnd"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBaseline_toBaselineOf="@id/title"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/title"
app:layout_constraintTop_toTopOf="@id/title"
tools:text="Recently Updated"
app:drawableEndCompat="@drawable/ic_sort_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@ -49,7 +49,7 @@
android:maxLines="1" android:maxLines="1"
android:textAppearance="@style/TextAppearance.Regular.SubHeading" android:textAppearance="@style/TextAppearance.Regular.SubHeading"
android:textSize="14sp" android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/lang" app:layout_constraintBottom_toTopOf="@id/version"
app:layout_constraintEnd_toStartOf="@id/button_layout" app:layout_constraintEnd_toStartOf="@id/button_layout"
app:layout_constraintStart_toEndOf="@id/source_image" app:layout_constraintStart_toEndOf="@id/source_image"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -63,8 +63,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxLines="1" android:maxLines="1"
android:textSize="12sp" android:textSize="12sp"
android:layout_marginEnd="4dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/source_image" app:layout_constraintStart_toEndOf="@id/source_image"
app:layout_constraintEnd_toStartOf="@id/version"
app:layout_constraintTop_toBottomOf="@+id/ext_title" app:layout_constraintTop_toBottomOf="@+id/ext_title"
tools:text="English" tools:text="English"
tools:visibility="visible" /> tools:visibility="visible" />
@ -74,13 +79,28 @@
style="@style/TextAppearance.Regular.Body1.Secondary" style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="middle"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBaseline_toBaselineOf="@id/lang" app:layout_constraintTop_toBottomOf="@+id/ext_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/lang" app:layout_constraintStart_toEndOf="@id/lang"
tools:text="Version" /> app:layout_constraintEnd_toStartOf="@id/date"
tools:text="Version • " />
<TextView
android:id="@+id/date"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="start"
app:layout_constrainedWidth="true"
android:textSize="12sp"
app:layout_constraintTop_toBottomOf="@+id/ext_title"
app:layout_constraintStart_toEndOf="@id/version"
app:layout_constraintEnd_toStartOf="@id/warning"
tools:text="Updated 5 days ago" />
<TextView <TextView
android:id="@+id/warning" android:id="@+id/warning"
@ -91,8 +111,11 @@
android:maxLines="1" android:maxLines="1"
android:textColor="?attr/colorError" android:textColor="?attr/colorError"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/version" android:layout_marginEnd="6dp"
app:layout_constraintEnd_toStartOf="@id/button_layout"
app:layout_constraintTop_toBottomOf="@+id/ext_title" app:layout_constraintTop_toBottomOf="@+id/ext_title"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/date"
tools:text="Warning" /> tools:text="Warning" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View File

@ -312,6 +312,7 @@
<string name="version_">Version: %1$s</string> <string name="version_">Version: %1$s</string>
<string name="language_">Language: %1$s</string> <string name="language_">Language: %1$s</string>
<string name="nsfw_short">18+</string> <string name="nsfw_short">18+</string>
<string name="installed_">Installed %1$s</string>
<string name="unofficial">Unofficial</string> <string name="unofficial">Unofficial</string>
<string name="extensions_miui_warning">MIUI Optimization must be disabled to install extensions.</string> <string name="extensions_miui_warning">MIUI Optimization must be disabled to install extensions.</string>
<string name="may_contain_nsfw">May contain NSFW (18+) content</string> <string name="may_contain_nsfw">May contain NSFW (18+) content</string>
@ -963,6 +964,9 @@
<string name="move_to_top">Move to top</string> <string name="move_to_top">Move to top</string>
<string name="move_to_">Move to %1$s</string> <string name="move_to_">Move to %1$s</string>
<string name="moved_to_">Moved to %1$s</string> <string name="moved_to_">Moved to %1$s</string>
<string name="name">Name</string>
<string name="recently_updated">Recently updated</string>
<string name="recently_installed">Recently installed</string>
<string name="never">Never</string> <string name="never">Never</string>
<string name="newest">Newest</string> <string name="newest">Newest</string>
<string name="next">Next</string> <string name="next">Next</string>