From 1c91c94d89c517ac9635acdd91b0d302237e2596 Mon Sep 17 00:00:00 2001 From: junkfood <69683722+JunkFood02@users.noreply.github.com> Date: Fri, 13 Jan 2023 23:29:51 +0800 Subject: [PATCH] Feature: Custom shortcuts for building command templates (WIP) --- app/build.gradle.kts | 2 +- .../5.json | 161 ++++++++++++ .../com/junkfood/seal/database/AppDatabase.kt | 7 +- .../junkfood/seal/database/CommandTemplate.kt | 3 +- .../junkfood/seal/database/OptionShortcut.kt | 12 + .../junkfood/seal/database/VideoInfoDao.kt | 9 + .../java/com/junkfood/seal/ui/common/Route.kt | 5 +- .../com/junkfood/seal/ui/component/Buttons.kt | 144 +---------- .../com/junkfood/seal/ui/component/Chips.kt | 142 +++++++++++ .../junkfood/seal/ui/component/IconButtons.kt | 73 ++++++ .../junkfood/seal/ui/component/TextField.kt | 109 +++++++++ .../com/junkfood/seal/ui/page/HomeEntry.kt | 19 +- .../seal/ui/page/download/DownloadPage.kt | 2 - .../page/download/DownloadSettingsDialog.kt | 15 +- .../ui/page/download/PlaylistSelectionPage.kt | 1 - .../settings/command/CommandTemplateDialog.kt | 77 +++++- .../page/settings/command/TemplateEditPage.kt | 229 ++++++++++++++++++ .../page/settings/command/TemplateListPage.kt | 52 +++- .../settings/network/CookieProfilesPage.kt | 4 +- .../seal/ui/page/videolist/VideoListPage.kt | 8 +- .../com/junkfood/seal/util/DatabaseUtil.kt | 6 + .../com/junkfood/seal/util/PreferenceUtil.kt | 3 + app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 + gradle/libs.versions.toml | 2 +- 25 files changed, 916 insertions(+), 175 deletions(-) create mode 100644 app/schemas/com.junkfood.seal.database.AppDatabase/5.json create mode 100644 app/src/main/java/com/junkfood/seal/database/OptionShortcut.kt create mode 100644 app/src/main/java/com/junkfood/seal/ui/component/Chips.kt create mode 100644 app/src/main/java/com/junkfood/seal/ui/component/IconButtons.kt create mode 100644 app/src/main/java/com/junkfood/seal/ui/component/TextField.kt create mode 100644 app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateEditPage.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 39538e9e..3dbf4e67 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -185,7 +185,7 @@ dependencies { implementation(libs.accompanist.webview) implementation(libs.accompanist.pager.layouts) implementation(libs.accompanist.pager.indicators) - + implementation(libs.accompanist.flowlayout) implementation(libs.coil.kt.compose) diff --git a/app/schemas/com.junkfood.seal.database.AppDatabase/5.json b/app/schemas/com.junkfood.seal.database.AppDatabase/5.json new file mode 100644 index 00000000..0c587ca1 --- /dev/null +++ b/app/schemas/com.junkfood.seal.database.AppDatabase/5.json @@ -0,0 +1,161 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "5eab3a1c93713521f1197fa2e2903231", + "entities": [ + { + "tableName": "DownloadedVideoInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `videoTitle` TEXT NOT NULL, `videoAuthor` TEXT NOT NULL, `videoUrl` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL, `videoPath` TEXT NOT NULL, `extractor` TEXT NOT NULL DEFAULT 'Unknown')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "videoTitle", + "columnName": "videoTitle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoAuthor", + "columnName": "videoAuthor", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoUrl", + "columnName": "videoUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoPath", + "columnName": "videoPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extractor", + "columnName": "extractor", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Unknown'" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CommandTemplate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `template` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "template", + "columnName": "template", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CookieProfile", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OptionShortcut", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `option` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "option", + "columnName": "option", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5eab3a1c93713521f1197fa2e2903231')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/database/AppDatabase.kt b/app/src/main/java/com/junkfood/seal/database/AppDatabase.kt index e820e353..bdd307b1 100644 --- a/app/src/main/java/com/junkfood/seal/database/AppDatabase.kt +++ b/app/src/main/java/com/junkfood/seal/database/AppDatabase.kt @@ -5,12 +5,13 @@ import androidx.room.Database import androidx.room.RoomDatabase @Database( - entities = [DownloadedVideoInfo::class, CommandTemplate::class, CookieProfile::class], - version = 4, + entities = [DownloadedVideoInfo::class, CommandTemplate::class, CookieProfile::class, OptionShortcut::class], + version = 5, autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), - AutoMigration(from = 3, to = 4) + AutoMigration(from = 3, to = 4), + AutoMigration(from = 4, to = 5), ] ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/junkfood/seal/database/CommandTemplate.kt b/app/src/main/java/com/junkfood/seal/database/CommandTemplate.kt index 2667d7d0..83bb77ef 100644 --- a/app/src/main/java/com/junkfood/seal/database/CommandTemplate.kt +++ b/app/src/main/java/com/junkfood/seal/database/CommandTemplate.kt @@ -2,9 +2,10 @@ package com.junkfood.seal.database import androidx.room.Entity import androidx.room.PrimaryKey +import kotlinx.serialization.Serializable @Entity -@kotlinx.serialization.Serializable +@Serializable data class CommandTemplate( @PrimaryKey(autoGenerate = true) val id: Int, val name: String, diff --git a/app/src/main/java/com/junkfood/seal/database/OptionShortcut.kt b/app/src/main/java/com/junkfood/seal/database/OptionShortcut.kt new file mode 100644 index 00000000..ec55eb2a --- /dev/null +++ b/app/src/main/java/com/junkfood/seal/database/OptionShortcut.kt @@ -0,0 +1,12 @@ +package com.junkfood.seal.database + +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.serialization.Serializable + +@Entity +@Serializable +data class OptionShortcut( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val option: String +) \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/database/VideoInfoDao.kt b/app/src/main/java/com/junkfood/seal/database/VideoInfoDao.kt index d9104db0..24369baa 100644 --- a/app/src/main/java/com/junkfood/seal/database/VideoInfoDao.kt +++ b/app/src/main/java/com/junkfood/seal/database/VideoInfoDao.kt @@ -87,4 +87,13 @@ interface VideoInfoDao { @Query("delete from CommandTemplate where id=:id") suspend fun deleteTemplateById(id: Int) + + @Query("select * from OptionShortcut") + fun getOptionShortcuts(): Flow> + + @Delete + suspend fun deleteShortcut(optionShortcut: OptionShortcut) + + @Insert + suspend fun insertShortcut(optionShortcut: OptionShortcut): Long } \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/ui/common/Route.kt b/app/src/main/java/com/junkfood/seal/ui/common/Route.kt index ef6801d4..9a83d081 100644 --- a/app/src/main/java/com/junkfood/seal/ui/common/Route.kt +++ b/app/src/main/java/com/junkfood/seal/ui/common/Route.kt @@ -16,6 +16,7 @@ object Route { const val CREDITS = "credits" const val LANGUAGES = "languages" const val TEMPLATE = "template" + const val TEMPLATE_EDIT = "template_edit" const val DARK_THEME = "dark_theme" const val DOWNLOAD_QUEUE = "queue" const val DOWNLOAD_FORMAT = "download_format" @@ -23,5 +24,7 @@ object Route { const val COOKIE_PROFILE = "cookie_profile" const val COOKIE_GENERATOR_WEBVIEW = "cookie_webview" const val SUBTITLE_PREFERENCES = "subtitle_preferences" +} -} \ No newline at end of file +fun String.toId(id: Int) = "$this/$id" +fun String.withArg(arg: String) = "$this/{$arg}" \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/ui/component/Buttons.kt b/app/src/main/java/com/junkfood/seal/ui/component/Buttons.kt index 30133498..72ea9690 100644 --- a/app/src/main/java/com/junkfood/seal/ui/component/Buttons.kt +++ b/app/src/main/java/com/junkfood/seal/ui/component/Buttons.kt @@ -1,25 +1,14 @@ package com.junkfood.seal.ui.component -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Check -import androidx.compose.material.icons.outlined.Clear -import androidx.compose.material.icons.outlined.ClearAll -import androidx.compose.material.icons.outlined.ContentPaste import androidx.compose.material.icons.outlined.OpenInNew -import androidx.compose.material3.AssistChipDefaults import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ElevatedAssistChip -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text @@ -27,111 +16,14 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.junkfood.seal.R import com.junkfood.seal.ui.page.settings.general.ytdlpReference -@Composable -fun BackButton(onClick: () -> Unit) { - IconButton(modifier = Modifier, onClick = onClick) { - Icon( - painter = painterResource(R.drawable.outline_arrow_back_24), - contentDescription = stringResource(R.string.back), - ) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ButtonChip( - modifier: Modifier = Modifier, - onClick: () -> Unit, - label: String, - enabled: Boolean = true, - icon: ImageVector? = null -) { - ElevatedAssistChip( - modifier = modifier.padding(horizontal = 4.dp), - onClick = onClick, - label = { Text(label) }, - colors = AssistChipDefaults.elevatedAssistChipColors(), - enabled = enabled, - leadingIcon = { - if (icon != null) Icon( - imageVector = icon, null, modifier = Modifier.size(18.dp) - ) - } - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun FilterChipWithIcons( - modifier: Modifier = Modifier, - selected: Boolean, - onClick: () -> Unit, - label: String, - leadingIcon: ImageVector = Icons.Outlined.Check -) { - FilterChip( - modifier = modifier.padding(horizontal = 4.dp), - selected = selected, - onClick = onClick, - label = { - Text(text = label) - }, - leadingIcon = { - Row { - AnimatedVisibility(visible = selected) { - Icon( - imageVector = leadingIcon, - contentDescription = null, - modifier = Modifier.requiredSize(18.dp) - ) - } - } - }, - ) -} - - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun FilterChip( - modifier: Modifier = Modifier, - selected: Boolean, - enabled: Boolean = true, - onClick: () -> Unit, - label: String, - animated: Boolean = false -) { - FilterChip( - modifier = modifier.padding(horizontal = 4.dp), - selected = selected, enabled = enabled, - onClick = onClick, - label = { - Text(text = label) - }, - trailingIcon = { - Row { - if (animated) - AnimatedVisibility(visible = selected) { - Icon( - Icons.Outlined.Check, - stringResource(R.string.checked), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(18.dp) - ) - } - } - } - ) -} @Composable fun OutlinedButtonWithIcon( @@ -163,12 +55,14 @@ fun TextButtonWithIcon( modifier: Modifier = Modifier, onClick: () -> Unit, icon: ImageVector, - text: String + text: String, + contentColor: Color = MaterialTheme.colorScheme.primary ) { TextButton( modifier = modifier, onClick = onClick, - contentPadding = ButtonDefaults.ButtonWithIconContentPadding + contentPadding = ButtonDefaults.ButtonWithIconContentPadding, + colors = ButtonDefaults.textButtonColors(contentColor = contentColor) ) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -269,32 +163,4 @@ fun LinkButton( ) } -@Composable -fun PasteButton(onPaste: (String) -> Unit = {}) { - val clipboardManager = LocalClipboardManager.current - PasteUrlButton(onClick = { - clipboardManager.getText().toString().let { onPaste(it) } - }) -} -@Composable -fun PasteUrlButton(onClick: () -> Unit = {}) { - IconButton(onClick = onClick) { - Icon( - Icons.Outlined.ContentPaste, - stringResource(R.string.paste) - ) - } -} - -@Composable -fun ClearButton(onClick: () -> Unit) { - IconButton(onClick = onClick) { - Icon( - modifier = Modifier.size(18.dp), - imageVector = Icons.Outlined.Clear, - contentDescription = stringResource(id = R.string.clear), - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } -} diff --git a/app/src/main/java/com/junkfood/seal/ui/component/Chips.kt b/app/src/main/java/com/junkfood/seal/ui/component/Chips.kt new file mode 100644 index 00000000..cda59286 --- /dev/null +++ b/app/src/main/java/com/junkfood/seal/ui/component/Chips.kt @@ -0,0 +1,142 @@ +package com.junkfood.seal.ui.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.Clear +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.ElevatedAssistChip +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.InputChipDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.junkfood.seal.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ButtonChip( + modifier: Modifier = Modifier, + onClick: () -> Unit, + label: String, + enabled: Boolean = true, + icon: ImageVector? = null +) { + ElevatedAssistChip( + modifier = modifier.padding(horizontal = 4.dp), + onClick = onClick, + label = { Text(label) }, + colors = AssistChipDefaults.elevatedAssistChipColors(), + enabled = enabled, + leadingIcon = { + if (icon != null) Icon( + imageVector = icon, null, modifier = Modifier.size(AssistChipDefaults.IconSize) + ) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FilterChipWithIcons( + modifier: Modifier = Modifier, + selected: Boolean, + onClick: () -> Unit, + label: String, + leadingIcon: ImageVector = Icons.Outlined.Check +) { + FilterChip( + modifier = modifier.padding(horizontal = 4.dp), + selected = selected, + onClick = onClick, + label = { + Text(text = label) + }, + leadingIcon = { + Row { + AnimatedVisibility(visible = selected) { + Icon( + imageVector = leadingIcon, + contentDescription = null, + modifier = Modifier.size(FilterChipDefaults.IconSize) + ) + } + } + }, + ) +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun VideoFilterChip( + modifier: Modifier = Modifier, + selected: Boolean, + enabled: Boolean = true, + onClick: () -> Unit, + label: String, + animated: Boolean = false +) { + FilterChip( + modifier = modifier.padding(horizontal = 4.dp), + selected = selected, enabled = enabled, + onClick = onClick, + label = { + Text(text = label) + }, + trailingIcon = { + Row { + if (animated) + AnimatedVisibility(visible = selected) { + Icon( + Icons.Outlined.Check, + stringResource(R.string.checked), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(FilterChipDefaults.IconSize) + ) + } + } + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ShortcutChip( + modifier: Modifier = Modifier, + text: String, + onClick: (() -> Unit)? = null, + onRemove: (() -> Unit)? = null, +) { + AssistChip( + modifier = modifier.padding(horizontal = 4.dp), + onClick = { onClick?.invoke() }, + label = { Text(text = text) }, + trailingIcon = { + onRemove?.let { + IconButton( + onClick = onRemove, + modifier = Modifier.size(InputChipDefaults.IconSize) + ) { + Icon( + imageVector = Icons.Outlined.Clear, + contentDescription = stringResource(id = R.string.remove), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + }) +} + diff --git a/app/src/main/java/com/junkfood/seal/ui/component/IconButtons.kt b/app/src/main/java/com/junkfood/seal/ui/component/IconButtons.kt new file mode 100644 index 00000000..216d2335 --- /dev/null +++ b/app/src/main/java/com/junkfood/seal/ui/component/IconButtons.kt @@ -0,0 +1,73 @@ +package com.junkfood.seal.ui.component + +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Cancel +import androidx.compose.material.icons.outlined.Clear +import androidx.compose.material.icons.outlined.ContentPaste +import androidx.compose.material.icons.outlined.HighlightOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.junkfood.seal.R + +@Composable +fun PasteFromClipBoardButton(onPaste: (String) -> Unit = {}) { + val clipboardManager = LocalClipboardManager.current + PasteButton(onClick = { + clipboardManager.getText().toString().let { onPaste(it) } + }) +} + +@Composable +fun PasteButton(onClick: () -> Unit = {}) { + IconButton(onClick = onClick) { + Icon( + Icons.Outlined.ContentPaste, + stringResource(R.string.paste) + ) + } +} + +@Composable +fun AddButton(onClick: () -> Unit, enabled: Boolean = true) { + IconButton( + onClick = onClick, enabled = enabled + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource( + R.string.add + ) + ) + } +} + +@Composable +fun ClearButton(onClick: () -> Unit) { + IconButton(onClick = onClick) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.Outlined.Cancel, + contentDescription = stringResource(id = R.string.clear), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } +} + +@Composable +fun BackButton(onClick: () -> Unit) { + IconButton(modifier = Modifier, onClick = onClick) { + Icon( + painter = painterResource(R.drawable.outline_arrow_back_24), + contentDescription = stringResource(R.string.back), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/ui/component/TextField.kt b/app/src/main/java/com/junkfood/seal/ui/component/TextField.kt new file mode 100644 index 00000000..74952758 --- /dev/null +++ b/app/src/main/java/com/junkfood/seal/ui/component/TextField.kt @@ -0,0 +1,109 @@ +package com.junkfood.seal.ui.component + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SealTextField( + modifier: Modifier = Modifier, + trailingIcon: @Composable (() -> Unit)? = null, + minLines: Int = 1, + maxLines: Int = 1, + value: String, onValueChange: (String) -> Unit, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), +) { + TextField( + modifier = modifier, + value = value, + colors = TextFieldDefaults.textFieldColors(containerColor = Color.Transparent), + onValueChange = onValueChange, + trailingIcon = trailingIcon, + minLines = minLines, + maxLines = maxLines, + keyboardActions = keyboardActions, + keyboardOptions = keyboardOptions + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccessibleOutlinedTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + labelText: String, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = TextFieldDefaults.outlinedShape, + colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() +) { + OutlinedTextField( + value, + onValueChange, + modifier, + enabled, + readOnly, + textStyle, + label, + placeholder, + leadingIcon, + trailingIcon, + supportingText = { + Text(text = labelText, color = Color.Transparent) + }, + isError, + visualTransformation, + keyboardOptions, + keyboardActions, + singleLine, + maxLines, + minLines, + interactionSource, + shape, colors + ) +} + +@Composable +fun AdjacentLabel(modifier: Modifier = Modifier, text: String) { + Text( + text = text, + modifier = modifier + .padding(bottom = 12.dp, start = 4.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/ui/page/HomeEntry.kt b/app/src/main/java/com/junkfood/seal/ui/page/HomeEntry.kt index c14f42ba..267c8ac7 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/HomeEntry.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/HomeEntry.kt @@ -28,6 +28,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.navArgument import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.navigation import com.google.accompanist.navigation.animation.rememberAnimatedNavController @@ -36,6 +38,8 @@ import com.junkfood.seal.ui.common.LocalWindowWidthState import com.junkfood.seal.ui.common.Route import com.junkfood.seal.ui.common.animatedComposable import com.junkfood.seal.ui.common.slideInVerticallyComposable +import com.junkfood.seal.ui.common.toId +import com.junkfood.seal.ui.common.withArg import com.junkfood.seal.ui.page.download.DownloadPage import com.junkfood.seal.ui.page.download.DownloadViewModel import com.junkfood.seal.ui.page.download.FormatPage @@ -48,6 +52,7 @@ import com.junkfood.seal.ui.page.settings.about.kotlin import com.junkfood.seal.ui.page.settings.appearance.AppearancePreferences import com.junkfood.seal.ui.page.settings.appearance.DarkThemePreferences import com.junkfood.seal.ui.page.settings.appearance.LanguagePage +import com.junkfood.seal.ui.page.settings.command.TemplateEditPage import com.junkfood.seal.ui.page.settings.command.TemplateListPage import com.junkfood.seal.ui.page.settings.directory.DownloadDirectoryPreferences import com.junkfood.seal.ui.page.settings.format.DownloadFormatPreferences @@ -143,7 +148,7 @@ fun HomeEntry( ) } animatedComposable(Route.DOWNLOADS) { VideoListPage { onBackPressed() } } - animatedComposable(Route.DOWNLOAD_QUEUE) { DownloadQueuePage { onBackPressed() } } +// animatedComposable(Route.DOWNLOAD_QUEUE) { DownloadQueuePage { onBackPressed() } } slideInVerticallyComposable(Route.PLAYLIST) { PlaylistSelectionPage { onBackPressed() } } slideInVerticallyComposable(Route.FORMAT_SELECTION) { FormatPage(downloadViewModel) { onBackPressed() } } settingsGraph(navController, cookiesViewModel) @@ -238,7 +243,17 @@ fun NavGraphBuilder.settingsGraph( animatedComposable(Route.DOWNLOAD_DIRECTORY) { DownloadDirectoryPreferences { onBackPressed() } } - animatedComposable(Route.TEMPLATE) { TemplateListPage { onBackPressed() } } + animatedComposable(Route.TEMPLATE) { + TemplateListPage(onBackPressed = onBackPressed) { + navController.navigate(Route.TEMPLATE_EDIT.toId(it)) + } + } + animatedComposable( + Route.TEMPLATE_EDIT.withArg("templateId"), + arguments = listOf(navArgument("templateId") { type = NavType.IntType }) + ) { + TemplateEditPage(onBackPressed, it.arguments?.getInt("templateId") ?: -1) + } animatedComposable(Route.DARK_THEME) { DarkThemePreferences { onBackPressed() } } animatedComposable(Route.NETWORK_PREFERENCES) { NetworkPreferences(navigateToCookieProfilePage = { diff --git a/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadPage.kt index 7b183b6c..a153063c 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadPage.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadPage.kt @@ -82,8 +82,6 @@ import com.junkfood.seal.R import com.junkfood.seal.ui.common.LocalWindowWidthState import com.junkfood.seal.ui.component.ClearButton import com.junkfood.seal.ui.component.NavigationBarSpacer -import com.junkfood.seal.ui.component.PasteButton -import com.junkfood.seal.ui.component.PasteUrlButton import com.junkfood.seal.ui.component.VideoCard import com.junkfood.seal.ui.theme.PreviewThemeLight import com.junkfood.seal.util.CONFIGURE diff --git a/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadSettingsDialog.kt b/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadSettingsDialog.kt index dd01a638..f80ccd93 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadSettingsDialog.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/download/DownloadSettingsDialog.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.google.android.material.badge.BadgeUtils import com.junkfood.seal.R import com.junkfood.seal.database.CommandTemplate import com.junkfood.seal.ui.common.booleanState @@ -56,7 +55,7 @@ import com.junkfood.seal.ui.component.ButtonChip import com.junkfood.seal.ui.component.DismissButton import com.junkfood.seal.ui.component.DrawerSheetSubtitle import com.junkfood.seal.ui.component.FilledButtonWithIcon -import com.junkfood.seal.ui.component.FilterChip +import com.junkfood.seal.ui.component.VideoFilterChip import com.junkfood.seal.ui.component.FilterChipWithIcons import com.junkfood.seal.ui.component.OutlinedButtonWithIcon import com.junkfood.seal.ui.page.settings.format.AudioFormatDialog @@ -144,7 +143,7 @@ fun DownloadSettingDialog( Row( modifier = Modifier.horizontalScroll(rememberScrollState()) ) { - FilterChip( + VideoFilterChip( selected = audio, enabled = !customCommand, onClick = { @@ -154,7 +153,7 @@ fun DownloadSettingDialog( label = stringResource(R.string.extract_audio) ) if (!isShareActivity) { - FilterChip( + VideoFilterChip( selected = playlist, enabled = !customCommand, onClick = { @@ -163,7 +162,7 @@ fun DownloadSettingDialog( }, label = stringResource(R.string.download_playlist) ) - FilterChip( + VideoFilterChip( selected = formatSelection, enabled = !customCommand && !playlist, onClick = { @@ -173,7 +172,7 @@ fun DownloadSettingDialog( label = stringResource(R.string.format_selection) ) } - FilterChip( + VideoFilterChip( selected = subtitle, enabled = !customCommand && !audio, onClick = { @@ -182,7 +181,7 @@ fun DownloadSettingDialog( }, label = stringResource(id = R.string.download_subtitles) ) - FilterChip( + VideoFilterChip( selected = thumbnail, enabled = !customCommand, onClick = { @@ -194,7 +193,7 @@ fun DownloadSettingDialog( } DrawerSheetSubtitle(text = stringResource(id = R.string.advanced_settings)) Row(modifier = Modifier.horizontalScroll(rememberScrollState())) { - FilterChip( + VideoFilterChip( selected = customCommand, onClick = { customCommand = !customCommand diff --git a/app/src/main/java/com/junkfood/seal/ui/page/download/PlaylistSelectionPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/download/PlaylistSelectionPage.kt index 6b49d928..8c3d8a42 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/download/PlaylistSelectionPage.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/download/PlaylistSelectionPage.kt @@ -76,7 +76,6 @@ fun PlaylistSelectionPage(onBackPressed: () -> Unit = {}) { }, navigationIcon = { IconButton( -// modifier = Modifier.padding(start = 8.dp), onClick = { onDismissRequest() }) { Icon(Icons.Outlined.Close, stringResource(R.string.close)) } diff --git a/app/src/main/java/com/junkfood/seal/ui/page/settings/command/CommandTemplateDialog.kt b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/CommandTemplateDialog.kt index e11e0d61..b6d21b48 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/settings/command/CommandTemplateDialog.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/CommandTemplateDialog.kt @@ -1,23 +1,28 @@ package com.junkfood.seal.ui.page.settings.command +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.ContentPaste +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.EditNote import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -27,17 +32,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import com.google.accompanist.flowlayout.FlowRow import com.junkfood.seal.R import com.junkfood.seal.database.CommandTemplate -import com.junkfood.seal.ui.component.ButtonChip +import com.junkfood.seal.database.OptionShortcut +import com.junkfood.seal.ui.component.AddButton import com.junkfood.seal.ui.component.ClearButton import com.junkfood.seal.ui.component.ConfirmButton import com.junkfood.seal.ui.component.LinkButton -import com.junkfood.seal.ui.component.PasteButton +import com.junkfood.seal.ui.component.PasteFromClipBoardButton +import com.junkfood.seal.ui.component.ShortcutChip +import com.junkfood.seal.ui.component.SealTextField import com.junkfood.seal.util.DatabaseUtil +import com.kyant.monet.a3 import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -125,7 +137,7 @@ fun CommandTemplateDialog( onValueChange = { templateText = it }, trailingIcon = { if (templateText.isEmpty()) - PasteButton { templateText = it } + PasteFromClipBoardButton { templateText = it } else ClearButton { templateText = "" } }, label = { Text(stringResource(R.string.custom_command_template)) }, @@ -135,4 +147,59 @@ fun CommandTemplateDialog( LinkButton() } }) +} + +@Composable +fun OptionChipsDialog(onDismissRequest: () -> Unit) { + val scope = rememberCoroutineScope() + val shortcuts by DatabaseUtil.getShortcuts().collectAsState(emptyList()) + var text by remember { mutableStateOf("") } + val addShortCuts = { + scope.launch { + if (shortcuts.find { it.option == text } == null) + DatabaseUtil.insertShortcut(OptionShortcut(option = text)) + text = "" + } + } + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = stringResource(id = R.string.edit_option_chips)) }, + icon = { Icon(Icons.Outlined.Edit, null) }, text = { + Column { + Column( + modifier = Modifier + .requiredHeight(400.dp) + .horizontalScroll(rememberScrollState()) + .verticalScroll(rememberScrollState()) + ) { + FlowRow(modifier = Modifier.width(400.dp)) { + shortcuts.forEach { item -> + ShortcutChip( + text = item.option, + onRemove = { + scope.launch { + DatabaseUtil.deleteShortcut(item) + } + }) + } + } + } + + SealTextField( + modifier = Modifier.padding(top = 12.dp), + value = text, + onValueChange = { text = it }, + trailingIcon = { + AddButton(onClick = { addShortCuts() }, enabled = text.isNotEmpty()) + }, + keyboardActions = KeyboardActions(onDone = { addShortCuts() }), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + maxLines = 2, + + ) + } + + }, confirmButton = { + ConfirmButton { onDismissRequest() } + }) } \ No newline at end of file diff --git a/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateEditPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateEditPage.kt new file mode 100644 index 00000000..a2b5939c --- /dev/null +++ b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateEditPage.kt @@ -0,0 +1,229 @@ +package com.junkfood.seal.ui.page.settings.command + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material3.Divider +import androidx.compose.material3.DividerDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.modifier.modifierLocalConsumer +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.google.accompanist.flowlayout.FlowRow +import com.google.accompanist.flowlayout.MainAxisAlignment +import com.google.accompanist.flowlayout.SizeMode +import com.junkfood.seal.R +import com.junkfood.seal.database.CommandTemplate +import com.junkfood.seal.database.OptionShortcut +import com.junkfood.seal.ui.component.AccessibleOutlinedTextField +import com.junkfood.seal.ui.component.ClearButton +import com.junkfood.seal.ui.component.AdjacentLabel +import com.junkfood.seal.ui.component.PasteFromClipBoardButton +import com.junkfood.seal.ui.component.ShortcutChip +import com.junkfood.seal.ui.component.TextButtonWithIcon +import com.junkfood.seal.util.DatabaseUtil +import com.junkfood.seal.util.PreferenceUtil +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TemplateEditPage(onDismissRequest: () -> Unit, templateId: Int) { + + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val commandTemplate = + if (templateId > 0) PreferenceUtil.templateStateFlow.collectAsState().value[templateId] else + CommandTemplate(0, "", "") + + var templateText by remember { mutableStateOf(commandTemplate.template) } + var templateName by remember { mutableStateOf(commandTemplate.name) } + + var isEditingShortcuts by remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopAppBar(title = { + Text( + text = stringResource(R.string.new_template), + style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp) + ) + }, + navigationIcon = { + IconButton( + onClick = { onDismissRequest() }) { + Icon(Icons.Outlined.Close, stringResource(R.string.close)) + } + }, actions = { + TextButton( + modifier = Modifier.padding(end = 8.dp), + onClick = { + onDismissRequest() + } + ) { + Text(text = stringResource(androidx.appcompat.R.string.abc_action_mode_done)) + } + }, scrollBehavior = scrollBehavior + ) + }) { paddings -> + LazyColumn( + modifier = Modifier.padding(paddings), + contentPadding = PaddingValues() + ) { + + item { + Column(androidx.compose.ui.Modifier.padding(horizontal = 24.dp)) { + AdjacentLabel( + text = stringResource(R.string.template_label), + modifier = Modifier + .padding(top = 12.dp) + .clearAndSetSemantics { } + ) + AccessibleOutlinedTextField( + labelText = stringResource(R.string.template_label), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + value = templateName, + onValueChange = { templateName = it }, + ) + } + + } + item { + Column(Modifier.padding(horizontal = 24.dp)) { + + + AdjacentLabel(text = stringResource(R.string.custom_command_template)) + OutlinedTextField( + supportingText = { Text(text = stringResource(id = R.string.edit_template_desc)) }, + modifier = Modifier.fillMaxWidth(), + value = templateText, + onValueChange = { templateText = it }, + trailingIcon = { + if (templateText.isEmpty()) + PasteFromClipBoardButton { templateText = it } + else ClearButton { templateText = "" } + }, + maxLines = 6, + minLines = 6 + ) + Divider( + Modifier + .fillMaxWidth() + .padding(top = 48.dp, bottom = 24.dp) + .size(DividerDefaults.Thickness) + .clip(CircleShape), + color = MaterialTheme.colorScheme.outlineVariant, + ) + } + } + + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 16.dp) + ) { + Text( + text = stringResource(R.string.shortcuts), + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.tertiary + ) + TextButtonWithIcon( + modifier = Modifier, + onClick = { isEditingShortcuts = true }, + icon = Icons.Outlined.Edit, + text = stringResource(id = R.string.edit), + contentColor = MaterialTheme.colorScheme.tertiary + ) + } + } + item { + val scope = rememberCoroutineScope() + val shortcuts by DatabaseUtil.getShortcuts().collectAsState(emptyList()) + var text by remember { mutableStateOf("") } + val addShortCuts = { + scope.launch { + if (shortcuts.find { it.option == text } == null) + DatabaseUtil.insertShortcut(OptionShortcut(option = text)) + text = "" + } + } + Column( + modifier = Modifier + .fillParentMaxWidth() + .horizontalScroll(rememberScrollState()) + ) { + FlowRow( + modifier = Modifier + .padding(horizontal = 8.dp) + .width(500.dp), + mainAxisSize = SizeMode.Expand, + crossAxisSpacing = 2.dp, + ) { + shortcuts.forEach { item -> + ShortcutChip( + text = item.option, + onClick = { + templateText.run { + if (isEmpty()) "$this${item.option}" + else this.removeSuffix(" ") + " ${item.option}" + } + } + ) + } + } + + } + } + } + } + if (isEditingShortcuts) + OptionChipsDialog { isEditingShortcuts = false } +} + diff --git a/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateListPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateListPage.kt index 3b03625e..48000bf2 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateListPage.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/settings/command/TemplateListPage.kt @@ -9,6 +9,8 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.AssignmentReturn +import androidx.compose.material.icons.outlined.Bookmark +import androidx.compose.material.icons.outlined.BookmarkAdd import androidx.compose.material.icons.outlined.ContentPasteGo import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.MoreVert @@ -63,7 +65,7 @@ private const val TAG = "TemplateListPage" @OptIn(ExperimentalMaterial3Api::class, ExperimentalLifecycleComposeApi::class) @Composable -fun TemplateListPage(onBackPressed: () -> Unit) { +fun TemplateListPage(onBackPressed: () -> Unit, onNavigateToEditPage: (Int) -> Unit) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( rememberTopAppBarState(), canScroll = { true }) @@ -74,7 +76,7 @@ fun TemplateListPage(onBackPressed: () -> Unit) { val context = LocalContext.current var showEditDialog by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) } - + var showShortcutsDialog by remember { mutableStateOf(false) } var isCustomCommandEnabled by remember { mutableStateOf(PreferenceUtil.getValue(CUSTOM_COMMAND)) } @@ -200,8 +202,45 @@ fun TemplateListPage(onBackPressed: () -> Unit) { title = stringResource(id = R.string.new_template), icon = Icons.Outlined.Add ) { - editingTemplateId = -1 - showEditDialog = true + onNavigateToEditPage(-1) +// editingTemplateId = -1 +// showEditDialog = true + + } + } + item { + var expanded by remember { mutableStateOf(false) } + Box( + modifier = Modifier.wrapContentSize(Alignment.TopEnd) + ) { + DropdownMenu( + modifier = Modifier, + expanded = expanded, + onDismissRequest = { expanded = false }) { + DropdownMenuItem( + leadingIcon = { Icon(Icons.Outlined.ContentPasteGo, null) }, + text = { + Text(stringResource(R.string.export_to_clipboard)) + }, + onClick = {}) + DropdownMenuItem( + leadingIcon = { Icon(Icons.Outlined.AssignmentReturn, null) }, + text = { + Text(stringResource(R.string.import_from_clipboard)) + }, + onClick = {}) + } + PreferenceItemVariant( + title = stringResource(id = R.string.edit_option_chips), + icon = Icons.Outlined.BookmarkAdd, + onLongClick = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + expanded = true + }, onLongClickLabel = stringResource(id = R.string.show_more_actions) + ) { + showShortcutsDialog = true + + } } } } @@ -236,6 +275,11 @@ fun TemplateListPage(onBackPressed: () -> Unit) { } }) } + if (showShortcutsDialog) { + OptionChipsDialog { + showShortcutsDialog = false + } + } LaunchedEffect(templates.size) { if (templates.isNotEmpty() && templates.find { it.id == selectedTemplateId } == null) { selectedTemplateId = templates.first().id diff --git a/app/src/main/java/com/junkfood/seal/ui/page/settings/network/CookieProfilesPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/settings/network/CookieProfilesPage.kt index 6bf83537..f01d945b 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/settings/network/CookieProfilesPage.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/settings/network/CookieProfilesPage.kt @@ -49,7 +49,7 @@ import com.junkfood.seal.ui.component.ConfirmButton import com.junkfood.seal.ui.component.DismissButton import com.junkfood.seal.ui.component.HelpDialog import com.junkfood.seal.ui.component.LargeTopAppBar -import com.junkfood.seal.ui.component.PasteButton +import com.junkfood.seal.ui.component.PasteFromClipBoardButton import com.junkfood.seal.ui.component.PreferenceItemVariant import com.junkfood.seal.ui.component.PreferenceSwitchWithContainer import com.junkfood.seal.ui.component.TextButtonWithIcon @@ -187,7 +187,7 @@ fun CookieGeneratorDialog( .padding(top = 16.dp), value = url, label = { Text("URL") }, onValueChange = { cookiesViewModel.updateUrl(it) }, trailingIcon = { - PasteButton { cookiesViewModel.updateUrl(TextUtil.matchUrlFromClipboard(it)) } + PasteFromClipBoardButton { cookiesViewModel.updateUrl(TextUtil.matchUrlFromClipboard(it)) } }, maxLines = 1 ) diff --git a/app/src/main/java/com/junkfood/seal/ui/page/videolist/VideoListPage.kt b/app/src/main/java/com/junkfood/seal/ui/page/videolist/VideoListPage.kt index 0fb07eb7..9a70dca1 100644 --- a/app/src/main/java/com/junkfood/seal/ui/page/videolist/VideoListPage.kt +++ b/app/src/main/java/com/junkfood/seal/ui/page/videolist/VideoListPage.kt @@ -62,7 +62,7 @@ import com.junkfood.seal.ui.common.LocalWindowWidthState import com.junkfood.seal.ui.component.BackButton import com.junkfood.seal.ui.component.ConfirmButton import com.junkfood.seal.ui.component.DismissButton -import com.junkfood.seal.ui.component.FilterChip +import com.junkfood.seal.ui.component.VideoFilterChip import com.junkfood.seal.ui.component.LargeTopAppBar import com.junkfood.seal.ui.component.MediaListItem import com.junkfood.seal.ui.component.MultiChoiceItem @@ -134,13 +134,13 @@ fun VideoListPage( .padding(8.dp) .selectableGroup() ) { - FilterChip( + VideoFilterChip( selected = viewState.audioFilter, onClick = { videoListViewModel.clickAudioFilter() }, label = stringResource(id = R.string.audio), ) - FilterChip( + VideoFilterChip( selected = viewState.videoFilter, onClick = { videoListViewModel.clickVideoFilter() }, label = stringResource(id = R.string.video), @@ -156,7 +156,7 @@ fun VideoListPage( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) ) for (i in 0 until filterSet.size) { - FilterChip( + VideoFilterChip( selected = viewState.activeFilterIndex == i, onClick = { videoListViewModel.clickExtractorFilter(i) }, label = filterSet.elementAt(i) diff --git a/app/src/main/java/com/junkfood/seal/util/DatabaseUtil.kt b/app/src/main/java/com/junkfood/seal/util/DatabaseUtil.kt index 9c3e75bc..561645d1 100644 --- a/app/src/main/java/com/junkfood/seal/util/DatabaseUtil.kt +++ b/app/src/main/java/com/junkfood/seal/util/DatabaseUtil.kt @@ -7,6 +7,7 @@ import com.junkfood.seal.database.AppDatabase import com.junkfood.seal.database.CommandTemplate import com.junkfood.seal.database.CookieProfile import com.junkfood.seal.database.DownloadedVideoInfo +import com.junkfood.seal.database.OptionShortcut import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.serialization.decodeFromString @@ -33,6 +34,11 @@ object DatabaseUtil { fun getCookiesFlow() = dao.getCookieProfileFlow() + fun getShortcuts() = dao.getOptionShortcuts() + + suspend fun deleteShortcut(shortcut: OptionShortcut) = dao.deleteShortcut(shortcut) + suspend fun insertShortcut(shortcut: OptionShortcut) = dao.insertShortcut(shortcut) + suspend fun getCookieById(id: Int) = dao.getCookieById(id) suspend fun deleteCookieProfile(profile: CookieProfile) = dao.deleteCookieProfile(profile) diff --git a/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt b/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt index a23c884c..c7382274 100644 --- a/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt +++ b/app/src/main/java/com/junkfood/seal/util/PreferenceUtil.kt @@ -81,6 +81,8 @@ const val SYSTEM_DEFAULT = 0 const val TEMPLATE_EXAMPLE = """--no-mtime -f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b"""" +const val TEMPLATE_SHORTCUTS = "template_shortcuts" + val palettesMap = mapOf( 0 to PaletteStyle.TonalSpot, 1 to PaletteStyle.Spritz, @@ -150,6 +152,7 @@ object PreferenceUtil { } } + fun getVideoResolution(): Int = VIDEO_QUALITY.getInt() fun getVideoResolutionDesc(videoQualityCode: Int = getVideoResolution()): String { diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index eb8997ea..a6e74b21 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -50,7 +50,7 @@ 自定义命令 使用自定义的命令模板运行 yt-dlp 命令模板 - 编辑模板 + 编辑 开始执行命令 高级 显示详细信息 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18a0825f..6e35a18e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -285,4 +285,8 @@ Languages, embed subtitles, auto captions Copy log Clear + Edit shortcuts + Add + Commands shortcut + Shortcuts diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c38f1e14..11f0c780 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ accompanist-systemuicontroller = { group = "com.google.accompanist", name = "acc accompanist-webview = { group = "com.google.accompanist", name = "accompanist-webview", version.ref = "accompanist" } accompanist-pager-layouts = { group = "com.google.accompanist", name = "accompanist-pager", version.ref = "accompanist" } accompanist-pager-indicators = { group = "com.google.accompanist", name = "accompanist-pager-indicators", version.ref = "accompanist" } - +accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }