commit 65cbf79dce99d689fcbe0244045d559353450b24 parent 1e02cbef11d8bdd872077cc17c67ce864103f697 Author: auth <64337177+authorisation@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:06:31 +0200 refactor: download manager ui (#69) * ui changes * fix more stuff * fix: set ffmpeg preset to ultrafast * refactor: ui tweaks getIdentifier function * feat: download manager filter * add placeholder bitmoji * change bitmoji blank color * add settings page (not done) * setting page impl * confirmation dialog * update theme * remove snap context buttons * fix: delete cache * fix: bitmoji icon --------- Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com> Diffstat:
22 files changed, 559 insertions(+), 127 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml @@ -43,6 +43,7 @@ <activity android:name=".ui.download.DownloadManagerActivity" + android:theme="@style/AppTheme" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> 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 @@ -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 @@ -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<Int, PendingDownload>() private val cachedTasks = mutableMapOf<Int, PendingDownload>() @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<Int, PendingDownload> { - cachedTasks.putAll(queryTasks( - from = cachedTasks.values.lastOrNull()?.id ?: Int.MAX_VALUE, - amount = 20 + fun queryAllTasks(filter: MediaFilter): Map<Int, PendingDownload> { + val isPendingFilter = filter == MediaFilter.PENDING + val tasks = mutableMapOf<Int, PendingDownload>() + + 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<Int, PendingDownload> { - 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<Int, PendingDownload> { + 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<Int, PendingDownload>() 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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<PendingDownload>() + 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) } + @SuppressLint("NotifyDataSetChanged") + private fun updateListContent() { + fetchedDownloadTasks.clear() + fetchedDownloadTasks.addAll(downloadTaskManager.queryAllTasks(filter = listFilter).values) - actionBar?.apply { - title = "Download Manager" - setBackgroundDrawable(ColorDrawable(getColor(R.color.actionBarColor))) + with(findViewById<RecyclerView>(R.id.download_list)) { + adapter?.notifyDataSetChanged() + scrollToPosition(0) } - setContentView(R.layout.download_manager_activity) + updateNoDownloadText() + } - fetchedDownloadTasks.addAll(downloadTaskManager.queryAllTasks().values) + @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<TextView>(R.id.title).text = resources.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + findViewById<ImageButton>(R.id.settings_button).setOnClickListener { + SettingLayoutInflater(this).inflate(findViewById<ViewGroup>(android.R.id.content)) + } + with(findViewById<RecyclerView>(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() { } } }) + + 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).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)) + } + } + } - with(this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button)) { - setOnClickListener { - downloadTaskManager.removeAllTasks() - fetchedDownloadTasks.removeIf { - if (it.isJobActive()) it.cancel() - true + this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button).setOnClickListener { + with(AlertDialog.Builder(this@DownloadManagerActivity)) { + setTitle(R.string.remove_all_title) + setMessage(R.string.remove_all_text) + setPositiveButton("Yes") { _, _ -> + downloadTaskManager.removeAllTasks() + fetchedDownloadTasks.removeIf { + if (it.isJobActive()) it.cancel() + true + } + adapter?.notifyDataSetChanged() + updateNoDownloadText() } - adapter?.notifyDataSetChanged() - updateNoDownloadText() + setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + show() } } + } - updateNoDownloadText() + updateListContent() - if (preferences.getBoolean("ask_battery_optimisations", true)) { - val pm = getSystemService(Context.POWER_SERVICE) as PowerManager - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - with(Intent()) { - action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$packageName") - startActivityForResult(this, 1) - } - } + 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) } } @@ -145,12 +205,6 @@ class DownloadManagerActivity : Activity() { @SuppressLint("NotifyDataSetChanged") override fun onResume() { super.onResume() - fetchedDownloadTasks.clear() - fetchedDownloadTasks.addAll(MediaDownloadReceiver.downloadTaskManager.queryAllTasks().values) - - with(findViewById<RecyclerView>(R.id.download_list)) { - adapter?.notifyDataSetChanged() - } - updateNoDownloadText() + updateListContent() } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/MediaFilter.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/MediaFilter.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.ui.download + +enum class MediaFilter( + val mediaDisplayType: String? = null +) { + NONE, + PENDING, + CHAT_MEDIA("Chat Media"), + STORY("Story"), + SPOTLIGHT("Spotlight"); + + fun matches(source: String?): Boolean { + if (mediaDisplayType == null) return true + if (source == null) return false + return source.contains(mediaDisplayType, ignoreCase = true) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/SettingLayoutInflater.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/download/SettingLayoutInflater.kt @@ -0,0 +1,96 @@ +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.R +import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType +import java.io.File + +class SettingAdapter( + 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 SettingLayoutInflater( + private val activity: DownloadManagerActivity +) { + private fun confirmAction(title: String, message: String, action: () -> Unit) { + activity.runOnUiThread { + AlertDialog.Builder(activity) + .setTitle(title) + .setMessage(message) + .setPositiveButton("Yes") { _, _ -> + action() + } + .setNegativeButton("No") { _, _ -> } + .show() + } + } + + + fun inflate(parent: ViewGroup) { + val settingsView = activity.layoutInflater.inflate(R.layout.settings_page, parent, false) + + settingsView.findViewById<ImageButton>(R.id.settings_button).setOnClickListener { + parent.removeView(settingsView) + } + + settingsView.findViewById<ListView>(R.id.setting_page_list).apply { + adapter = SettingAdapter(activity, R.layout.setting_item, mutableListOf<Pair<String, () -> Unit>>().apply { + add("Clear Cache" to { + context.cacheDir.listFiles()?.forEach { + it.deleteRecursively() + } + Toast.makeText(context, "Cache cleared", Toast.LENGTH_SHORT).show() + }) + + BridgeFileType.values().forEach { fileType -> + val actionName = "Clear ${fileType.displayName} File" + add(actionName to { + confirmAction(actionName, "Are you sure you want to clear ${fileType.displayName} file?") { + fileType.resolve(context).deleteRecursively() + Toast.makeText(context, "${fileType.displayName} file cleared", Toast.LENGTH_SHORT).show() + } + }) + } + + add("Reset All" to { + confirmAction("Reset All", "Are you sure you want to reset all?") { + arrayOf(context.cacheDir, context.filesDir, File(context.dataDir, "databases"), File(context.dataDir, "shared_prefs")).forEach { + it.listFiles()?.forEach { file -> + file.deleteRecursively() + } + } + Toast.makeText(context, "Success!", Toast.LENGTH_SHORT).show() + } + }) + }.toTypedArray()) + } + + activity.registerBackCallback { + parent.removeView(settingsView) + } + + parent.addView(settingsView) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/util/snap/MediaDownloaderHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/util/snap/MediaDownloaderHelper.kt @@ -75,6 +75,8 @@ object MediaDownloaderHelper { Executors.newSingleThreadExecutor()) } + //TODO: implement setting parameters + suspend fun downloadDashChapterFile( dashPlaylist: File, output: File, @@ -82,7 +84,7 @@ object MediaDownloaderHelper { duration: Long?) { runFFmpegAsync( "-y", "-i", dashPlaylist.absolutePath, "-ss", "'${startTime}ms'", *(if (duration != null) arrayOf("-t", "'${duration}ms'") else arrayOf()), - "-c:v", "libx264", "-threads", "6", "-q:v", "13", output.absolutePath + "-c:v", "libx264", "-preset", "ultrafast", "-threads", "6", "-q:v", "13", output.absolutePath ) } @@ -94,7 +96,7 @@ object MediaDownloaderHelper { runFFmpegAsync( "-y", "-i", media.absolutePath, "-i", overlay.absolutePath, "-filter_complex", "\"[0]scale2ref[img][vid];[img]setsar=1[img];[vid]nullsink;[img][1]overlay=(W-w)/2:(H-h)/2,scale=2*trunc(iw*sar/2):2*trunc(ih/2)\"", - "-c:v", "libx264", "-b:v", "5M", "-c:a", "copy", "-threads", "6", output.absolutePath + "-c:v", "libx264", "-b:v", "5M", "-c:a", "copy", "-preset", "ultrafast", "-threads", "6", output.absolutePath ) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/back_arrow.xml b/app/src/main/res/drawable/back_arrow.xml @@ -0,0 +1,11 @@ +<vector android:height="25dp" android:viewportHeight="512" + android:viewportWidth="512" android:width="25dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#00000000" + android:pathData="M244,400l-144,-144l144,-144" + android:strokeColor="#FFFFFF" android:strokeLineCap="round" + android:strokeLineJoin="round" android:strokeWidth="48"/> + <path android:fillColor="#00000000" + android:pathData="M120,256L412,256" + android:strokeColor="#FFFFFF" android:strokeLineCap="round" + android:strokeLineJoin="round" android:strokeWidth="48"/> +</vector> diff --git a/app/src/main/res/drawable/bitmoji_blank.xml b/app/src/main/res/drawable/bitmoji_blank.xml @@ -0,0 +1,11 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="90dp" + android:height="90dp" + android:viewportWidth="90" + android:viewportHeight="90"> + <path + android:pathData="M45,90.1c10.8,0 20.8,-3.8 28.6,-10.2c-1.4,-2.1 -3,-3.6 -4.7,-5c-5.2,-4.1 -12.6,-5.6 -17.7,-6.5l-0.2,-2c7.8,-4.6 9.7,-9.5 12.8,-19.8l0.1,-0.7c0,0 2.7,-1.1 3.1,-6.1c0.6,-6.8 -2.2,-4.8 -2.2,-5.3C65.1,31 65,26.4 64,23c-2.1,-7.3 -9.2,-13.1 -19,-13.1S28.1,15.6 26,23c-1,3.4 -1.1,8 -0.8,11.6c0,0.5 -2.7,-1.5 -2.2,5.3c0.4,5 3.1,6.1 3.1,6.1l0.1,0.7c3.1,10.3 5,15.2 12.8,19.8l-0.2,2c-5,0.9 -12.5,2.4 -17.7,6.5c-1.7,1.4 -3.3,2.9 -4.7,5C24.2,86.3 34.2,90.1 45,90.1z" + android:strokeWidth="1.5" + android:fillColor="#979797" + android:strokeColor="#000000"/> +</vector> diff --git a/app/src/main/res/drawable/settings_icon.xml b/app/src/main/res/drawable/settings_icon.xml @@ -0,0 +1,11 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:width="30dp" + android:height="30dp" + android:viewportWidth="50" + android:viewportHeight="50"> + <path + android:fillColor="#FFFFFF" + android:pathData="M40.843,22.134C41.511,22.249 42,22.829 42,23.507v2.985c0,0.678 -0.489,1.258 -1.157,1.373l-2.777,0.478c-0.321,1.258 -0.816,2.443 -1.462,3.531l1.626,2.301c0.391,0.554 0.327,1.31 -0.153,1.789l-2.111,2.111c-0.538,0.517 -1.277,0.521 -1.789,0.153l-2.301,-1.626c-1.088,0.646 -2.273,1.141 -3.531,1.462l-0.478,2.777C27.751,41.511 27.171,42 26.492,42h-2.985c-0.678,0 -1.258,-0.489 -1.373,-1.157l-0.478,-2.777c-1.258,-0.321 -2.443,-0.816 -3.531,-1.462l-2.301,1.626c-0.534,0.381 -1.283,0.343 -1.789,-0.153l-2.111,-2.111c-0.48,-0.48 -0.544,-1.235 -0.153,-1.789l1.626,-2.301c-0.646,-1.088 -1.141,-2.273 -1.462,-3.531l-2.777,-0.478C8.489,27.751 8,27.171 8,26.492v-2.985c0,-0.678 0.489,-1.258 1.157,-1.373l2.777,-0.478c0.321,-1.258 0.816,-2.443 1.462,-3.531l-1.626,-2.301c-0.391,-0.554 -0.327,-1.31 0.153,-1.789l2.111,-2.111c0.596,-0.579 1.369,-0.456 1.789,-0.153l2.301,1.626c1.088,-0.646 2.273,-1.141 3.531,-1.462l0.478,-2.777C22.249,8.489 22.829,8 23.508,8h2.985c0.678,0 1.258,0.489 1.373,1.157l0.478,2.777c1.258,0.321 2.443,0.816 3.531,1.462l2.301,-1.626c0.617,-0.414 1.351,-0.282 1.789,0.153l2.111,2.111c0.48,0.48 0.544,1.235 0.153,1.789l-1.626,2.301c0.646,1.088 1.141,2.273 1.462,3.531L40.843,22.134zM25,31.955c3.841,0 6.955,-3.114 6.955,-6.955c0,-3.841 -3.114,-6.955 -6.955,-6.955S18.045,21.159 18.045,25C18.045,28.841 21.159,31.955 25,31.955z" + tools:ignore="VectorPath" /> +</vector> diff --git a/app/src/main/res/font/avenir_next_bold.ttf b/app/src/main/res/font/avenir_next_bold.ttf Binary files differ. diff --git a/app/src/main/res/layout/download_manager_activity.xml b/app/src/main/res/layout/download_manager_activity.xml @@ -1,60 +1,150 @@ -<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/download_manager_activity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/primaryBackground" android:orientation="vertical"> + <FrameLayout + android:id="@+id/title_bar" + android:layout_width="match_parent" + android:layout_height="50dp"> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/borderColor" + android:layout_gravity="bottom" /> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:text="@string/app_name" + android:textColor="@color/primaryText" + android:textSize="23sp" + android:fontFamily="@font/avenir_next_bold" /> + + <ImageButton + android:id="@+id/settings_button" + android:layout_width="45dp" + android:layout_height="match_parent" + android:background="@null" + android:src="@drawable/settings_icon" + android:layout_gravity="center_vertical|end" + android:padding="8dp" + android:contentDescription="@string/settings" /> + + </FrameLayout> + <LinearLayout + android:id="@+id/category_bar" android:layout_width="match_parent" android:layout_height="50dp" - android:background="@color/secondaryBackground" - android:gravity="center_vertical" - android:orientation="horizontal" - android:layoutDirection="rtl"> + android:orientation="horizontal"> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="right" - android:padding="10dp" - tools:ignore="RtlHardcoded"> - <Button - android:id="@+id/remove_all_button" - android:layout_width="wrap_content" - android:layout_height="35dp" - android:background="@drawable/action_button_cancel" - android:padding="5dp" - android:text="Remove All" - android:textColor="@color/darkText" /> - </RelativeLayout> + <TextView + android:id="@+id/all_category" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:clickable="true" + android:focusable="true" + android:fontFamily="@font/avenir_next_medium" + android:gravity="center" + android:text="@string/all_category" + android:textColor="@color/focusedCategoryColor" /> + + <TextView + android:id="@+id/pending_category" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:clickable="true" + android:focusable="true" + android:fontFamily="@font/avenir_next_medium" + android:gravity="center" + android:text="@string/pending_category" + android:textColor="@color/primaryText" /> + + <TextView + android:id="@+id/snap_category" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:clickable="true" + android:focusable="true" + android:fontFamily="@font/avenir_next_medium" + android:gravity="center" + android:text="@string/snap_category" + android:textColor="@color/primaryText" /> + + <TextView + android:id="@+id/story_category" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:clickable="true" + android:focusable="true" + android:fontFamily="@font/avenir_next_medium" + android:gravity="center" + android:text="@string/story_category" + android:textColor="@color/primaryText" /> + + <TextView + android:id="@+id/spotlight_category" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:clickable="true" + android:focusable="true" + android:fontFamily="@font/avenir_next_medium" + android:gravity="center" + android:text="@string/spotlight_category" + android:textColor="@color/primaryText" /> </LinearLayout> <RelativeLayout + android:id="@+id/download_list_container" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> + android:layout_height="0dp" + android:layout_weight="1"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/download_list" android:layout_width="match_parent" - android:layout_height="match_parent"> - - </androidx.recyclerview.widget.RecyclerView> + android:layout_height="match_parent" /> <TextView android:id="@+id/no_download_title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:foregroundGravity="clip_horizontal" + android:layout_centerInParent="true" android:gravity="center" android:paddingVertical="40dp" - android:text="No downloads" + android:text="@string/no_downloads" android:textColor="@color/primaryText" /> - </RelativeLayout> -</LinearLayout>- \ No newline at end of file + <FrameLayout + android:id="@+id/floating_button_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:padding="16dp"> + + <Button + android:id="@+id/remove_all_button" + android:layout_width="120dp" + android:layout_height="45dp" + android:background="@drawable/action_button_cancel" + android:padding="5dp" + android:text="@string/remove_all" + android:textColor="@color/darkText" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginBottom="5dp"/> + </FrameLayout> + +</LinearLayout> diff --git a/app/src/main/res/layout/download_manager_item.xml b/app/src/main/res/layout/download_manager_item.xml @@ -1,37 +1,44 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="100dp" android:layout_margin="5dp" - android:layout_marginBottom="10dp" + android:layout_marginBottom="15dp" android:background="@drawable/download_manager_item_background" android:gravity="center_vertical" android:orientation="horizontal" - android:padding="5dp"> + android:padding="10dp"> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/download_manager_item_background" android:orientation="horizontal" - android:padding="5dp" + android:padding="10dp" tools:ignore="UselessParent"> + <ImageView android:id="@+id/bitmoji_icon" - android:layout_width="45dp" - android:layout_height="45dp" - android:layout_margin="5dp" + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_gravity="center_vertical" + android:layout_marginEnd="10dp" tools:ignore="ContentDescription" /> + <LinearLayout android:layout_width="wrap_content" - android:layout_height="match_parent" - android:padding="5dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginEnd="15dp" android:orientation="vertical"> + <TextView android:id="@+id/item_title" android:layout_width="wrap_content" @@ -44,13 +51,12 @@ android:id="@+id/item_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="14sp" - android:textColor="@color/secondaryText" /> + android:textColor="@color/secondaryText" + android:textSize="14sp" /> </LinearLayout> </LinearLayout> </RelativeLayout> - <TextView android:id="@+id/item_status" android:layout_width="wrap_content" @@ -60,11 +66,12 @@ <Button android:id="@+id/item_action_button" - android:layout_width="wrap_content" - android:layout_height="35dp" + android:layout_width="90dp" + android:layout_height="39dp" android:background="@drawable/action_button_cancel" android:padding="5dp" - android:text="Cancel" + android:layout_marginEnd="10dp" + android:text="@string/cancel" android:textColor="@color/darkText" - tools:ignore="ButtonOrder,HardcodedText" /> -</LinearLayout>- \ No newline at end of file + tools:ignore="ButtonOrder" /> +</LinearLayout> diff --git a/app/src/main/res/layout/setting_item.xml b/app/src/main/res/layout/setting_item.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/preference_item" + android:layout_width="match_parent" + android:layout_height="60dp" + android:orientation="horizontal" + android:padding="16dp"> + + <TextView + android:id="@+id/feature_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:text="" + android:textColor="@color/primaryText" + android:textSize="16sp" /> + +</LinearLayout>+ \ No newline at end of file diff --git a/app/src/main/res/layout/settings_page.xml b/app/src/main/res/layout/settings_page.xml @@ -0,0 +1,52 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/setting_page" + android:clickable="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primaryBackground" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/title_bar" + android:layout_width="match_parent" + android:layout_height="50dp" + tools:ignore="UselessParent"> + + <ImageButton + android:id="@+id/settings_button" + android:layout_width="45dp" + android:layout_height="match_parent" + android:background="@null" + android:src="@drawable/back_arrow" + android:layout_gravity="center_vertical|start" + android:padding="8dp" + tools:ignore="ContentDescription" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/borderColor" + android:layout_gravity="bottom" /> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:text="@string/settings" + android:textColor="@color/primaryText" + android:textSize="23sp" + android:fontFamily="@font/avenir_next_bold" /> + + </FrameLayout> + + <ListView + android:id="@+id/setting_page_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:listSelector="@android:color/transparent" + android:orientation="vertical"> + </ListView> + +</LinearLayout> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml @@ -9,4 +9,6 @@ <color name="secondaryText">#999999</color> <color name="errorColor">#DF4C5C</color> <color name="successColor">#4FABF8</color> + <color name="borderColor">#424242</color> + <color name="focusedCategoryColor">#4FABF8</color> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml @@ -1,3 +1,15 @@ <resources> - <string name="app_name" translatable="false">Snap Enhance</string> + <string name="app_name" translatable="false">SnapEnhance</string> + <string name="remove_all_title">Remove all Downloads</string> + <string name="remove_all_text">Are you sure you want to do this?</string> + <string name="remove_all">Remove All</string> + <string name="no_downloads">No downloads</string> + <string name="cancel">Cancel</string> + <string name="all_category">All</string> + <string name="pending_category">Pending</string> + <string name="snap_category">Snaps</string> + <string name="story_category">Stories</string> + <string name="spotlight_category">Spotlight</string> + <string name="settings">Settings</string> + <string name="refresh_mappings">Refresh Mappings</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml @@ -2,5 +2,7 @@ <resources> <style name="AppTheme"> <item name="android:fontFamily">@font/avenir_next_medium</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowContentOverlay">@null</item> </style> </resources> \ No newline at end of file