Merge branch 'pager' into develop
This commit is contained in:
commit
15eb463752
@ -33,6 +33,7 @@ import com.readrops.app.util.Preferences
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.account.Account
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
import com.readrops.db.filters.QueryFilters
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@ -52,7 +53,15 @@ val appModule = module {
|
||||
|
||||
factory { AccountScreenModel(get(), androidContext()) }
|
||||
|
||||
factory { (itemId: Int) -> ItemScreenModel(get(), itemId, get()) }
|
||||
factory { (itemId: Int, itemIndex: Int, queryFilters: QueryFilters) ->
|
||||
ItemScreenModel(
|
||||
itemId = itemId,
|
||||
itemIndex = itemIndex,
|
||||
queryFilters = queryFilters,
|
||||
database = get(),
|
||||
preferences = get()
|
||||
)
|
||||
}
|
||||
|
||||
factory { (accountType: Account, mode: AccountCredentialsScreenMode) ->
|
||||
AccountCredentialsScreenModel(accountType, mode, get())
|
||||
|
@ -1,113 +1,53 @@
|
||||
package com.readrops.app.item
|
||||
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.children
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import cafe.adriel.voyager.koin.koinScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import coil3.compose.AsyncImage
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.item.view.ItemNestedScrollView
|
||||
import com.readrops.app.item.view.ItemWebView
|
||||
import com.readrops.app.util.components.AndroidScreen
|
||||
import com.readrops.app.util.components.CenteredProgressIndicator
|
||||
import com.readrops.app.util.components.FeedIcon
|
||||
import com.readrops.app.util.components.IconText
|
||||
import com.readrops.app.util.components.Placeholder
|
||||
import com.readrops.app.util.extensions.isError
|
||||
import com.readrops.app.util.extensions.isLoading
|
||||
import com.readrops.app.util.extensions.openInCustomTab
|
||||
import com.readrops.app.util.extensions.openUrl
|
||||
import com.readrops.app.util.theme.MediumSpacer
|
||||
import com.readrops.app.util.theme.ShortSpacer
|
||||
import com.readrops.app.util.theme.spacing
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
import com.readrops.db.util.DateUtils
|
||||
import com.readrops.db.filters.QueryFilters
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ItemScreen(
|
||||
private val itemId: Int
|
||||
private val itemId: Int,
|
||||
private val itemIndex: Int,
|
||||
private val queryFilters: QueryFilters
|
||||
) : AndroidScreen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel =
|
||||
koinScreenModel<ItemScreenModel>(parameters = { parametersOf(itemId) })
|
||||
koinScreenModel<ItemScreenModel>(parameters = { parametersOf(itemId, itemIndex, queryFilters) })
|
||||
val state by screenModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
val backgroundColor = MaterialTheme.colorScheme.background
|
||||
val onBackgroundColor = MaterialTheme.colorScheme.onBackground
|
||||
val items = state.itemState.collectAsLazyPagingItems()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
var isScrollable by remember { mutableStateOf(true) }
|
||||
var refreshAndroidView by remember { mutableStateOf(true) }
|
||||
|
||||
// https://developer.android.com/develop/ui/compose/touch-input/pointer-input/scroll#parent-compose-child-view
|
||||
val bottomBarHeight = 64.dp
|
||||
val bottomBarHeightPx = with(density) { bottomBarHeight.roundToPx().toFloat() }
|
||||
val bottomBarOffsetHeightPx = remember { mutableFloatStateOf(0f) }
|
||||
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.y
|
||||
val newOffset = bottomBarOffsetHeightPx.floatValue + delta
|
||||
bottomBarOffsetHeightPx.floatValue = newOffset.coerceIn(-bottomBarHeightPx, 0f)
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.imageDialogUrl != null) {
|
||||
ItemImageDialog(
|
||||
@ -116,7 +56,6 @@ class ItemScreen(
|
||||
screenModel.shareImage(state.imageDialogUrl!!, context)
|
||||
} else {
|
||||
screenModel.downloadImage(state.imageDialogUrl!!, context)
|
||||
|
||||
}
|
||||
|
||||
screenModel.closeImageDialog()
|
||||
@ -137,254 +76,75 @@ class ItemScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (state.itemWithFeed != null) {
|
||||
val itemWithFeed = state.itemWithFeed!!
|
||||
val item = itemWithFeed.item
|
||||
|
||||
val accentColor = if (itemWithFeed.color != 0) {
|
||||
Color(itemWithFeed.color)
|
||||
} else {
|
||||
primaryColor
|
||||
when {
|
||||
items.isLoading() -> {
|
||||
CenteredProgressIndicator()
|
||||
}
|
||||
|
||||
fun openUrl(url: String) {
|
||||
if (state.openInExternalBrowser) {
|
||||
context.openUrl(url)
|
||||
} else {
|
||||
context.openInCustomTab(url, state.theme, accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
bottomBar = {
|
||||
ItemScreenBottomBar(
|
||||
state = state.bottomBarState,
|
||||
accentColor = accentColor,
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.height(bottomBarHeight)
|
||||
.offset {
|
||||
if (isScrollable) {
|
||||
IntOffset(
|
||||
x = 0,
|
||||
y = -bottomBarOffsetHeightPx.floatValue.roundToInt()
|
||||
)
|
||||
} else {
|
||||
IntOffset(0, 0)
|
||||
}
|
||||
},
|
||||
onShare = { screenModel.shareItem(item, context) },
|
||||
onOpenUrl = { openUrl(item.link!!) },
|
||||
onChangeReadState = {
|
||||
screenModel.setItemReadState(item.apply { isRead = it })
|
||||
},
|
||||
onChangeStarState = {
|
||||
screenModel.setItemStarState(item.apply { isStarred = it })
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
ItemNestedScrollView(
|
||||
context = context,
|
||||
useBackgroundTitle = item.imageLink != null,
|
||||
onGlobalLayoutListener = { viewHeight, contentHeight ->
|
||||
isScrollable = viewHeight - contentHeight < 0
|
||||
},
|
||||
onUrlClick = { url -> openUrl(url) },
|
||||
onImageLongPress = { url -> screenModel.openImageDialog(url) }
|
||||
) {
|
||||
if (item.imageLink != null) {
|
||||
BackgroundTitle(itemWithFeed = itemWithFeed)
|
||||
} else {
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = { navigator.pop() },
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.align(Alignment.TopStart)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
SimpleTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
titleColor = accentColor,
|
||||
accentColor = accentColor,
|
||||
baseColor = MaterialTheme.colorScheme.onBackground,
|
||||
bottomPadding = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
update = { nestedScrollView ->
|
||||
if (refreshAndroidView) {
|
||||
val relativeLayout =
|
||||
(nestedScrollView.children.toList()[0] as RelativeLayout)
|
||||
val webView = relativeLayout.children.toList()[1] as ItemWebView
|
||||
|
||||
webView.loadText(
|
||||
itemWithFeed = itemWithFeed,
|
||||
accentColor = accentColor,
|
||||
backgroundColor = backgroundColor,
|
||||
onBackgroundColor = onBackgroundColor
|
||||
)
|
||||
|
||||
refreshAndroidView = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CenteredProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackgroundTitle(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
) {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val onScrimColor = Color.White.copy(alpha = 0.85f)
|
||||
val accentColor = if (itemWithFeed.color != 0) {
|
||||
Color(itemWithFeed.color)
|
||||
} else {
|
||||
onScrimColor
|
||||
}
|
||||
|
||||
Surface(
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = 24.dp,
|
||||
bottomEnd = 24.dp
|
||||
),
|
||||
modifier = Modifier.height(IntrinsicSize.Max)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = itemWithFeed.item.imageLink,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
error = painterResource(id = R.drawable.ic_broken_image),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
)
|
||||
|
||||
Surface(
|
||||
color = Color.Black.copy(alpha = 0.6f),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = { navigator.pop() },
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.align(Alignment.TopStart)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
SimpleTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
titleColor = onScrimColor,
|
||||
accentColor = accentColor,
|
||||
baseColor = onScrimColor,
|
||||
bottomPadding = true
|
||||
items.isError() -> {
|
||||
Placeholder(
|
||||
text = stringResource(R.string.error_occured),
|
||||
painter = painterResource(id = R.drawable.ic_error)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = if (itemIndex > -1) itemIndex else 0,
|
||||
pageCount = { items.itemCount }
|
||||
)
|
||||
|
||||
LaunchedEffect(pagerState.currentPage) {
|
||||
snapshotFlow { pagerState.currentPage }
|
||||
.distinctUntilChanged()
|
||||
.collect { pageIndex ->
|
||||
items[pageIndex]?.let {
|
||||
if (!it.isRead) {
|
||||
screenModel.setItemReadState(it.item.apply { isRead = true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
beyondViewportPageCount = 2,
|
||||
key = items.itemKey { it.item.id }
|
||||
) { page ->
|
||||
val itemWithFeed = items[page]
|
||||
|
||||
if (itemWithFeed != null) {
|
||||
val accentColor = if (itemWithFeed.color != 0) {
|
||||
Color(itemWithFeed.color)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primary
|
||||
}
|
||||
|
||||
val item = itemWithFeed.item
|
||||
|
||||
ItemScreenPage(
|
||||
itemWithFeed = itemWithFeed,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onOpenUrl = { url ->
|
||||
if (state.openInExternalBrowser) {
|
||||
context.openUrl(url)
|
||||
} else {
|
||||
context.openInCustomTab(url, state.theme, accentColor)
|
||||
}
|
||||
},
|
||||
onShareItem = { screenModel.shareItem(item, context) },
|
||||
onSetReadState = {
|
||||
screenModel.setItemReadState(item.apply { isRead = it })
|
||||
},
|
||||
onSetStarState = {
|
||||
screenModel.setItemStarState(item.apply { isStarred = it })
|
||||
},
|
||||
onOpenImageDialog = { screenModel.openImageDialog(it) },
|
||||
onPop = { navigator.pop() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediumSpacer()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleTitle(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
titleColor: Color,
|
||||
accentColor: Color,
|
||||
baseColor: Color,
|
||||
bottomPadding: Boolean,
|
||||
) {
|
||||
val item = itemWithFeed.item
|
||||
val spacing = MaterialTheme.spacing.mediumSpacing
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = spacing,
|
||||
end = spacing,
|
||||
top = spacing,
|
||||
bottom = if (bottomPadding) spacing else 0.dp
|
||||
)
|
||||
) {
|
||||
FeedIcon(
|
||||
iconUrl = itemWithFeed.feedIconUrl,
|
||||
name = itemWithFeed.feedName,
|
||||
size = 48.dp,
|
||||
modifier = Modifier.clip(CircleShape)
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Text(
|
||||
text = itemWithFeed.feedName,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = baseColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Text(
|
||||
text = item.title!!,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = titleColor,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
if (item.author != null) {
|
||||
ShortSpacer()
|
||||
|
||||
IconText(
|
||||
icon = painterResource(id = R.drawable.ic_person),
|
||||
text = itemWithFeed.item.author!!,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = baseColor,
|
||||
tint = accentColor
|
||||
)
|
||||
}
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
val readTime = if (item.readTime > 1) {
|
||||
stringResource(id = R.string.read_time, item.readTime.roundToInt())
|
||||
} else {
|
||||
stringResource(id = R.string.read_time_lower_than_1)
|
||||
}
|
||||
Text(
|
||||
text = "${DateUtils.formattedDate(item.pubDate!!)} ${stringResource(id = R.string.interpoint)} $readTime",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = baseColor
|
||||
)
|
||||
}
|
||||
}
|
@ -10,6 +10,10 @@ import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import coil3.imageLoader
|
||||
@ -18,18 +22,24 @@ import coil3.request.allowHardware
|
||||
import coil3.toBitmap
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.repositories.BaseRepository
|
||||
import com.readrops.app.util.PAGING_PAGE_SIZE
|
||||
import com.readrops.app.util.PAGING_PREFETCH_DISTANCE
|
||||
import com.readrops.app.util.Preferences
|
||||
import com.readrops.db.Database
|
||||
import com.readrops.db.entities.Item
|
||||
import com.readrops.db.entities.account.Account
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
import com.readrops.db.filters.QueryFilters
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
import com.readrops.db.queries.ItemSelectionQueryBuilder
|
||||
import com.readrops.db.queries.ItemsQueryBuilder
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -38,10 +48,11 @@ import org.koin.core.parameter.parametersOf
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ItemScreenModel(
|
||||
private val database: Database,
|
||||
private val itemId: Int,
|
||||
private val itemIndex: Int,
|
||||
private val queryFilters: QueryFilters,
|
||||
private val database: Database,
|
||||
private val preferences: Preferences,
|
||||
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) : StateScreenModel<ItemState>(ItemState()), KoinComponent {
|
||||
@ -53,9 +64,11 @@ class ItemScreenModel(
|
||||
init {
|
||||
screenModelScope.launch(dispatcher) {
|
||||
database.accountDao().selectCurrentAccount()
|
||||
.flatMapLatest { account ->
|
||||
.collect { account ->
|
||||
this@ItemScreenModel.account = account!!
|
||||
|
||||
// With Fever, we notify directly the server about state changes
|
||||
// so we need account credentials
|
||||
if (account.type == AccountType.FEVER) {
|
||||
get<SharedPreferences>().apply {
|
||||
account.login = getString(account.loginKey, null)
|
||||
@ -65,22 +78,21 @@ class ItemScreenModel(
|
||||
|
||||
repository = get { parametersOf(account) }
|
||||
|
||||
val query = ItemSelectionQueryBuilder.buildQuery(
|
||||
itemId = itemId,
|
||||
separateState = account.config.useSeparateState
|
||||
)
|
||||
|
||||
database.itemDao().selectItemById(query)
|
||||
}
|
||||
.collect { itemWithFeed ->
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
itemWithFeed = itemWithFeed,
|
||||
bottomBarState = BottomBarState(
|
||||
isRead = itemWithFeed.item.isRead,
|
||||
isStarred = itemWithFeed.item.isStarred
|
||||
)
|
||||
if (itemIndex > -1) {
|
||||
mutableState.update { it.copy(itemState = buildPager()) }
|
||||
} else {
|
||||
val query = ItemSelectionQueryBuilder.buildQuery(
|
||||
itemId = itemId,
|
||||
separateState = account.config.useSeparateState
|
||||
)
|
||||
|
||||
database.itemDao().selectItemById(query)
|
||||
.distinctUntilChanged()
|
||||
.collect { itemWithFeed ->
|
||||
mutableState.update {
|
||||
it.copy(itemState = flowOf(PagingData.from(listOf(itemWithFeed))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,6 +117,27 @@ class ItemScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPager(): Flow<PagingData<ItemWithFeed>> {
|
||||
val query = ItemsQueryBuilder.buildItemsQuery(
|
||||
queryFilters = queryFilters,
|
||||
separateState = account.config.useSeparateState
|
||||
)
|
||||
|
||||
val pageNb = (((itemIndex + PAGING_PAGE_SIZE - 1) / PAGING_PAGE_SIZE) + 1)
|
||||
.coerceAtLeast(1)
|
||||
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
initialLoadSize = PAGING_PAGE_SIZE * pageNb,
|
||||
pageSize = PAGING_PAGE_SIZE,
|
||||
prefetchDistance = PAGING_PREFETCH_DISTANCE
|
||||
),
|
||||
pagingSourceFactory = { database.itemDao().selectAll(query) }
|
||||
)
|
||||
.flow
|
||||
.cachedIn(screenModelScope)
|
||||
}
|
||||
|
||||
fun shareItem(item: Item, context: Context) {
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
@ -213,11 +246,10 @@ class ItemScreenModel(
|
||||
|
||||
@Stable
|
||||
data class ItemState(
|
||||
val itemWithFeed: ItemWithFeed? = null,
|
||||
val bottomBarState: BottomBarState = BottomBarState(),
|
||||
val itemState: Flow<PagingData<ItemWithFeed>> = emptyFlow(),
|
||||
val imageDialogUrl: String? = null,
|
||||
val fileDownloadedEvent: Boolean = false,
|
||||
val openInExternalBrowser: Boolean = false,
|
||||
val theme: String? = "",
|
||||
val error: String? = null
|
||||
val error: String? = null,
|
||||
)
|
163
app/src/main/java/com/readrops/app/item/ItemScreenPage.kt
Normal file
163
app/src/main/java/com/readrops/app/item/ItemScreenPage.kt
Normal file
@ -0,0 +1,163 @@
|
||||
package com.readrops.app.item
|
||||
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.children
|
||||
import com.readrops.app.item.components.BackgroundTitle
|
||||
import com.readrops.app.item.components.BottomBarState
|
||||
import com.readrops.app.item.components.ItemScreenBottomBar
|
||||
import com.readrops.app.item.components.SimpleTitle
|
||||
import com.readrops.app.item.components.rememberBottomBarNestedScrollConnection
|
||||
import com.readrops.app.item.view.ItemNestedScrollView
|
||||
import com.readrops.app.item.view.ItemWebView
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
|
||||
@Composable
|
||||
fun ItemScreenPage(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onOpenUrl: (String) -> Unit,
|
||||
onShareItem: () -> Unit,
|
||||
onSetReadState: (Boolean) -> Unit,
|
||||
onSetStarState: (Boolean) -> Unit,
|
||||
onOpenImageDialog: (String) -> Unit,
|
||||
onPop: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val item = itemWithFeed.item
|
||||
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
val backgroundColor = MaterialTheme.colorScheme.background
|
||||
val onBackgroundColor = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val accentColor = if (itemWithFeed.color != 0) {
|
||||
Color(itemWithFeed.color)
|
||||
} else {
|
||||
primaryColor
|
||||
}
|
||||
|
||||
val nestedScrollConnection = rememberBottomBarNestedScrollConnection()
|
||||
var refreshAndroidView by remember { mutableStateOf(true) }
|
||||
var isScrollable by remember { mutableStateOf(true) }
|
||||
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(nestedScrollConnection),
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
bottomBar = {
|
||||
ItemScreenBottomBar(
|
||||
state = BottomBarState(
|
||||
isRead = itemWithFeed.item.isRead,
|
||||
isStarred = itemWithFeed.item.isStarred
|
||||
),
|
||||
accentColor = accentColor,
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.height(nestedScrollConnection.bottomBarHeight)
|
||||
.offset {
|
||||
if (isScrollable) {
|
||||
IntOffset(
|
||||
x = 0,
|
||||
y = -nestedScrollConnection.bottomBarOffset
|
||||
)
|
||||
} else {
|
||||
IntOffset(0, 0)
|
||||
}
|
||||
},
|
||||
onShare = onShareItem,
|
||||
onOpenUrl = { onOpenUrl(item.link!!) },
|
||||
onChangeReadState = onSetReadState,
|
||||
onChangeStarState = onSetStarState
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
ItemNestedScrollView(
|
||||
context = context,
|
||||
useBackgroundTitle = item.imageLink != null,
|
||||
onGlobalLayoutListener = { viewHeight, contentHeight ->
|
||||
isScrollable = viewHeight - contentHeight < 0
|
||||
},
|
||||
onUrlClick = { url -> onOpenUrl(url) },
|
||||
onImageLongPress = { url -> onOpenImageDialog(url) }
|
||||
) {
|
||||
if (item.imageLink != null) {
|
||||
BackgroundTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
onClickBack = onPop
|
||||
)
|
||||
} else {
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = onPop,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.align(Alignment.TopStart)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
SimpleTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
titleColor = accentColor,
|
||||
accentColor = accentColor,
|
||||
baseColor = MaterialTheme.colorScheme.onBackground,
|
||||
bottomPadding = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
update = { nestedScrollView ->
|
||||
if (refreshAndroidView) {
|
||||
val relativeLayout =
|
||||
(nestedScrollView.children.toList()
|
||||
.first() as RelativeLayout)
|
||||
val webView =
|
||||
relativeLayout.children.toList()[1] as ItemWebView
|
||||
|
||||
webView.loadText(
|
||||
itemWithFeed = itemWithFeed,
|
||||
accentColor = accentColor,
|
||||
backgroundColor = backgroundColor,
|
||||
onBackgroundColor = onBackgroundColor
|
||||
)
|
||||
|
||||
refreshAndroidView = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package com.readrops.app.item.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.timelime.components.itemWithFeed
|
||||
import com.readrops.app.util.DefaultPreview
|
||||
import com.readrops.app.util.theme.MediumSpacer
|
||||
import com.readrops.app.util.theme.ReadropsTheme
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
|
||||
@Composable
|
||||
fun BackgroundTitle(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
onClickBack: () -> Unit,
|
||||
) {
|
||||
val onScrimColor = Color.White.copy(alpha = 0.85f)
|
||||
val accentColor = if (itemWithFeed.color != 0) {
|
||||
Color(itemWithFeed.color)
|
||||
} else {
|
||||
onScrimColor
|
||||
}
|
||||
|
||||
Surface(
|
||||
shape = RoundedCornerShape(
|
||||
bottomStart = 24.dp,
|
||||
bottomEnd = 24.dp
|
||||
),
|
||||
modifier = Modifier.height(IntrinsicSize.Max)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = itemWithFeed.item.imageLink,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
error = painterResource(id = R.drawable.ic_broken_image),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
)
|
||||
|
||||
Surface(
|
||||
color = Color.Black.copy(alpha = 0.6f),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = onClickBack,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.align(Alignment.TopStart)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
SimpleTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
titleColor = onScrimColor,
|
||||
accentColor = accentColor,
|
||||
baseColor = onScrimColor,
|
||||
bottomPadding = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediumSpacer()
|
||||
}
|
||||
|
||||
@DefaultPreview
|
||||
@Composable
|
||||
private fun BackgroundTitlePreview() {
|
||||
ReadropsTheme {
|
||||
BackgroundTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
onClickBack = {}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.readrops.app.item.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun rememberBottomBarNestedScrollConnection(density: Density = LocalDensity.current) =
|
||||
remember { BottomBarNestedScrollConnection(density) }
|
||||
|
||||
class BottomBarNestedScrollConnection(
|
||||
density: Density,
|
||||
val bottomBarHeight: Dp = 64.dp,
|
||||
) : NestedScrollConnection {
|
||||
|
||||
private val bottomBarHeightPx = with(density) { bottomBarHeight.roundToPx().toFloat() }
|
||||
|
||||
var bottomBarOffset: Int by mutableIntStateOf(0)
|
||||
private set
|
||||
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.y
|
||||
val newOffset = bottomBarOffset.toFloat() + delta
|
||||
bottomBarOffset = newOffset.coerceIn(-bottomBarHeightPx, 0f).roundToInt()
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.readrops.app.item
|
||||
package com.readrops.app.item.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -16,7 +16,9 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.util.DefaultPreview
|
||||
import com.readrops.app.util.FeedColors
|
||||
import com.readrops.app.util.theme.ReadropsTheme
|
||||
import com.readrops.app.util.theme.spacing
|
||||
|
||||
data class BottomBarState(
|
||||
@ -97,3 +99,21 @@ fun ItemScreenBottomBar(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreview
|
||||
@Composable
|
||||
private fun ItemScreenBottomBarPreview() {
|
||||
ReadropsTheme {
|
||||
ItemScreenBottomBar(
|
||||
state = BottomBarState(
|
||||
isRead = false,
|
||||
isStarred = false
|
||||
),
|
||||
accentColor = MaterialTheme.colorScheme.primary,
|
||||
onShare = {},
|
||||
onOpenUrl = {},
|
||||
onChangeReadState = {},
|
||||
onChangeStarState = {},
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package com.readrops.app.item.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.timelime.components.itemWithFeed
|
||||
import com.readrops.app.util.DefaultPreview
|
||||
import com.readrops.app.util.components.FeedIcon
|
||||
import com.readrops.app.util.components.IconText
|
||||
import com.readrops.app.util.theme.ReadropsTheme
|
||||
import com.readrops.app.util.theme.ShortSpacer
|
||||
import com.readrops.app.util.theme.spacing
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
import com.readrops.db.util.DateUtils
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun SimpleTitle(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
titleColor: Color,
|
||||
accentColor: Color,
|
||||
baseColor: Color,
|
||||
bottomPadding: Boolean,
|
||||
) {
|
||||
val item = itemWithFeed.item
|
||||
val spacing = MaterialTheme.spacing.mediumSpacing
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = spacing,
|
||||
end = spacing,
|
||||
top = spacing,
|
||||
bottom = if (bottomPadding) spacing else 0.dp
|
||||
)
|
||||
) {
|
||||
FeedIcon(
|
||||
iconUrl = itemWithFeed.feedIconUrl,
|
||||
name = itemWithFeed.feedName,
|
||||
size = 48.dp,
|
||||
modifier = Modifier.clip(CircleShape)
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Text(
|
||||
text = itemWithFeed.feedName,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = baseColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
Text(
|
||||
text = item.title!!,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = titleColor,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
if (item.author != null) {
|
||||
ShortSpacer()
|
||||
|
||||
IconText(
|
||||
icon = painterResource(id = R.drawable.ic_person),
|
||||
text = itemWithFeed.item.author!!,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = baseColor,
|
||||
tint = accentColor
|
||||
)
|
||||
}
|
||||
|
||||
ShortSpacer()
|
||||
|
||||
val readTime = if (item.readTime > 1) {
|
||||
stringResource(id = R.string.read_time, item.readTime.roundToInt())
|
||||
} else {
|
||||
stringResource(id = R.string.read_time_lower_than_1)
|
||||
}
|
||||
Text(
|
||||
text = "${DateUtils.formattedDate(item.pubDate!!)} ${stringResource(id = R.string.interpoint)} $readTime",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = baseColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreview
|
||||
@Composable
|
||||
private fun SimpleTitlePreview() {
|
||||
ReadropsTheme {
|
||||
SimpleTitle(
|
||||
itemWithFeed = itemWithFeed,
|
||||
titleColor = MaterialTheme.colorScheme.primary,
|
||||
accentColor = MaterialTheme.colorScheme.primary,
|
||||
baseColor = MaterialTheme.colorScheme.onBackground,
|
||||
bottomPadding = true
|
||||
)
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ class ItemNestedScrollView(
|
||||
addView(
|
||||
RelativeLayout(context).apply {
|
||||
ViewCompat.setNestedScrollingEnabled(this, true)
|
||||
descendantFocusability = FOCUS_BLOCK_DESCENDANTS
|
||||
|
||||
val composeView = ComposeView(context).apply {
|
||||
id = 1
|
||||
|
@ -14,6 +14,9 @@ import com.readrops.app.repositories.ErrorResult
|
||||
import com.readrops.app.repositories.GetFoldersWithFeeds
|
||||
import com.readrops.app.sync.SyncWorker
|
||||
import com.readrops.app.timelime.components.TimelineItemSize
|
||||
import com.readrops.app.util.PAGING_INITIAL_SIZE
|
||||
import com.readrops.app.util.PAGING_PAGE_SIZE
|
||||
import com.readrops.app.util.PAGING_PREFETCH_DISTANCE
|
||||
import com.readrops.app.util.Preferences
|
||||
import com.readrops.app.util.extensions.clearSerializables
|
||||
import com.readrops.app.util.extensions.getSerializable
|
||||
@ -167,9 +170,9 @@ class TimelineScreenModel(
|
||||
|
||||
val pager = Pager(
|
||||
config = PagingConfig(
|
||||
initialLoadSize = 50,
|
||||
pageSize = 50,
|
||||
prefetchDistance = 15
|
||||
initialLoadSize = PAGING_INITIAL_SIZE,
|
||||
pageSize = PAGING_PAGE_SIZE,
|
||||
prefetchDistance = PAGING_PREFETCH_DISTANCE
|
||||
),
|
||||
pagingSourceFactory = {
|
||||
database.itemDao().selectAll(query)
|
||||
|
@ -69,6 +69,7 @@ import com.readrops.app.util.extensions.openUrl
|
||||
import com.readrops.app.util.theme.spacing
|
||||
import com.readrops.db.entities.OpenIn
|
||||
import com.readrops.db.filters.MainFilter
|
||||
import com.readrops.db.filters.QueryFilters
|
||||
import com.readrops.db.pojo.ItemWithFeed
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.filter
|
||||
@ -118,7 +119,16 @@ object TimelineTab : Tab {
|
||||
openItemChannel.receiveAsFlow()
|
||||
.collect { itemId ->
|
||||
screenModel.selectItemWithFeed(itemId)
|
||||
?.let { openItem(it, preferences, navigator, context) }
|
||||
?.let {
|
||||
openItem(
|
||||
itemWithFeed = it,
|
||||
itemIndex = items.itemSnapshotList.indexOf(it),
|
||||
queryFilters = state.filters,
|
||||
preferences = preferences,
|
||||
navigator = navigator,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +220,8 @@ object TimelineTab : Tab {
|
||||
onOpenItem = { itemWithFeed, openIn ->
|
||||
openItem(
|
||||
itemWithFeed = itemWithFeed,
|
||||
itemIndex = items.itemSnapshotList.indexOf(itemWithFeed),
|
||||
queryFilters = state.filters,
|
||||
openIn = openIn,
|
||||
preferences = preferences,
|
||||
navigator = navigator,
|
||||
@ -318,6 +330,8 @@ object TimelineTab : Tab {
|
||||
|
||||
openItem(
|
||||
itemWithFeed = itemWithFeed,
|
||||
itemIndex = index,
|
||||
queryFilters = state.filters,
|
||||
preferences = preferences,
|
||||
navigator = navigator,
|
||||
context = context
|
||||
@ -363,6 +377,8 @@ object TimelineTab : Tab {
|
||||
|
||||
private fun openItem(
|
||||
itemWithFeed: ItemWithFeed,
|
||||
itemIndex: Int,
|
||||
queryFilters: QueryFilters,
|
||||
preferences: TimelinePreferences,
|
||||
navigator: Navigator,
|
||||
context: Context,
|
||||
@ -371,7 +387,7 @@ object TimelineTab : Tab {
|
||||
val url = itemWithFeed.item.link!!
|
||||
|
||||
if (openIn == OpenIn.LOCAL_VIEW) {
|
||||
navigator.push(ItemScreen(itemWithFeed.item.id))
|
||||
navigator.push(ItemScreen(itemWithFeed.item.id, itemIndex, queryFilters))
|
||||
} else {
|
||||
if (preferences.openInExternalBrowser) {
|
||||
context.openUrl(url)
|
||||
|
@ -151,7 +151,7 @@ fun TimelineItem(
|
||||
}
|
||||
}
|
||||
|
||||
private val itemWithFeed = ItemWithFeed(
|
||||
val itemWithFeed = ItemWithFeed(
|
||||
item = com.readrops.db.entities.Item(
|
||||
title = "This is a not so long item title",
|
||||
pubDate = LocalDateTime.now(),
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.readrops.app.util
|
||||
|
||||
const val PAGING_INITIAL_SIZE = 50
|
||||
|
||||
const val PAGING_PAGE_SIZE = 50
|
||||
|
||||
const val PAGING_PREFETCH_DISTANCE = 15
|
@ -1,5 +1,7 @@
|
||||
package com.readrops.db.filters
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
enum class MainFilter {
|
||||
STARS,
|
||||
NEW,
|
||||
@ -31,4 +33,4 @@ data class QueryFilters(
|
||||
val subFilter: SubFilter = SubFilter.ALL,
|
||||
val orderField: OrderField = OrderField.DATE,
|
||||
val orderType: OrderType = OrderType.DESC,
|
||||
)
|
||||
) : Serializable
|
@ -23,4 +23,23 @@ data class ItemWithFeed(
|
||||
@ColumnInfo(name = "is_read") val isRead: Boolean = false,
|
||||
@ColumnInfo(name = "open_in") val openIn: OpenIn?,
|
||||
@ColumnInfo(name = "open_in_ask") val openInAsk: Boolean = true
|
||||
)
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ItemWithFeed
|
||||
|
||||
return item.id == other.item.id
|
||||
&& isRead == other.isRead
|
||||
&& isStarred == other.isStarred
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = item.id.hashCode()
|
||||
result = 31 * result + isStarred.hashCode()
|
||||
result = 31 * result + isRead.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ object ItemsQueryBuilder {
|
||||
"Item.remote_id",
|
||||
"title",
|
||||
"clean_description",
|
||||
"content",
|
||||
"image_link",
|
||||
"pub_date",
|
||||
"link",
|
||||
|
Loading…
x
Reference in New Issue
Block a user