Improve ArticleList and add SwipeRefresh and WebView loading
Fix palettes not selected when first launch
This commit is contained in:
parent
c621f7d794
commit
7f3f5482eb
|
@ -2,6 +2,8 @@ package me.ash.reader.data.repository
|
|||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.ash.reader.ui.ext.formatAsString
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class StringsRepository @Inject constructor(
|
||||
|
@ -9,4 +11,5 @@ class StringsRepository @Inject constructor(
|
|||
private val context: Context,
|
||||
) {
|
||||
fun getString(resId: Int) = context.getString(resId)
|
||||
fun formatAsString(date: Date?) = date?.formatAsString(context)
|
||||
}
|
||||
|
|
31
app/src/main/java/me/ash/reader/ui/component/SwipeRefresh.kt
Normal file
31
app/src/main/java/me/ash/reader/ui/component/SwipeRefresh.kt
Normal file
|
@ -0,0 +1,31 @@
|
|||
package me.ash.reader.ui.component
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.google.accompanist.swiperefresh.SwipeRefreshIndicator
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import me.ash.reader.ui.theme.palette.onDark
|
||||
|
||||
@Composable
|
||||
fun SwipeRefresh(
|
||||
isRefresh: Boolean = false,
|
||||
onRefresh: () -> Unit = {},
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
com.google.accompanist.swiperefresh.SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(isRefresh),
|
||||
onRefresh = onRefresh,
|
||||
indicator = { state, trigger ->
|
||||
SwipeRefreshIndicator(
|
||||
state = state,
|
||||
refreshTriggerDistance = trigger,
|
||||
fade = true,
|
||||
scale = true,
|
||||
contentColor = MaterialTheme.colorScheme.primary,
|
||||
backgroundColor = MaterialTheme.colorScheme.surface onDark MaterialTheme.colorScheme.surfaceVariant,
|
||||
)
|
||||
}
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package me.ash.reader.ui.component
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.net.http.SslError
|
||||
import android.util.Log
|
||||
|
@ -15,9 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import me.ash.reader.ui.page.home.read.ReadViewAction
|
||||
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||
|
||||
const val INJECTION_TOKEN = "/android_asset_font/"
|
||||
|
||||
|
@ -25,8 +21,6 @@ const val INJECTION_TOKEN = "/android_asset_font/"
|
|||
fun WebView(
|
||||
modifier: Modifier = Modifier,
|
||||
content: String,
|
||||
viewModel: ReadViewModel = hiltViewModel(),
|
||||
onProgressChange: (progress: Int) -> Unit = {},
|
||||
onReceivedError: (error: WebResourceError?) -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
@ -57,16 +51,6 @@ fun WebView(
|
|||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
|
||||
override fun onPageStarted(
|
||||
view: WebView?,
|
||||
url: String?,
|
||||
favicon: Bitmap?
|
||||
) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
// _isLoading = true
|
||||
onProgressChange(-1)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
val jsCode = "javascript:(function(){" +
|
||||
|
@ -78,8 +62,6 @@ fun WebView(
|
|||
"alert('asf');" +
|
||||
"}}})()"
|
||||
view!!.loadUrl(jsCode)
|
||||
viewModel.dispatch(ReadViewAction.ChangeLoading(false))
|
||||
onProgressChange(100)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(
|
||||
|
@ -173,10 +155,12 @@ fun getStyle(argb: Int): String = """
|
|||
padding: 0 24px;
|
||||
}
|
||||
|
||||
img {
|
||||
img, video {
|
||||
margin: 0 -24px 20px;
|
||||
width: calc(100% + 48px);
|
||||
height: auto;
|
||||
border-top: 1px solid ${argbToCssColor(argb)}08;
|
||||
border-bottom: 1px solid ${argbToCssColor(argb)}08;
|
||||
}
|
||||
|
||||
p,span,a,ol,ul,blockquote,article,section {
|
||||
|
|
|
@ -12,7 +12,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.insets.statusBarsPadding
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.ui.component.ViewPager
|
||||
import me.ash.reader.ui.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.findActivity
|
||||
|
@ -49,23 +48,10 @@ fun HomePage(
|
|||
|
||||
LaunchedEffect(openArticleId) {
|
||||
if (openArticleId.isNotEmpty()) {
|
||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(2))
|
||||
launch {
|
||||
val article = readViewModel
|
||||
.rssRepository.get()
|
||||
.findArticleById(openArticleId) ?: return@launch
|
||||
readViewModel.dispatch(ReadViewAction.InitData(article))
|
||||
if (article.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
homeViewModel.dispatch(
|
||||
HomeViewAction.ScrollToPage(
|
||||
scope = scope,
|
||||
targetPage = 2,
|
||||
)
|
||||
)
|
||||
openArticleId = ""
|
||||
}
|
||||
readViewModel.dispatch(ReadViewAction.InitData(openArticleId))
|
||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(0))
|
||||
homeViewModel.dispatch(HomeViewAction.ScrollToPage(scope, 2))
|
||||
openArticleId = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +128,7 @@ fun HomePage(
|
|||
},
|
||||
onItemClick = {
|
||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(0))
|
||||
readViewModel.dispatch(ReadViewAction.InitData(it))
|
||||
readViewModel.dispatch(ReadViewAction.InitData(it.article.id))
|
||||
if (it.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
|
|
|
@ -22,7 +22,7 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
private val rssRepository: RssRepository,
|
||||
private val workManager: WorkManager,
|
||||
workManager: WorkManager,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _viewState = MutableStateFlow(HomeViewState())
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
@ -9,33 +8,31 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import me.ash.reader.data.entity.ArticleWithFeed
|
||||
import me.ash.reader.ui.ext.formatAsString
|
||||
|
||||
@Suppress("FunctionName")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun LazyListScope.generateArticleList(
|
||||
context: Context,
|
||||
pagingItems: LazyPagingItems<ArticleWithFeed>,
|
||||
fun LazyListScope.ArticleList(
|
||||
pagingItems: LazyPagingItems<FlowItemView>,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
var lastItemDay: String? = null
|
||||
for (itemIndex in 0 until pagingItems.itemCount) {
|
||||
val currentItem = pagingItems.peek(itemIndex) ?: continue
|
||||
val currentItemDay = currentItem.article.date.formatAsString(context)
|
||||
if (lastItemDay != currentItemDay) {
|
||||
if (itemIndex != 0) {
|
||||
item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||
when (val item = pagingItems[itemIndex]) {
|
||||
is FlowItemView.Article -> {
|
||||
item {
|
||||
ArticleItem(
|
||||
articleWithFeed = item.articleWithFeed,
|
||||
) {
|
||||
onClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
stickyHeader {
|
||||
StickyHeader(currentItemDay)
|
||||
is FlowItemView.Date -> {
|
||||
if (itemIndex != 0) item { Spacer(modifier = Modifier.height(40.dp)) }
|
||||
stickyHeader {
|
||||
StickyHeader(item.date)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
item {
|
||||
ArticleItem(
|
||||
articleWithFeed = pagingItems[itemIndex] ?: return@item,
|
||||
) {
|
||||
onClick(it)
|
||||
}
|
||||
}
|
||||
lastItemDay = currentItemDay
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import androidx.compose.material3.SmallTopAppBar
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -35,6 +34,7 @@ import me.ash.reader.data.entity.ArticleWithFeed
|
|||
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
|
||||
import me.ash.reader.ui.component.DisplayText
|
||||
import me.ash.reader.ui.component.FeedbackIconButton
|
||||
import me.ash.reader.ui.component.SwipeRefresh
|
||||
import me.ash.reader.ui.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.getName
|
||||
import me.ash.reader.ui.page.home.FilterBar
|
||||
|
@ -56,7 +56,6 @@ fun FlowPage(
|
|||
onScrollToPage: (targetPage: Int) -> Unit = {},
|
||||
onItemClick: (item: ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
@ -168,90 +167,89 @@ fun FlowPage(
|
|||
// url = "https://assets7.lottiefiles.com/packages/lf20_l4ny0jjm.json",
|
||||
// )
|
||||
// }
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = viewState.listState,
|
||||
SwipeRefresh(
|
||||
onRefresh = {
|
||||
if (!isSyncing) {
|
||||
flowViewModel.dispatch(FlowViewAction.Sync)
|
||||
}
|
||||
}
|
||||
) {
|
||||
item {
|
||||
DisplayText(
|
||||
modifier = Modifier.padding(start = 30.dp),
|
||||
text = when {
|
||||
filterState.group != null -> filterState.group.name
|
||||
filterState.feed != null -> filterState.feed.name
|
||||
else -> filterState.filter.getName()
|
||||
},
|
||||
desc = if (isSyncing) stringResource(R.string.syncing) else "",
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = markAsRead,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||
}
|
||||
MarkAsReadBar(
|
||||
visible = markAsRead,
|
||||
absoluteY = if (isSyncing) (4 + 16 + 180).dp else 180.dp,
|
||||
onDismissRequest = {
|
||||
markAsRead = false
|
||||
},
|
||||
) {
|
||||
markAsRead = false
|
||||
flowViewModel.dispatch(
|
||||
FlowViewAction.MarkAsRead(
|
||||
groupId = filterState.group?.id,
|
||||
feedId = filterState.feed?.id,
|
||||
articleId = null,
|
||||
markAsReadBefore = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = onSearch,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
SearchBar(
|
||||
value = viewState.searchContent,
|
||||
placeholder = when {
|
||||
filterState.group != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.group.name
|
||||
)
|
||||
filterState.feed != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.feed.name
|
||||
)
|
||||
else -> stringResource(
|
||||
R.string.search_for,
|
||||
filterState.filter.getName()
|
||||
)
|
||||
},
|
||||
focusRequester = focusRequester,
|
||||
onValueChange = {
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(it))
|
||||
},
|
||||
onClose = {
|
||||
onSearch = false
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(""))
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||
}
|
||||
}
|
||||
generateArticleList(
|
||||
context = context,
|
||||
pagingItems = pagingItems,
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = viewState.listState,
|
||||
) {
|
||||
onSearch = false
|
||||
onItemClick(it)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount != 0) {
|
||||
item {
|
||||
DisplayTextHeader(filterState, isSyncing)
|
||||
AnimatedVisibility(
|
||||
visible = markAsRead,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||
}
|
||||
MarkAsReadBar(
|
||||
visible = markAsRead,
|
||||
absoluteY = if (isSyncing) (4 + 16 + 180).dp else 180.dp,
|
||||
onDismissRequest = {
|
||||
markAsRead = false
|
||||
},
|
||||
) {
|
||||
markAsRead = false
|
||||
flowViewModel.dispatch(
|
||||
FlowViewAction.MarkAsRead(
|
||||
groupId = filterState.group?.id,
|
||||
feedId = filterState.feed?.id,
|
||||
articleId = null,
|
||||
markAsReadBefore = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = onSearch,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
SearchBar(
|
||||
value = viewState.searchContent,
|
||||
placeholder = when {
|
||||
filterState.group != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.group.name
|
||||
)
|
||||
filterState.feed != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.feed.name
|
||||
)
|
||||
else -> stringResource(
|
||||
R.string.search_for,
|
||||
filterState.filter.getName()
|
||||
)
|
||||
},
|
||||
focusRequester = focusRequester,
|
||||
onValueChange = {
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(it))
|
||||
},
|
||||
onClose = {
|
||||
onSearch = false
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(""))
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||
}
|
||||
}
|
||||
ArticleList(
|
||||
pagingItems = pagingItems,
|
||||
) {
|
||||
onSearch = false
|
||||
onItemClick(it)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount != 0) {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,3 +267,19 @@ fun FlowPage(
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayTextHeader(
|
||||
filterState: FilterState,
|
||||
isSyncing: Boolean
|
||||
) {
|
||||
DisplayText(
|
||||
modifier = Modifier.padding(start = 30.dp),
|
||||
text = when {
|
||||
filterState.group != null -> filterState.group.name
|
||||
filterState.feed != null -> filterState.feed.name
|
||||
else -> filterState.filter.getName()
|
||||
},
|
||||
desc = if (isSyncing) stringResource(R.string.syncing) else "",
|
||||
)
|
||||
}
|
|
@ -3,16 +3,13 @@ package me.ash.reader.ui.page.home.flow
|
|||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.*
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.data.entity.ArticleWithFeed
|
||||
import me.ash.reader.data.repository.RssRepository
|
||||
import me.ash.reader.data.repository.StringsRepository
|
||||
import me.ash.reader.ui.page.home.FilterState
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
@ -20,14 +17,16 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class FlowViewModel @Inject constructor(
|
||||
private val rssRepository: RssRepository,
|
||||
private val stringsRepository: StringsRepository,
|
||||
) : ViewModel() {
|
||||
private val _viewState = MutableStateFlow(ArticleViewState())
|
||||
val viewState: StateFlow<ArticleViewState> = _viewState.asStateFlow()
|
||||
|
||||
fun dispatch(action: FlowViewAction) {
|
||||
when (action) {
|
||||
is FlowViewAction.Sync -> sync()
|
||||
is FlowViewAction.FetchData -> fetchData(action.filterState)
|
||||
is FlowViewAction.ChangeRefreshing -> changeRefreshing(action.isRefreshing)
|
||||
is FlowViewAction.ChangeIsBack -> changeIsBack(action.isBack)
|
||||
is FlowViewAction.ScrollToItem -> scrollToItem(action.index)
|
||||
is FlowViewAction.MarkAsRead -> markAsRead(
|
||||
action.groupId,
|
||||
|
@ -39,6 +38,10 @@ class FlowViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun sync() {
|
||||
rssRepository.get().doSync()
|
||||
}
|
||||
|
||||
private fun fetchData(filterState: FilterState? = null) {
|
||||
// viewModelScope.launch(Dispatchers.Default) {
|
||||
// rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
|
||||
|
@ -62,7 +65,21 @@ class FlowViewModel @Inject constructor(
|
|||
isStarred = _viewState.value.filterState?.filter?.isStarred() ?: false,
|
||||
isUnread = _viewState.value.filterState?.filter?.isUnread() ?: false,
|
||||
)
|
||||
}.flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)
|
||||
}.flow.map {
|
||||
it.map {
|
||||
FlowItemView.Article(it)
|
||||
}.insertSeparators { before, after ->
|
||||
val beforeDate =
|
||||
stringsRepository.formatAsString(before?.articleWithFeed?.article?.date)
|
||||
val afterDate =
|
||||
stringsRepository.formatAsString(after?.articleWithFeed?.article?.date)
|
||||
if (beforeDate != afterDate) {
|
||||
afterDate?.let { FlowItemView.Date(it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.cachedIn(viewModelScope)
|
||||
)
|
||||
}
|
||||
} else if (filterState != null) {
|
||||
|
@ -76,7 +93,21 @@ class FlowViewModel @Inject constructor(
|
|||
isStarred = filterState.filter.isStarred(),
|
||||
isUnread = filterState.filter.isUnread(),
|
||||
)
|
||||
}.flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)
|
||||
}.flow.map {
|
||||
it.map {
|
||||
FlowItemView.Article(it)
|
||||
}.insertSeparators { before, after ->
|
||||
val beforeDate =
|
||||
stringsRepository.formatAsString(before?.articleWithFeed?.article?.date)
|
||||
val afterDate =
|
||||
stringsRepository.formatAsString(after?.articleWithFeed?.article?.date)
|
||||
if (beforeDate != afterDate) {
|
||||
afterDate?.let { FlowItemView.Date(it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.cachedIn(viewModelScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -88,9 +119,9 @@ class FlowViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun changeRefreshing(isRefreshing: Boolean) {
|
||||
private fun changeIsBack(isBack: Boolean) {
|
||||
_viewState.update {
|
||||
it.copy(isRefreshing = isRefreshing)
|
||||
it.copy(isBack = isBack)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,19 +170,21 @@ data class ArticleViewState(
|
|||
val filterState: FilterState? = null,
|
||||
val filterImportant: Int = 0,
|
||||
val listState: LazyListState = LazyListState(),
|
||||
val isRefreshing: Boolean = false,
|
||||
val pagingData: Flow<PagingData<ArticleWithFeed>> = emptyFlow(),
|
||||
val isBack: Boolean = false,
|
||||
val pagingData: Flow<PagingData<FlowItemView>> = emptyFlow(),
|
||||
val syncWorkInfo: String = "",
|
||||
val searchContent: String = "",
|
||||
)
|
||||
|
||||
sealed class FlowViewAction {
|
||||
object Sync : FlowViewAction()
|
||||
|
||||
data class FetchData(
|
||||
val filterState: FilterState,
|
||||
) : FlowViewAction()
|
||||
|
||||
data class ChangeRefreshing(
|
||||
val isRefreshing: Boolean
|
||||
data class ChangeIsBack(
|
||||
val isBack: Boolean
|
||||
) : FlowViewAction()
|
||||
|
||||
data class ScrollToItem(
|
||||
|
@ -176,3 +209,8 @@ enum class MarkAsReadBefore {
|
|||
OneDay,
|
||||
All,
|
||||
}
|
||||
|
||||
sealed class FlowItemView {
|
||||
class Article(val articleWithFeed: ArticleWithFeed) : FlowItemView()
|
||||
class Date(val date: String) : FlowItemView()
|
||||
}
|
|
@ -20,7 +20,8 @@ fun StickyHeader(currentItemDay: String) {
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = if (true) 54.dp else 24.dp),
|
||||
modifier = Modifier
|
||||
.padding(start = if (true) 54.dp else 24.dp, bottom = 4.dp),
|
||||
text = currentItemDay,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
|
|
|
@ -67,9 +67,6 @@ fun ReadPage(
|
|||
if (it.article.isUnread) {
|
||||
readViewModel.dispatch(ReadViewAction.MarkUnread(false))
|
||||
}
|
||||
if (it.feed.isFullContent) {
|
||||
readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +93,7 @@ fun ReadPage(
|
|||
Content(
|
||||
content = viewState.content ?: "",
|
||||
articleWithFeed = viewState.articleWithFeed,
|
||||
viewState = viewState,
|
||||
LazyListState = viewState.listState,
|
||||
)
|
||||
Box(
|
||||
|
@ -156,7 +154,9 @@ private fun TopBar(
|
|||
actions = {
|
||||
if (isShowActions) {
|
||||
FeedbackIconButton(
|
||||
modifier = Modifier.size(22.dp).alpha(0.5f),
|
||||
modifier = Modifier
|
||||
.size(22.dp)
|
||||
.alpha(0.5f),
|
||||
imageVector = Icons.Outlined.Headphones,
|
||||
contentDescription = stringResource(R.string.mark_all_as_read),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
|
@ -179,6 +179,7 @@ private fun TopBar(
|
|||
private fun Content(
|
||||
content: String,
|
||||
articleWithFeed: ArticleWithFeed?,
|
||||
viewState: ReadViewState,
|
||||
LazyListState: LazyListState = rememberLazyListState(),
|
||||
) {
|
||||
Column {
|
||||
|
@ -208,7 +209,27 @@ private fun Content(
|
|||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
Crossfade(targetState = content) { content ->
|
||||
AnimatedVisibility(
|
||||
visible = viewState.isLoading,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(30.dp),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!viewState.isLoading) {
|
||||
WebView(
|
||||
content = content
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ class ReadViewModel @Inject constructor(
|
|||
|
||||
fun dispatch(action: ReadViewAction) {
|
||||
when (action) {
|
||||
is ReadViewAction.InitData -> bindArticleWithFeed(action.articleWithFeed)
|
||||
is ReadViewAction.InitData -> bindArticleWithFeed(action.articleId)
|
||||
is ReadViewAction.RenderDescriptionContent -> renderDescriptionContent()
|
||||
is ReadViewAction.RenderFullContent -> renderFullContent()
|
||||
is ReadViewAction.MarkUnread -> markUnread(action.isUnread)
|
||||
|
@ -37,9 +37,17 @@ class ReadViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun bindArticleWithFeed(articleWithFeed: ArticleWithFeed) {
|
||||
_viewState.update {
|
||||
it.copy(articleWithFeed = articleWithFeed)
|
||||
private fun bindArticleWithFeed(articleId: String) {
|
||||
changeLoading(true)
|
||||
viewModelScope.launch {
|
||||
_viewState.update {
|
||||
it.copy(articleWithFeed = rssRepository.get().findArticleById(articleId))
|
||||
}
|
||||
_viewState.value.articleWithFeed?.let {
|
||||
if (it.feed.isFullContent) internalRenderFullContent()
|
||||
else renderDescriptionContent()
|
||||
}
|
||||
changeLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,26 +63,31 @@ class ReadViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun renderFullContent() {
|
||||
changeLoading(true)
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
content = rssHelper.parseFullContent(
|
||||
_viewState.value.articleWithFeed?.article?.link ?: "",
|
||||
_viewState.value.articleWithFeed?.article?.title ?: ""
|
||||
)
|
||||
internalRenderFullContent()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun internalRenderFullContent() {
|
||||
changeLoading(true)
|
||||
try {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
content = rssHelper.parseFullContent(
|
||||
_viewState.value.articleWithFeed?.article?.link ?: "",
|
||||
_viewState.value.articleWithFeed?.article?.title ?: ""
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("RLog", "renderFullContent: ${e.message}")
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
content = e.message
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("RLog", "renderFullContent: ${e.message}")
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
content = e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
changeLoading(false)
|
||||
}
|
||||
|
||||
private fun markUnread(isUnread: Boolean) {
|
||||
|
@ -141,13 +154,13 @@ class ReadViewModel @Inject constructor(
|
|||
data class ReadViewState(
|
||||
val articleWithFeed: ArticleWithFeed? = null,
|
||||
val content: String? = null,
|
||||
val isLoading: Boolean = false,
|
||||
val isLoading: Boolean = true,
|
||||
val listState: LazyListState = LazyListState(),
|
||||
)
|
||||
|
||||
sealed class ReadViewAction {
|
||||
data class InitData(
|
||||
val articleWithFeed: ArticleWithFeed,
|
||||
val articleId: String,
|
||||
) : ReadViewAction()
|
||||
|
||||
object RenderDescriptionContent : ReadViewAction()
|
||||
|
|
|
@ -191,8 +191,8 @@ fun Palettes(
|
|||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val themeIndex = context.dataStore.data
|
||||
.map { it[DataStoreKeys.ThemeIndex.key] ?: 0 }
|
||||
.collectAsState(initial = 0).value
|
||||
.map { it[DataStoreKeys.ThemeIndex.key] ?: 5 }
|
||||
.collectAsState(initial = 5).value
|
||||
val customPrimaryColor = context.dataStore.data
|
||||
.map { it[DataStoreKeys.CustomPrimaryColor.key] ?: "" }
|
||||
.collectAsState(initial = "").value
|
||||
|
|
Loading…
Reference in New Issue
Block a user