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>
This commit is contained in:
auth
2023-06-21 16:06:31 +02:00
committed by GitHub
parent 1e02cbef11
commit 65cbf79dce
22 changed files with 561 additions and 130 deletions

View File

@@ -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" />

View File

@@ -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? {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) }
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<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)
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() {
}
}
})
with(this@DownloadManagerActivity.findViewById<Button>(R.id.remove_all_button)) {
setOnClickListener {
downloadTaskManager.removeAllTasks()
fetchedDownloadTasks.removeIf {
if (it.isJobActive()) it.cancel()
true
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))
}
adapter?.notifyDataSetChanged()
updateNoDownloadText()
}
}
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()
}
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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -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>
tools:ignore="ButtonOrder" />
</LinearLayout>

View File

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

View File

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

View File

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

View File

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

View File

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