mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
package refactor
This commit is contained in:
@ -101,6 +101,7 @@ dependencies {
|
|||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.coil.video)
|
implementation(libs.coil.video)
|
||||||
|
implementation(libs.osmdroid.android)
|
||||||
|
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling:1.4.3")
|
debugImplementation("androidx.compose.ui:ui-tooling:1.4.3")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview:1.4.3")
|
implementation("androidx.compose.ui:ui-tooling-preview:1.4.3")
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:excludeFromRecents="true" />
|
android:excludeFromRecents="true" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.map.MapActivity"
|
android:name=".ui.MapActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:excludeFromRecents="true" />
|
android:excludeFromRecents="true" />
|
||||||
<activity android:name=".bridge.ForceStartActivity"
|
<activity android:name=".bridge.ForceStartActivity"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.ui.map
|
package me.rhunk.snapenhance.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
@ -58,7 +58,7 @@ import kotlinx.coroutines.launch
|
|||||||
import me.rhunk.snapenhance.R
|
import me.rhunk.snapenhance.R
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.ui.download.MediaFilter
|
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
|
|
||||||
class DownloadsSection : Section() {
|
class DownloadsSection : Section() {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package me.rhunk.snapenhance.ui.manager.sections.features
|
||||||
|
|
||||||
|
class PickLocation {
|
||||||
|
}
|
@ -38,7 +38,6 @@ dependencies {
|
|||||||
implementation(libs.recyclerview)
|
implementation(libs.recyclerview)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
implementation(libs.ffmpeg.kit)
|
implementation(libs.ffmpeg.kit)
|
||||||
implementation(libs.osmdroid.android)
|
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
implementation(libs.androidx.documentfile)
|
implementation(libs.androidx.documentfile)
|
||||||
|
|
||||||
|
@ -4,13 +4,12 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import me.rhunk.snapenhance.action.AbstractAction
|
import me.rhunk.snapenhance.action.AbstractAction
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
import me.rhunk.snapenhance.ui.map.MapActivity
|
|
||||||
|
|
||||||
class OpenMap: AbstractAction("action.open_map") {
|
class OpenMap: AbstractAction("action.open_map") {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
context.runOnUiThread {
|
context.runOnUiThread {
|
||||||
val mapActivityIntent = Intent()
|
val mapActivityIntent = Intent()
|
||||||
mapActivityIntent.setClassName(BuildConfig.APPLICATION_ID, MapActivity::class.java.name)
|
mapActivityIntent.setClassName(BuildConfig.APPLICATION_ID, "me.rhunk.snapenhance.ui.MapActivity")
|
||||||
mapActivityIntent.putExtra("location", Bundle().apply {
|
mapActivityIntent.putExtra("location", Bundle().apply {
|
||||||
putDouble("latitude", context.config.spoof.location.latitude.get().toDouble())
|
putDouble("latitude", context.config.spoof.location.latitude.get().toDouble())
|
||||||
putDouble("longitude", context.config.spoof.location.longitude.get().toDouble())
|
putDouble("longitude", context.config.spoof.location.longitude.get().toDouble())
|
||||||
|
@ -6,7 +6,7 @@ import android.database.sqlite.SQLiteDatabase
|
|||||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
import me.rhunk.snapenhance.download.data.DownloadStage
|
||||||
import me.rhunk.snapenhance.ui.download.MediaFilter
|
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.getIntOrNull
|
import me.rhunk.snapenhance.util.getIntOrNull
|
||||||
import me.rhunk.snapenhance.util.getStringOrNull
|
import me.rhunk.snapenhance.util.getStringOrNull
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.ui.download
|
package me.rhunk.snapenhance.download.data
|
||||||
|
|
||||||
enum class MediaFilter(
|
enum class MediaFilter(
|
||||||
val key: String,
|
val key: String,
|
@ -32,7 +32,7 @@ import me.rhunk.snapenhance.hook.HookAdapter
|
|||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
import me.rhunk.snapenhance.hook.Hooker
|
import me.rhunk.snapenhance.hook.Hooker
|
||||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||||
import me.rhunk.snapenhance.ui.download.MediaFilter
|
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
||||||
import me.rhunk.snapenhance.util.getObjectField
|
import me.rhunk.snapenhance.util.getObjectField
|
||||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.ui.download
|
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.ListView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import me.rhunk.snapenhance.SharedContext
|
|
||||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
|
||||||
import me.rhunk.snapenhance.core.R
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class ActionListAdapter(
|
|
||||||
private val activity: DownloadManagerActivity,
|
|
||||||
private val layoutId: Int,
|
|
||||||
private val actions: Array<Pair<String, () -> Unit>>
|
|
||||||
) : ArrayAdapter<Pair<String, () -> Unit>>(activity, layoutId, actions) {
|
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
|
||||||
val view = convertView ?: activity.layoutInflater.inflate(layoutId, parent, false)
|
|
||||||
val action = actions[position]
|
|
||||||
view.isClickable = true
|
|
||||||
|
|
||||||
view.findViewById<TextView>(R.id.feature_text).text = action.first
|
|
||||||
view.setOnClickListener {
|
|
||||||
action.second()
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DebugSettingsLayoutInflater(
|
|
||||||
private val activity: DownloadManagerActivity
|
|
||||||
) {
|
|
||||||
private fun confirmAction(title: String, message: String, action: () -> Unit) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
AlertDialog.Builder(activity)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.setPositiveButton(SharedContext.translation["button.positive"]) { _, _ ->
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
.setNegativeButton(SharedContext.translation["button.negative"]) { _, _ -> }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSuccessToast() {
|
|
||||||
Toast.makeText(activity, "Success", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inflate(parent: ViewGroup) {
|
|
||||||
val debugSettingsLayout = activity.layoutInflater.inflate(R.layout.debug_settings_page, parent, false)
|
|
||||||
|
|
||||||
val debugSettingsTranslation = activity.translation.getCategory("debug_settings_page")
|
|
||||||
|
|
||||||
debugSettingsLayout.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
|
|
||||||
parent.removeView(debugSettingsLayout)
|
|
||||||
}
|
|
||||||
|
|
||||||
debugSettingsLayout.findViewById<TextView>(R.id.title).text = activity.translation["debug_settings"]
|
|
||||||
|
|
||||||
debugSettingsLayout.findViewById<ListView>(R.id.setting_page_list).apply {
|
|
||||||
adapter = ActionListAdapter(activity, R.layout.debug_setting_item, mutableListOf<Pair<String, () -> Unit>>().apply {
|
|
||||||
add(debugSettingsTranslation["clear_cache_title"] to {
|
|
||||||
context.cacheDir.listFiles()?.forEach {
|
|
||||||
it.deleteRecursively()
|
|
||||||
}
|
|
||||||
showSuccessToast()
|
|
||||||
})
|
|
||||||
|
|
||||||
BridgeFileType.values().forEach { fileType ->
|
|
||||||
val actionName = debugSettingsTranslation.format("clear_file_title", "file_name" to fileType.displayName)
|
|
||||||
add(actionName to {
|
|
||||||
confirmAction(actionName, debugSettingsTranslation.format("clear_file_confirmation", "file_name" to fileType.displayName)) {
|
|
||||||
fileType.resolve(context).deleteRecursively()
|
|
||||||
showSuccessToast()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
add(debugSettingsTranslation["reset_all_title"] to {
|
|
||||||
confirmAction(debugSettingsTranslation["reset_all_title"], debugSettingsTranslation["reset_all_confirmation"]) {
|
|
||||||
arrayOf(context.cacheDir, context.filesDir, File(context.dataDir, "databases"), File(context.dataDir, "shared_prefs")).forEach {
|
|
||||||
it.listFiles()?.forEach { file ->
|
|
||||||
file.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showSuccessToast()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.registerBackCallback {
|
|
||||||
parent.removeView(debugSettingsLayout)
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.addView(debugSettingsLayout)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,242 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.ui.download
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Handler
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.Adapter
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.SharedContext
|
|
||||||
import me.rhunk.snapenhance.core.R
|
|
||||||
import me.rhunk.snapenhance.data.FileType
|
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
|
||||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
|
||||||
import me.rhunk.snapenhance.util.snap.PreviewUtils
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.net.URL
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
|
|
||||||
class DownloadListAdapter(
|
|
||||||
private val activity: DownloadManagerActivity,
|
|
||||||
private val downloadList: MutableList<DownloadObject>
|
|
||||||
): Adapter<DownloadListAdapter.ViewHolder>() {
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
|
||||||
private val previewJobs = mutableMapOf<Int, Job>()
|
|
||||||
|
|
||||||
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val bitmojiIcon: ImageView = view.findViewById(R.id.bitmoji_icon)
|
|
||||||
val title: TextView = view.findViewById(R.id.item_title)
|
|
||||||
val subtitle: TextView = view.findViewById(R.id.item_subtitle)
|
|
||||||
val status: TextView = view.findViewById(R.id.item_status)
|
|
||||||
val actionButton: Button = view.findViewById(R.id.item_action_button)
|
|
||||||
val radius by lazy {
|
|
||||||
view.context.resources.getDimensionPixelSize(R.dimen.download_manager_item_preview_radius)
|
|
||||||
}
|
|
||||||
val viewWidth by lazy {
|
|
||||||
view.resources.displayMetrics.widthPixels
|
|
||||||
}
|
|
||||||
val viewHeight by lazy {
|
|
||||||
view.layoutParams.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.download_manager_item, parent, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return downloadList.size
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("Recycle")
|
|
||||||
private suspend fun handlePreview(download: DownloadObject, holder: ViewHolder) {
|
|
||||||
download.outputFile?.let {
|
|
||||||
val uri = Uri.parse(it)
|
|
||||||
runCatching {
|
|
||||||
if (uri.scheme == "content") {
|
|
||||||
val fileType = activity.contentResolver.openInputStream(uri)!!.use { stream ->
|
|
||||||
FileType.fromInputStream(stream)
|
|
||||||
}
|
|
||||||
fileType to activity.contentResolver.openInputStream(uri)
|
|
||||||
} else {
|
|
||||||
FileType.fromFile(File(it)) to FileInputStream(it)
|
|
||||||
}
|
|
||||||
}.getOrNull()
|
|
||||||
}?.also { (fileType, assetStream) ->
|
|
||||||
val previewBitmap = assetStream?.use { stream ->
|
|
||||||
//don't preview files larger than 30MB
|
|
||||||
if (stream.available() > 30 * 1024 * 1024) return@also
|
|
||||||
|
|
||||||
val tempFile = File.createTempFile("preview", ".${fileType.fileExtension}")
|
|
||||||
tempFile.outputStream().use { output ->
|
|
||||||
stream.copyTo(output)
|
|
||||||
}
|
|
||||||
runCatching {
|
|
||||||
PreviewUtils.createPreviewFromFile(tempFile)?.let { preview ->
|
|
||||||
val offsetY = (preview.height / 2 - holder.viewHeight / 2).coerceAtLeast(0)
|
|
||||||
|
|
||||||
Bitmap.createScaledBitmap(
|
|
||||||
Bitmap.createBitmap(
|
|
||||||
preview, 0, offsetY,
|
|
||||||
preview.width.coerceAtMost(holder.viewWidth),
|
|
||||||
preview.height.coerceAtMost(holder.viewHeight)
|
|
||||||
),
|
|
||||||
holder.viewWidth,
|
|
||||||
holder.viewHeight,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
Logger.error("failed to create preview $fileType", it)
|
|
||||||
}.also {
|
|
||||||
tempFile.delete()
|
|
||||||
}.getOrNull()
|
|
||||||
} ?: return@also
|
|
||||||
|
|
||||||
if (coroutineContext.job.isCancelled) return@also
|
|
||||||
Handler(holder.view.context.mainLooper).post {
|
|
||||||
holder.view.background = RoundedBitmapDrawableFactory.create(
|
|
||||||
holder.view.context.resources,
|
|
||||||
previewBitmap
|
|
||||||
).also {
|
|
||||||
it.cornerRadius = holder.radius.toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateViewHolder(download: DownloadObject, holder: ViewHolder) {
|
|
||||||
holder.status.text = download.downloadStage.toString()
|
|
||||||
holder.view.background = holder.view.context.getDrawable(R.drawable.download_manager_item_background)
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
withTimeout(2000) {
|
|
||||||
handlePreview(download, holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isSaved = download.downloadStage == DownloadStage.SAVED
|
|
||||||
//if the download is in progress, the user can cancel it
|
|
||||||
val canInteract = if (download.job != null) !download.downloadStage.isFinalStage || isSaved
|
|
||||||
else isSaved
|
|
||||||
|
|
||||||
holder.status.visibility = if (isSaved) View.GONE else View.VISIBLE
|
|
||||||
|
|
||||||
with(holder.actionButton) {
|
|
||||||
isEnabled = canInteract
|
|
||||||
alpha = if (canInteract) 1f else 0.5f
|
|
||||||
background = context.getDrawable(if (isSaved) R.drawable.action_button_success else R.drawable.action_button_cancel)
|
|
||||||
setTextColor(context.getColor(if (isSaved) R.color.successColor else R.color.actionBarColor))
|
|
||||||
text = if (isSaved)
|
|
||||||
SharedContext.translation["button.open"]
|
|
||||||
else
|
|
||||||
SharedContext.translation["button.cancel"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val pendingDownload = downloadList[position]
|
|
||||||
|
|
||||||
pendingDownload.changeListener = { _, _ ->
|
|
||||||
Handler(holder.view.context.mainLooper).post {
|
|
||||||
updateViewHolder(pendingDownload, holder)
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// holder.bitmojiIcon.setImageResource(R.drawable.bitmoji_blank)
|
|
||||||
|
|
||||||
pendingDownload.metadata.iconUrl?.let { url ->
|
|
||||||
thread(start = true) {
|
|
||||||
runCatching {
|
|
||||||
val iconBitmap = URL(url).openStream().use {
|
|
||||||
BitmapFactory.decodeStream(it)
|
|
||||||
}
|
|
||||||
Handler(holder.view.context.mainLooper).post {
|
|
||||||
holder.bitmojiIcon.setImageBitmap(iconBitmap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.title.visibility = View.GONE
|
|
||||||
holder.subtitle.visibility = View.GONE
|
|
||||||
|
|
||||||
pendingDownload.metadata.mediaDisplayType?.let {
|
|
||||||
holder.title.text = it
|
|
||||||
holder.title.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingDownload.metadata.mediaDisplaySource?.let {
|
|
||||||
holder.subtitle.text = it
|
|
||||||
holder.subtitle.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.actionButton.setOnClickListener {
|
|
||||||
if (pendingDownload.downloadStage != DownloadStage.SAVED) {
|
|
||||||
pendingDownload.cancel()
|
|
||||||
pendingDownload.downloadStage = DownloadStage.CANCELLED
|
|
||||||
updateViewHolder(pendingDownload, holder)
|
|
||||||
notifyItemChanged(position);
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingDownload.outputFile?.let {
|
|
||||||
fun showFileNotFound() {
|
|
||||||
Toast.makeText(holder.view.context, SharedContext.translation["download_manager_activity.file_not_found_toast"], Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri = Uri.parse(it)
|
|
||||||
val fileType = runCatching {
|
|
||||||
if (uri.scheme == "content") {
|
|
||||||
activity.contentResolver.openInputStream(uri)?.use { input ->
|
|
||||||
FileType.fromInputStream(input)
|
|
||||||
} ?: run {
|
|
||||||
showFileNotFound()
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val file = File(it)
|
|
||||||
if (!file.exists()) {
|
|
||||||
showFileNotFound()
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
FileType.fromFile(file)
|
|
||||||
}
|
|
||||||
}.onFailure { exception ->
|
|
||||||
Logger.error("Failed to open file", exception)
|
|
||||||
}.getOrDefault(FileType.UNKNOWN)
|
|
||||||
if (fileType == FileType.UNKNOWN) {
|
|
||||||
showFileNotFound()
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
intent.setDataAndType(uri, fileType.mimeType)
|
|
||||||
holder.view.context.startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateViewHolder(pendingDownload, holder)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.ui.download
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import me.rhunk.snapenhance.SharedContext
|
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
|
||||||
import me.rhunk.snapenhance.core.R
|
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
|
||||||
|
|
||||||
class DownloadManagerActivity : Activity() {
|
|
||||||
lateinit var translation: LocaleWrapper
|
|
||||||
|
|
||||||
private val backCallbacks = mutableListOf<() -> Unit>()
|
|
||||||
private val fetchedDownloadTasks = mutableListOf<DownloadObject>()
|
|
||||||
private var listFilter = MediaFilter.NONE
|
|
||||||
|
|
||||||
private val preferences by lazy {
|
|
||||||
getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateNoDownloadText() {
|
|
||||||
findViewById<TextView>(R.id.no_download_title).let {
|
|
||||||
it.text = translation["no_downloads"]
|
|
||||||
it.visibility = if (fetchedDownloadTasks.isEmpty()) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private fun updateListContent() {
|
|
||||||
fetchedDownloadTasks.clear()
|
|
||||||
fetchedDownloadTasks.addAll(SharedContext.downloadTaskManager.queryFirstTasks(filter = listFilter).values)
|
|
||||||
|
|
||||||
with(findViewById<RecyclerView>(R.id.download_list)) {
|
|
||||||
adapter?.notifyDataSetChanged()
|
|
||||||
scrollToPosition(0)
|
|
||||||
}
|
|
||||||
updateNoDownloadText()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun onBackPressed() {
|
|
||||||
backCallbacks.lastOrNull()?.let {
|
|
||||||
it()
|
|
||||||
backCallbacks.removeLast()
|
|
||||||
} ?: super.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun registerBackCallback(callback: () -> Unit) {
|
|
||||||
backCallbacks.add(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife", "NotifyDataSetChanged", "SetTextI18n")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
SharedContext.ensureInitialized(this)
|
|
||||||
translation = SharedContext.translation.getCategory("download_manager_activity")
|
|
||||||
|
|
||||||
setContentView(R.layout.download_manager_activity)
|
|
||||||
|
|
||||||
findViewById<TextView>(R.id.title).text = resources.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME
|
|
||||||
|
|
||||||
findViewById<ImageButton>(R.id.debug_settings_button).setOnClickListener {
|
|
||||||
DebugSettingsLayoutInflater(this).inflate(findViewById(android.R.id.content))
|
|
||||||
}
|
|
||||||
|
|
||||||
with(findViewById<RecyclerView>(R.id.download_list)) {
|
|
||||||
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this@DownloadManagerActivity)
|
|
||||||
|
|
||||||
adapter = DownloadListAdapter(this@DownloadManagerActivity, fetchedDownloadTasks).apply {
|
|
||||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
updateNoDownloadText()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
|
||||||
override fun getMovementFlags(
|
|
||||||
recyclerView: RecyclerView,
|
|
||||||
viewHolder: RecyclerView.ViewHolder
|
|
||||||
): Int {
|
|
||||||
val download = fetchedDownloadTasks[viewHolder.absoluteAdapterPosition]
|
|
||||||
return if (download.isJobActive()) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
super.getMovementFlags(recyclerView, viewHolder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMove(
|
|
||||||
recyclerView: RecyclerView,
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
target: RecyclerView.ViewHolder
|
|
||||||
): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
|
||||||
fetchedDownloadTasks.removeAt(viewHolder.absoluteAdapterPosition).let {
|
|
||||||
SharedContext.downloadTaskManager.removeTask(it)
|
|
||||||
}
|
|
||||||
adapter?.notifyItemRemoved(viewHolder.absoluteAdapterPosition)
|
|
||||||
}
|
|
||||||
}).attachToRecyclerView(this)
|
|
||||||
|
|
||||||
var isLoading = false
|
|
||||||
|
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
|
||||||
val layoutManager = recyclerView.layoutManager as androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
|
|
||||||
|
|
||||||
if (lastVisibleItemPosition == RecyclerView.NO_POSITION) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastVisibleItemPosition == fetchedDownloadTasks.size - 1 && !isLoading) {
|
|
||||||
isLoading = true
|
|
||||||
|
|
||||||
SharedContext.downloadTaskManager.queryTasks(fetchedDownloadTasks.last().downloadId, filter = listFilter).forEach {
|
|
||||||
fetchedDownloadTasks.add(it.value)
|
|
||||||
adapter?.notifyItemInserted(fetchedDownloadTasks.size - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
arrayOf(
|
|
||||||
Pair(R.id.all_category, MediaFilter.NONE),
|
|
||||||
Pair(R.id.pending_category, MediaFilter.PENDING),
|
|
||||||
Pair(R.id.snap_category, MediaFilter.CHAT_MEDIA),
|
|
||||||
Pair(R.id.story_category, MediaFilter.STORY),
|
|
||||||
Pair(R.id.spotlight_category, MediaFilter.SPOTLIGHT)
|
|
||||||
).let { categoryPairs ->
|
|
||||||
categoryPairs.forEach { pair ->
|
|
||||||
this@DownloadManagerActivity.findViewById<TextView>(pair.first).apply {
|
|
||||||
text = translation["category.${resources.getResourceEntryName(pair.first)}"]
|
|
||||||
}.setOnClickListener { view ->
|
|
||||||
listFilter = pair.second
|
|
||||||
updateListContent()
|
|
||||||
categoryPairs.map { this@DownloadManagerActivity.findViewById<TextView>(it.first) }.forEach {
|
|
||||||
it.setTextColor(getColor(R.color.primaryText))
|
|
||||||
}
|
|
||||||
(view as TextView).setTextColor(getColor(R.color.focusedCategoryColor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button).also {
|
|
||||||
it.text = translation["remove_all"]
|
|
||||||
}.setOnClickListener {
|
|
||||||
with(AlertDialog.Builder(this@DownloadManagerActivity)) {
|
|
||||||
setTitle(translation["remove_all_title"])
|
|
||||||
setMessage(translation["remove_all_text"])
|
|
||||||
setPositiveButton(SharedContext.translation["button.positive"]) { _, _ ->
|
|
||||||
SharedContext.downloadTaskManager.removeAllTasks()
|
|
||||||
fetchedDownloadTasks.removeIf {
|
|
||||||
if (it.isJobActive()) it.cancel()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
adapter?.notifyDataSetChanged()
|
|
||||||
updateNoDownloadText()
|
|
||||||
}
|
|
||||||
setNegativeButton(SharedContext.translation["button.negative"]) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
updateListContent()
|
|
||||||
|
|
||||||
if (!preferences.getBoolean("ask_battery_optimisations", true) ||
|
|
||||||
!(getSystemService(Context.POWER_SERVICE) as PowerManager).isIgnoringBatteryOptimizations(packageName)) return
|
|
||||||
|
|
||||||
with(Intent()) {
|
|
||||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
|
||||||
data = Uri.parse("package:$packageName")
|
|
||||||
startActivityForResult(this, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
if (requestCode == 1) {
|
|
||||||
preferences.edit().putBoolean("ask_battery_optimisations", false).apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
updateListContent()
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user