mirror of
https://github.com/JunkFood02/Seal.git
synced 2025-05-17 13:35:55 +08:00
Feature: Custom shortcuts for building command templates (WIP)
This commit is contained in:
@ -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)
|
||||
|
||||
|
161
app/schemas/com.junkfood.seal.database.AppDatabase/5.json
Normal file
161
app/schemas/com.junkfood.seal.database.AppDatabase/5.json
Normal file
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
@ -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<List<OptionShortcut>>
|
||||
|
||||
@Delete
|
||||
suspend fun deleteShortcut(optionShortcut: OptionShortcut)
|
||||
|
||||
@Insert
|
||||
suspend fun insertShortcut(optionShortcut: OptionShortcut): Long
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
fun String.toId(id: Int) = "$this/$id"
|
||||
fun String.withArg(arg: String) = "$this/{$arg}"
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
142
app/src/main/java/com/junkfood/seal/ui/component/Chips.kt
Normal file
142
app/src/main/java/com/junkfood/seal/ui/component/Chips.kt
Normal file
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
109
app/src/main/java/com/junkfood/seal/ui/component/TextField.kt
Normal file
109
app/src/main/java/com/junkfood/seal/ui/component/TextField.kt
Normal file
@ -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,
|
||||
)
|
||||
}
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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() }
|
||||
})
|
||||
}
|
@ -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 }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -50,7 +50,7 @@
|
||||
<string name="custom_command">自定义命令</string>
|
||||
<string name="custom_command_desc">使用自定义的命令模板运行 yt-dlp</string>
|
||||
<string name="custom_command_template">命令模板</string>
|
||||
<string name="edit">编辑模板</string>
|
||||
<string name="edit">编辑</string>
|
||||
<string name="start_execute">开始执行命令</string>
|
||||
<string name="advanced_settings">高级</string>
|
||||
<string name="print_details">显示详细信息</string>
|
||||
|
@ -285,4 +285,8 @@
|
||||
<string name="subtitle_desc">Languages, embed subtitles, auto captions</string>
|
||||
<string name="copy_log">Copy log</string>
|
||||
<string name="clear">Clear</string>
|
||||
<string name="edit_option_chips">Edit shortcuts</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="command_shortcut">Commands shortcut</string>
|
||||
<string name="shortcuts">Shortcuts</string>
|
||||
</resources>
|
||||
|
@ -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" }
|
||||
|
Reference in New Issue
Block a user