Add a subscribe feed dialog

This commit is contained in:
Ash 2022-03-06 05:29:23 +08:00
parent f260175f25
commit 5fff554bba
5 changed files with 160 additions and 22 deletions

View File

@ -81,7 +81,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha01" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha01"
implementation "androidx.compose.material3:material3:1.0.0-alpha04" implementation "androidx.compose.material:material:1.2.0-alpha03"
implementation "androidx.compose.material3:material3:1.0.0-alpha05"
implementation "androidx.compose.material:material-icons-extended:$compose_version" implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'

View File

@ -214,7 +214,7 @@ private fun ArticleItem(
if (isStarredFilter || articleWithFeed.article.isUnread) { if (isStarredFilter || articleWithFeed.article.isUnread) {
1f 1f
} else { } else {
0.75f 0.7f
} }
) )
) { ) {

View File

@ -6,23 +6,30 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.*
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@ -40,8 +47,10 @@ import me.ash.reader.ui.page.home.HomeViewAction
import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.HomeViewModel
import me.ash.reader.ui.util.collectAsStateValue import me.ash.reader.ui.util.collectAsStateValue
import me.ash.reader.ui.widget.* import me.ash.reader.ui.widget.*
import java.io.InputStream
@ExperimentalComposeUiApi
@ExperimentalAnimationApi @ExperimentalAnimationApi
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
@ExperimentalPagerApi @ExperimentalPagerApi
@ -55,17 +64,9 @@ fun FeedPage(
filter: Filter, filter: Filter,
groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> }, groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> },
) { ) {
val context = LocalContext.current
val viewState = viewModel.viewState.collectAsStateValue() val viewState = viewModel.viewState.collectAsStateValue()
val syncState = RssRepository.syncState.collectAsStateValue() val syncState = RssRepository.syncState.collectAsStateValue()
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { var addFeedDialogVisible by remember { mutableStateOf(false) }
Log.i("RLog", "launcher: ${it}")
it?.let { uri ->
context.contentResolver.openInputStream(uri)?.let { inputStream ->
viewModel.dispatch(FeedViewAction.AddFromFile(inputStream))
}
}
}
LaunchedEffect(homeViewModel.filterState) { LaunchedEffect(homeViewModel.filterState) {
homeViewModel.filterState.collect { state -> homeViewModel.filterState.collect { state ->
@ -88,6 +89,13 @@ fun FeedPage(
Box( Box(
modifier.fillMaxSize() modifier.fillMaxSize()
) { ) {
AddFeedDialog(
visible = addFeedDialogVisible,
hiddenFunction = { addFeedDialogVisible = false },
openInputStreamCallback = {
viewModel.dispatch(FeedViewAction.AddFromFile(it))
},
)
TopTitleBox( TopTitleBox(
title = viewState.account?.name ?: "未知账户", title = viewState.account?.name ?: "未知账户",
description = if (syncState.isSyncing) { description = if (syncState.isSyncing) {
@ -132,7 +140,7 @@ fun FeedPage(
) )
} }
IconButton(onClick = { IconButton(onClick = {
launcher.launch("*/*") addFeedDialogVisible = true
}) { }) {
Icon( Icon(
modifier = Modifier.size(26.dp), modifier = Modifier.size(26.dp),
@ -186,6 +194,107 @@ fun FeedPage(
} }
} }
@ExperimentalComposeUiApi
@Composable
private fun AddFeedDialog(
visible: Boolean,
hiddenFunction: () -> Unit,
openInputStreamCallback: (InputStream) -> Unit,
) {
val context = LocalContext.current
var inputString by remember { mutableStateOf("") }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
it?.let { uri ->
context.contentResolver.openInputStream(uri)?.let { inputStream ->
openInputStreamCallback(inputStream)
}
}
}
val focusRequester = remember { FocusRequester() }
val localFocusManager = LocalFocusManager.current
val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
Dialog(
visible = visible,
onDismissRequest = hiddenFunction,
icon = {
Icon(
imageVector = Icons.Rounded.RssFeed,
contentDescription = "Subscribe",
)
},
title = { Text("订阅") },
text = {
Spacer(modifier = Modifier.height(10.dp))
TextField(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { if(it.isFocused) localSoftwareKeyboardController?.hide() }
.focusable(),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
cursorColor = MaterialTheme.colorScheme.onSurface,
textColor = MaterialTheme.colorScheme.onSurface,
focusedIndicatorColor = MaterialTheme.colorScheme.primary,
),
value = inputString,
onValueChange = {
inputString = it
},
placeholder = {
Text(
text = "订阅源或站点链接",
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
)
},
singleLine = true,
trailingIcon = {
IconButton(onClick = {}) {
Icon(
imageVector = Icons.Rounded.ContentPaste,
contentDescription = "Paste",
tint = MaterialTheme.colorScheme.primary
)
}
},
keyboardActions = KeyboardActions(
onDone = {
hiddenFunction()
}
)
)
Spacer(modifier = Modifier.height(10.dp))
},
confirmButton = {
TextButton(
enabled = inputString.isNotEmpty(),
onClick = {
hiddenFunction()
}
) {
Text(
text = "搜索",
color = if (inputString.isNotEmpty()) {
Color.Unspecified
} else {
MaterialTheme.colorScheme.outline.copy(alpha = 0.7f)
}
)
}
},
dismissButton = {
TextButton(
onClick = {
launcher.launch("*/*")
hiddenFunction()
}
) {
Text("导入OPML文件")
}
},
)
}
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
private fun ColumnScope.GroupList( private fun ColumnScope.GroupList(
@ -230,9 +339,6 @@ private fun ColumnScope.FeedList(
feeds: List<Feed>, feeds: List<Feed>,
onClick: (currentFeed: Feed?) -> Unit = {}, onClick: (currentFeed: Feed?) -> Unit = {},
) { ) {
// LaunchedEffect(feeds) {
//
// }
AnimatedVisibility( AnimatedVisibility(
visible = visible, visible = visible,
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),

View File

@ -22,7 +22,7 @@ fun CanBeDisabledIconButton(
IconButton( IconButton(
modifier = Modifier.alpha( modifier = Modifier.alpha(
if (disabled) { if (disabled) {
0.75f 0.7f
} else { } else {
1f 1f
} }

View File

@ -0,0 +1,31 @@
package me.ash.reader.ui.widget
import androidx.compose.animation.*
import androidx.compose.material3.AlertDialog
import androidx.compose.runtime.Composable
@Composable
fun Dialog(
visible: Boolean,
onDismissRequest: () -> Unit = {},
icon: @Composable (() -> Unit)? = null,
title: @Composable (() -> Unit)? = null,
text: @Composable (() -> Unit)? = null,
confirmButton: @Composable () -> Unit,
dismissButton: @Composable (() -> Unit)? = null,
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
AlertDialog(
onDismissRequest = onDismissRequest,
icon = icon,
title = title,
text = text,
confirmButton = confirmButton,
dismissButton = dismissButton,
)
}
}