Improve FeedOptionView

This commit is contained in:
Ash 2022-04-03 23:25:23 +08:00
parent 43bbb87280
commit efba776db3
17 changed files with 501 additions and 287 deletions

View File

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

View File

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

View File

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

View File

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

View 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,
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,15 +171,27 @@ 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))
if (groups.size > 6) {
LazyRow {
items(groups) {
SelectionChip(
modifier = Modifier.animateContentSize(),
content = it.name,
selected = it.id == selectedGroupId,
) {
onGroupClick(it.id)
}
Spacer(modifier = Modifier.width(10.dp))
}
item { NewGroupButton(onAddNewGroup) }
}
} else {
FlowRow( FlowRow(
mainAxisAlignment = MainAxisAlignment.Start, mainAxisAlignment = MainAxisAlignment.Start,
crossAxisSpacing = 10.dp, crossAxisSpacing = 10.dp,
@ -172,21 +201,31 @@ private fun AddToGroup(
SelectionChip( SelectionChip(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
content = it.name, content = it.name,
selected = !newGroupSelected && it.id == selectedGroupId, selected = it.id == selectedGroupId,
) { ) {
changeNewGroupSelected(false)
onGroupClick(it.id) onGroupClick(it.id)
} }
} }
NewGroupButton(onAddNewGroup)
}
}
}
SelectionEditorChip( @Composable
modifier = Modifier.animateContentSize(), private fun NewGroupButton(onAddNewGroup: () -> Unit) {
content = newGroupContent, Box(
onValueChange = onNewGroupValueChange, modifier = Modifier
selected = newGroupSelected, .size(36.dp)
onKeyboardAction = onKeyboardAction, .clip(CircleShape)
.background(MaterialTheme.colorScheme.surfaceVariant)
.clickable { onAddNewGroup() },
contentAlignment = Alignment.Center,
) { ) {
changeNewGroupSelected(true) 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),
)
} }
} }

View File

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

View File

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

View File

@ -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,15 +92,8 @@ 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.newGroupSelected &&
_viewState.value.newGroupContent.isNotBlank()
) {
rssRepository.get().addGroup(_viewState.value.newGroupContent)
} else {
_viewState.value.selectedGroupId _viewState.value.selectedGroupId
} }
}
rssRepository.get().subscribe( rssRepository.get().subscribe(
feed.copy( feed.copy(
groupId = groupId.await(), groupId = groupId.await(),
@ -118,13 +113,19 @@ class SubscribeViewModel @Inject constructor(
} }
} }
private fun selectedNewGroup(selected: Boolean) { private fun addNewGroup() {
if (_viewState.value.newGroupContent.isNotBlank()) {
viewModelScope.launch {
selectedGroup(rssRepository.get().addGroup(_viewState.value.newGroupContent))
changeNewGroupDialogVisible(false)
_viewState.update { _viewState.update {
it.copy( it.copy(
newGroupSelected = selected, newGroupContent = "",
) )
} }
} }
}
}
private fun changeParseFullContentPreset() { private fun changeParseFullContentPreset() {
_viewState.update { _viewState.update {
@ -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()
@ -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()
} }

View File

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

View File

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