Improve FeedOptionView
This commit is contained in:
parent
43bbb87280
commit
efba776db3
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
|
@ -14,11 +14,11 @@
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
# Uncomment this to preserve the line number information for
|
||||||
# debugging stack traces.
|
# debugging stack traces.
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package me.ash.reader.ui.component
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.ModalBottomSheetDefaults
|
import androidx.compose.material.ModalBottomSheetDefaults
|
||||||
|
@ -12,6 +13,7 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
|
||||||
|
@ -55,7 +57,8 @@ fun BottomDrawer(
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(38.dp, 4.dp)
|
.size(30.dp, 4.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
.background(MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||||
.zIndex(1f)
|
.zIndex(1f)
|
||||||
) {}
|
) {}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package me.ash.reader.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardActionScope
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusManager
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClipboardTextField(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
value: String = "",
|
||||||
|
onValueChange: (String) -> Unit = {},
|
||||||
|
placeholder: String = "",
|
||||||
|
errorText: String = "",
|
||||||
|
imeAction: ImeAction = ImeAction.Done,
|
||||||
|
focusManager: FocusManager? = null,
|
||||||
|
onConfirm: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
TextField(
|
||||||
|
readOnly = readOnly,
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
placeholder = placeholder,
|
||||||
|
errorMessage = errorText,
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = if (imeAction == ImeAction.Done)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
onGo = if (imeAction == ImeAction.Go)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
onNext = if (imeAction == ImeAction.Next)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
onPrevious = if (imeAction == ImeAction.Previous)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
onSearch = if (imeAction == ImeAction.Search)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
onSend = if (imeAction == ImeAction.Send)
|
||||||
|
action(focusManager, onConfirm, value) else null,
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = imeAction
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (errorText.isNotEmpty()) {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
text = errorText,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun action(
|
||||||
|
focusManager: FocusManager?,
|
||||||
|
onConfirm: (String) -> Unit,
|
||||||
|
value: String
|
||||||
|
): KeyboardActionScope.() -> Unit = {
|
||||||
|
focusManager?.clearFocus()
|
||||||
|
onConfirm(value)
|
||||||
|
}
|
|
@ -44,7 +44,7 @@ fun SelectionChip(
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
FilterChip(
|
FilterChip(
|
||||||
modifier = modifier,
|
modifier = modifier.defaultMinSize(minHeight = 36.dp),
|
||||||
colors = ChipDefaults.filterChipColors(
|
colors = ChipDefaults.filterChipColors(
|
||||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
|
90
app/src/main/java/me/ash/reader/ui/component/TextField.kt
Normal file
90
app/src/main/java/me/ash/reader/ui/component/TextField.kt
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package me.ash.reader.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material.icons.rounded.ContentPaste
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import me.ash.reader.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextField(
|
||||||
|
readOnly: Boolean,
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
placeholder: String,
|
||||||
|
errorMessage: String,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||||
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(100) // ???
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
androidx.compose.material.TextField(
|
||||||
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
cursorColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
enabled = !readOnly,
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
if (!readOnly) onValueChange(it)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = placeholder,
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isError = errorMessage.isNotEmpty(),
|
||||||
|
singleLine = true,
|
||||||
|
trailingIcon = {
|
||||||
|
if (value.isNotEmpty()) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
if (!readOnly) onValueChange("")
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Close,
|
||||||
|
contentDescription = stringResource(R.string.clear),
|
||||||
|
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IconButton(onClick = {
|
||||||
|
onValueChange(clipboardManager.getText()?.text ?: "")
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.ContentPaste,
|
||||||
|
contentDescription = stringResource(R.string.paste),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = keyboardOptions,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package me.ash.reader.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import me.ash.reader.R
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
fun TextFieldDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
properties: DialogProperties = DialogProperties(),
|
||||||
|
visible: Boolean = false,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
title: String = "",
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
value: String = "",
|
||||||
|
placeholder: String = "",
|
||||||
|
errorText: String = "",
|
||||||
|
dismissText: String = stringResource(R.string.cancel),
|
||||||
|
confirmText: String = stringResource(R.string.confirm),
|
||||||
|
onValueChange: (String) -> Unit = {},
|
||||||
|
onDismissRequest: () -> Unit = {},
|
||||||
|
onConfirm: (String) -> Unit = {},
|
||||||
|
imeAction: ImeAction = ImeAction.Done,
|
||||||
|
) {
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
modifier = modifier,
|
||||||
|
visible = visible,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
icon = {
|
||||||
|
icon?.let {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
ClipboardTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
readOnly = readOnly,
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
placeholder = placeholder,
|
||||||
|
errorText = errorText,
|
||||||
|
imeAction = imeAction,
|
||||||
|
focusManager = focusManager,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
enabled = value.isNotBlank(),
|
||||||
|
onClick = {
|
||||||
|
focusManager.clearFocus()
|
||||||
|
onConfirm(value)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = confirmText,
|
||||||
|
color = if (value.isNotBlank()) {
|
||||||
|
Color.Unspecified
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = dismissText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -67,6 +67,10 @@ fun HomePage(
|
||||||
|
|
||||||
BackHandler(true) {
|
BackHandler(true) {
|
||||||
val currentPage = viewState.pagerState.currentPage
|
val currentPage = viewState.pagerState.currentPage
|
||||||
|
if (currentPage == 0) {
|
||||||
|
context.findActivity()?.moveTaskToBack(false)
|
||||||
|
return@BackHandler
|
||||||
|
}
|
||||||
homeViewModel.dispatch(
|
homeViewModel.dispatch(
|
||||||
HomeViewAction.ScrollToPage(
|
HomeViewAction.ScrollToPage(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
|
|
|
@ -2,7 +2,7 @@ package me.ash.reader.ui.page.home.drawer.feed
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
import androidx.compose.material.icons.outlined.DeleteForever
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
@ -36,7 +36,7 @@ fun DeleteFeedDialog(
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.DeleteOutline,
|
imageVector = Icons.Outlined.DeleteForever,
|
||||||
contentDescription = stringResource(R.string.subscribe),
|
contentDescription = stringResource(R.string.subscribe),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package me.ash.reader.ui.page.home.drawer.feed
|
package me.ash.reader.ui.page.home.drawer.feed
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
import androidx.compose.material.icons.outlined.CreateNewFolder
|
||||||
import androidx.compose.material.icons.rounded.RssFeed
|
import androidx.compose.material.icons.rounded.RssFeed
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
@ -18,7 +21,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.ui.component.BottomDrawer
|
import me.ash.reader.ui.component.BottomDrawer
|
||||||
import me.ash.reader.ui.component.Subtitle
|
import me.ash.reader.ui.component.TextFieldDialog
|
||||||
import me.ash.reader.ui.ext.collectAsStateValue
|
import me.ash.reader.ui.ext.collectAsStateValue
|
||||||
import me.ash.reader.ui.ext.roundClick
|
import me.ash.reader.ui.ext.roundClick
|
||||||
import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
|
import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
|
||||||
|
@ -27,10 +30,10 @@ import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedOptionDrawer(
|
fun FeedOptionDrawer(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: FeedOptionViewModel = hiltViewModel(),
|
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
|
||||||
content: @Composable () -> Unit = {},
|
content: @Composable () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val viewState = viewModel.viewState.collectAsStateValue()
|
val viewState = feedOptionViewModel.viewState.collectAsStateValue()
|
||||||
val feed = viewState.feed
|
val feed = viewState.feed
|
||||||
|
|
||||||
BottomDrawer(
|
BottomDrawer(
|
||||||
|
@ -65,53 +68,24 @@ fun FeedOptionDrawer(
|
||||||
groups = viewState.groups,
|
groups = viewState.groups,
|
||||||
selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false,
|
selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false,
|
||||||
selectedParseFullContentPreset = viewState.feed?.isFullContent ?: false,
|
selectedParseFullContentPreset = viewState.feed?.isFullContent ?: false,
|
||||||
|
showUnsubscribe = true,
|
||||||
selectedGroupId = viewState.feed?.groupId ?: "",
|
selectedGroupId = viewState.feed?.groupId ?: "",
|
||||||
newGroupContent = viewState.newGroupContent,
|
|
||||||
onNewGroupValueChange = {
|
|
||||||
viewModel.dispatch(FeedOptionViewAction.InputNewGroup(it))
|
|
||||||
},
|
|
||||||
newGroupSelected = viewState.newGroupSelected,
|
|
||||||
changeNewGroupSelected = {
|
|
||||||
viewModel.dispatch(FeedOptionViewAction.SelectedNewGroup(it))
|
|
||||||
},
|
|
||||||
allowNotificationPresetOnClick = {
|
allowNotificationPresetOnClick = {
|
||||||
viewModel.dispatch(FeedOptionViewAction.ChangeAllowNotificationPreset)
|
feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeAllowNotificationPreset)
|
||||||
},
|
},
|
||||||
parseFullContentPresetOnClick = {
|
parseFullContentPresetOnClick = {
|
||||||
viewModel.dispatch(FeedOptionViewAction.ChangeParseFullContentPreset)
|
feedOptionViewModel.dispatch(FeedOptionViewAction.ChangeParseFullContentPreset)
|
||||||
|
},
|
||||||
|
unsubscribeOnClick = {
|
||||||
|
feedOptionViewModel.dispatch(FeedOptionViewAction.ShowDeleteDialog)
|
||||||
},
|
},
|
||||||
onGroupClick = {
|
onGroupClick = {
|
||||||
viewModel.dispatch(FeedOptionViewAction.SelectedGroup(it))
|
feedOptionViewModel.dispatch(FeedOptionViewAction.SelectedGroup(it))
|
||||||
},
|
},
|
||||||
onKeyboardAction = { },
|
onAddNewGroup = {
|
||||||
)
|
feedOptionViewModel.dispatch(FeedOptionViewAction.ShowNewGroupDialog)
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
Subtitle(text = stringResource(R.string.options))
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
Button(
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
contentColor = MaterialTheme.colorScheme.error,
|
|
||||||
),
|
|
||||||
border = BorderStroke(
|
|
||||||
width = 1.dp,
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
),
|
|
||||||
onClick = {
|
|
||||||
viewModel.dispatch(FeedOptionViewAction.ShowDeleteDialog)
|
|
||||||
}
|
}
|
||||||
) {
|
)
|
||||||
Icon(
|
|
||||||
modifier = Modifier.size(ButtonDefaults.IconSize),
|
|
||||||
imageVector = Icons.Rounded.DeleteOutline,
|
|
||||||
contentDescription = stringResource(R.string.delete),
|
|
||||||
)
|
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.unsubscribe),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -119,4 +93,21 @@ fun FeedOptionDrawer(
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteFeedDialog(feedName = feed?.name ?: "")
|
DeleteFeedDialog(feedName = feed?.name ?: "")
|
||||||
|
|
||||||
|
TextFieldDialog(
|
||||||
|
visible = viewState.newGroupDialogVisible,
|
||||||
|
title = stringResource(R.string.create_new_group),
|
||||||
|
icon = Icons.Outlined.CreateNewFolder,
|
||||||
|
value = viewState.newGroupContent,
|
||||||
|
placeholder = stringResource(R.string.name),
|
||||||
|
onValueChange = {
|
||||||
|
feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewGroup(it))
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
feedOptionViewModel.dispatch(FeedOptionViewAction.HideNewGroupDialog)
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
feedOptionViewModel.dispatch(FeedOptionViewAction.AddNewGroup)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -49,12 +49,14 @@ class FeedOptionViewModel @Inject constructor(
|
||||||
is FeedOptionViewAction.Hide -> hide(action.scope)
|
is FeedOptionViewAction.Hide -> hide(action.scope)
|
||||||
is FeedOptionViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
is FeedOptionViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
||||||
is FeedOptionViewAction.InputNewGroup -> inputNewGroup(action.content)
|
is FeedOptionViewAction.InputNewGroup -> inputNewGroup(action.content)
|
||||||
is FeedOptionViewAction.SelectedNewGroup -> selectedNewGroup(action.selected)
|
|
||||||
is FeedOptionViewAction.ChangeAllowNotificationPreset -> changeAllowNotificationPreset()
|
is FeedOptionViewAction.ChangeAllowNotificationPreset -> changeAllowNotificationPreset()
|
||||||
is FeedOptionViewAction.ChangeParseFullContentPreset -> changeParseFullContentPreset()
|
is FeedOptionViewAction.ChangeParseFullContentPreset -> changeParseFullContentPreset()
|
||||||
is FeedOptionViewAction.ShowDeleteDialog -> showDeleteDialog()
|
is FeedOptionViewAction.ShowDeleteDialog -> showDeleteDialog()
|
||||||
is FeedOptionViewAction.HideDeleteDialog -> hideDeleteDialog()
|
is FeedOptionViewAction.HideDeleteDialog -> hideDeleteDialog()
|
||||||
is FeedOptionViewAction.Delete -> delete(action.callback)
|
is FeedOptionViewAction.Delete -> delete(action.callback)
|
||||||
|
is FeedOptionViewAction.AddNewGroup -> addNewGroup()
|
||||||
|
is FeedOptionViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true)
|
||||||
|
is FeedOptionViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +83,15 @@ class FeedOptionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun changeNewGroupDialogVisible(visible: Boolean) {
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(
|
||||||
|
newGroupDialogVisible = visible,
|
||||||
|
newGroupContent = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun inputNewGroup(content: String) {
|
private fun inputNewGroup(content: String) {
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -89,6 +100,15 @@ class FeedOptionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addNewGroup() {
|
||||||
|
if (_viewState.value.newGroupContent.isNotBlank()) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
selectedGroup(rssRepository.get().addGroup(_viewState.value.newGroupContent))
|
||||||
|
changeNewGroupDialogVisible(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun selectedGroup(groupId: String) {
|
private fun selectedGroup(groupId: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_viewState.value.feed?.let {
|
_viewState.value.feed?.let {
|
||||||
|
@ -102,14 +122,6 @@ class FeedOptionViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectedNewGroup(selected: Boolean) {
|
|
||||||
_viewState.update {
|
|
||||||
it.copy(
|
|
||||||
newGroupSelected = selected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun changeParseFullContentPreset() {
|
private fun changeParseFullContentPreset() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_viewState.value.feed?.let {
|
_viewState.value.feed?.let {
|
||||||
|
@ -170,7 +182,7 @@ data class FeedOptionViewState(
|
||||||
val feed: Feed? = null,
|
val feed: Feed? = null,
|
||||||
val selectedGroupId: String = "",
|
val selectedGroupId: String = "",
|
||||||
val newGroupContent: String = "",
|
val newGroupContent: String = "",
|
||||||
val newGroupSelected: Boolean = false,
|
val newGroupDialogVisible: Boolean = false,
|
||||||
val groups: List<Group> = emptyList(),
|
val groups: List<Group> = emptyList(),
|
||||||
val deleteDialogVisible: Boolean = false,
|
val deleteDialogVisible: Boolean = false,
|
||||||
)
|
)
|
||||||
|
@ -196,14 +208,14 @@ sealed class FeedOptionViewAction {
|
||||||
val content: String
|
val content: String
|
||||||
) : FeedOptionViewAction()
|
) : FeedOptionViewAction()
|
||||||
|
|
||||||
data class SelectedNewGroup(
|
|
||||||
val selected: Boolean
|
|
||||||
) : FeedOptionViewAction()
|
|
||||||
|
|
||||||
data class Delete(
|
data class Delete(
|
||||||
val callback: () -> Unit = {}
|
val callback: () -> Unit = {}
|
||||||
) : FeedOptionViewAction()
|
) : FeedOptionViewAction()
|
||||||
|
|
||||||
object ShowDeleteDialog : FeedOptionViewAction()
|
object ShowDeleteDialog : FeedOptionViewAction()
|
||||||
object HideDeleteDialog : FeedOptionViewAction()
|
object HideDeleteDialog : FeedOptionViewAction()
|
||||||
|
|
||||||
|
object ShowNewGroupDialog : FeedOptionViewAction()
|
||||||
|
object HideNewGroupDialog : FeedOptionViewAction()
|
||||||
|
object AddNewGroup : FeedOptionViewAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.KeyboardArrowRight
|
import androidx.compose.material.icons.outlined.KeyboardArrowRight
|
||||||
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.rounded.Add
|
import androidx.compose.material.icons.rounded.Add
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
import androidx.compose.material.icons.rounded.Refresh
|
||||||
import androidx.compose.material.icons.rounded.Tune
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -106,7 +106,8 @@ fun FeedsPage(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
FeedbackIconButton(
|
FeedbackIconButton(
|
||||||
isHaptic = false,
|
isHaptic = false,
|
||||||
imageVector = Icons.Rounded.Tune,
|
modifier = Modifier.size(20.dp),
|
||||||
|
imageVector = Icons.Outlined.Settings,
|
||||||
contentDescription = stringResource(R.string.settings),
|
contentDescription = stringResource(R.string.settings),
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -3,18 +3,27 @@ package me.ash.reader.ui.page.home.feeds.subscribe
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material.icons.outlined.Article
|
import androidx.compose.material.icons.outlined.Article
|
||||||
import androidx.compose.material.icons.outlined.Notifications
|
import androidx.compose.material.icons.outlined.Notifications
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
@ -24,7 +33,6 @@ import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.Group
|
import me.ash.reader.data.entity.Group
|
||||||
import me.ash.reader.ui.component.SelectionChip
|
import me.ash.reader.ui.component.SelectionChip
|
||||||
import me.ash.reader.ui.component.SelectionEditorChip
|
|
||||||
import me.ash.reader.ui.component.Subtitle
|
import me.ash.reader.ui.component.Subtitle
|
||||||
import me.ash.reader.ui.ext.roundClick
|
import me.ash.reader.ui.ext.roundClick
|
||||||
|
|
||||||
|
@ -35,41 +43,39 @@ fun ResultView(
|
||||||
groups: List<Group> = emptyList(),
|
groups: List<Group> = emptyList(),
|
||||||
selectedAllowNotificationPreset: Boolean = false,
|
selectedAllowNotificationPreset: Boolean = false,
|
||||||
selectedParseFullContentPreset: Boolean = false,
|
selectedParseFullContentPreset: Boolean = false,
|
||||||
|
showUnsubscribe: Boolean = false,
|
||||||
selectedGroupId: String = "",
|
selectedGroupId: String = "",
|
||||||
newGroupContent: String = "",
|
|
||||||
newGroupSelected: Boolean,
|
|
||||||
onNewGroupValueChange: (String) -> Unit = {},
|
|
||||||
changeNewGroupSelected: (Boolean) -> Unit = {},
|
|
||||||
allowNotificationPresetOnClick: () -> Unit = {},
|
allowNotificationPresetOnClick: () -> Unit = {},
|
||||||
parseFullContentPresetOnClick: () -> Unit = {},
|
parseFullContentPresetOnClick: () -> Unit = {},
|
||||||
|
unsubscribeOnClick: () -> Unit = {},
|
||||||
onGroupClick: (groupId: String) -> Unit = {},
|
onGroupClick: (groupId: String) -> Unit = {},
|
||||||
onKeyboardAction: () -> Unit = {},
|
onAddNewGroup: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (groups.isNotEmpty()) onGroupClick(groups.first().id)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.verticalScroll(rememberScrollState())
|
modifier = modifier.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Link(
|
Link(text = link)
|
||||||
text = link
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(26.dp))
|
Spacer(modifier = Modifier.height(26.dp))
|
||||||
|
|
||||||
Preset(
|
Preset(
|
||||||
selectedAllowNotificationPreset = selectedAllowNotificationPreset,
|
selectedAllowNotificationPreset = selectedAllowNotificationPreset,
|
||||||
selectedParseFullContentPreset = selectedParseFullContentPreset,
|
selectedParseFullContentPreset = selectedParseFullContentPreset,
|
||||||
|
showUnsubscribe = showUnsubscribe,
|
||||||
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
|
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
|
||||||
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
|
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
|
||||||
|
unsubscribeOnClick = unsubscribeOnClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(26.dp))
|
Spacer(modifier = Modifier.height(26.dp))
|
||||||
|
|
||||||
AddToGroup(
|
AddToGroup(
|
||||||
groups = groups,
|
groups = groups,
|
||||||
selectedGroupId = selectedGroupId,
|
selectedGroupId = selectedGroupId,
|
||||||
newGroupContent = newGroupContent,
|
|
||||||
newGroupSelected = newGroupSelected,
|
|
||||||
onNewGroupValueChange = onNewGroupValueChange,
|
|
||||||
changeNewGroupSelected = changeNewGroupSelected,
|
|
||||||
onGroupClick = onGroupClick,
|
onGroupClick = onGroupClick,
|
||||||
onKeyboardAction = onKeyboardAction,
|
onAddNewGroup = onAddNewGroup,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
}
|
}
|
||||||
|
@ -105,8 +111,10 @@ private fun Link(
|
||||||
private fun Preset(
|
private fun Preset(
|
||||||
selectedAllowNotificationPreset: Boolean = false,
|
selectedAllowNotificationPreset: Boolean = false,
|
||||||
selectedParseFullContentPreset: Boolean = false,
|
selectedParseFullContentPreset: Boolean = false,
|
||||||
|
showUnsubscribe: Boolean = false,
|
||||||
allowNotificationPresetOnClick: () -> Unit = {},
|
allowNotificationPresetOnClick: () -> Unit = {},
|
||||||
parseFullContentPresetOnClick: () -> Unit = {},
|
parseFullContentPresetOnClick: () -> Unit = {},
|
||||||
|
unsubscribeOnClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Subtitle(text = stringResource(R.string.preset))
|
Subtitle(text = stringResource(R.string.preset))
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
@ -147,6 +155,15 @@ private fun Preset(
|
||||||
) {
|
) {
|
||||||
parseFullContentPresetOnClick()
|
parseFullContentPresetOnClick()
|
||||||
}
|
}
|
||||||
|
if (showUnsubscribe) {
|
||||||
|
SelectionChip(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = stringResource(R.string.unsubscribe),
|
||||||
|
selected = false,
|
||||||
|
) {
|
||||||
|
unsubscribeOnClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,39 +171,61 @@ private fun Preset(
|
||||||
private fun AddToGroup(
|
private fun AddToGroup(
|
||||||
groups: List<Group>,
|
groups: List<Group>,
|
||||||
selectedGroupId: String,
|
selectedGroupId: String,
|
||||||
newGroupContent: String,
|
|
||||||
newGroupSelected: Boolean,
|
|
||||||
onNewGroupValueChange: (String) -> Unit = {},
|
|
||||||
changeNewGroupSelected: (Boolean) -> Unit = {},
|
|
||||||
onGroupClick: (groupId: String) -> Unit = {},
|
onGroupClick: (groupId: String) -> Unit = {},
|
||||||
onKeyboardAction: () -> Unit = {},
|
onAddNewGroup: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Subtitle(text = stringResource(R.string.add_to_group))
|
Subtitle(text = stringResource(R.string.add_to_group))
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
FlowRow(
|
|
||||||
mainAxisAlignment = MainAxisAlignment.Start,
|
|
||||||
crossAxisSpacing = 10.dp,
|
|
||||||
mainAxisSpacing = 10.dp,
|
|
||||||
) {
|
|
||||||
groups.forEach {
|
|
||||||
SelectionChip(
|
|
||||||
modifier = Modifier.animateContentSize(),
|
|
||||||
content = it.name,
|
|
||||||
selected = !newGroupSelected && it.id == selectedGroupId,
|
|
||||||
) {
|
|
||||||
changeNewGroupSelected(false)
|
|
||||||
onGroupClick(it.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectionEditorChip(
|
if (groups.size > 6) {
|
||||||
modifier = Modifier.animateContentSize(),
|
LazyRow {
|
||||||
content = newGroupContent,
|
items(groups) {
|
||||||
onValueChange = onNewGroupValueChange,
|
SelectionChip(
|
||||||
selected = newGroupSelected,
|
modifier = Modifier.animateContentSize(),
|
||||||
onKeyboardAction = onKeyboardAction,
|
content = it.name,
|
||||||
|
selected = it.id == selectedGroupId,
|
||||||
|
) {
|
||||||
|
onGroupClick(it.id)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
}
|
||||||
|
item { NewGroupButton(onAddNewGroup) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FlowRow(
|
||||||
|
mainAxisAlignment = MainAxisAlignment.Start,
|
||||||
|
crossAxisSpacing = 10.dp,
|
||||||
|
mainAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
changeNewGroupSelected(true)
|
groups.forEach {
|
||||||
|
SelectionChip(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
content = it.name,
|
||||||
|
selected = it.id == selectedGroupId,
|
||||||
|
) {
|
||||||
|
onGroupClick(it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NewGroupButton(onAddNewGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NewGroupButton(onAddNewGroup: () -> Unit) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.clickable { onAddNewGroup() },
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
imageVector = Icons.Outlined.Add,
|
||||||
|
contentDescription = stringResource(R.string.create_new_group),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,125 +0,0 @@
|
||||||
package me.ash.reader.ui.page.home.feeds.subscribe
|
|
||||||
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material.TextField
|
|
||||||
import androidx.compose.material.TextFieldDefaults
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Close
|
|
||||||
import androidx.compose.material.icons.rounded.ContentPaste
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import me.ash.reader.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SearchView(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
readOnly: Boolean = false,
|
|
||||||
inputLink: String = "",
|
|
||||||
errorMessage: String = "",
|
|
||||||
onLinkValueChange: (String) -> Unit = {},
|
|
||||||
onKeyboardAction: () -> Unit = {},
|
|
||||||
) {
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
delay(100) // ???
|
|
||||||
focusRequester.requestFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
TextField(
|
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
|
||||||
colors = TextFieldDefaults.textFieldColors(
|
|
||||||
backgroundColor = Color.Transparent,
|
|
||||||
cursorColor = MaterialTheme.colorScheme.onSurface,
|
|
||||||
textColor = MaterialTheme.colorScheme.onSurface,
|
|
||||||
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
enabled = !readOnly,
|
|
||||||
value = inputLink,
|
|
||||||
onValueChange = {
|
|
||||||
if (!readOnly) onLinkValueChange(it)
|
|
||||||
},
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.feed_or_site_url),
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
isError = errorMessage.isNotEmpty(),
|
|
||||||
singleLine = true,
|
|
||||||
trailingIcon = {
|
|
||||||
if (inputLink.isNotEmpty()) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
if (!readOnly) onLinkValueChange("")
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Close,
|
|
||||||
contentDescription = stringResource(R.string.clear),
|
|
||||||
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
IconButton(onClick = {
|
|
||||||
onLinkValueChange(clipboardManager.getText()?.text ?: "")
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.ContentPaste,
|
|
||||||
contentDescription = stringResource(R.string.paste),
|
|
||||||
tint = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
onKeyboardAction()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
imeAction = ImeAction.Search
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (errorMessage.isNotEmpty()) {
|
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
.horizontalScroll(rememberScrollState()),
|
|
||||||
text = errorMessage,
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
maxLines = 1,
|
|
||||||
softWrap = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.CreateNewFolder
|
||||||
import androidx.compose.material.icons.rounded.RssFeed
|
import androidx.compose.material.icons.rounded.RssFeed
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -18,12 +19,16 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
|
import me.ash.reader.ui.component.ClipboardTextField
|
||||||
import me.ash.reader.ui.component.Dialog
|
import me.ash.reader.ui.component.Dialog
|
||||||
import me.ash.reader.ui.ext.*
|
import me.ash.reader.ui.component.TextFieldDialog
|
||||||
|
import me.ash.reader.ui.ext.collectAsStateValue
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
androidx.compose.ui.ExperimentalComposeUiApi::class,
|
androidx.compose.ui.ExperimentalComposeUiApi::class,
|
||||||
|
@ -45,15 +50,9 @@ fun SubscribeDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val readYouString = stringResource(R.string.read_you)
|
|
||||||
val defaultString = stringResource(R.string.defaults)
|
|
||||||
|
|
||||||
LaunchedEffect(viewState.visible) {
|
LaunchedEffect(viewState.visible) {
|
||||||
if (viewState.visible) {
|
if (viewState.visible) {
|
||||||
val defaultGroupId = context.dataStore
|
|
||||||
.get(DataStoreKeys.CurrentAccountId)!!
|
|
||||||
.spacerDollar(readYouString + defaultString)
|
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(defaultGroupId))
|
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.Init)
|
subscribeViewModel.dispatch(SubscribeViewAction.Init)
|
||||||
} else {
|
} else {
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.Reset)
|
subscribeViewModel.dispatch(SubscribeViewAction.Reset)
|
||||||
|
@ -77,11 +76,13 @@ fun SubscribeDialog(
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
if (viewState.isSearchPage) {
|
text = if (viewState.isSearchPage) {
|
||||||
viewState.title
|
viewState.title
|
||||||
} else {
|
} else {
|
||||||
viewState.feed?.name ?: stringResource(R.string.unknown)
|
viewState.feed?.name ?: stringResource(R.string.unknown)
|
||||||
}
|
},
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
|
@ -93,14 +94,17 @@ fun SubscribeDialog(
|
||||||
}
|
}
|
||||||
) { targetExpanded ->
|
) { targetExpanded ->
|
||||||
if (targetExpanded) {
|
if (targetExpanded) {
|
||||||
SearchView(
|
ClipboardTextField(
|
||||||
readOnly = viewState.lockLinkInput,
|
readOnly = viewState.lockLinkInput,
|
||||||
inputLink = viewState.linkContent,
|
value = viewState.linkContent,
|
||||||
errorMessage = viewState.errorMessage,
|
onValueChange = {
|
||||||
onLinkValueChange = {
|
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it))
|
subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it))
|
||||||
},
|
},
|
||||||
onKeyboardAction = {
|
placeholder = stringResource(R.string.feed_or_site_url),
|
||||||
|
errorText = viewState.errorMessage,
|
||||||
|
imeAction = ImeAction.Search,
|
||||||
|
focusManager = focusManager,
|
||||||
|
onConfirm = {
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.Search)
|
subscribeViewModel.dispatch(SubscribeViewAction.Search)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -111,14 +115,6 @@ fun SubscribeDialog(
|
||||||
selectedAllowNotificationPreset = viewState.allowNotificationPreset,
|
selectedAllowNotificationPreset = viewState.allowNotificationPreset,
|
||||||
selectedParseFullContentPreset = viewState.parseFullContentPreset,
|
selectedParseFullContentPreset = viewState.parseFullContentPreset,
|
||||||
selectedGroupId = viewState.selectedGroupId,
|
selectedGroupId = viewState.selectedGroupId,
|
||||||
newGroupContent = viewState.newGroupContent,
|
|
||||||
onNewGroupValueChange = {
|
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
|
|
||||||
},
|
|
||||||
newGroupSelected = viewState.newGroupSelected,
|
|
||||||
changeNewGroupSelected = {
|
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it))
|
|
||||||
},
|
|
||||||
allowNotificationPresetOnClick = {
|
allowNotificationPresetOnClick = {
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
||||||
},
|
},
|
||||||
|
@ -128,8 +124,8 @@ fun SubscribeDialog(
|
||||||
onGroupClick = {
|
onGroupClick = {
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
|
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
|
||||||
},
|
},
|
||||||
onKeyboardAction = {
|
onAddNewGroup = {
|
||||||
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe)
|
subscribeViewModel.dispatch(SubscribeViewAction.ShowNewGroupDialog)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -138,7 +134,7 @@ fun SubscribeDialog(
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
if (viewState.isSearchPage) {
|
if (viewState.isSearchPage) {
|
||||||
TextButton(
|
TextButton(
|
||||||
enabled = viewState.linkContent.isNotEmpty()
|
enabled = viewState.linkContent.isNotBlank()
|
||||||
&& viewState.title != stringResource(R.string.searching),
|
&& viewState.title != stringResource(R.string.searching),
|
||||||
onClick = {
|
onClick = {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
|
@ -147,7 +143,7 @@ fun SubscribeDialog(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.search),
|
text = stringResource(R.string.search),
|
||||||
color = if (viewState.linkContent.isNotEmpty()) {
|
color = if (viewState.linkContent.isNotBlank()) {
|
||||||
Color.Unspecified
|
Color.Unspecified
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
|
||||||
|
@ -188,4 +184,21 @@ fun SubscribeDialog(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TextFieldDialog(
|
||||||
|
visible = viewState.newGroupDialogVisible,
|
||||||
|
title = stringResource(R.string.create_new_group),
|
||||||
|
icon = Icons.Outlined.CreateNewFolder,
|
||||||
|
value = viewState.newGroupContent,
|
||||||
|
placeholder = stringResource(R.string.name),
|
||||||
|
onValueChange = {
|
||||||
|
subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
subscribeViewModel.dispatch(SubscribeViewAction.HideNewGroupDialog)
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
subscribeViewModel.dispatch(SubscribeViewAction.AddNewGroup)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -40,6 +40,8 @@ class SubscribeViewModel @Inject constructor(
|
||||||
is SubscribeViewAction.Reset -> reset()
|
is SubscribeViewAction.Reset -> reset()
|
||||||
is SubscribeViewAction.Show -> changeVisible(true)
|
is SubscribeViewAction.Show -> changeVisible(true)
|
||||||
is SubscribeViewAction.Hide -> changeVisible(false)
|
is SubscribeViewAction.Hide -> changeVisible(false)
|
||||||
|
is SubscribeViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true)
|
||||||
|
is SubscribeViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false)
|
||||||
is SubscribeViewAction.SwitchPage -> switchPage(action.isSearchPage)
|
is SubscribeViewAction.SwitchPage -> switchPage(action.isSearchPage)
|
||||||
is SubscribeViewAction.ImportFromInputStream -> importFromInputStream(action.inputStream)
|
is SubscribeViewAction.ImportFromInputStream -> importFromInputStream(action.inputStream)
|
||||||
is SubscribeViewAction.InputLink -> inputLink(action.content)
|
is SubscribeViewAction.InputLink -> inputLink(action.content)
|
||||||
|
@ -50,7 +52,7 @@ class SubscribeViewModel @Inject constructor(
|
||||||
changeParseFullContentPreset()
|
changeParseFullContentPreset()
|
||||||
is SubscribeViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
is SubscribeViewAction.SelectedGroup -> selectedGroup(action.groupId)
|
||||||
is SubscribeViewAction.InputNewGroup -> inputNewGroup(action.content)
|
is SubscribeViewAction.InputNewGroup -> inputNewGroup(action.content)
|
||||||
is SubscribeViewAction.SelectedNewGroup -> selectedNewGroup(action.selected)
|
is SubscribeViewAction.AddNewGroup -> addNewGroup()
|
||||||
is SubscribeViewAction.Subscribe -> subscribe()
|
is SubscribeViewAction.Subscribe -> subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,14 +92,7 @@ class SubscribeViewModel @Inject constructor(
|
||||||
val articles = _viewState.value.articles
|
val articles = _viewState.value.articles
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val groupId = async {
|
val groupId = async {
|
||||||
if (
|
_viewState.value.selectedGroupId
|
||||||
_viewState.value.newGroupSelected &&
|
|
||||||
_viewState.value.newGroupContent.isNotBlank()
|
|
||||||
) {
|
|
||||||
rssRepository.get().addGroup(_viewState.value.newGroupContent)
|
|
||||||
} else {
|
|
||||||
_viewState.value.selectedGroupId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rssRepository.get().subscribe(
|
rssRepository.get().subscribe(
|
||||||
feed.copy(
|
feed.copy(
|
||||||
|
@ -118,11 +113,17 @@ class SubscribeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectedNewGroup(selected: Boolean) {
|
private fun addNewGroup() {
|
||||||
_viewState.update {
|
if (_viewState.value.newGroupContent.isNotBlank()) {
|
||||||
it.copy(
|
viewModelScope.launch {
|
||||||
newGroupSelected = selected,
|
selectedGroup(rssRepository.get().addGroup(_viewState.value.newGroupContent))
|
||||||
)
|
changeNewGroupDialogVisible(false)
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(
|
||||||
|
newGroupContent = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +225,14 @@ class SubscribeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun changeNewGroupDialogVisible(visible: Boolean) {
|
||||||
|
_viewState.update {
|
||||||
|
it.copy(
|
||||||
|
newGroupDialogVisible = visible,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun switchPage(isSearchPage: Boolean) {
|
private fun switchPage(isSearchPage: Boolean) {
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -245,8 +254,8 @@ data class SubscribeViewState(
|
||||||
val allowNotificationPreset: Boolean = false,
|
val allowNotificationPreset: Boolean = false,
|
||||||
val parseFullContentPreset: Boolean = false,
|
val parseFullContentPreset: Boolean = false,
|
||||||
val selectedGroupId: String = "",
|
val selectedGroupId: String = "",
|
||||||
|
val newGroupDialogVisible: Boolean = false,
|
||||||
val newGroupContent: String = "",
|
val newGroupContent: String = "",
|
||||||
val newGroupSelected: Boolean = false,
|
|
||||||
val groups: Flow<List<Group>> = emptyFlow(),
|
val groups: Flow<List<Group>> = emptyFlow(),
|
||||||
val isSearchPage: Boolean = true,
|
val isSearchPage: Boolean = true,
|
||||||
)
|
)
|
||||||
|
@ -258,6 +267,10 @@ sealed class SubscribeViewAction {
|
||||||
object Show : SubscribeViewAction()
|
object Show : SubscribeViewAction()
|
||||||
object Hide : SubscribeViewAction()
|
object Hide : SubscribeViewAction()
|
||||||
|
|
||||||
|
object ShowNewGroupDialog : SubscribeViewAction()
|
||||||
|
object HideNewGroupDialog : SubscribeViewAction()
|
||||||
|
object AddNewGroup : SubscribeViewAction()
|
||||||
|
|
||||||
data class SwitchPage(
|
data class SwitchPage(
|
||||||
val isSearchPage: Boolean
|
val isSearchPage: Boolean
|
||||||
) : SubscribeViewAction()
|
) : SubscribeViewAction()
|
||||||
|
@ -270,7 +283,7 @@ sealed class SubscribeViewAction {
|
||||||
val content: String
|
val content: String
|
||||||
) : SubscribeViewAction()
|
) : SubscribeViewAction()
|
||||||
|
|
||||||
object Search: SubscribeViewAction()
|
object Search : SubscribeViewAction()
|
||||||
|
|
||||||
object ChangeAllowNotificationPreset : SubscribeViewAction()
|
object ChangeAllowNotificationPreset : SubscribeViewAction()
|
||||||
object ChangeParseFullContentPreset : SubscribeViewAction()
|
object ChangeParseFullContentPreset : SubscribeViewAction()
|
||||||
|
@ -283,9 +296,5 @@ sealed class SubscribeViewAction {
|
||||||
val content: String
|
val content: String
|
||||||
) : SubscribeViewAction()
|
) : SubscribeViewAction()
|
||||||
|
|
||||||
data class SelectedNewGroup(
|
|
||||||
val selected: Boolean
|
|
||||||
) : SubscribeViewAction()
|
|
||||||
|
|
||||||
object Subscribe : SubscribeViewAction()
|
object Subscribe : SubscribeViewAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,14 @@
|
||||||
<string name="allow_notification">允许通知</string>
|
<string name="allow_notification">允许通知</string>
|
||||||
<string name="parse_full_content">全文解析</string>
|
<string name="parse_full_content">全文解析</string>
|
||||||
<string name="add_to_group">添加到组</string>
|
<string name="add_to_group">添加到组</string>
|
||||||
<string name="new_group">新建分组</string>
|
<string name="create_new_group">新建分组</string>
|
||||||
|
<string name="name">名称</string>
|
||||||
<string name="open_with">打开 %1$s</string>
|
<string name="open_with">打开 %1$s</string>
|
||||||
<string name="options">选项</string>
|
<string name="options">选项</string>
|
||||||
<string name="delete">删除</string>
|
<string name="delete">删除</string>
|
||||||
<string name="has_been_deleted">“%1$s” 已被删除</string>
|
<string name="has_been_deleted">\"%1$s\" 已被删除</string>
|
||||||
<string name="unsubscribe">取消订阅</string>
|
<string name="unsubscribe">取消订阅</string>
|
||||||
<string name="unsubscribe_tip">不再订阅 “%1$s”,同时删除其所有已归档的文章。</string>
|
<string name="unsubscribe_tip">不再订阅 \"%1$s\",同时删除其所有已归档的文章。</string>
|
||||||
<string name="today">今天</string>
|
<string name="today">今天</string>
|
||||||
<string name="yesterday">昨天</string>
|
<string name="yesterday">昨天</string>
|
||||||
<string name="date_at_time">%1$s %2$s</string>
|
<string name="date_at_time">%1$s %2$s</string>
|
||||||
|
|
|
@ -31,13 +31,14 @@
|
||||||
<string name="allow_notification">Allow Notification</string>
|
<string name="allow_notification">Allow Notification</string>
|
||||||
<string name="parse_full_content">Parse Full Content</string>
|
<string name="parse_full_content">Parse Full Content</string>
|
||||||
<string name="add_to_group">Add to Group</string>
|
<string name="add_to_group">Add to Group</string>
|
||||||
<string name="new_group">New Group</string>
|
<string name="create_new_group">Create New Group</string>
|
||||||
|
<string name="name">Name</string>
|
||||||
<string name="open_with">Open %1$s</string>
|
<string name="open_with">Open %1$s</string>
|
||||||
<string name="options">Options</string>
|
<string name="options">Options</string>
|
||||||
<string name="delete">Delete</string>
|
<string name="delete">Delete</string>
|
||||||
<string name="has_been_deleted">"%1$s" has been deleted</string>
|
<string name="has_been_deleted">\"%1$s\" has been deleted</string>
|
||||||
<string name="unsubscribe">Unsubscribe</string>
|
<string name="unsubscribe">Unsubscribe</string>
|
||||||
<string name="unsubscribe_tip">Unsubscribe "%1$s" and delete all its archived articles.</string>
|
<string name="unsubscribe_tip">Unsubscribe \"%1$s\" and delete all its archived articles.</string>
|
||||||
<string name="today">Today</string>
|
<string name="today">Today</string>
|
||||||
<string name="yesterday">Yesterday</string>
|
<string name="yesterday">Yesterday</string>
|
||||||
<string name="date_at_time">%1$s At %2$s</string>
|
<string name="date_at_time">%1$s At %2$s</string>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user