mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-06-20 06:09:19 +00:00
Rename package to com.machaiv3lli.fdroid
This commit is contained in:
277
src/main/kotlin/com/machiav3lli/fdroid/screen/MessageDialog.kt
Normal file
277
src/main/kotlin/com/machiav3lli/fdroid/screen/MessageDialog.kt
Normal file
@ -0,0 +1,277 @@
|
||||
package com.machiav3lli.fdroid.screen
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.machiav3lli.fdroid.R
|
||||
import com.machiav3lli.fdroid.RepoManager
|
||||
import com.machiav3lli.fdroid.database.entity.Release
|
||||
import com.machiav3lli.fdroid.utility.KParcelable
|
||||
import com.machiav3lli.fdroid.utility.PackageItemResolver
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import com.machiav3lli.fdroid.utility.extension.text.nullIfEmpty
|
||||
|
||||
class MessageDialog() : DialogFragment() {
|
||||
companion object {
|
||||
private const val EXTRA_MESSAGE = "message"
|
||||
}
|
||||
|
||||
sealed class Message : KParcelable {
|
||||
object DeleteRepositoryConfirm : Message() {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator { DeleteRepositoryConfirm }
|
||||
}
|
||||
|
||||
object CantEditSyncing : Message() {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator { CantEditSyncing }
|
||||
}
|
||||
|
||||
class Link(val uri: Uri) : Message() {
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(uri.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator {
|
||||
val uri = Uri.parse(it.readString()!!)
|
||||
Link(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Permissions(val group: String?, val permissions: List<String>) : Message() {
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(group)
|
||||
dest.writeStringList(permissions)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator {
|
||||
val group = it.readString()
|
||||
val permissions = it.createStringArrayList()!!
|
||||
Permissions(group, permissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReleaseIncompatible(
|
||||
val incompatibilities: List<Release.Incompatibility>,
|
||||
val platforms: List<String>, val minSdkVersion: Int, val maxSdkVersion: Int,
|
||||
) : Message() {
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(incompatibilities.size)
|
||||
for (incompatibility in incompatibilities) {
|
||||
when (incompatibility) {
|
||||
is Release.Incompatibility.MinSdk -> {
|
||||
dest.writeInt(0)
|
||||
}
|
||||
is Release.Incompatibility.MaxSdk -> {
|
||||
dest.writeInt(1)
|
||||
}
|
||||
is Release.Incompatibility.Platform -> {
|
||||
dest.writeInt(2)
|
||||
}
|
||||
is Release.Incompatibility.Feature -> {
|
||||
dest.writeInt(3)
|
||||
dest.writeString(incompatibility.feature)
|
||||
}
|
||||
}::class
|
||||
}
|
||||
dest.writeStringList(platforms)
|
||||
dest.writeInt(minSdkVersion)
|
||||
dest.writeInt(maxSdkVersion)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator {
|
||||
val count = it.readInt()
|
||||
val incompatibilities = generateSequence {
|
||||
when (it.readInt()) {
|
||||
0 -> Release.Incompatibility.MinSdk
|
||||
1 -> Release.Incompatibility.MaxSdk
|
||||
2 -> Release.Incompatibility.Platform
|
||||
3 -> Release.Incompatibility.Feature(it.readString()!!)
|
||||
else -> throw RuntimeException()
|
||||
}
|
||||
}.take(count).toList()
|
||||
val platforms = it.createStringArrayList()!!
|
||||
val minSdkVersion = it.readInt()
|
||||
val maxSdkVersion = it.readInt()
|
||||
ReleaseIncompatible(incompatibilities, platforms, minSdkVersion, maxSdkVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ReleaseOlder : Message() {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator { ReleaseOlder }
|
||||
}
|
||||
|
||||
object ReleaseSignatureMismatch : Message() {
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR = KParcelable.creator { ReleaseSignatureMismatch }
|
||||
}
|
||||
}
|
||||
|
||||
constructor(message: Message) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(EXTRA_MESSAGE, message)
|
||||
}
|
||||
}
|
||||
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
show(fragmentManager, this::class.java.name)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
when (val message = requireArguments().getParcelable<Message>(EXTRA_MESSAGE)!!) {
|
||||
is Message.DeleteRepositoryConfirm -> {
|
||||
dialog.setTitle(R.string.confirmation)
|
||||
dialog.setMessage(R.string.delete_repository_DESC)
|
||||
dialog.setPositiveButton(R.string.delete) { _, _ -> (parentFragment as RepoManager).onDeleteConfirm() }
|
||||
dialog.setNegativeButton(R.string.cancel, null)
|
||||
}
|
||||
is Message.CantEditSyncing -> {
|
||||
dialog.setTitle(R.string.action_failed)
|
||||
dialog.setMessage(R.string.cant_edit_sync_DESC)
|
||||
dialog.setPositiveButton(R.string.ok, null)
|
||||
}
|
||||
is Message.Link -> {
|
||||
dialog.setTitle(R.string.confirmation)
|
||||
dialog.setMessage(getString(R.string.open_DESC_FORMAT, message.uri.toString()))
|
||||
dialog.setPositiveButton(R.string.ok) { _, _ ->
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, message.uri))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
dialog.setNegativeButton(R.string.cancel, null)
|
||||
}
|
||||
is Message.Permissions -> {
|
||||
val packageManager = requireContext().packageManager
|
||||
val builder = StringBuilder()
|
||||
val localCache = PackageItemResolver.LocalCache()
|
||||
val title = if (message.group != null) {
|
||||
val name = try {
|
||||
val permissionGroupInfo =
|
||||
packageManager.getPermissionGroupInfo(message.group, 0)
|
||||
PackageItemResolver.loadLabel(
|
||||
requireContext(),
|
||||
localCache,
|
||||
permissionGroupInfo
|
||||
)
|
||||
?.nullIfEmpty()?.let { if (it == message.group) null else it }
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
name ?: getString(R.string.unknown)
|
||||
} else {
|
||||
getString(R.string.other)
|
||||
}
|
||||
for (permission in message.permissions) {
|
||||
val description = try {
|
||||
val permissionInfo = packageManager.getPermissionInfo(permission, 0)
|
||||
PackageItemResolver.loadDescription(
|
||||
requireContext(),
|
||||
localCache,
|
||||
permissionInfo
|
||||
)
|
||||
?.nullIfEmpty()?.let { if (it == permission) null else it }
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
description?.let { builder.append(it).append("\n\n") }
|
||||
}
|
||||
if (builder.isNotEmpty()) {
|
||||
builder.delete(builder.length - 2, builder.length)
|
||||
} else {
|
||||
builder.append(getString(R.string.no_description_available_DESC))
|
||||
}
|
||||
dialog.setTitle(title)
|
||||
dialog.setMessage(builder)
|
||||
dialog.setPositiveButton(R.string.ok, null)
|
||||
}
|
||||
is Message.ReleaseIncompatible -> {
|
||||
val builder = StringBuilder()
|
||||
val minSdkVersion = if (Release.Incompatibility.MinSdk in message.incompatibilities)
|
||||
message.minSdkVersion else null
|
||||
val maxSdkVersion = if (Release.Incompatibility.MaxSdk in message.incompatibilities)
|
||||
message.maxSdkVersion else null
|
||||
if (minSdkVersion != null || maxSdkVersion != null) {
|
||||
val versionMessage = minSdkVersion?.let {
|
||||
getString(
|
||||
R.string.incompatible_api_min_DESC_FORMAT,
|
||||
it
|
||||
)
|
||||
}
|
||||
?: maxSdkVersion?.let {
|
||||
getString(
|
||||
R.string.incompatible_api_max_DESC_FORMAT,
|
||||
it
|
||||
)
|
||||
}
|
||||
builder.append(
|
||||
getString(
|
||||
R.string.incompatible_api_DESC_FORMAT,
|
||||
Android.name, Android.sdk, versionMessage.orEmpty()
|
||||
)
|
||||
).append("\n\n")
|
||||
}
|
||||
if (Release.Incompatibility.Platform in message.incompatibilities) {
|
||||
builder.append(
|
||||
getString(
|
||||
R.string.incompatible_platforms_DESC_FORMAT,
|
||||
Android.primaryPlatform ?: getString(R.string.unknown),
|
||||
message.platforms.joinToString(separator = ", ")
|
||||
)
|
||||
).append("\n\n")
|
||||
}
|
||||
val features =
|
||||
message.incompatibilities.mapNotNull { it as? Release.Incompatibility.Feature }
|
||||
if (features.isNotEmpty()) {
|
||||
builder.append(getString(R.string.incompatible_features_DESC))
|
||||
for (feature in features) {
|
||||
builder.append("\n\u2022 ").append(feature.feature)
|
||||
}
|
||||
builder.append("\n\n")
|
||||
}
|
||||
if (builder.isNotEmpty()) {
|
||||
builder.delete(builder.length - 2, builder.length)
|
||||
}
|
||||
dialog.setTitle(R.string.incompatible_version)
|
||||
dialog.setMessage(builder)
|
||||
dialog.setPositiveButton(R.string.ok, null)
|
||||
}
|
||||
is Message.ReleaseOlder -> {
|
||||
dialog.setTitle(R.string.incompatible_version)
|
||||
dialog.setMessage(R.string.incompatible_older_DESC)
|
||||
dialog.setPositiveButton(R.string.ok, null)
|
||||
}
|
||||
is Message.ReleaseSignatureMismatch -> {
|
||||
dialog.setTitle(R.string.incompatible_version)
|
||||
dialog.setMessage(R.string.incompatible_signature_DESC)
|
||||
dialog.setPositiveButton(R.string.ok, null)
|
||||
}
|
||||
}::class
|
||||
return dialog.create()
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
package com.machiav3lli.fdroid.screen
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.MarginPageTransformer
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import coil.load
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.machiav3lli.fdroid.R
|
||||
import com.machiav3lli.fdroid.database.DatabaseX
|
||||
import com.machiav3lli.fdroid.database.entity.Repository
|
||||
import com.machiav3lli.fdroid.entity.Screenshot
|
||||
import com.machiav3lli.fdroid.graphics.PaddingDrawable
|
||||
import com.machiav3lli.fdroid.network.CoilDownloader
|
||||
import com.machiav3lli.fdroid.utility.RxUtils
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import com.machiav3lli.fdroid.utility.extension.resources.getColorFromAttr
|
||||
import com.machiav3lli.fdroid.utility.extension.resources.getDrawableCompat
|
||||
import com.machiav3lli.fdroid.utility.extension.resources.sizeScaled
|
||||
import com.machiav3lli.fdroid.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
|
||||
|
||||
class ScreenshotsFragment() : DialogFragment() {
|
||||
companion object {
|
||||
private const val EXTRA_PACKAGE_NAME = "packageName"
|
||||
private const val EXTRA_REPOSITORY_ID = "repositoryId"
|
||||
private const val EXTRA_IDENTIFIER = "identifier"
|
||||
|
||||
private const val STATE_IDENTIFIER = "identifier"
|
||||
}
|
||||
|
||||
constructor(packageName: String, repositoryId: Long, identifier: String) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putString(EXTRA_PACKAGE_NAME, packageName)
|
||||
putLong(EXTRA_REPOSITORY_ID, repositoryId)
|
||||
putString(EXTRA_IDENTIFIER, identifier)
|
||||
}
|
||||
}
|
||||
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
show(fragmentManager, this::class.java.name)
|
||||
}
|
||||
|
||||
private var viewPager: ViewPager2? = null
|
||||
|
||||
private var productDisposable: Disposable? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val packageName = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
||||
val repositoryId = requireArguments().getLong(EXTRA_REPOSITORY_ID)
|
||||
val dialog = Dialog(requireContext(), R.style.Theme_Main_Dark)
|
||||
|
||||
val window = dialog.window
|
||||
val decorView = window?.decorView
|
||||
val db = DatabaseX.getInstance(requireContext())
|
||||
|
||||
if (window != null) {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
}
|
||||
|
||||
val background = dialog.context.getColorFromAttr(R.attr.colorSurface).defaultColor
|
||||
decorView?.setBackgroundColor(
|
||||
ColorUtils.blendARGB(
|
||||
0x00FFFFFF and background,
|
||||
background,
|
||||
0.9f
|
||||
)
|
||||
)
|
||||
decorView?.setPadding(0, 0, 0, 0)
|
||||
if (window != null) {
|
||||
window.attributes = window.attributes.apply {
|
||||
title = ScreenshotsFragment::class.java.name
|
||||
format = PixelFormat.TRANSLUCENT
|
||||
windowAnimations = run {
|
||||
val typedArray = dialog.context.obtainStyledAttributes(
|
||||
null, intArrayOf(android.R.attr.windowAnimationStyle),
|
||||
android.R.attr.dialogTheme, 0
|
||||
)
|
||||
try {
|
||||
typedArray.getResourceId(0, 0)
|
||||
} finally {
|
||||
typedArray.recycle()
|
||||
}
|
||||
}
|
||||
if (Android.sdk(28)) {
|
||||
layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val toggleSystemUi = {
|
||||
if (window != null && decorView != null) {
|
||||
WindowInsetsControllerCompat(window, decorView).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.statusBars())
|
||||
controller.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val viewPager = ViewPager2(dialog.context)
|
||||
viewPager.adapter = Adapter(packageName) { toggleSystemUi() }
|
||||
viewPager.setPageTransformer(MarginPageTransformer(resources.sizeScaled(16)))
|
||||
viewPager.viewTreeObserver.addOnGlobalLayoutListener {
|
||||
(viewPager.adapter as Adapter).size = Pair(viewPager.width, viewPager.height)
|
||||
}
|
||||
dialog.addContentView(
|
||||
viewPager, ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
this.viewPager = viewPager
|
||||
|
||||
var restored = false
|
||||
productDisposable = Observable.just(Unit)
|
||||
//.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMapSingle {
|
||||
RxUtils.querySingle {
|
||||
db.productDao.get(packageName).filterNotNull()
|
||||
}
|
||||
}
|
||||
.map { it ->
|
||||
Pair(
|
||||
it.find { it.repositoryId == repositoryId },
|
||||
db.repositoryDao.get(repositoryId)
|
||||
)
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { it ->
|
||||
val (product, repository) = it
|
||||
val screenshots = product?.screenshots.orEmpty()
|
||||
(viewPager.adapter as Adapter).update(viewPager, repository, screenshots)
|
||||
if (!restored) {
|
||||
restored = true
|
||||
val identifier = savedInstanceState?.getString(STATE_IDENTIFIER)
|
||||
?: requireArguments().getString(STATE_IDENTIFIER)
|
||||
if (identifier != null) {
|
||||
val index = screenshots.indexOfFirst { it.identifier == identifier }
|
||||
if (index >= 0) {
|
||||
viewPager.setCurrentItem(index, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
viewPager = null
|
||||
|
||||
productDisposable?.dispose()
|
||||
productDisposable = null
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
val viewPager = viewPager
|
||||
if (viewPager != null) {
|
||||
val identifier = (viewPager.adapter as Adapter).getCurrentIdentifier(viewPager)
|
||||
identifier?.let { outState.putString(STATE_IDENTIFIER, it) }
|
||||
}
|
||||
}
|
||||
|
||||
private class Adapter(private val packageName: String, private val onClick: () -> Unit) :
|
||||
StableRecyclerAdapter<Adapter.ViewType, RecyclerView.ViewHolder>() {
|
||||
enum class ViewType { SCREENSHOT }
|
||||
|
||||
private class ViewHolder(context: Context) :
|
||||
RecyclerView.ViewHolder(ShapeableImageView(context)) {
|
||||
val image: ShapeableImageView
|
||||
get() = itemView as ShapeableImageView
|
||||
|
||||
val placeholder: Drawable
|
||||
|
||||
init {
|
||||
itemView.layoutParams = RecyclerView.LayoutParams(
|
||||
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
RecyclerView.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
val placeholder =
|
||||
itemView.context.getDrawableCompat(R.drawable.ic_photo_camera).mutate()
|
||||
placeholder.setTint(itemView.context.getColorFromAttr(R.attr.colorSurface).defaultColor)
|
||||
this.placeholder = PaddingDrawable(placeholder, 4f)
|
||||
}
|
||||
}
|
||||
|
||||
private var repository: Repository? = null
|
||||
private var screenshots = emptyList<Screenshot>()
|
||||
|
||||
fun update(
|
||||
viewPager: ViewPager2,
|
||||
repository: Repository?,
|
||||
screenshots: List<Screenshot>,
|
||||
) {
|
||||
this.repository = repository
|
||||
this.screenshots = screenshots
|
||||
notifyItemChanged(viewPager.currentItem)
|
||||
}
|
||||
|
||||
var size = Pair(0, 0)
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentIdentifier(viewPager: ViewPager2): String? {
|
||||
val position = viewPager.currentItem
|
||||
return screenshots.getOrNull(position)?.identifier
|
||||
}
|
||||
|
||||
override val viewTypeClass: Class<ViewType>
|
||||
get() = ViewType::class.java
|
||||
|
||||
override fun getItemCount(): Int = screenshots.size
|
||||
override fun getItemDescriptor(position: Int): String = screenshots[position].identifier
|
||||
override fun getItemEnumViewType(position: Int): ViewType = ViewType.SCREENSHOT
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: ViewType,
|
||||
): RecyclerView.ViewHolder {
|
||||
return ViewHolder(parent.context).apply {
|
||||
itemView.setOnClickListener { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
holder as ViewHolder
|
||||
val screenshot = screenshots[position]
|
||||
val (width, height) = size
|
||||
repository?.let {
|
||||
holder.image.load(
|
||||
CoilDownloader.createScreenshotUri(
|
||||
it,
|
||||
packageName,
|
||||
screenshot
|
||||
)
|
||||
) {
|
||||
placeholder(holder.placeholder)
|
||||
error(holder.placeholder)
|
||||
size(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user