mirror of
https://github.com/Michatec/michas-droid.git
synced 2026-05-30 18:02:43 +02:00
Allow to sort by date added and last update
This commit is contained in:
@@ -5,22 +5,26 @@ import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.Loader
|
||||
import nya.kitsunyan.foxydroid.entity.ProductItem
|
||||
|
||||
class CursorOwner: Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
||||
sealed class Request {
|
||||
internal abstract val id: Int
|
||||
|
||||
data class ProductsAvailable(val searchQuery: String, val category: String): Request() {
|
||||
data class ProductsAvailable(val searchQuery: String, val category: String,
|
||||
val order: ProductItem.Order): Request() {
|
||||
override val id: Int
|
||||
get() = 1
|
||||
}
|
||||
|
||||
data class ProductsInstalled(val searchQuery: String, val category: String): Request() {
|
||||
data class ProductsInstalled(val searchQuery: String, val category: String,
|
||||
val order: ProductItem.Order): Request() {
|
||||
override val id: Int
|
||||
get() = 2
|
||||
}
|
||||
|
||||
data class ProductsUpdates(val searchQuery: String, val category: String): Request() {
|
||||
data class ProductsUpdates(val searchQuery: String, val category: String,
|
||||
val order: ProductItem.Order): Request() {
|
||||
override val id: Int
|
||||
get() = 3
|
||||
}
|
||||
@@ -75,11 +79,11 @@ class CursorOwner: Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
||||
return QueryLoader(requireContext()) {
|
||||
when (request) {
|
||||
is Request.ProductsAvailable -> Database.ProductAdapter
|
||||
.query(false, false, request.searchQuery, request.category, it)
|
||||
.query(false, false, request.searchQuery, request.category, request.order, it)
|
||||
is Request.ProductsInstalled -> Database.ProductAdapter
|
||||
.query(true, false, request.searchQuery, request.category, it)
|
||||
.query(true, false, request.searchQuery, request.category, request.order, it)
|
||||
is Request.ProductsUpdates -> Database.ProductAdapter
|
||||
.query(true, true, request.searchQuery, request.category, it)
|
||||
.query(true, true, request.searchQuery, request.category, request.order, it)
|
||||
is Request.Repositories -> Database.RepositoryAdapter.query(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ object Database {
|
||||
const val ROW_PACKAGE_NAME = "package_name"
|
||||
const val ROW_NAME = "name"
|
||||
const val ROW_SUMMARY = "summary"
|
||||
const val ROW_ADDED = "added"
|
||||
const val ROW_UPDATED = "updated"
|
||||
const val ROW_VERSION_CODE = "version_code"
|
||||
const val ROW_SIGNATURE = "signature"
|
||||
const val ROW_COMPATIBLE = "compatible"
|
||||
@@ -88,6 +90,8 @@ object Database {
|
||||
$ROW_PACKAGE_NAME TEXT NOT NULL,
|
||||
$ROW_NAME TEXT NOT NULL,
|
||||
$ROW_SUMMARY TEXT NOT NULL,
|
||||
$ROW_ADDED INTEGER NOT NULL,
|
||||
$ROW_UPDATED INTEGER NOT NULL,
|
||||
$ROW_VERSION_CODE INTEGER NOT NULL,
|
||||
$ROW_SIGNATURE TEXT NOT NULL,
|
||||
$ROW_COMPATIBLE INTEGER NOT NULL,
|
||||
@@ -386,7 +390,7 @@ object Database {
|
||||
}
|
||||
|
||||
fun query(installed: Boolean, updates: Boolean, searchQuery: String,
|
||||
category: String, signal: CancellationSignal?): Cursor {
|
||||
category: String, order: ProductItem.Order, signal: CancellationSignal?): Cursor {
|
||||
val builder = QueryBuilder()
|
||||
|
||||
builder += """SELECT product.rowid AS _id, product.${Schema.Product.ROW_REPOSITORY_ID},
|
||||
@@ -432,7 +436,13 @@ object Database {
|
||||
if (updates) {
|
||||
builder += "AND ${Schema.Synthetic.ROW_CAN_UPDATE}"
|
||||
}
|
||||
builder += "ORDER BY product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC"
|
||||
builder += "ORDER BY"
|
||||
when (order) {
|
||||
ProductItem.Order.NAME -> Unit
|
||||
ProductItem.Order.DATE_ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC,"
|
||||
ProductItem.Order.LAST_UPDATE -> builder += "product.${Schema.Product.ROW_UPDATED} DESC,"
|
||||
}::class
|
||||
builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC"
|
||||
|
||||
return builder.query(db, signal).observable(Subject.Products)
|
||||
}
|
||||
@@ -569,6 +579,8 @@ object Database {
|
||||
put(Schema.Product.ROW_PACKAGE_NAME, product.packageName)
|
||||
put(Schema.Product.ROW_NAME, product.name)
|
||||
put(Schema.Product.ROW_SUMMARY, product.summary)
|
||||
put(Schema.Product.ROW_ADDED, product.added)
|
||||
put(Schema.Product.ROW_UPDATED, product.updated)
|
||||
put(Schema.Product.ROW_VERSION_CODE, product.versionCode)
|
||||
put(Schema.Product.ROW_SIGNATURE, product.signature)
|
||||
put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0)
|
||||
|
||||
@@ -2,11 +2,18 @@ package nya.kitsunyan.foxydroid.entity
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import nya.kitsunyan.foxydroid.R
|
||||
import nya.kitsunyan.foxydroid.utility.extension.json.*
|
||||
|
||||
data class ProductItem(val repositoryId: Long, val packageName: String,
|
||||
val name: String, val summary: String, val icon: String, val version: String, val installedVersion: String,
|
||||
val compatible: Boolean, val canUpdate: Boolean) {
|
||||
enum class Order(val titleResId: Int) {
|
||||
NAME(R.string.name),
|
||||
DATE_ADDED(R.string.date_added),
|
||||
LAST_UPDATE(R.string.last_update)
|
||||
}
|
||||
|
||||
fun serialize(generator: JsonGenerator) {
|
||||
generator.writeNumberField("serialVersion", 1)
|
||||
generator.writeStringField("icon", icon)
|
||||
|
||||
@@ -16,6 +16,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import nya.kitsunyan.foxydroid.R
|
||||
import nya.kitsunyan.foxydroid.database.CursorOwner
|
||||
import nya.kitsunyan.foxydroid.database.Database
|
||||
import nya.kitsunyan.foxydroid.entity.ProductItem
|
||||
import nya.kitsunyan.foxydroid.utility.RxUtils
|
||||
import nya.kitsunyan.foxydroid.utility.extension.resources.*
|
||||
import nya.kitsunyan.foxydroid.widget.DividerItemDecoration
|
||||
@@ -27,13 +28,14 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
|
||||
private const val STATE_CURRENT_SEARCH_QUERY = "currentSearchQuery"
|
||||
private const val STATE_CURRENT_CATEGORY = "currentCategory"
|
||||
private const val STATE_CURRENT_ORDER = "currentOrder"
|
||||
private const val STATE_LAYOUT_MANAGER = "layoutManager"
|
||||
}
|
||||
|
||||
enum class Source(val titleResId: Int, val categories: Boolean) {
|
||||
AVAILABLE(R.string.available, true),
|
||||
INSTALLED(R.string.installed, false),
|
||||
UPDATES(R.string.updates, false)
|
||||
enum class Source(val titleResId: Int, val categories: Boolean, val order: Boolean) {
|
||||
AVAILABLE(R.string.available, true, true),
|
||||
INSTALLED(R.string.installed, false, false),
|
||||
UPDATES(R.string.updates, false, false)
|
||||
}
|
||||
|
||||
constructor(source: Source): this() {
|
||||
@@ -47,9 +49,11 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
|
||||
private var searchQuery = ""
|
||||
private var category = ""
|
||||
private var order = ProductItem.Order.NAME
|
||||
|
||||
private var currentSearchQuery = ""
|
||||
private var currentCategory = ""
|
||||
private var currentOrder = ProductItem.Order.NAME
|
||||
private var layoutManagerState: Parcelable? = null
|
||||
|
||||
private var recyclerView: RecyclerView? = null
|
||||
@@ -60,10 +64,11 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
get() {
|
||||
val searchQuery = searchQuery
|
||||
val category = if (source.categories) category else ""
|
||||
val order = if (source.order) order else ProductItem.Order.NAME
|
||||
return when (source) {
|
||||
Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable(searchQuery, category)
|
||||
Source.INSTALLED -> CursorOwner.Request.ProductsInstalled(searchQuery, category)
|
||||
Source.UPDATES -> CursorOwner.Request.ProductsUpdates(searchQuery, category)
|
||||
Source.AVAILABLE -> CursorOwner.Request.ProductsAvailable(searchQuery, category, order)
|
||||
Source.INSTALLED -> CursorOwner.Request.ProductsInstalled(searchQuery, category, order)
|
||||
Source.UPDATES -> CursorOwner.Request.ProductsUpdates(searchQuery, category, order)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +92,8 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
|
||||
currentSearchQuery = savedInstanceState?.getString(STATE_CURRENT_SEARCH_QUERY).orEmpty()
|
||||
currentCategory = savedInstanceState?.getString(STATE_CURRENT_CATEGORY).orEmpty()
|
||||
currentOrder = savedInstanceState?.getString(STATE_CURRENT_ORDER)
|
||||
?.let(ProductItem.Order::valueOf) ?: ProductItem.Order.NAME
|
||||
layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER)
|
||||
|
||||
screenActivity.cursorOwner.attach(this, request)
|
||||
@@ -114,6 +121,7 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
|
||||
outState.putString(STATE_CURRENT_SEARCH_QUERY, currentSearchQuery)
|
||||
outState.putString(STATE_CURRENT_CATEGORY, currentCategory)
|
||||
outState.putString(STATE_CURRENT_ORDER, currentOrder.name)
|
||||
(layoutManagerState ?: recyclerView?.layoutManager?.onSaveInstanceState())
|
||||
?.let { outState.putParcelable(STATE_LAYOUT_MANAGER, it) }
|
||||
}
|
||||
@@ -137,9 +145,10 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
recyclerView?.layoutManager?.onRestoreInstanceState(it)
|
||||
}
|
||||
|
||||
if (currentSearchQuery != searchQuery || currentCategory != category) {
|
||||
if (currentSearchQuery != searchQuery || currentCategory != category || currentOrder != order) {
|
||||
currentSearchQuery = searchQuery
|
||||
currentCategory = category
|
||||
currentOrder = order
|
||||
recyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
@@ -161,4 +170,13 @@ class ProductsFragment(): Fragment(), CursorOwner.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setOrder(order: ProductItem.Order) {
|
||||
if (this.order != order) {
|
||||
this.order = order
|
||||
if (view != null) {
|
||||
screenActivity.cursorOwner.attach(this, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class RepositoryFragment(): Fragment() {
|
||||
getString(R.string.unknown)
|
||||
}
|
||||
})
|
||||
if (repository.enabled && repository.entityTag.isNotEmpty()) {
|
||||
if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) {
|
||||
layout.addTitleText(R.string.number_of_applications,
|
||||
Database.ProductAdapter.getCount(repository.id).toString())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nya.kitsunyan.foxydroid.screen
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
@@ -15,6 +16,7 @@ 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 androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -27,11 +29,14 @@ import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import nya.kitsunyan.foxydroid.R
|
||||
import nya.kitsunyan.foxydroid.database.Database
|
||||
import nya.kitsunyan.foxydroid.entity.ProductItem
|
||||
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.resources.*
|
||||
import nya.kitsunyan.foxydroid.utility.extension.text.*
|
||||
import nya.kitsunyan.foxydroid.widget.EnumRecyclerAdapter
|
||||
import kotlin.math.*
|
||||
|
||||
class TabsFragment: Fragment() {
|
||||
@@ -40,11 +45,19 @@ class TabsFragment: Fragment() {
|
||||
private const val STATE_SHOW_CATEGORIES = "showCategories"
|
||||
private const val STATE_CATEGORIES = "categories"
|
||||
private const val STATE_CATEGORY = "category"
|
||||
private const val STATE_ORDER = "order"
|
||||
}
|
||||
|
||||
private var tabLayout: TabLayout? = null
|
||||
private var categoryName: TextView? = null
|
||||
private var categoryIcon: ImageView? = null
|
||||
private class Layout(view: View) {
|
||||
val tabLayout = view.findViewById<TabLayout>(R.id.tabs)!!
|
||||
val categoryLayout = view.findViewById<ViewGroup>(R.id.category_layout)!!
|
||||
val categoryChange = view.findViewById<View>(R.id.category_change)!!
|
||||
val categoryName = view.findViewById<TextView>(R.id.category_name)!!
|
||||
val categoryIcon = view.findViewById<ImageView>(R.id.category_icon)!!
|
||||
}
|
||||
|
||||
private var sortOrderMenu: Pair<MenuItem, List<MenuItem>>? = null
|
||||
private var layout: Layout? = null
|
||||
private var categoriesList: RecyclerView? = null
|
||||
private var viewPager: ViewPager? = null
|
||||
|
||||
@@ -52,9 +65,10 @@ class TabsFragment: Fragment() {
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
tabLayout?.let { (0 until it.tabCount)
|
||||
val layout = layout
|
||||
layout?.tabLayout?.let { (0 until it.tabCount)
|
||||
.forEach { index -> it.getTabAt(index)!!.view.isEnabled = !value } }
|
||||
categoryIcon?.scaleY = if (value) -1f else 1f
|
||||
layout?.categoryIcon?.scaleY = if (value) -1f else 1f
|
||||
if ((categoriesList?.parent as? View)?.height ?: 0 > 0) {
|
||||
animateCategoriesList()
|
||||
}
|
||||
@@ -64,6 +78,7 @@ class TabsFragment: Fragment() {
|
||||
private var searchQuery = ""
|
||||
private var categories = emptyList<String>()
|
||||
private var category = ""
|
||||
private var order = ProductItem.Order.NAME
|
||||
|
||||
private val syncConnection = Connection<SyncService.Binder>(SyncService::class.java, onBind = {
|
||||
viewPager?.let {
|
||||
@@ -112,26 +127,44 @@ class TabsFragment: Fragment() {
|
||||
})
|
||||
|
||||
toolbar.menu.apply {
|
||||
MenuCompat.setGroupDividerEnabled(this, true)
|
||||
|
||||
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)
|
||||
|
||||
add(R.string.sync_repositories)
|
||||
sortOrderMenu = addSubMenu(0, 0, 0, R.string.sort_order)
|
||||
.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
|
||||
} }
|
||||
menu.setGroupCheckable(0, true, true)
|
||||
Pair(menu.item, items)
|
||||
}
|
||||
|
||||
add(0, 0, 0, R.string.sync_repositories)
|
||||
.setIcon(Utils.getToolbarIcon(toolbar.context, R.drawable.ic_sync))
|
||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
.setOnMenuItemClickListener {
|
||||
syncConnection.binder?.sync(SyncService.SyncRequest.MANUAL)
|
||||
true
|
||||
}
|
||||
|
||||
add(R.string.repositories)
|
||||
add(1, 0, 0, R.string.repositories)
|
||||
.setOnMenuItemClickListener {
|
||||
view.post { screenActivity.navigateRepositories() }
|
||||
true
|
||||
}
|
||||
|
||||
add(R.string.preferences)
|
||||
add(1, 0, 0, R.string.preferences)
|
||||
.setOnMenuItemClickListener {
|
||||
view.post { screenActivity.navigatePreferences() }
|
||||
true
|
||||
@@ -143,11 +176,12 @@ class TabsFragment: Fragment() {
|
||||
|
||||
val toolbarExtra = view.findViewById<FrameLayout>(R.id.toolbar_extra)
|
||||
toolbarExtra.addView(toolbarExtra.inflate(R.layout.tabs_toolbar))
|
||||
val layout = Layout(view)
|
||||
this.layout = layout
|
||||
|
||||
val tabLayout = view.findViewById<TabLayout>(R.id.tabs)
|
||||
this.tabLayout = tabLayout
|
||||
ProductsFragment.Source.values().forEach { tabLayout.addTab(tabLayout.newTab().setText(it.titleResId)) }
|
||||
tabLayout.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener {
|
||||
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
|
||||
}
|
||||
@@ -156,16 +190,14 @@ class TabsFragment: Fragment() {
|
||||
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
||||
})
|
||||
|
||||
val categoryLayout = view.findViewById<ViewGroup>(R.id.category_layout)
|
||||
val categoryChange = view.findViewById<View>(R.id.category_change)
|
||||
val categoryName = view.findViewById<TextView>(R.id.category_name)
|
||||
val categoryIcon = view.findViewById<ImageView>(R.id.category_icon)
|
||||
this.categoryName = categoryName
|
||||
this.categoryIcon = categoryIcon
|
||||
showCategories = savedInstanceState?.getByte(STATE_SHOW_CATEGORIES)?.toInt() ?: 0 != 0
|
||||
categories = savedInstanceState?.getStringArrayList(STATE_CATEGORIES).orEmpty()
|
||||
category = savedInstanceState?.getString(STATE_CATEGORY).orEmpty()
|
||||
categoryChange.setOnClickListener { showCategories = categories.isNotEmpty() && !showCategories }
|
||||
layout.categoryChange.setOnClickListener { showCategories = categories.isNotEmpty() && !showCategories }
|
||||
|
||||
order = savedInstanceState?.getString(STATE_ORDER)?.let(ProductItem.Order::valueOf) ?: ProductItem.Order.NAME
|
||||
sortOrderMenu!!.second[order.ordinal].isChecked = true
|
||||
productFragments.forEach { it.setOrder(order) }
|
||||
|
||||
val content = view.findViewById<FrameLayout>(R.id.fragment_content)
|
||||
|
||||
@@ -176,45 +208,7 @@ class TabsFragment: Fragment() {
|
||||
override fun getCount(): Int = ProductsFragment.Source.values().size
|
||||
}
|
||||
content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
||||
addOnPageChangeListener(object: ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
val fromCategories = ProductsFragment.Source.values()[position].categories
|
||||
val toCategories = if (positionOffset <= 0f) fromCategories else
|
||||
ProductsFragment.Source.values()[position + 1].categories
|
||||
val offset = if (fromCategories != toCategories) {
|
||||
if (fromCategories) 1f - positionOffset else positionOffset
|
||||
} else {
|
||||
if (fromCategories) 1f else 0f
|
||||
}
|
||||
if (categoryLayout.childCount != 1) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
val child = categoryLayout.getChildAt(0)
|
||||
val height = child.layoutParams.height
|
||||
if (height <= 0) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
val currentHeight = (offset * height).roundToInt()
|
||||
if (categoryLayout.layoutParams.height != currentHeight) {
|
||||
categoryLayout.layoutParams.height = currentHeight
|
||||
categoryLayout.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
val source = ProductsFragment.Source.values()[position]
|
||||
updateUpdateNotificationBlocker(source)
|
||||
tabLayout.selectTab(tabLayout.getTabAt(source.ordinal))
|
||||
if (showCategories && !source.categories) {
|
||||
showCategories = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
categoryChange.isEnabled = state != ViewPager.SCROLL_STATE_DRAGGING &&
|
||||
ProductsFragment.Source.values()[this@apply.currentItem].categories
|
||||
}
|
||||
})
|
||||
addOnPageChangeListener(pageChangeListener)
|
||||
}
|
||||
|
||||
categoriesDisposable = Observable.just(Unit)
|
||||
@@ -237,34 +231,11 @@ class TabsFragment: Fragment() {
|
||||
isMotionEventSplittingEnabled = false
|
||||
isVerticalScrollBarEnabled = false
|
||||
setHasFixedSize(true)
|
||||
adapter = object: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
override fun getItemCount(): Int = categories.size + 1
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return object: RecyclerView.ViewHolder(AppCompatTextView(parent.context).apply {
|
||||
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
resources.sizeScaled(48))
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setPadding(resources.sizeScaled(16), 0, resources.sizeScaled(16), 0)
|
||||
setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
background = context.getDrawableFromAttr(R.attr.selectableItemBackground)
|
||||
setTextSizeScaled(16)
|
||||
}) {
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
if (showCategories) {
|
||||
showCategories = false
|
||||
category = if (adapterPosition == 0) "" else categories[adapterPosition - 1]
|
||||
updateCategory()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
(holder.itemView as TextView).text = if (position == 0)
|
||||
getString(R.string.all_applications_category) else categories[position - 1]
|
||||
this.adapter = CategoriesAdapter({ categories }) {
|
||||
if (showCategories) {
|
||||
showCategories = false
|
||||
category = it
|
||||
updateCategory()
|
||||
}
|
||||
}
|
||||
setBackgroundColor(context.getColorFromAttr(R.attr.colorPrimaryDark).defaultColor)
|
||||
@@ -296,9 +267,8 @@ class TabsFragment: Fragment() {
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
tabLayout = null
|
||||
categoryName = null
|
||||
categoryIcon = null
|
||||
sortOrderMenu = null
|
||||
layout = null
|
||||
categoriesList = null
|
||||
viewPager = null
|
||||
|
||||
@@ -316,6 +286,7 @@ class TabsFragment: Fragment() {
|
||||
outState.putByte(STATE_SHOW_CATEGORIES, if (showCategories) 1 else 0)
|
||||
outState.putStringArrayList(STATE_CATEGORIES, ArrayList(categories))
|
||||
outState.putString(STATE_CATEGORY, category)
|
||||
outState.putString(STATE_ORDER, order.name)
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
@@ -333,6 +304,7 @@ class TabsFragment: Fragment() {
|
||||
if (view != null && childFragment is ProductsFragment) {
|
||||
childFragment.setSearchQuery(searchQuery)
|
||||
childFragment.setCategory(category)
|
||||
childFragment.setOrder(order)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +312,7 @@ class TabsFragment: Fragment() {
|
||||
if (view == null) {
|
||||
needSelectUpdates = true
|
||||
} else {
|
||||
tabLayout?.getTabAt(ProductsFragment.Source.UPDATES.ordinal)!!.select()
|
||||
layout?.tabLayout?.getTabAt(ProductsFragment.Source.UPDATES.ordinal)!!.select()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,20 +326,12 @@ class TabsFragment: Fragment() {
|
||||
}
|
||||
|
||||
private fun updateCategory() {
|
||||
val categories = categories
|
||||
val category = category
|
||||
val categoryName = categoryName!!
|
||||
val index = categories.indexOf(category)
|
||||
if (index < 0) {
|
||||
categoryName.setText(R.string.all_applications_category)
|
||||
if (category.isNotEmpty()) {
|
||||
this.category = ""
|
||||
}
|
||||
} else {
|
||||
categoryName.text = category
|
||||
if (category.isNotEmpty() && categories.indexOf(category) < 0) {
|
||||
category = ""
|
||||
}
|
||||
categoryIcon?.visibility = if (categories.isEmpty()) View.GONE else View.VISIBLE
|
||||
productFragments.forEach { it.setCategory(this.category) }
|
||||
layout?.categoryName?.text = category.nullIfEmpty() ?: getString(R.string.all_applications)
|
||||
layout?.categoryIcon?.visibility = if (categories.isEmpty()) View.GONE else View.VISIBLE
|
||||
productFragments.forEach { it.setCategory(category) }
|
||||
categoriesList?.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@@ -404,4 +368,86 @@ class TabsFragment: Fragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val pageChangeListener = object: ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
val layout = layout!!
|
||||
val fromCategories = ProductsFragment.Source.values()[position].categories
|
||||
val toCategories = if (positionOffset <= 0f) fromCategories else
|
||||
ProductsFragment.Source.values()[position + 1].categories
|
||||
val offset = if (fromCategories != toCategories) {
|
||||
if (fromCategories) 1f - positionOffset else positionOffset
|
||||
} else {
|
||||
if (fromCategories) 1f else 0f
|
||||
}
|
||||
if (layout.categoryLayout.childCount != 1) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
val child = layout.categoryLayout.getChildAt(0)
|
||||
val height = child.layoutParams.height
|
||||
if (height <= 0) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
val currentHeight = (offset * height).roundToInt()
|
||||
if (layout.categoryLayout.layoutParams.height != currentHeight) {
|
||||
layout.categoryLayout.layoutParams.height = currentHeight
|
||||
layout.categoryLayout.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
if (showCategories && !source.categories) {
|
||||
showCategories = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
layout!!.categoryChange.isEnabled = state != ViewPager.SCROLL_STATE_DRAGGING &&
|
||||
ProductsFragment.Source.values()[viewPager!!.currentItem].categories
|
||||
}
|
||||
}
|
||||
|
||||
private class CategoriesAdapter(private val categories: () -> List<String>, private val onClick: (String) -> Unit):
|
||||
EnumRecyclerAdapter<CategoriesAdapter.ViewType, RecyclerView.ViewHolder>() {
|
||||
enum class ViewType { CATEGORY }
|
||||
|
||||
private class CategoryViewHolder(context: Context): RecyclerView.ViewHolder(AppCompatTextView(context)) {
|
||||
val title: TextView
|
||||
get() = itemView as TextView
|
||||
|
||||
init {
|
||||
itemView as TextView
|
||||
itemView.gravity = Gravity.CENTER_VERTICAL
|
||||
itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) }
|
||||
itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
||||
itemView.setTextSizeScaled(16)
|
||||
itemView.background = context.getDrawableFromAttr(R.attr.selectableItemBackground)
|
||||
itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
itemView.resources.sizeScaled(48))
|
||||
}
|
||||
}
|
||||
|
||||
override val viewTypeClass: Class<ViewType>
|
||||
get() = ViewType::class.java
|
||||
|
||||
override fun getItemCount(): Int = 1 + categories().size
|
||||
override fun getItemEnumViewType(position: Int): ViewType = ViewType.CATEGORY
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder {
|
||||
return CategoryViewHolder(parent.context).apply {
|
||||
itemView.setOnClickListener { onClick(categories().getOrNull(adapterPosition - 1).orEmpty()) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
holder as CategoryViewHolder
|
||||
holder.title.text = categories().getOrNull(position - 1)
|
||||
?: holder.itemView.resources.getString(R.string.all_applications)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ class SyncService: Service() {
|
||||
if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) {
|
||||
val disposable = RxUtils
|
||||
.querySingle { Database.ProductAdapter
|
||||
.query(true, true, "", "", it)
|
||||
.query(true, true, "", "", ProductItem.Order.NAME, it)
|
||||
.use { it.asSequence().map(Database.ProductAdapter::transformItem).toList() } }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z" />
|
||||
|
||||
</vector>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<nya.kitsunyan.foxydroid.widget.FragmentLinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@@ -13,7 +14,8 @@
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
app:popupTheme="@style/Theme.Toolbar.Popup" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/toolbar_extra"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="action_failed">Action failed</string>
|
||||
<string name="add_repository">Add repository</string>
|
||||
<string name="address">Address</string>
|
||||
<string name="all_applications_category">All Applications</string>
|
||||
<string name="all_applications">All applications</string>
|
||||
<string name="already_exists">Already exists</string>
|
||||
<string name="always">Always</string>
|
||||
<string name="anti_feature_advertising">Has advertising</string>
|
||||
@@ -35,6 +35,7 @@
|
||||
<string name="confirm_action">Confirm action</string>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="dark">Dark</string>
|
||||
<string name="date_added">Date added</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_repository_confirm">Are you sure you want to delete the repository?</string>
|
||||
<string name="description">Description</string>
|
||||
@@ -124,6 +125,7 @@
|
||||
<string name="show_older_releases">Show older releases</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="socks_proxy">SOCKS proxy</string>
|
||||
<string name="sort_order">Sort order</string>
|
||||
<string name="source_code">Source code</string>
|
||||
<string name="suggested">Suggested</string>
|
||||
<string name="sync_repositories">Sync repositories</string>
|
||||
|
||||
@@ -23,4 +23,8 @@
|
||||
<item name="colorError">@color/error_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Toolbar.Popup" parent="Theme.Main.Dark">
|
||||
<item name="android:colorBackground">?attr/colorPrimaryDark</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user