diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedBottomSheet.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedBottomSheet.kt new file mode 100644 index 00000000..b855e63d --- /dev/null +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedBottomSheet.kt @@ -0,0 +1,141 @@ +package com.readrops.app.compose.feeds + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.readrops.app.compose.R +import com.readrops.app.compose.util.theme.LargeSpacer +import com.readrops.app.compose.util.theme.MediumSpacer +import com.readrops.app.compose.util.theme.VeryShortSpacer +import com.readrops.app.compose.util.theme.spacing +import com.readrops.db.entities.Feed + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FeedModalBottomSheet( + feed: Feed, + onDismissRequest: () -> Unit, + onOpen: () -> Unit, + onModify: () -> Unit, + onDelete: () -> Unit, +) { + ModalBottomSheet( + onDismissRequest = { onDismissRequest() } + ) { + Column { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding( + horizontal = 24.dp + ) + ) { + AsyncImage( + model = feed.iconUrl, + contentDescription = feed.name!!, + modifier = Modifier.size(48.dp) + ) + + MediumSpacer() + + Column { + Text( + text = feed.name!!, + style = MaterialTheme.typography.headlineSmall, + ) + + VeryShortSpacer() + + Text( + text = "folder name if it exists", + style = MaterialTheme.typography.labelSmall + ) + } + } + + MediumSpacer() + + Divider( + modifier = Modifier.padding( + horizontal = MaterialTheme.spacing.shortSpacing + ) + ) + + MediumSpacer() + + BottomSheetOption( + text = "Open", + icon = ImageVector.vectorResource(id = R.drawable.ic_open_in_browser), + onClick = onOpen + ) + + BottomSheetOption( + text = "Modify", + icon = Icons.Default.Create, + onClick = onModify + ) + + BottomSheetOption( + text = "Delete", + icon = Icons.Default.Delete, + onClick = onDelete + ) + } + + LargeSpacer() + } +} + +@Composable +fun BottomSheetOption( + text: String, + icon: ImageVector, + onClick: () -> Unit, +) { + Box( + modifier = Modifier.clickable { onClick() } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 16.dp, + vertical = 8.dp + ) + + ) { + Icon( + imageVector = icon, + contentDescription = text + ) + + MediumSpacer() + + Text( + text = text, + style = MaterialTheme.typography.bodyMedium + ) + } + } +} \ No newline at end of file diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedItem.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedItem.kt index 0ea04f2b..af27626b 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedItem.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedItem.kt @@ -1,34 +1,54 @@ package com.readrops.app.compose.feeds -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size 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.platform.LocalContext import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.readrops.app.compose.util.theme.ShortSpacer +import com.readrops.app.compose.util.toDp import com.readrops.db.entities.Feed +@OptIn(ExperimentalFoundationApi::class) @Composable -fun FeedItem(feed: Feed) { - val context = LocalContext.current - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) { - /*CoilImage(imageRequest = { - ImageRequest.Builder(context) - .data(feed.url) - .build() - })*/ - - Text( - text = feed.name!!, - style = MaterialTheme.typography.headlineSmall +fun FeedItem( + feed: Feed, + onClick: () -> Unit, + onLongClick: () -> Unit, +) { + Box( + modifier = Modifier.combinedClickable( + onClick = onClick, + onLongClick = onLongClick ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + AsyncImage( + model = feed.iconUrl, + contentDescription = feed.name!!, + modifier = Modifier.size(MaterialTheme.typography.bodyLarge.toDp()) + ) + + ShortSpacer() + + Text( + text = feed.name!!, + style = MaterialTheme.typography.bodyLarge + ) + } } } diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt index eed5555c..c9fc4029 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt @@ -7,9 +7,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -20,10 +22,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import cafe.adriel.voyager.navigator.tab.Tab import cafe.adriel.voyager.navigator.tab.TabOptions +import com.readrops.db.entities.Feed import org.koin.androidx.compose.getViewModel object FeedTab : Tab { @@ -31,37 +37,66 @@ object FeedTab : Tab { override val options: TabOptions @Composable get() = TabOptions( - index = 2u, - title = "Feeds" + index = 2u, + title = "Feeds" ) @OptIn(ExperimentalMaterial3Api::class) @Composable override fun Content() { + val haptic = LocalHapticFeedback.current + val uriHandler = LocalUriHandler.current val viewModel = getViewModel() - var showDialog by remember { mutableStateOf(false) } val state by viewModel.feedsState.collectAsStateWithLifecycle() + var showDialog by remember { mutableStateOf(false) } + + var selectedFeed by remember { mutableStateOf(null) } + var showBottomSheet by remember { mutableStateOf(false) } + + if (showBottomSheet) { + FeedModalBottomSheet( + feed = selectedFeed!!, + onDismissRequest = { showBottomSheet = false }, + onOpen = { uriHandler.openUri(selectedFeed!!.siteUrl!!) }, + onModify = { }, + onDelete = {}, + ) + } if (showDialog) { AddFeedDialog( - onDismiss = { showDialog = false }, - onValidate = { - showDialog = false - viewModel.insertFeed(it) - } + onDismiss = { showDialog = false }, + onValidate = { + showDialog = false + viewModel.insertFeed(it) + } ) } Scaffold( - topBar = { - TopAppBar(title = { Text(text = "Feeds") }) - } + topBar = { + TopAppBar( + title = { + Text(text = "Feeds") + }, + actions = { + IconButton( + onClick = {} + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null + ) + } + } + ) + }, ) { paddingValues -> Box( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) ) { when (state) { is FeedsState.LoadedState -> { @@ -70,10 +105,18 @@ object FeedTab : Tab { if (feeds.isNotEmpty()) { LazyColumn { items( - items = feeds + items = feeds ) { feed -> FeedItem( - feed = feed, + feed = feed, + onClick = { + selectedFeed = feed + showBottomSheet = true + }, + onLongClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + uriHandler.openUri(feed.siteUrl!!) + } ) } } @@ -90,14 +133,14 @@ object FeedTab : Tab { } FloatingActionButton( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(16.dp), - onClick = { showDialog = true } + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(16.dp), + onClick = { showDialog = true } ) { Icon( - imageVector = Icons.Default.Add, - contentDescription = null + imageVector = Icons.Default.Add, + contentDescription = null ) } } diff --git a/appcompose/src/main/res/drawable/ic_open_in_browser.xml b/appcompose/src/main/res/drawable/ic_open_in_browser.xml new file mode 100644 index 00000000..f416ca58 --- /dev/null +++ b/appcompose/src/main/res/drawable/ic_open_in_browser.xml @@ -0,0 +1,9 @@ + + +