mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
- Screenshots are Expanded by Default
- Reformatted Code - Added background to Show More Button - Shortened Tab Indicator...
This commit is contained in:
parent
8c0c48236e
commit
baf9944dc9
File diff suppressed because it is too large
Load Diff
@ -18,24 +18,19 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.ProductPreferences
|
import com.looker.droidify.content.ProductPreferences
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
import com.looker.droidify.entity.InstalledItem
|
import com.looker.droidify.entity.*
|
||||||
import com.looker.droidify.entity.Product
|
|
||||||
import com.looker.droidify.entity.ProductPreference
|
|
||||||
import com.looker.droidify.entity.Release
|
|
||||||
import com.looker.droidify.entity.Repository
|
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.DownloadService
|
import com.looker.droidify.service.DownloadService
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.android.*
|
import com.looker.droidify.utility.extension.android.*
|
||||||
import com.looker.droidify.widget.DividerItemDecoration
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
|
||||||
class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks {
|
class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks {
|
||||||
companion object {
|
companion object {
|
||||||
@ -53,7 +48,11 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
|
|
||||||
private class Nullable<T>(val value: T?)
|
private class Nullable<T>(val value: T?)
|
||||||
|
|
||||||
private enum class Action(val id: Int, val adapterAction: ProductAdapter.Action, val iconResId: Int) {
|
private enum class Action(
|
||||||
|
val id: Int,
|
||||||
|
val adapterAction: ProductAdapter.Action,
|
||||||
|
val iconResId: Int
|
||||||
|
) {
|
||||||
INSTALL(1, ProductAdapter.Action.INSTALL, R.drawable.ic_archive),
|
INSTALL(1, ProductAdapter.Action.INSTALL, R.drawable.ic_archive),
|
||||||
UPDATE(2, ProductAdapter.Action.UPDATE, R.drawable.ic_archive),
|
UPDATE(2, ProductAdapter.Action.UPDATE, R.drawable.ic_archive),
|
||||||
LAUNCH(3, ProductAdapter.Action.LAUNCH, R.drawable.ic_launch),
|
LAUNCH(3, ProductAdapter.Action.LAUNCH, R.drawable.ic_launch),
|
||||||
@ -61,8 +60,10 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
UNINSTALL(5, ProductAdapter.Action.UNINSTALL, R.drawable.ic_delete)
|
UNINSTALL(5, ProductAdapter.Action.UNINSTALL, R.drawable.ic_delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Installed(val installedItem: InstalledItem, val isSystem: Boolean,
|
private class Installed(
|
||||||
val launcherActivities: List<Pair<String, String>>)
|
val installedItem: InstalledItem, val isSystem: Boolean,
|
||||||
|
val launcherActivities: List<Pair<String, String>>
|
||||||
|
)
|
||||||
|
|
||||||
val packageName: String
|
val packageName: String
|
||||||
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
||||||
@ -87,7 +88,11 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
downloadDisposable = null
|
downloadDisposable = null
|
||||||
})
|
})
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
return inflater.inflate(R.layout.fragment, container, false)
|
return inflater.inflate(R.layout.fragment, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,8 +134,9 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
addOnScrollListener(scrollListener)
|
addOnScrollListener(scrollListener)
|
||||||
addItemDecoration(adapter.gridItemDecoration)
|
addItemDecoration(adapter.gridItemDecoration)
|
||||||
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
// addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
||||||
savedInstanceState?.getParcelable<ProductAdapter.SavedState>(STATE_ADAPTER)?.let(adapter::restoreState)
|
savedInstanceState?.getParcelable<ProductAdapter.SavedState>(STATE_ADAPTER)
|
||||||
|
?.let(adapter::restoreState)
|
||||||
layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER)
|
layoutManagerState = savedInstanceState?.getParcelable(STATE_LAYOUT_MANAGER)
|
||||||
recyclerView = this
|
recyclerView = this
|
||||||
}, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
}, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
||||||
@ -140,22 +146,41 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
.concatWith(Database.observable(Database.Subject.Products))
|
.concatWith(Database.observable(Database.Subject.Products))
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } }
|
.flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } }
|
||||||
.flatMapSingle { products -> RxUtils
|
.flatMapSingle { products ->
|
||||||
|
RxUtils
|
||||||
.querySingle { Database.RepositoryAdapter.getAll(it) }
|
.querySingle { Database.RepositoryAdapter.getAll(it) }
|
||||||
.map { it.asSequence().map { Pair(it.id, it) }.toMap()
|
.map { it ->
|
||||||
.let { products.mapNotNull { product -> it[product.repositoryId]?.let { Pair(product, it) } } } } }
|
it.asSequence().map { Pair(it.id, it) }.toMap()
|
||||||
.flatMapSingle { products -> RxUtils
|
.let {
|
||||||
|
products.mapNotNull { product ->
|
||||||
|
it[product.repositoryId]?.let {
|
||||||
|
Pair(
|
||||||
|
product,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.flatMapSingle { products ->
|
||||||
|
RxUtils
|
||||||
.querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) }
|
.querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) }
|
||||||
.map { Pair(products, it) } }
|
.map { Pair(products, it) }
|
||||||
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe { it ->
|
||||||
val (products, installedItem) = it
|
val (products, installedItem) = it
|
||||||
val firstChanged = first
|
val firstChanged = first
|
||||||
first = false
|
first = false
|
||||||
val productChanged = this.products != products
|
val productChanged = this.products != products
|
||||||
val installedItemChanged = this.installed?.installedItem != installedItem.value
|
val installedItemChanged = this.installed?.installedItem != installedItem.value
|
||||||
if (firstChanged || productChanged || installedItemChanged) {
|
if (firstChanged || productChanged || installedItemChanged) {
|
||||||
layoutManagerState?.let { recyclerView?.layoutManager!!.onRestoreInstanceState(it) }
|
layoutManagerState?.let {
|
||||||
|
recyclerView?.layoutManager!!.onRestoreInstanceState(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
layoutManagerState = null
|
layoutManagerState = null
|
||||||
if (firstChanged || productChanged) {
|
if (firstChanged || productChanged) {
|
||||||
this.products = products
|
this.products = products
|
||||||
@ -163,19 +188,28 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
if (firstChanged || installedItemChanged) {
|
if (firstChanged || installedItemChanged) {
|
||||||
installed = installedItem.value?.let {
|
installed = installedItem.value?.let {
|
||||||
val isSystem = try {
|
val isSystem = try {
|
||||||
((requireContext().packageManager.getApplicationInfo(packageName, 0).flags)
|
((requireContext().packageManager.getApplicationInfo(
|
||||||
|
packageName,
|
||||||
|
0
|
||||||
|
).flags)
|
||||||
and ApplicationInfo.FLAG_SYSTEM) != 0
|
and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
val launcherActivities = if (packageName == requireContext().packageName) {
|
val launcherActivities =
|
||||||
|
if (packageName == requireContext().packageName) {
|
||||||
// Don't allow to launch self
|
// Don't allow to launch self
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
val packageManager = requireContext().packageManager
|
val packageManager = requireContext().packageManager
|
||||||
packageManager
|
packageManager
|
||||||
.queryIntentActivities(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 0)
|
.queryIntentActivities(
|
||||||
.asSequence().mapNotNull { it.activityInfo }.filter { it.packageName == packageName }
|
Intent(Intent.ACTION_MAIN).addCategory(
|
||||||
|
Intent.CATEGORY_LAUNCHER
|
||||||
|
), 0
|
||||||
|
)
|
||||||
|
.asSequence().mapNotNull { it.activityInfo }
|
||||||
|
.filter { it.packageName == packageName }
|
||||||
.mapNotNull { activityInfo ->
|
.mapNotNull { activityInfo ->
|
||||||
val label = try {
|
val label = try {
|
||||||
activityInfo.loadLabel(packageManager).toString()
|
activityInfo.loadLabel(packageManager).toString()
|
||||||
@ -193,7 +227,12 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val recyclerView = recyclerView!!
|
val recyclerView = recyclerView!!
|
||||||
val adapter = recyclerView.adapter as ProductAdapter
|
val adapter = recyclerView.adapter as ProductAdapter
|
||||||
if (firstChanged || productChanged || installedItemChanged) {
|
if (firstChanged || productChanged || installedItemChanged) {
|
||||||
adapter.setProducts(recyclerView.context, packageName, products, installedItem.value)
|
adapter.setProducts(
|
||||||
|
recyclerView.context,
|
||||||
|
packageName,
|
||||||
|
products,
|
||||||
|
installedItem.value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
updateButtons()
|
updateButtons()
|
||||||
}
|
}
|
||||||
@ -218,7 +257,8 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
val layoutManagerState = layoutManagerState ?: recyclerView?.layoutManager?.onSaveInstanceState()
|
val layoutManagerState =
|
||||||
|
layoutManagerState ?: recyclerView?.layoutManager?.onSaveInstanceState()
|
||||||
layoutManagerState?.let { outState.putParcelable(STATE_LAYOUT_MANAGER, it) }
|
layoutManagerState?.let { outState.putParcelable(STATE_LAYOUT_MANAGER, it) }
|
||||||
val adapterState = (recyclerView?.adapter as? ProductAdapter)?.saveState()
|
val adapterState = (recyclerView?.adapter as? ProductAdapter)?.saveState()
|
||||||
adapterState?.let { outState.putParcelable(STATE_ADAPTER, it) }
|
adapterState?.let { outState.putParcelable(STATE_ADAPTER, it) }
|
||||||
@ -234,10 +274,12 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val compatible = product != null && product.selectedReleases.firstOrNull()
|
val compatible = product != null && product.selectedReleases.firstOrNull()
|
||||||
.let { it != null && it.incompatibilities.isEmpty() }
|
.let { it != null && it.incompatibilities.isEmpty() }
|
||||||
val canInstall = product != null && installed == null && compatible
|
val canInstall = product != null && installed == null && compatible
|
||||||
val canUpdate = product != null && compatible && product.canUpdate(installed?.installedItem) &&
|
val canUpdate =
|
||||||
|
product != null && compatible && product.canUpdate(installed?.installedItem) &&
|
||||||
!preference.shouldIgnoreUpdate(product.versionCode)
|
!preference.shouldIgnoreUpdate(product.versionCode)
|
||||||
val canUninstall = product != null && installed != null && !installed.isSystem
|
val canUninstall = product != null && installed != null && !installed.isSystem
|
||||||
val canLaunch = product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
val canLaunch =
|
||||||
|
product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
||||||
|
|
||||||
val actions = mutableSetOf<Action>()
|
val actions = mutableSetOf<Action>()
|
||||||
if (canInstall) {
|
if (canInstall) {
|
||||||
@ -263,7 +305,8 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapterAction = if (downloading) ProductAdapter.Action.CANCEL else primaryAction?.adapterAction
|
val adapterAction =
|
||||||
|
if (downloading) ProductAdapter.Action.CANCEL else primaryAction?.adapterAction
|
||||||
(recyclerView?.adapter as? ProductAdapter)?.setAction(adapterAction)
|
(recyclerView?.adapter as? ProductAdapter)?.setAction(adapterAction)
|
||||||
|
|
||||||
val toolbar = toolbar
|
val toolbar = toolbar
|
||||||
@ -299,7 +342,10 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val status = when (state) {
|
val status = when (state) {
|
||||||
is DownloadService.State.Pending -> ProductAdapter.Status.Pending
|
is DownloadService.State.Pending -> ProductAdapter.Status.Pending
|
||||||
is DownloadService.State.Connecting -> ProductAdapter.Status.Connecting
|
is DownloadService.State.Connecting -> ProductAdapter.Status.Connecting
|
||||||
is DownloadService.State.Downloading -> ProductAdapter.Status.Downloading(state.read, state.total)
|
is DownloadService.State.Downloading -> ProductAdapter.Status.Downloading(
|
||||||
|
state.read,
|
||||||
|
state.total
|
||||||
|
)
|
||||||
is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null
|
is DownloadService.State.Success, is DownloadService.State.Error, is DownloadService.State.Cancel, null -> null
|
||||||
}
|
}
|
||||||
val downloading = status != null
|
val downloading = status != null
|
||||||
@ -318,7 +364,8 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
private var lastPosition = -1
|
private var lastPosition = -1
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
val position = (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
val position =
|
||||||
|
(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||||
val lastPosition = lastPosition
|
val lastPosition = lastPosition
|
||||||
this.lastPosition = position
|
this.lastPosition = position
|
||||||
if ((lastPosition == 0) != (position == 0)) {
|
if ((lastPosition == 0) != (position == 0)) {
|
||||||
@ -338,36 +385,48 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val release = if (compatibleReleases.size >= 2) {
|
val release = if (compatibleReleases.size >= 2) {
|
||||||
compatibleReleases
|
compatibleReleases
|
||||||
.filter { it.platforms.contains(Android.primaryPlatform) }
|
.filter { it.platforms.contains(Android.primaryPlatform) }
|
||||||
.minBy { it.platforms.size }
|
.minByOrNull { it.platforms.size }
|
||||||
?: compatibleReleases.minBy { it.platforms.size }
|
?: compatibleReleases.minByOrNull { it.platforms.size }
|
||||||
?: compatibleReleases.firstOrNull()
|
?: compatibleReleases.firstOrNull()
|
||||||
} else {
|
} else {
|
||||||
compatibleReleases.firstOrNull()
|
compatibleReleases.firstOrNull()
|
||||||
}
|
}
|
||||||
val binder = downloadConnection.binder
|
val binder = downloadConnection.binder
|
||||||
if (productRepository != null && release != null && binder != null) {
|
if (productRepository != null && release != null && binder != null) {
|
||||||
binder.enqueue(packageName, productRepository.first.name, productRepository.second, release)
|
binder.enqueue(
|
||||||
|
packageName,
|
||||||
|
productRepository.first.name,
|
||||||
|
productRepository.second,
|
||||||
|
release
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
ProductAdapter.Action.LAUNCH -> {
|
ProductAdapter.Action.LAUNCH -> {
|
||||||
val launcherActivities = installed?.launcherActivities.orEmpty()
|
val launcherActivities = installed?.launcherActivities.orEmpty()
|
||||||
if (launcherActivities.size >= 2) {
|
if (launcherActivities.size >= 2) {
|
||||||
LaunchDialog(launcherActivities).show(childFragmentManager, LaunchDialog::class.java.name)
|
LaunchDialog(launcherActivities).show(
|
||||||
|
childFragmentManager,
|
||||||
|
LaunchDialog::class.java.name
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
launcherActivities.firstOrNull()?.let { startLauncherActivity(it.first) }
|
launcherActivities.firstOrNull()?.let { startLauncherActivity(it.first) }
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
ProductAdapter.Action.DETAILS -> {
|
ProductAdapter.Action.DETAILS -> {
|
||||||
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
startActivity(
|
||||||
.setData(Uri.parse("package:$packageName")))
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
.setData(Uri.parse("package:$packageName"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ProductAdapter.Action.UNINSTALL -> {
|
ProductAdapter.Action.UNINSTALL -> {
|
||||||
// TODO Handle deprecation
|
// TODO Handle deprecation
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE)
|
startActivity(
|
||||||
.setData(Uri.parse("package:$packageName")))
|
Intent(Intent.ACTION_UNINSTALL_PACKAGE)
|
||||||
|
.setData(Uri.parse("package:$packageName"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ProductAdapter.Action.CANCEL -> {
|
ProductAdapter.Action.CANCEL -> {
|
||||||
val binder = downloadConnection.binder
|
val binder = downloadConnection.binder
|
||||||
@ -381,10 +440,12 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
|
|
||||||
private fun startLauncherActivity(name: String) {
|
private fun startLauncherActivity(name: String) {
|
||||||
try {
|
try {
|
||||||
startActivity(Intent(Intent.ACTION_MAIN)
|
startActivity(
|
||||||
|
Intent(Intent.ACTION_MAIN)
|
||||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
.setComponent(ComponentName(packageName, name))
|
.setComponent(ComponentName(packageName, name))
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@ -395,17 +456,26 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPermissionsClick(group: String?, permissions: List<String>) {
|
override fun onPermissionsClick(group: String?, permissions: List<String>) {
|
||||||
MessageDialog(MessageDialog.Message.Permissions(group, permissions)).show(childFragmentManager)
|
MessageDialog(MessageDialog.Message.Permissions(group, permissions)).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScreenshotClick(screenshot: Product.Screenshot) {
|
override fun onScreenshotClick(screenshot: Product.Screenshot) {
|
||||||
val pair = products.asSequence()
|
val pair = products.asSequence()
|
||||||
.map { Pair(it.second, it.first.screenshots.find { it === screenshot }?.identifier) }
|
.map { it ->
|
||||||
|
Pair(
|
||||||
|
it.second,
|
||||||
|
it.first.screenshots.find { it === screenshot }?.identifier
|
||||||
|
)
|
||||||
|
}
|
||||||
.filter { it.second != null }.firstOrNull()
|
.filter { it.second != null }.firstOrNull()
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
val (repository, identifier) = pair
|
val (repository, identifier) = pair
|
||||||
if (identifier != null) {
|
if (identifier != null) {
|
||||||
ScreenshotsFragment(packageName, repository.id, identifier).show(childFragmentManager)
|
ScreenshotsFragment(packageName, repository.id, identifier).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,20 +484,30 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val installedItem = installed?.installedItem
|
val installedItem = installed?.installedItem
|
||||||
when {
|
when {
|
||||||
release.incompatibilities.isNotEmpty() -> {
|
release.incompatibilities.isNotEmpty() -> {
|
||||||
MessageDialog(MessageDialog.Message.ReleaseIncompatible(release.incompatibilities,
|
MessageDialog(
|
||||||
release.platforms, release.minSdkVersion, release.maxSdkVersion)).show(childFragmentManager)
|
MessageDialog.Message.ReleaseIncompatible(
|
||||||
|
release.incompatibilities,
|
||||||
|
release.platforms, release.minSdkVersion, release.maxSdkVersion
|
||||||
|
)
|
||||||
|
).show(childFragmentManager)
|
||||||
}
|
}
|
||||||
installedItem != null && installedItem.versionCode > release.versionCode -> {
|
installedItem != null && installedItem.versionCode > release.versionCode -> {
|
||||||
MessageDialog(MessageDialog.Message.ReleaseOlder).show(childFragmentManager)
|
MessageDialog(MessageDialog.Message.ReleaseOlder).show(childFragmentManager)
|
||||||
}
|
}
|
||||||
installedItem != null && installedItem.signature != release.signature -> {
|
installedItem != null && installedItem.signature != release.signature -> {
|
||||||
MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(childFragmentManager)
|
MessageDialog(MessageDialog.Message.ReleaseSignatureMismatch).show(
|
||||||
|
childFragmentManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val productRepository = products.asSequence().filter { it.first.releases.any { it === release } }.firstOrNull()
|
val productRepository =
|
||||||
|
products.asSequence().filter { it -> it.first.releases.any { it === release } }
|
||||||
|
.firstOrNull()
|
||||||
if (productRepository != null) {
|
if (productRepository != null) {
|
||||||
downloadConnection.binder?.enqueue(packageName, productRepository.first.name,
|
downloadConnection.binder?.enqueue(
|
||||||
productRepository.second, release)
|
packageName, productRepository.first.name,
|
||||||
|
productRepository.second, release
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,8 +546,10 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val labels = requireArguments().getStringArrayList(EXTRA_LABELS)!!
|
val labels = requireArguments().getStringArrayList(EXTRA_LABELS)!!
|
||||||
return AlertDialog.Builder(requireContext())
|
return AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.launch)
|
.setTitle(R.string.launch)
|
||||||
.setItems(labels.toTypedArray()) { _, position -> (parentFragment as ProductFragment)
|
.setItems(labels.toTypedArray()) { _, position ->
|
||||||
.startLauncherActivity(names[position]) }
|
(parentFragment as ProductFragment)
|
||||||
|
.startLauncherActivity(names[position])
|
||||||
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,8 @@ import com.looker.droidify.entity.Repository
|
|||||||
import com.looker.droidify.network.PicassoDownloader
|
import com.looker.droidify.network.PicassoDownloader
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.*
|
||||||
import com.looker.droidify.utility.extension.text.*
|
import com.looker.droidify.utility.extension.text.nullIfEmpty
|
||||||
import com.looker.droidify.widget.CursorRecyclerAdapter
|
import com.looker.droidify.widget.CursorRecyclerAdapter
|
||||||
import com.looker.droidify.widget.DividerItemDecoration
|
|
||||||
|
|
||||||
class ProductsAdapter(private val onClick: (ProductItem) -> Unit) :
|
class ProductsAdapter(private val onClick: (ProductItem) -> Unit) :
|
||||||
CursorRecyclerAdapter<ProductsAdapter.ViewType, RecyclerView.ViewHolder>() {
|
CursorRecyclerAdapter<ProductsAdapter.ViewType, RecyclerView.ViewHolder>() {
|
||||||
@ -42,14 +41,19 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadingViewHolder(context: Context): RecyclerView.ViewHolder(FrameLayout(context)) {
|
private class LoadingViewHolder(context: Context) :
|
||||||
|
RecyclerView.ViewHolder(FrameLayout(context)) {
|
||||||
init {
|
init {
|
||||||
itemView as FrameLayout
|
itemView as FrameLayout
|
||||||
val progressBar = ProgressBar(itemView.context)
|
val progressBar = ProgressBar(itemView.context)
|
||||||
itemView.addView(progressBar, FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
|
itemView.addView(progressBar, FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER })
|
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||||
itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||||
RecyclerView.LayoutParams.MATCH_PARENT)
|
).apply { gravity = Gravity.CENTER })
|
||||||
|
itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,22 +68,10 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
itemView.typeface = TypefaceExtra.light
|
itemView.typeface = TypefaceExtra.light
|
||||||
itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
||||||
itemView.setTextSizeScaled(20)
|
itemView.setTextSizeScaled(20)
|
||||||
itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
RecyclerView.LayoutParams.MATCH_PARENT)
|
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||||
}
|
RecyclerView.LayoutParams.MATCH_PARENT
|
||||||
}
|
)
|
||||||
|
|
||||||
fun configureDivider(context: Context, position: Int, configuration: DividerItemDecoration.Configuration) {
|
|
||||||
val currentItem = if (getItemEnumViewType(position) == ViewType.PRODUCT) getProductItem(position) else null
|
|
||||||
val nextItem = if (position + 1 < itemCount && getItemEnumViewType(position + 1) == ViewType.PRODUCT)
|
|
||||||
getProductItem(position + 1) else null
|
|
||||||
when {
|
|
||||||
currentItem != null && nextItem != null && currentItem.matchRank != nextItem.matchRank -> {
|
|
||||||
configuration.set(true, false, 0, 0)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
configuration.set(true, false, context.resources.sizeScaled(72), 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +112,10 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
return Database.ProductAdapter.transformItem(moveTo(position))
|
return Database.ProductAdapter.transformItem(moveTo(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: ViewType
|
||||||
|
): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply {
|
ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply {
|
||||||
itemView.setOnClickListener { onClick(getProductItem(adapterPosition)) }
|
itemView.setOnClickListener { onClick(getProductItem(adapterPosition)) }
|
||||||
@ -136,12 +131,18 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
holder as ProductViewHolder
|
holder as ProductViewHolder
|
||||||
val productItem = getProductItem(position)
|
val productItem = getProductItem(position)
|
||||||
holder.name.text = productItem.name
|
holder.name.text = productItem.name
|
||||||
holder.summary.text = if (productItem.name == productItem.summary) "" else productItem.summary
|
holder.summary.text =
|
||||||
holder.summary.visibility = if (holder.summary.text.isNotEmpty()) View.VISIBLE else View.GONE
|
if (productItem.name == productItem.summary) "" else productItem.summary
|
||||||
|
holder.summary.visibility =
|
||||||
|
if (holder.summary.text.isNotEmpty()) View.VISIBLE else View.GONE
|
||||||
val repository: Repository? = repositories[productItem.repositoryId]
|
val repository: Repository? = repositories[productItem.repositoryId]
|
||||||
if ((productItem.icon.isNotEmpty() || productItem.metadataIcon.isNotEmpty()) && repository != null) {
|
if ((productItem.icon.isNotEmpty() || productItem.metadataIcon.isNotEmpty()) && repository != null) {
|
||||||
holder.icon.load(PicassoDownloader.createIconUri(holder.icon, productItem.packageName,
|
holder.icon.load(
|
||||||
productItem.icon, productItem.metadataIcon, repository)) {
|
PicassoDownloader.createIconUri(
|
||||||
|
holder.icon, productItem.packageName,
|
||||||
|
productItem.icon, productItem.metadataIcon, repository
|
||||||
|
)
|
||||||
|
) {
|
||||||
placeholder(holder.progressIcon)
|
placeholder(holder.progressIcon)
|
||||||
error(holder.defaultIcon)
|
error(holder.defaultIcon)
|
||||||
}
|
}
|
||||||
@ -155,8 +156,12 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
if (background == null) {
|
if (background == null) {
|
||||||
resources.sizeScaled(4).let { setPadding(it, 0, it, 0) }
|
resources.sizeScaled(4).let { setPadding(it, 0, it, 0) }
|
||||||
setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorBackground))
|
setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorBackground))
|
||||||
background = GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, null).apply {
|
background = GradientDrawable(
|
||||||
color = holder.status.context.getColorFromAttr(android.R.attr.colorAccent)
|
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||||
|
null
|
||||||
|
).apply {
|
||||||
|
color =
|
||||||
|
holder.status.context.getColorFromAttr(android.R.attr.colorAccent)
|
||||||
cornerRadius = holder.status.resources.sizeScaled(2).toFloat()
|
cornerRadius = holder.status.resources.sizeScaled(2).toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +175,9 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty()
|
val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty()
|
||||||
sequenceOf(holder.name, holder.status, holder.summary).forEach { it.isEnabled = enabled }
|
sequenceOf(holder.name, holder.status, holder.summary).forEach {
|
||||||
|
it.isEnabled = enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ViewType.LOADING -> {
|
ViewType.LOADING -> {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -17,7 +17,6 @@ import com.looker.droidify.database.CursorOwner
|
|||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
import com.looker.droidify.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
import com.looker.droidify.utility.RxUtils
|
import com.looker.droidify.utility.RxUtils
|
||||||
import com.looker.droidify.widget.DividerItemDecoration
|
|
||||||
import com.looker.droidify.widget.RecyclerFastScroller
|
import com.looker.droidify.widget.RecyclerFastScroller
|
||||||
|
|
||||||
class ProductsFragment(): ScreenFragment(), CursorOwner.Callback {
|
class ProductsFragment(): ScreenFragment(), CursorOwner.Callback {
|
||||||
@ -80,7 +79,6 @@ class ProductsFragment(): ScreenFragment(), CursorOwner.Callback {
|
|||||||
recycledViewPool.setMaxRecycledViews(ProductsAdapter.ViewType.PRODUCT.ordinal, 30)
|
recycledViewPool.setMaxRecycledViews(ProductsAdapter.ViewType.PRODUCT.ordinal, 30)
|
||||||
val adapter = ProductsAdapter { screenActivity.navigateProduct(it.packageName) }
|
val adapter = ProductsAdapter { screenActivity.navigateProduct(it.packageName) }
|
||||||
this.adapter = adapter
|
this.adapter = adapter
|
||||||
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
|
||||||
RecyclerFastScroller(this)
|
RecyclerFastScroller(this)
|
||||||
recyclerView = this
|
recyclerView = this
|
||||||
}
|
}
|
||||||
|
@ -9,28 +9,15 @@ import android.graphics.Paint
|
|||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.*
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.AccelerateInterpolator
|
import android.view.animation.AccelerateInterpolator
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.FrameLayout
|
import android.widget.*
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.SearchView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toolbar
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.Preferences
|
import com.looker.droidify.content.Preferences
|
||||||
import com.looker.droidify.database.Database
|
import com.looker.droidify.database.Database
|
||||||
@ -44,6 +31,10 @@ import com.looker.droidify.utility.extension.resources.*
|
|||||||
import com.looker.droidify.widget.DividerItemDecoration
|
import com.looker.droidify.widget.DividerItemDecoration
|
||||||
import com.looker.droidify.widget.FocusSearchView
|
import com.looker.droidify.widget.FocusSearchView
|
||||||
import com.looker.droidify.widget.StableRecyclerAdapter
|
import com.looker.droidify.widget.StableRecyclerAdapter
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
class TabsFragment : ScreenFragment() {
|
class TabsFragment : ScreenFragment() {
|
||||||
@ -75,8 +66,10 @@ class TabsFragment: ScreenFragment() {
|
|||||||
if (field != value) {
|
if (field != value) {
|
||||||
field = value
|
field = value
|
||||||
val layout = layout
|
val layout = layout
|
||||||
layout?.tabs?.let { (0 until it.childCount)
|
layout?.tabs?.let {
|
||||||
.forEach { index -> it.getChildAt(index)!!.isEnabled = !value } }
|
(0 until it.childCount)
|
||||||
|
.forEach { index -> it.getChildAt(index)!!.isEnabled = !value }
|
||||||
|
}
|
||||||
layout?.sectionIcon?.scaleY = if (value) -1f else 1f
|
layout?.sectionIcon?.scaleY = if (value) -1f else 1f
|
||||||
if ((sectionsList?.parent as? View)?.height ?: 0 > 0) {
|
if ((sectionsList?.parent as? View)?.height ?: 0 > 0) {
|
||||||
animateSectionsList()
|
animateSectionsList()
|
||||||
@ -106,7 +99,11 @@ class TabsFragment: ScreenFragment() {
|
|||||||
get() = if (host == null) emptySequence() else
|
get() = if (host == null) emptySequence() else
|
||||||
childFragmentManager.fragments.asSequence().mapNotNull { it as? ProductsFragment }
|
childFragmentManager.fragments.asSequence().mapNotNull { it as? ProductsFragment }
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment, container, false)
|
return inflater.inflate(R.layout.fragment, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,12 +152,14 @@ class TabsFragment: ScreenFragment() {
|
|||||||
.let { menu ->
|
.let { menu ->
|
||||||
menu.item.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
menu.item.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||||
val items = Preferences.Key.SortOrder.default.value.values
|
val items = Preferences.Key.SortOrder.default.value.values
|
||||||
.map { sortOrder -> menu
|
.map { sortOrder ->
|
||||||
|
menu
|
||||||
.add(sortOrder.order.titleResId)
|
.add(sortOrder.order.titleResId)
|
||||||
.setOnMenuItemClickListener {
|
.setOnMenuItemClickListener {
|
||||||
Preferences[Preferences.Key.SortOrder] = sortOrder
|
Preferences[Preferences.Key.SortOrder] = sortOrder
|
||||||
true
|
true
|
||||||
} }
|
}
|
||||||
|
}
|
||||||
menu.setGroupCheckable(0, true, true)
|
menu.setGroupCheckable(0, true, true)
|
||||||
Pair(menu.item, items)
|
Pair(menu.item, items)
|
||||||
}
|
}
|
||||||
@ -193,20 +192,29 @@ class TabsFragment: ScreenFragment() {
|
|||||||
val layout = Layout(view)
|
val layout = Layout(view)
|
||||||
this.layout = layout
|
this.layout = layout
|
||||||
|
|
||||||
layout.tabs.background = TabsBackgroundDrawable(layout.tabs.context,
|
layout.tabs.background = TabsBackgroundDrawable(
|
||||||
layout.tabs.layoutDirection == View.LAYOUT_DIRECTION_RTL)
|
layout.tabs.context,
|
||||||
|
layout.tabs.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
|
)
|
||||||
ProductsFragment.Source.values().forEach {
|
ProductsFragment.Source.values().forEach {
|
||||||
val tab = TextView(layout.tabs.context)
|
val tab = TextView(layout.tabs.context)
|
||||||
val selectedColor = tab.context.getColorFromAttr(android.R.attr.textColorPrimary).defaultColor
|
val selectedColor =
|
||||||
val normalColor = tab.context.getColorFromAttr(android.R.attr.textColorSecondary).defaultColor
|
tab.context.getColorFromAttr(android.R.attr.textColorPrimary).defaultColor
|
||||||
|
val normalColor =
|
||||||
|
tab.context.getColorFromAttr(android.R.attr.textColorSecondary).defaultColor
|
||||||
tab.gravity = Gravity.CENTER
|
tab.gravity = Gravity.CENTER
|
||||||
tab.typeface = TypefaceExtra.medium
|
tab.typeface = TypefaceExtra.medium
|
||||||
tab.setTextColor(ColorStateList(arrayOf(intArrayOf(android.R.attr.state_selected), intArrayOf()),
|
tab.setTextColor(
|
||||||
intArrayOf(selectedColor, normalColor)))
|
ColorStateList(
|
||||||
|
arrayOf(intArrayOf(android.R.attr.state_selected), intArrayOf()),
|
||||||
|
intArrayOf(selectedColor, normalColor)
|
||||||
|
)
|
||||||
|
)
|
||||||
tab.setTextSizeScaled(14)
|
tab.setTextSizeScaled(14)
|
||||||
tab.isAllCaps = true
|
tab.isAllCaps = true
|
||||||
tab.text = getString(it.titleResId)
|
tab.text = getString(it.titleResId)
|
||||||
tab.background = tab.context.getDrawableFromAttr(android.R.attr.selectableItemBackground)
|
tab.background =
|
||||||
|
tab.context.getDrawableFromAttr(android.R.attr.selectableItemBackground)
|
||||||
tab.setOnClickListener { _ ->
|
tab.setOnClickListener { _ ->
|
||||||
setSelectedTab(it)
|
setSelectedTab(it)
|
||||||
viewPager!!.setCurrentItem(it.ordinal, Utils.areAnimationsEnabled(tab.context))
|
viewPager!!.setCurrentItem(it.ordinal, Utils.areAnimationsEnabled(tab.context))
|
||||||
@ -216,10 +224,13 @@ class TabsFragment: ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showSections = savedInstanceState?.getByte(STATE_SHOW_SECTIONS)?.toInt() ?: 0 != 0
|
showSections = savedInstanceState?.getByte(STATE_SHOW_SECTIONS)?.toInt() ?: 0 != 0
|
||||||
sections = savedInstanceState?.getParcelableArrayList<ProductItem.Section>(STATE_SECTIONS).orEmpty()
|
sections = savedInstanceState?.getParcelableArrayList<ProductItem.Section>(STATE_SECTIONS)
|
||||||
|
.orEmpty()
|
||||||
section = savedInstanceState?.getParcelable(STATE_SECTION) ?: ProductItem.Section.All
|
section = savedInstanceState?.getParcelable(STATE_SECTION) ?: ProductItem.Section.All
|
||||||
layout.sectionChange.setOnClickListener { showSections = sections
|
layout.sectionChange.setOnClickListener {
|
||||||
.any { it !is ProductItem.Section.All } && !showSections }
|
showSections = sections
|
||||||
|
.any { it !is ProductItem.Section.All } && !showSections
|
||||||
|
}
|
||||||
|
|
||||||
updateOrder()
|
updateOrder()
|
||||||
sortOrderDisposable = Preferences.observable.subscribe {
|
sortOrderDisposable = Preferences.observable.subscribe {
|
||||||
@ -234,10 +245,16 @@ class TabsFragment: ScreenFragment() {
|
|||||||
id = R.id.fragment_pager
|
id = R.id.fragment_pager
|
||||||
adapter = object : FragmentStateAdapter(this@TabsFragment) {
|
adapter = object : FragmentStateAdapter(this@TabsFragment) {
|
||||||
override fun getItemCount(): Int = ProductsFragment.Source.values().size
|
override fun getItemCount(): Int = ProductsFragment.Source.values().size
|
||||||
override fun createFragment(position: Int): Fragment = ProductsFragment(ProductsFragment
|
override fun createFragment(position: Int): Fragment = ProductsFragment(
|
||||||
.Source.values()[position])
|
ProductsFragment
|
||||||
|
.Source.values()[position]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
content.addView(
|
||||||
|
this,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
registerOnPageChangeCallback(pageChangeCallback)
|
registerOnPageChangeCallback(pageChangeCallback)
|
||||||
offscreenPageLimit = 1
|
offscreenPageLimit = 1
|
||||||
}
|
}
|
||||||
@ -247,15 +264,21 @@ class TabsFragment: ScreenFragment() {
|
|||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { setSectionsAndUpdate(it.asSequence().sorted()
|
.subscribe {
|
||||||
.map(ProductItem.Section::Category).toList(), null) }
|
setSectionsAndUpdate(
|
||||||
|
it.asSequence().sorted()
|
||||||
|
.map(ProductItem.Section::Category).toList(), null
|
||||||
|
)
|
||||||
|
}
|
||||||
repositoriesDisposable = Observable.just(Unit)
|
repositoriesDisposable = Observable.just(Unit)
|
||||||
.concatWith(Database.observable(Database.Subject.Repositories))
|
.concatWith(Database.observable(Database.Subject.Repositories))
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { setSectionsAndUpdate(null, it.asSequence().filter { it.enabled }
|
.subscribe { it ->
|
||||||
.map { ProductItem.Section.Repository(it.id, it.name) }.toList()) }
|
setSectionsAndUpdate(null, it.asSequence().filter { it.enabled }
|
||||||
|
.map { ProductItem.Section.Repository(it.id, it.name) }.toList())
|
||||||
|
}
|
||||||
updateSection()
|
updateSection()
|
||||||
|
|
||||||
val sectionsList = RecyclerView(toolbar.context).apply {
|
val sectionsList = RecyclerView(toolbar.context).apply {
|
||||||
@ -273,7 +296,7 @@ class TabsFragment: ScreenFragment() {
|
|||||||
}
|
}
|
||||||
this.adapter = adapter
|
this.adapter = adapter
|
||||||
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
||||||
setBackgroundColor(context.getColorFromAttr(android.R.attr.colorPrimaryDark).defaultColor)
|
setBackgroundResource(R.drawable.background_border)
|
||||||
elevation = resources.sizeScaled(4).toFloat()
|
elevation = resources.sizeScaled(4).toFloat()
|
||||||
content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, 0)
|
content.addView(this, FrameLayout.LayoutParams.MATCH_PARENT, 0)
|
||||||
visibility = View.GONE
|
visibility = View.GONE
|
||||||
@ -368,7 +391,9 @@ class TabsFragment: ScreenFragment() {
|
|||||||
|
|
||||||
private fun setSelectedTab(source: ProductsFragment.Source) {
|
private fun setSelectedTab(source: ProductsFragment.Source) {
|
||||||
val layout = layout!!
|
val layout = layout!!
|
||||||
(0 until layout.tabs.childCount).forEach { layout.tabs.getChildAt(it).isSelected = it == source.ordinal }
|
(0 until layout.tabs.childCount).forEach {
|
||||||
|
layout.tabs.getChildAt(it).isSelected = it == source.ordinal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun selectUpdates() = selectUpdatesInternal(true)
|
internal fun selectUpdates() = selectUpdatesInternal(true)
|
||||||
@ -376,7 +401,10 @@ class TabsFragment: ScreenFragment() {
|
|||||||
private fun selectUpdatesInternal(allowSmooth: Boolean) {
|
private fun selectUpdatesInternal(allowSmooth: Boolean) {
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val viewPager = viewPager
|
val viewPager = viewPager
|
||||||
viewPager?.setCurrentItem(ProductsFragment.Source.UPDATES.ordinal, allowSmooth && viewPager.isLaidOut)
|
viewPager?.setCurrentItem(
|
||||||
|
ProductsFragment.Source.UPDATES.ordinal,
|
||||||
|
allowSmooth && viewPager.isLaidOut
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
needSelectUpdates = true
|
needSelectUpdates = true
|
||||||
}
|
}
|
||||||
@ -402,8 +430,10 @@ class TabsFragment: ScreenFragment() {
|
|||||||
return if (list == null || oldList == list) oldList else null
|
return if (list == null || oldList == list) oldList else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSectionsAndUpdate(categories: List<ProductItem.Section.Category>?,
|
private fun setSectionsAndUpdate(
|
||||||
repositories: List<ProductItem.Section.Repository>?) {
|
categories: List<ProductItem.Section.Category>?,
|
||||||
|
repositories: List<ProductItem.Section.Repository>?
|
||||||
|
) {
|
||||||
val oldCategories = collectOldSections(categories)
|
val oldCategories = collectOldSections(categories)
|
||||||
val oldRepositories = collectOldSections(repositories)
|
val oldRepositories = collectOldSections(repositories)
|
||||||
if (oldCategories == null || oldRepositories == null) {
|
if (oldCategories == null || oldRepositories == null) {
|
||||||
@ -423,7 +453,8 @@ class TabsFragment: ScreenFragment() {
|
|||||||
is ProductItem.Section.Category -> section.name
|
is ProductItem.Section.Category -> section.name
|
||||||
is ProductItem.Section.Repository -> section.name
|
is ProductItem.Section.Repository -> section.name
|
||||||
}
|
}
|
||||||
layout?.sectionIcon?.visibility = if (sections.any { it !is ProductItem.Section.All }) View.VISIBLE else View.GONE
|
layout?.sectionIcon?.visibility =
|
||||||
|
if (sections.any { it !is ProductItem.Section.All }) View.VISIBLE else View.GONE
|
||||||
productFragments.forEach { it.setSection(section) }
|
productFragments.forEach { it.setSection(section) }
|
||||||
sectionsList?.adapter?.notifyDataSetChanged()
|
sectionsList?.adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
@ -439,7 +470,8 @@ class TabsFragment: ScreenFragment() {
|
|||||||
if (value != target) {
|
if (value != target) {
|
||||||
sectionsAnimator = ValueAnimator.ofFloat(value, target).apply {
|
sectionsAnimator = ValueAnimator.ofFloat(value, target).apply {
|
||||||
duration = (250 * abs(target - value)).toLong()
|
duration = (250 * abs(target - value)).toLong()
|
||||||
interpolator = if (target >= 1f) AccelerateInterpolator(2f) else DecelerateInterpolator(2f)
|
interpolator =
|
||||||
|
if (target >= 1f) AccelerateInterpolator(2f) else DecelerateInterpolator(2f)
|
||||||
addUpdateListener {
|
addUpdateListener {
|
||||||
val newValue = animatedValue as Float
|
val newValue = animatedValue as Float
|
||||||
sectionsList.apply {
|
sectionsList.apply {
|
||||||
@ -463,7 +495,11 @@ class TabsFragment: ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
|
private val pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
override fun onPageScrolled(
|
||||||
|
position: Int,
|
||||||
|
positionOffset: Float,
|
||||||
|
positionOffsetPixels: Int
|
||||||
|
) {
|
||||||
val layout = layout!!
|
val layout = layout!!
|
||||||
val fromSections = ProductsFragment.Source.values()[position].sections
|
val fromSections = ProductsFragment.Source.values()[position].sections
|
||||||
val toSections = if (positionOffset <= 0f) fromSections else
|
val toSections = if (positionOffset <= 0f) fromSections else
|
||||||
@ -490,8 +526,11 @@ class TabsFragment: ScreenFragment() {
|
|||||||
val source = ProductsFragment.Source.values()[position]
|
val source = ProductsFragment.Source.values()[position]
|
||||||
updateUpdateNotificationBlocker(source)
|
updateUpdateNotificationBlocker(source)
|
||||||
sortOrderMenu!!.first.isVisible = source.order
|
sortOrderMenu!!.first.isVisible = source.order
|
||||||
syncRepositoriesMenuItem!!.setShowAsActionFlags(if (!source.order ||
|
syncRepositoriesMenuItem!!.setShowAsActionFlags(
|
||||||
resources.configuration.screenWidthDp >= 400) MenuItem.SHOW_AS_ACTION_ALWAYS else 0)
|
if (!source.order ||
|
||||||
|
resources.configuration.screenWidthDp >= 400
|
||||||
|
) MenuItem.SHOW_AS_ACTION_ALWAYS else 0
|
||||||
|
)
|
||||||
setSelectedTab(source)
|
setSelectedTab(source)
|
||||||
if (showSections && !source.sections) {
|
if (showSections && !source.sections) {
|
||||||
showSections = false
|
showSections = false
|
||||||
@ -500,7 +539,8 @@ class TabsFragment: ScreenFragment() {
|
|||||||
|
|
||||||
override fun onPageScrollStateChanged(state: Int) {
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
val source = ProductsFragment.Source.values()[viewPager!!.currentItem]
|
val source = ProductsFragment.Source.values()[viewPager!!.currentItem]
|
||||||
layout!!.sectionChange.isEnabled = state != ViewPager2.SCROLL_STATE_DRAGGING && source.sections
|
layout!!.sectionChange.isEnabled =
|
||||||
|
state != ViewPager2.SCROLL_STATE_DRAGGING && source.sections
|
||||||
if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
||||||
// onPageSelected can be called earlier than fragments created
|
// onPageSelected can be called earlier than fragments created
|
||||||
updateUpdateNotificationBlocker(source)
|
updateUpdateNotificationBlocker(source)
|
||||||
@ -511,7 +551,7 @@ class TabsFragment: ScreenFragment() {
|
|||||||
private class TabsBackgroundDrawable(context: Context, private val rtl: Boolean) : Drawable() {
|
private class TabsBackgroundDrawable(context: Context, private val rtl: Boolean) : Drawable() {
|
||||||
private val height = context.resources.sizeScaled(2)
|
private val height = context.resources.sizeScaled(2)
|
||||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
color = context.getColorFromAttr(android.R.attr.colorPrimary).defaultColor
|
color = context.getColorFromAttr(android.R.attr.textColor).defaultColor
|
||||||
}
|
}
|
||||||
|
|
||||||
private var position = 0f
|
private var position = 0f
|
||||||
@ -529,11 +569,18 @@ class TabsFragment: ScreenFragment() {
|
|||||||
val width = bounds.width() / total.toFloat()
|
val width = bounds.width() / total.toFloat()
|
||||||
val x = width * position
|
val x = width * position
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
canvas.drawRect(bounds.right - width - x, (bounds.bottom - height).toFloat(),
|
canvas.drawRect(
|
||||||
bounds.right - x, bounds.bottom.toFloat(), paint)
|
bounds.right - width - x, (bounds.bottom - height).toFloat(),
|
||||||
|
bounds.right - x, bounds.bottom.toFloat(), paint
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
canvas.drawRect(bounds.left + x, (bounds.bottom - height).toFloat(),
|
canvas.drawRect(
|
||||||
bounds.left + x + width, bounds.bottom.toFloat(), paint)
|
bounds.left + x + width / 4, // + width/4 from start
|
||||||
|
(bounds.bottom - height).toFloat(),
|
||||||
|
bounds.left + x + width - width / 4, // - width/4 from end
|
||||||
|
bounds.bottom.toFloat(),
|
||||||
|
paint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -543,12 +590,15 @@ class TabsFragment: ScreenFragment() {
|
|||||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SectionsAdapter(private val sections: () -> List<ProductItem.Section>,
|
private class SectionsAdapter(
|
||||||
private val onClick: (ProductItem.Section) -> Unit): StableRecyclerAdapter<SectionsAdapter.ViewType,
|
private val sections: () -> List<ProductItem.Section>,
|
||||||
|
private val onClick: (ProductItem.Section) -> Unit
|
||||||
|
) : StableRecyclerAdapter<SectionsAdapter.ViewType,
|
||||||
RecyclerView.ViewHolder>() {
|
RecyclerView.ViewHolder>() {
|
||||||
enum class ViewType { SECTION }
|
enum class ViewType { SECTION }
|
||||||
|
|
||||||
private class SectionViewHolder(context: Context): RecyclerView.ViewHolder(TextView(context)) {
|
private class SectionViewHolder(context: Context) :
|
||||||
|
RecyclerView.ViewHolder(TextView(context)) {
|
||||||
val title: TextView
|
val title: TextView
|
||||||
get() = itemView as TextView
|
get() = itemView as TextView
|
||||||
|
|
||||||
@ -556,24 +606,41 @@ class TabsFragment: ScreenFragment() {
|
|||||||
itemView as TextView
|
itemView as TextView
|
||||||
itemView.gravity = Gravity.CENTER_VERTICAL
|
itemView.gravity = Gravity.CENTER_VERTICAL
|
||||||
itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) }
|
itemView.resources.sizeScaled(16).let { itemView.setPadding(it, 0, it, 0) }
|
||||||
itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColorPrimary))
|
itemView.setTextColor(context.getColorFromAttr(android.R.attr.textColor))
|
||||||
itemView.setTextSizeScaled(16)
|
itemView.setTextSizeScaled(16)
|
||||||
itemView.background = context.getDrawableFromAttr(android.R.attr.selectableItemBackground)
|
itemView.background =
|
||||||
itemView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
context.getDrawableFromAttr(android.R.attr.selectableItemBackground)
|
||||||
itemView.resources.sizeScaled(48))
|
itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||||
|
itemView.resources.sizeScaled(48)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureDivider(context: Context, position: Int, configuration: DividerItemDecoration.Configuration) {
|
fun configureDivider(
|
||||||
|
context: Context,
|
||||||
|
position: Int,
|
||||||
|
configuration: DividerItemDecoration.Configuration
|
||||||
|
) {
|
||||||
val currentSection = sections()[position]
|
val currentSection = sections()[position]
|
||||||
val nextSection = sections().getOrNull(position + 1)
|
val nextSection = sections().getOrNull(position + 1)
|
||||||
when {
|
when {
|
||||||
nextSection != null && currentSection.javaClass != nextSection.javaClass -> {
|
nextSection != null && currentSection.javaClass != nextSection.javaClass -> {
|
||||||
val padding = context.resources.sizeScaled(16)
|
val padding = context.resources.sizeScaled(16)
|
||||||
configuration.set(true, false, padding, padding)
|
configuration.set(
|
||||||
|
needDivider = true,
|
||||||
|
toTop = false,
|
||||||
|
paddingStart = padding,
|
||||||
|
paddingEnd = padding
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
configuration.set(false, false, 0, 0)
|
configuration.set(
|
||||||
|
needDivider = false,
|
||||||
|
toTop = false,
|
||||||
|
paddingStart = 0,
|
||||||
|
paddingEnd = 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,7 +652,10 @@ class TabsFragment: ScreenFragment() {
|
|||||||
override fun getItemDescriptor(position: Int): String = sections()[position].toString()
|
override fun getItemDescriptor(position: Int): String = sections()[position].toString()
|
||||||
override fun getItemEnumViewType(position: Int): ViewType = ViewType.SECTION
|
override fun getItemEnumViewType(position: Int): ViewType = ViewType.SECTION
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: ViewType): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: ViewType
|
||||||
|
): RecyclerView.ViewHolder {
|
||||||
return SectionViewHolder(parent.context).apply {
|
return SectionViewHolder(parent.context).apply {
|
||||||
itemView.setOnClickListener { onClick(sections()[adapterPosition]) }
|
itemView.setOnClickListener { onClick(sections()[adapterPosition]) }
|
||||||
}
|
}
|
||||||
@ -599,9 +669,11 @@ class TabsFragment: ScreenFragment() {
|
|||||||
val margin = holder.itemView.resources.sizeScaled(8)
|
val margin = holder.itemView.resources.sizeScaled(8)
|
||||||
val layoutParams = holder.itemView.layoutParams as RecyclerView.LayoutParams
|
val layoutParams = holder.itemView.layoutParams as RecyclerView.LayoutParams
|
||||||
layoutParams.topMargin = if (previousSection == null ||
|
layoutParams.topMargin = if (previousSection == null ||
|
||||||
section.javaClass != previousSection.javaClass) margin else 0
|
section.javaClass != previousSection.javaClass
|
||||||
|
) margin else 0
|
||||||
layoutParams.bottomMargin = if (nextSection == null ||
|
layoutParams.bottomMargin = if (nextSection == null ||
|
||||||
section.javaClass != nextSection.javaClass) margin else 0
|
section.javaClass != nextSection.javaClass
|
||||||
|
) margin else 0
|
||||||
holder.title.text = when (section) {
|
holder.title.text = when (section) {
|
||||||
is ProductItem.Section.All -> holder.itemView.resources.getString(R.string.all_applications)
|
is ProductItem.Section.All -> holder.itemView.resources.getString(R.string.all_applications)
|
||||||
is ProductItem.Section.Category -> section.name
|
is ProductItem.Section.Category -> section.name
|
||||||
|
Loading…
x
Reference in New Issue
Block a user