Improve SubscribeDialog

This commit is contained in:
Ash 2022-04-03 01:52:56 +08:00
parent 31a8445df2
commit 54506e5019
6 changed files with 127 additions and 181 deletions

View File

@ -21,7 +21,7 @@ import me.ash.reader.ui.component.BottomDrawer
import me.ash.reader.ui.component.Subtitle import me.ash.reader.ui.component.Subtitle
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.ResultViewPage import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@ -60,7 +60,7 @@ fun FeedOptionDrawer(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
Spacer(modifier = modifier.height(16.dp)) Spacer(modifier = modifier.height(16.dp))
ResultViewPage( ResultView(
link = feed?.url ?: stringResource(R.string.unknown), link = feed?.url ?: stringResource(R.string.unknown),
groups = viewState.groups, groups = viewState.groups,
selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false, selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false,

View File

@ -29,7 +29,7 @@ import me.ash.reader.ui.component.Subtitle
import me.ash.reader.ui.ext.roundClick import me.ash.reader.ui.ext.roundClick
@Composable @Composable
fun ResultViewPage( fun ResultView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
link: String = "", link: String = "",
groups: List<Group> = emptyList(), groups: List<Group> = emptyList(),

View File

@ -30,16 +30,12 @@ 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.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import me.ash.reader.R import me.ash.reader.R
@OptIn(ExperimentalPagerApi::class)
@Composable @Composable
fun SearchViewPage( fun SearchView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
pagerState: PagerState,
readOnly: Boolean = false, readOnly: Boolean = false,
inputLink: String = "", inputLink: String = "",
errorMessage: String = "", errorMessage: String = "",

View File

@ -2,7 +2,7 @@ package me.ash.reader.ui.page.home.feeds.subscribe
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.height 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.rounded.RssFeed import androidx.compose.material.icons.rounded.RssFeed
@ -10,22 +10,25 @@ 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.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.unit.Dp
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 com.google.accompanist.pager.ExperimentalPagerApi
import me.ash.reader.R import me.ash.reader.R
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.ext.*
@OptIn(ExperimentalPagerApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @OptIn(
androidx.compose.ui.ExperimentalComposeUiApi::class,
ExperimentalAnimationApi::class
)
@Composable @Composable
fun SubscribeDialog( fun SubscribeDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -33,10 +36,8 @@ fun SubscribeDialog(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val scope = rememberCoroutineScope()
val viewState = subscribeViewModel.viewState.collectAsStateValue() val viewState = subscribeViewModel.viewState.collectAsStateValue()
val groupsState = viewState.groups.collectAsState(initial = emptyList()) val groupsState = viewState.groups.collectAsState(initial = emptyList())
var dialogHeight by remember { mutableStateOf(300.dp) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
it?.let { uri -> it?.let { uri ->
context.contentResolver.openInputStream(uri)?.let { inputStream -> context.contentResolver.openInputStream(uri)?.let { inputStream ->
@ -56,22 +57,12 @@ fun SubscribeDialog(
subscribeViewModel.dispatch(SubscribeViewAction.Init) subscribeViewModel.dispatch(SubscribeViewAction.Init)
} else { } else {
subscribeViewModel.dispatch(SubscribeViewAction.Reset) subscribeViewModel.dispatch(SubscribeViewAction.Reset)
viewState.pagerState.scrollToPage(0) subscribeViewModel.dispatch(SubscribeViewAction.SwitchPage(true))
}
}
LaunchedEffect(viewState.pagerState.currentPage) {
focusManager.clearFocus()
when (viewState.pagerState.currentPage) {
0 -> dialogHeight = 300.dp
1 -> dialogHeight = Dp.Unspecified
} }
} }
Dialog( Dialog(
modifier = Modifier modifier = Modifier.padding(horizontal = 44.dp),
.padding(horizontal = 44.dp)
.height(dialogHeight),
visible = viewState.visible, visible = viewState.visible,
properties = DialogProperties(usePlatformDefaultWidth = false), properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest = { onDismissRequest = {
@ -86,97 +77,113 @@ fun SubscribeDialog(
}, },
title = { title = {
Text( Text(
when (viewState.pagerState.currentPage) { if (viewState.isSearchPage) {
0 -> viewState.title viewState.title
else -> viewState.feed?.name ?: stringResource(R.string.unknown) } else {
viewState.feed?.name ?: stringResource(R.string.unknown)
} }
) )
}, },
text = { text = {
SubscribeViewPager( AnimatedContent(
viewState = viewState, targetState = viewState.isSearchPage,
onLinkValueChange = { transitionSpec = {
subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it)) slideInHorizontally { width -> width } + fadeIn() with
}, slideOutHorizontally { width -> -width } + fadeOut()
onSearchKeyboardAction = { }
subscribeViewModel.dispatch(SubscribeViewAction.Search(scope)) ) { targetExpanded ->
}, if (targetExpanded) {
groups = groupsState.value, SearchView(
onNewGroupValueChange = { readOnly = viewState.lockLinkInput,
subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it)) inputLink = viewState.linkContent,
}, errorMessage = viewState.errorMessage,
changeNewGroupSelected = { onLinkValueChange = {
subscribeViewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it)) subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it))
}, },
allowNotificationPresetOnClick = { onKeyboardAction = {
subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset) subscribeViewModel.dispatch(SubscribeViewAction.Search)
}, },
parseFullContentPresetOnClick = { )
subscribeViewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset) } else {
}, ResultView(
onGroupClick = { link = viewState.linkContent,
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it)) groups = groupsState.value,
}, selectedAllowNotificationPreset = viewState.allowNotificationPreset,
onResultKeyboardAction = { selectedParseFullContentPreset = viewState.parseFullContentPreset,
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe) selectedGroupId = viewState.selectedGroupId,
}, newGroupContent = viewState.newGroupContent,
) onNewGroupValueChange = {
subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
},
newGroupSelected = viewState.newGroupSelected,
changeNewGroupSelected = {
subscribeViewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it))
},
allowNotificationPresetOnClick = {
subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
},
parseFullContentPresetOnClick = {
subscribeViewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset)
},
onGroupClick = {
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
},
onKeyboardAction = {
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe)
},
)
}
}
}, },
confirmButton = { confirmButton = {
when (viewState.pagerState.currentPage) { if (viewState.isSearchPage) {
0 -> { TextButton(
TextButton( enabled = viewState.linkContent.isNotEmpty()
enabled = viewState.linkContent.isNotEmpty() && viewState.title != stringResource(R.string.searching),
&& viewState.title != stringResource(R.string.searching), onClick = {
onClick = { focusManager.clearFocus()
focusManager.clearFocus() subscribeViewModel.dispatch(SubscribeViewAction.Search)
subscribeViewModel.dispatch(SubscribeViewAction.Search(scope))
}
) {
Text(
text = stringResource(R.string.search),
color = if (viewState.linkContent.isNotEmpty()) {
Color.Unspecified
} else {
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
}
)
} }
) {
Text(
text = stringResource(R.string.search),
color = if (viewState.linkContent.isNotEmpty()) {
Color.Unspecified
} else {
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
}
)
} }
1 -> { } else {
TextButton( TextButton(
onClick = { onClick = {
focusManager.clearFocus() focusManager.clearFocus()
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe) subscribeViewModel.dispatch(SubscribeViewAction.Subscribe)
}
) {
Text(stringResource(R.string.subscribe))
} }
) {
Text(stringResource(R.string.subscribe))
} }
} }
}, },
dismissButton = { dismissButton = {
when (viewState.pagerState.currentPage) { if (viewState.isSearchPage) {
0 -> { TextButton(
TextButton( onClick = {
onClick = { focusManager.clearFocus()
focusManager.clearFocus() launcher.launch("*/*")
launcher.launch("*/*") subscribeViewModel.dispatch(SubscribeViewAction.Hide)
subscribeViewModel.dispatch(SubscribeViewAction.Hide)
}
) {
Text(text = stringResource(R.string.import_from_opml))
} }
) {
Text(text = stringResource(R.string.import_from_opml))
} }
1 -> { } else {
TextButton( TextButton(
onClick = { onClick = {
focusManager.clearFocus() focusManager.clearFocus()
subscribeViewModel.dispatch(SubscribeViewAction.Hide) subscribeViewModel.dispatch(SubscribeViewAction.Hide)
}
) {
Text(text = stringResource(R.string.cancel))
} }
) {
Text(text = stringResource(R.string.cancel))
} }
} }
}, },

View File

@ -4,10 +4,12 @@ import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.Article import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
@ -16,7 +18,6 @@ import me.ash.reader.data.repository.OpmlRepository
import me.ash.reader.data.repository.RssHelper import me.ash.reader.data.repository.RssHelper
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
import me.ash.reader.data.repository.StringsRepository import me.ash.reader.data.repository.StringsRepository
import me.ash.reader.ui.ext.animateScrollToPage
import me.ash.reader.ui.ext.formatUrl import me.ash.reader.ui.ext.formatUrl
import java.io.InputStream import java.io.InputStream
import javax.inject.Inject import javax.inject.Inject
@ -39,9 +40,10 @@ 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.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)
is SubscribeViewAction.Search -> search(action.scope) is SubscribeViewAction.Search -> search()
is SubscribeViewAction.ChangeAllowNotificationPreset -> is SubscribeViewAction.ChangeAllowNotificationPreset ->
changeAllowNotificationPreset() changeAllowNotificationPreset()
is SubscribeViewAction.ChangeParseFullContentPreset -> is SubscribeViewAction.ChangeParseFullContentPreset ->
@ -140,7 +142,7 @@ class SubscribeViewModel @Inject constructor(
} }
} }
private fun search(scope: CoroutineScope) { private fun search() {
searchJob?.cancel() searchJob?.cancel()
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
@ -181,7 +183,7 @@ class SubscribeViewModel @Inject constructor(
articles = feedWithArticle.articles, articles = feedWithArticle.articles,
) )
} }
_viewState.value.pagerState.animateScrollToPage(scope, 1) switchPage(false)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
_viewState.update { _viewState.update {
@ -221,6 +223,14 @@ class SubscribeViewModel @Inject constructor(
) )
} }
} }
private fun switchPage(isSearchPage: Boolean) {
_viewState.update {
it.copy(
isSearchPage = isSearchPage
)
}
}
} }
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@ -238,7 +248,7 @@ data class SubscribeViewState(
val newGroupContent: String = "", val newGroupContent: String = "",
val newGroupSelected: Boolean = false, val newGroupSelected: Boolean = false,
val groups: Flow<List<Group>> = emptyFlow(), val groups: Flow<List<Group>> = emptyFlow(),
val pagerState: PagerState = PagerState(), val isSearchPage: Boolean = true,
) )
sealed class SubscribeViewAction { sealed class SubscribeViewAction {
@ -248,6 +258,10 @@ sealed class SubscribeViewAction {
object Show : SubscribeViewAction() object Show : SubscribeViewAction()
object Hide : SubscribeViewAction() object Hide : SubscribeViewAction()
data class SwitchPage(
val isSearchPage: Boolean
) : SubscribeViewAction()
data class ImportFromInputStream( data class ImportFromInputStream(
val inputStream: InputStream val inputStream: InputStream
) : SubscribeViewAction() ) : SubscribeViewAction()
@ -256,9 +270,7 @@ sealed class SubscribeViewAction {
val content: String val content: String
) : SubscribeViewAction() ) : SubscribeViewAction()
data class Search( object Search: SubscribeViewAction()
val scope: CoroutineScope,
) : SubscribeViewAction()
object ChangeAllowNotificationPreset : SubscribeViewAction() object ChangeAllowNotificationPreset : SubscribeViewAction()
object ChangeParseFullContentPreset : SubscribeViewAction() object ChangeParseFullContentPreset : SubscribeViewAction()

View File

@ -1,69 +0,0 @@
package me.ash.reader.ui.page.home.feeds.subscribe
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import com.google.accompanist.pager.ExperimentalPagerApi
import me.ash.reader.data.entity.Group
import me.ash.reader.ui.component.ViewPager
@OptIn(ExperimentalPagerApi::class)
@Composable
fun SubscribeViewPager(
viewState: SubscribeViewState,
modifier: Modifier = Modifier,
onLinkValueChange: (String) -> Unit = {},
onSearchKeyboardAction: () -> Unit = {},
groups: List<Group> = emptyList(),
onNewGroupValueChange: (String) -> Unit = {},
changeNewGroupSelected: (Boolean) -> Unit = {},
allowNotificationPresetOnClick: () -> Unit = {},
parseFullContentPresetOnClick: () -> Unit = {},
onGroupClick: (groupId: String) -> Unit = {},
onResultKeyboardAction: () -> Unit = {},
) {
val focusManager = LocalFocusManager.current
ViewPager(
modifier = modifier.pointerInput(Unit) {
detectTapGestures(
onTap = {
focusManager.clearFocus()
}
)
},
state = viewState.pagerState,
userScrollEnabled = false,
composableList = listOf(
{
SearchViewPage(
pagerState = viewState.pagerState,
readOnly = viewState.lockLinkInput,
inputLink = viewState.linkContent,
errorMessage = viewState.errorMessage,
onLinkValueChange = onLinkValueChange,
onKeyboardAction = onSearchKeyboardAction,
)
},
{
ResultViewPage(
link = viewState.linkContent,
groups = groups,
selectedAllowNotificationPreset = viewState.allowNotificationPreset,
selectedParseFullContentPreset = viewState.parseFullContentPreset,
selectedGroupId = viewState.selectedGroupId,
newGroupContent = viewState.newGroupContent,
onNewGroupValueChange = onNewGroupValueChange,
newGroupSelected = viewState.newGroupSelected,
changeNewGroupSelected = changeNewGroupSelected,
allowNotificationPresetOnClick = allowNotificationPresetOnClick,
parseFullContentPresetOnClick = parseFullContentPresetOnClick,
onGroupClick = onGroupClick,
onKeyboardAction = onResultKeyboardAction,
)
}
)
)
}