diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7473815a..0d39986c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,7 @@ diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt index 9e211868..382cab51 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/common/impl/file/BridgeFileType.kt @@ -1,15 +1,24 @@ package me.rhunk.snapenhance.bridge.common.impl.file +import android.content.Context +import java.io.File -enum class BridgeFileType(val value: Int, val fileName: String, val isDatabase: Boolean = false) { - CONFIG(0, "config.json"), - MAPPINGS(1, "mappings.json"), - MESSAGE_LOGGER_DATABASE(2, "message_logger.db", true), - STEALTH(3, "stealth.txt"), - ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt"), - ANTI_AUTO_SAVE(5, "anti_auto_save.txt"), - AUTO_UPDATER_TIMESTAMP(6, "auto_updater_timestamp.txt"), - PINNED_CONVERSATIONS(7, "pinned_conversations.txt"); + +enum class BridgeFileType(val value: Int, val fileName: String, val displayName: String, val isDatabase: Boolean = false) { + CONFIG(0, "config.json", "Config"), + MAPPINGS(1, "mappings.json", "Mappings"), + MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true), + STEALTH(3, "stealth.txt", "Stealth Conversations"), + ANTI_AUTO_DOWNLOAD(4, "anti_auto_download.txt", "Anti Auto Download"), + ANTI_AUTO_SAVE(5, "anti_auto_save.txt", "Anti Auto Save"), + AUTO_UPDATER_TIMESTAMP(6, "auto_updater_timestamp.txt", "Auto Updater Timestamp"), + PINNED_CONVERSATIONS(7, "pinned_conversations.txt", "Pinned Conversations"); + + fun resolve(context: Context): File = if (isDatabase) { + context.getDatabasePath(fileName) + } else { + File(context.filesDir, fileName) + } companion object { fun fromValue(value: Int): BridgeFileType? { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadTaskManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadTaskManager.kt index 0bf37090..7fa919b8 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadTaskManager.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/download/DownloadTaskManager.kt @@ -5,10 +5,12 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import me.rhunk.snapenhance.download.data.PendingDownload import me.rhunk.snapenhance.download.enums.DownloadStage +import me.rhunk.snapenhance.ui.download.MediaFilter import me.rhunk.snapenhance.util.SQLiteDatabaseHelper class DownloadTaskManager { private lateinit var taskDatabase: SQLiteDatabase + private val pendingTasks = mutableMapOf() private val cachedTasks = mutableMapOf() @SuppressLint("Range") @@ -44,7 +46,7 @@ class DownloadTaskManager { it.moveToFirst() it.getInt(0) } - cachedTasks[task.id] = task + pendingTasks[task.id] = task return task.id } @@ -60,33 +62,62 @@ class DownloadTaskManager { task.id ) ) - cachedTasks[task.id] = task + //if the task is no longer active, move it to the cached tasks + if (task.isJobActive()) { + pendingTasks[task.id] = task + } else { + pendingTasks.remove(task.id) + cachedTasks[task.id] = task + } } fun isEmpty(): Boolean { - return cachedTasks.isEmpty() + return cachedTasks.isEmpty() && pendingTasks.isEmpty() } private fun removeTask(id: Int) { taskDatabase.execSQL("DELETE FROM tasks WHERE id = ?", arrayOf(id)) cachedTasks.remove(id) + pendingTasks.remove(id) } fun removeTask(task: PendingDownload) { removeTask(task.id) } - fun queryAllTasks(): Map { - cachedTasks.putAll(queryTasks( - from = cachedTasks.values.lastOrNull()?.id ?: Int.MAX_VALUE, - amount = 20 + fun queryAllTasks(filter: MediaFilter): Map { + val isPendingFilter = filter == MediaFilter.PENDING + val tasks = mutableMapOf() + + tasks.putAll(pendingTasks.filter { isPendingFilter || filter.matches(it.value.mediaDisplayType) }) + if (isPendingFilter) { + return tasks.toSortedMap(reverseOrder()) + } + + tasks.putAll(queryTasks( + from = tasks.values.lastOrNull()?.id ?: Int.MAX_VALUE, + amount = 30, + filter = filter )) - return cachedTasks.toSortedMap(reverseOrder()) + + return tasks.toSortedMap(reverseOrder()) } @SuppressLint("Range") - fun queryTasks(from: Int, amount: Int = 20): Map { - val cursor = taskDatabase.rawQuery("SELECT * FROM tasks WHERE id < ? ORDER BY id DESC LIMIT ?", arrayOf(from.toString(), amount.toString())) + fun queryTasks(from: Int, amount: Int = 20, filter: MediaFilter = MediaFilter.NONE): Map { + if (filter == MediaFilter.PENDING) { + return emptyMap() + } + + val cursor = taskDatabase.rawQuery( + "SELECT * FROM tasks WHERE id < ? AND mediaDisplayType LIKE ? ORDER BY id DESC LIMIT ?", + arrayOf( + from.toString(), + filter.mediaDisplayType.let { if (it == null) "%" else "%$it" }, + amount.toString() + ) + ) + val result = sortedMapOf() while (cursor.moveToNext()) { @@ -107,11 +138,13 @@ class DownloadTaskManager { result[task.id] = task } cursor.close() + return result.toSortedMap(reverseOrder()) } fun removeAllTasks() { taskDatabase.execSQL("DELETE FROM tasks") cachedTasks.clear() + pendingTasks.clear() } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt index 8329c3aa..8c1cb278 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/downloader/MediaDownloader.kt @@ -29,6 +29,7 @@ import me.rhunk.snapenhance.features.impl.spying.MessageLogger import me.rhunk.snapenhance.hook.HookAdapter import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.Hooker +import me.rhunk.snapenhance.ui.download.MediaFilter import me.rhunk.snapenhance.util.snap.EncryptionHelper import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper import me.rhunk.snapenhance.util.snap.MediaType @@ -193,7 +194,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam val author = context.database.getFriendInfo(senderId) ?: return val authorUsername = author.usernameForSorting!! - downloadOperaMedia(provideClientDownloadManager(authorUsername, authorUsername, "Chat Media", friendInfo = author), mediaInfoMap) + downloadOperaMedia(provideClientDownloadManager(authorUsername, authorUsername, MediaFilter.CHAT_MEDIA.mediaDisplayType, friendInfo = author), mediaInfoMap) return } @@ -209,7 +210,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam val author = context.database.getFriendInfo(if (storyUserId == "null") context.database.getMyUserId()!! else storyUserId) ?: return val authorName = author.usernameForSorting!! - downloadOperaMedia(provideClientDownloadManager(authorName, authorName, "Story", friendInfo = author), mediaInfoMap, ) + downloadOperaMedia(provideClientDownloadManager(authorName, authorName, MediaFilter.STORY.mediaDisplayType, friendInfo = author), mediaInfoMap, ) return } @@ -227,7 +228,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam //spotlight if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) { - downloadOperaMedia(provideClientDownloadManager("Spotlight", mediaDisplayType = "Spotlight", mediaDisplaySource = paramMap["TIME_STAMP"].toString()), mediaInfoMap) + downloadOperaMedia(provideClientDownloadManager("Spotlight", mediaDisplayType = MediaFilter.SPOTLIGHT.mediaDisplayType, mediaDisplaySource = paramMap["TIME_STAMP"].toString()), mediaInfoMap) return } @@ -383,7 +384,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam runCatching { if (!isPreviewMode) { val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage) - provideClientDownloadManager(authorName, authorName, "Chat Media", friendInfo = friendInfo).downloadMedia( + provideClientDownloadManager(authorName, authorName, MediaFilter.CHAT_MEDIA.mediaDisplayType, friendInfo = friendInfo).downloadMedia( Base64.UrlSafe.encode(urlProto), DownloadMediaType.PROTO_MEDIA, encryption = encryptionKeys?.toKeyPair() diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/UITweaks.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/UITweaks.kt index e75623be..bb0b1c1f 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/UITweaks.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/UITweaks.kt @@ -19,19 +19,21 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE val isImmersiveCamera = context.config.bool(ConfigProperty.IMMERSIVE_CAMERA_PREVIEW) val resources = context.resources + fun findIdentifier(name: String, defType: String) = resources.getIdentifier(name, defType, Constants.SNAPCHAT_PACKAGE_NAME) + val displayMetrics = context.resources.displayMetrics - val capriViewfinderDefaultCornerRadius = resources.getIdentifier("capri_viewfinder_default_corner_radius", "dimen", Constants.SNAPCHAT_PACKAGE_NAME) - val ngsHovaNavLargerCameraButtonSize = resources.getIdentifier("ngs_hova_nav_larger_camera_button_size", "dimen", Constants.SNAPCHAT_PACKAGE_NAME) - val fullScreenSurfaceView = resources.getIdentifier("full_screen_surface_view", "id", Constants.SNAPCHAT_PACKAGE_NAME) + val capriViewfinderDefaultCornerRadius = findIdentifier("capri_viewfinder_default_corner_radius", "dimen") + val ngsHovaNavLargerCameraButtonSize = findIdentifier("ngs_hova_nav_larger_camera_button_size", "dimen") + val fullScreenSurfaceView = findIdentifier("full_screen_surface_view", "id") - val callButtonsStub = resources.getIdentifier("call_buttons_stub", "id", Constants.SNAPCHAT_PACKAGE_NAME) - val callButton1 = resources.getIdentifier("friend_action_button3", "id", Constants.SNAPCHAT_PACKAGE_NAME) - val callButton2 = resources.getIdentifier("friend_action_button4", "id", Constants.SNAPCHAT_PACKAGE_NAME) + val callButtonsStub = findIdentifier("call_buttons_stub", "id") + val callButton1 = findIdentifier("friend_action_button3", "id") + val callButton2 = findIdentifier("friend_action_button4", "id") - val chatNoteRecordButton = resources.getIdentifier("chat_note_record_button", "id", Constants.SNAPCHAT_PACKAGE_NAME) - val chatInputBarSticker = resources.getIdentifier("chat_input_bar_sticker", "id", Constants.SNAPCHAT_PACKAGE_NAME) - val chatInputBarCognac = resources.getIdentifier("chat_input_bar_cognac", "id", Constants.SNAPCHAT_PACKAGE_NAME) + val chatNoteRecordButton = findIdentifier("chat_note_record_button", "id") + val chatInputBarSticker = findIdentifier("chat_input_bar_sticker", "id") + val chatInputBarCognac = findIdentifier("chat_input_bar_cognac", "id") Resources::class.java.methods.first { it.name == "getDimensionPixelSize"}.hook(HookStage.AFTER, { isImmersiveCamera } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt index d1b56773..f588cab4 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ActionManager.kt @@ -22,9 +22,9 @@ class ActionManager( actions[action.nameKey] = action } override fun init() { - load(CleanCache::class) - load(ClearMessageLogger::class) - load(RefreshMappings::class) + //load(CleanCache::class) + //load(ClearMessageLogger::class) + //load(RefreshMappings::class) load(OpenMap::class) load(CheckForUpdates::class) load(ExportChatMessages::class) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt index 2ac67382..233d7744 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadListAdapter.kt @@ -131,7 +131,7 @@ class DownloadListAdapter( } } - holder.bitmojiIcon.visibility = View.GONE + holder.bitmojiIcon.setImageResource(R.drawable.bitmoji_blank) pendingDownload.iconUrl?.let { url -> thread(start = true) { @@ -141,7 +141,6 @@ class DownloadListAdapter( } Handler(holder.view.context.mainLooper).post { holder.bitmojiIcon.setImageBitmap(iconBitmap) - holder.bitmojiIcon.visibility = View.VISIBLE } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt index 598774cf..07776f1b 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/DownloadManagerActivity.kt @@ -2,23 +2,33 @@ 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.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.PowerManager import android.provider.Settings import android.view.View +import android.view.ViewGroup 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.BuildConfig import me.rhunk.snapenhance.R import me.rhunk.snapenhance.download.MediaDownloadReceiver import me.rhunk.snapenhance.download.data.PendingDownload class DownloadManagerActivity : Activity() { + private val backCallbacks = mutableListOf<() -> Unit>() private val fetchedDownloadTasks = mutableListOf() + private var listFilter = MediaFilter.NONE + + private val downloadTaskManager by lazy { + MediaDownloadReceiver.downloadTaskManager.also { it.init(this) } + } private val preferences by lazy { getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -30,21 +40,47 @@ class DownloadManagerActivity : Activity() { } } - @SuppressLint("BatteryLife", "NotifyDataSetChanged") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val downloadTaskManager = MediaDownloadReceiver.downloadTaskManager.also { it.init(this) } - - actionBar?.apply { - title = "Download Manager" - setBackgroundDrawable(ColorDrawable(getColor(R.color.actionBarColor))) - } - setContentView(R.layout.download_manager_activity) - - fetchedDownloadTasks.addAll(downloadTaskManager.queryAllTasks().values) - + @SuppressLint("NotifyDataSetChanged") + private fun updateListContent() { + fetchedDownloadTasks.clear() + fetchedDownloadTasks.addAll(downloadTaskManager.queryAllTasks(filter = listFilter).values) with(findViewById(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) + + setContentView(R.layout.download_manager_activity) + + window.navigationBarColor = getColor(R.color.primaryBackground) + findViewById(R.id.title).text = resources.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + + findViewById(R.id.settings_button).setOnClickListener { + SettingLayoutInflater(this).inflate(findViewById(android.R.id.content)) + } + + with(findViewById(R.id.download_list)) { + layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this@DownloadManagerActivity) + adapter = DownloadListAdapter(fetchedDownloadTasks).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { @@ -53,8 +89,6 @@ class DownloadManagerActivity : Activity() { }) } - layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this@DownloadManagerActivity) - ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun getMovementFlags( recyclerView: RecyclerView, @@ -99,7 +133,7 @@ class DownloadManagerActivity : Activity() { if (lastVisibleItemPosition == fetchedDownloadTasks.size - 1 && !isLoading) { isLoading = true - downloadTaskManager.queryTasks(fetchedDownloadTasks.last().id).forEach { + downloadTaskManager.queryTasks(fetchedDownloadTasks.last().id, filter = listFilter).forEach { fetchedDownloadTasks.add(it.value) adapter?.notifyItemInserted(fetchedDownloadTasks.size - 1) } @@ -108,31 +142,57 @@ class DownloadManagerActivity : Activity() { } } }) - - with(this@DownloadManagerActivity.findViewById