Rename package to com.machaiv3lli.fdroid

This commit is contained in:
machiav3lli
2022-07-05 03:21:35 +02:00
parent b8deae87ea
commit c482580686
126 changed files with 689 additions and 684 deletions

View 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()
}
}

View File

@ -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)
}
}
}
}
}