From e1a3975a07a69e50d48cdd52d200f3ad235861c7 Mon Sep 17 00:00:00 2001 From: KotlinGeekDev Date: Fri, 20 Oct 2023 03:40:33 +0100 Subject: [PATCH] Move NotificationScreen back and rework it in the manner of TranslatableRichTextViewer. --- .../loggedIn/CustomNotificationScreen.kt | 117 ++++++ .../ui/screen/loggedIn/NotificationScreen.kt | 371 ------------------ .../amethyst/ui/navigation/AppNavigation.kt | 4 +- .../ui/screen/loggedIn/NotificationScreen.kt | 3 - .../loggedIn/CustomNotificationScreen.kt | 18 + 5 files changed, 137 insertions(+), 376 deletions(-) create mode 100644 app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt delete mode 100644 app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt rename app/src/{play => main}/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt (98%) create mode 100644 app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt new file mode 100644 index 000000000..a91cf4984 --- /dev/null +++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt @@ -0,0 +1,117 @@ +package com.vitorpamplona.amethyst.ui.screen.loggedIn + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.TextButton +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.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.window.Dialog +import com.halilibo.richtext.markdown.Markdown +import com.halilibo.richtext.ui.RichTextStyle +import com.halilibo.richtext.ui.material3.Material3RichText +import com.halilibo.richtext.ui.resolveDefaults +import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler +import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel +import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel +import com.vitorpamplona.amethyst.ui.theme.HalfPadding +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun CustomNotificationScreen( + notifFeedViewModel: NotificationViewModel, + userReactionsStatsModel: UserReactionsViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { + val pushHandler = PushDistributorHandler + var distributorPresent by remember { + mutableStateOf(pushHandler.savedDistributorExists()) + } + val list = pushHandler.getInstalledDistributors() + val readableList = pushHandler.formattedDistributorNames() + if (!distributorPresent) { + SelectPushDistributor( + distrbutorList = readableList.toImmutableList(), + onDistributorSelected = { index, name -> + val fullDistributorName = list[index] + pushHandler.saveDistributor(fullDistributorName) + Log.d("Amethyst", "NotificationScreen: Distributor registered.") + }, + onDismiss = { + distributorPresent = true + Log.d("Amethyst", "NotificationScreen: Distributor dialog dismissed.") + } + ) + } else { + val currentDistributor = pushHandler.getSavedDistributor() + pushHandler.saveDistributor(currentDistributor) + } + + NotificationScreen( + notifFeedViewModel = notifFeedViewModel, + userReactionsStatsModel = userReactionsStatsModel, + accountViewModel = accountViewModel, + nav = nav + ) +} + +@Composable +fun SelectPushDistributor( + distrbutorList: ImmutableList, + onDistributorSelected: (Int, String) -> Unit, + onDismiss: () -> Unit +) { + Dialog(onDismissRequest = onDismiss) { + Surface( + modifier = Modifier + .border( + width = Dp.Hairline, + color = MaterialTheme.colorScheme.background, + shape = CircleShape + ) + .fillMaxWidth() + ) { + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + ) { + Box(modifier = Modifier.align(CenterHorizontally)) { + Material3RichText( + style = RichTextStyle().resolveDefaults() + ) { + Markdown(content = "### Select a distributor") + } + } + distrbutorList.forEachIndexed { index, distributor -> + TextButton( + onClick = { + onDistributorSelected(index, distributor) + onDismiss() + }, + modifier = HalfPadding.fillMaxWidth() + ) { + Material3RichText( + style = RichTextStyle().resolveDefaults() + ) { + Markdown(content = distributor) + } + } + } + } + } + } +} diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt deleted file mode 100644 index 6a0b76331..000000000 --- a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ /dev/null @@ -1,371 +0,0 @@ -package com.vitorpamplona.amethyst.ui.screen.loggedIn - -import android.Manifest -import android.os.Build -import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Divider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment.Companion.CenterHorizontally -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.isGranted -import com.google.accompanist.permissions.rememberPermissionState -import com.halilibo.richtext.markdown.Markdown -import com.halilibo.richtext.ui.RichTextStyle -import com.halilibo.richtext.ui.material3.Material3RichText -import com.halilibo.richtext.ui.resolveDefaults -import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis -import com.patrykandpatrick.vico.compose.axis.vertical.endAxis -import com.patrykandpatrick.vico.compose.axis.vertical.startAxis -import com.patrykandpatrick.vico.compose.chart.Chart -import com.patrykandpatrick.vico.compose.chart.line.lineChart -import com.patrykandpatrick.vico.compose.component.shape.shader.fromBrush -import com.patrykandpatrick.vico.compose.style.ProvideChartStyle -import com.patrykandpatrick.vico.core.DefaultAlpha -import com.patrykandpatrick.vico.core.axis.AxisPosition -import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter -import com.patrykandpatrick.vico.core.chart.composed.plus -import com.patrykandpatrick.vico.core.chart.line.LineChart -import com.patrykandpatrick.vico.core.chart.values.ChartValues -import com.patrykandpatrick.vico.core.component.shape.shader.DynamicShaders -import com.vitorpamplona.amethyst.service.NostrAccountDataSource -import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler -import com.vitorpamplona.amethyst.ui.navigation.Route -import com.vitorpamplona.amethyst.ui.note.OneGiga -import com.vitorpamplona.amethyst.ui.note.OneKilo -import com.vitorpamplona.amethyst.ui.note.OneMega -import com.vitorpamplona.amethyst.ui.note.UserReactionsRow -import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel -import com.vitorpamplona.amethyst.ui.note.showCount -import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableCardView -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys -import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange -import com.vitorpamplona.amethyst.ui.theme.HalfPadding -import com.vitorpamplona.amethyst.ui.theme.RoyalBlue -import com.vitorpamplona.amethyst.ui.theme.chartStyle -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal -import java.math.RoundingMode -import java.text.DecimalFormat -import kotlin.math.roundToInt - -@Composable -fun NotificationScreen( - notifFeedViewModel: NotificationViewModel, - userReactionsStatsModel: UserReactionsViewModel, - accountViewModel: AccountViewModel, - nav: (String) -> Unit -) { - WatchAccountForNotifications(notifFeedViewModel, accountViewModel) - - CheckifItNeedsToRequestNotificationPermission() - - val lifeCycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifeCycleOwner) { - val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_RESUME) { - NostrAccountDataSource.invalidateFilters() - } - } - - lifeCycleOwner.lifecycle.addObserver(observer) - onDispose { - lifeCycleOwner.lifecycle.removeObserver(observer) - } - } - val pushHandler = PushDistributorHandler - var distributorPresent by remember { - mutableStateOf(pushHandler.savedDistributorExists()) - } - val list = pushHandler.getInstalledDistributors() - val readableList = pushHandler.formattedDistributorNames() - if (!distributorPresent) { - SelectPushDistributor( - distrbutorList = readableList.toImmutableList(), - onDistributorSelected = { index, name -> - val fullDistributorName = list[index] - pushHandler.saveDistributor(fullDistributorName) - Log.d("Amethyst", "NotificationScreen: Distributor registered.") - }, - onDismiss = { - distributorPresent = true - Log.d("Amethyst", "NotificationScreen: Distributor dialog dismissed.") - } - ) - } else { - val currentDistributor = pushHandler.getSavedDistributor() - pushHandler.saveDistributor(currentDistributor) - } - - Column(Modifier.fillMaxHeight()) { - Column( - modifier = Modifier.padding(vertical = 0.dp) - ) { - SummaryBar( - model = userReactionsStatsModel - ) - - RefresheableCardView( - viewModel = notifFeedViewModel, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = Route.Notification.base, - scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN - ) - } - } -} - -// TODO: Turn this into an Account flag -var hasAlreadyAskedNotificationPermissions = false - -@OptIn(ExperimentalPermissionsApi::class) -@Composable -fun CheckifItNeedsToRequestNotificationPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasAlreadyAskedNotificationPermissions) { - val notificationPermissionState = rememberPermissionState( - Manifest.permission.POST_NOTIFICATIONS - ) - - if (!notificationPermissionState.status.isGranted) { - hasAlreadyAskedNotificationPermissions = true - - // This will pause the APP, including the connection with relays. - LaunchedEffect(notificationPermissionState) { - notificationPermissionState.launchPermissionRequest() - } - } - } -} - -@Composable -fun WatchAccountForNotifications( - notifFeedViewModel: NotificationViewModel, - accountViewModel: AccountViewModel -) { - val accountState by accountViewModel.accountLiveData.observeAsState() - - LaunchedEffect(accountViewModel, accountState?.account?.defaultNotificationFollowList) { - NostrAccountDataSource.invalidateFilters() - notifFeedViewModel.checkKeysInvalidateDataAndSendToTop() - } -} - -@Composable -fun SelectPushDistributor( - distrbutorList: ImmutableList, - onDistributorSelected: (Int, String) -> Unit, - onDismiss: () -> Unit -) { - Dialog(onDismissRequest = onDismiss) { - Surface( - modifier = Modifier - .border( - width = Dp.Hairline, - color = MaterialTheme.colorScheme.background, - shape = CircleShape - ) - .fillMaxWidth() - ) { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - ) { - Box(modifier = Modifier.align(CenterHorizontally)) { - Material3RichText( - style = RichTextStyle().resolveDefaults() - ) { - Markdown(content = "### Select a distributor") - } - } - distrbutorList.forEachIndexed { index, distributor -> - TextButton( - onClick = { - onDistributorSelected(index, distributor) - onDismiss() - }, - modifier = HalfPadding.fillMaxWidth() - ) { - Material3RichText( - style = RichTextStyle().resolveDefaults() - ) { - Markdown(content = distributor) - } - } - } - } - } - } -} - -@Composable -fun SummaryBar(model: UserReactionsViewModel) { - var showChart by remember { - mutableStateOf(false) - } - - UserReactionsRow(model) { - showChart = !showChart - } - - if (showChart) { - val lineChartCount = - lineChart( - lines = listOf(RoyalBlue, Color.Green, Color.Red).map { lineChartColor -> - LineChart.LineSpec( - lineColor = lineChartColor.toArgb(), - lineBackgroundShader = DynamicShaders.fromBrush( - Brush.verticalGradient( - listOf( - lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_START), - lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_END) - ) - ) - ) - ) - }, - targetVerticalAxisPosition = AxisPosition.Vertical.Start - ) - - val lineChartZaps = - lineChart( - lines = listOf(BitcoinOrange).map { lineChartColor -> - LineChart.LineSpec( - lineColor = lineChartColor.toArgb(), - lineBackgroundShader = DynamicShaders.fromBrush( - Brush.verticalGradient( - listOf( - lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_START), - lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_END) - ) - ) - ) - ) - }, - targetVerticalAxisPosition = AxisPosition.Vertical.End - ) - - Row( - modifier = Modifier - .padding(vertical = 10.dp, horizontal = 20.dp) - .clickable(onClick = { showChart = !showChart }) - ) { - ProvideChartStyle( - chartStyle = MaterialTheme.colorScheme.chartStyle - ) { - ObserveAndShowChart(model, lineChartCount, lineChartZaps) - } - } - } - - Divider( - thickness = 0.25.dp - ) -} - -@Composable -private fun ObserveAndShowChart( - model: UserReactionsViewModel, - lineChartCount: LineChart, - lineChartZaps: LineChart -) { - val axisModel by model.axisLabels.collectAsState() - val chartModel by model.chartModel.collectAsState() - chartModel?.let { - Chart( - chart = remember(lineChartCount, lineChartZaps) { - lineChartCount.plus(lineChartZaps) - }, - model = it, - startAxis = startAxis( - valueFormatter = CountAxisValueFormatter() - ), - endAxis = endAxis( - valueFormatter = AmountAxisValueFormatter() - ), - bottomAxis = bottomAxis( - valueFormatter = LabelValueFormatter(axisModel) - ) - ) - } -} - -@Stable -class LabelValueFormatter(val axisLabels: List) : AxisValueFormatter { - override fun formatValue( - value: Float, - chartValues: ChartValues - ): String { - return axisLabels[value.roundToInt()] - } -} - -@Stable -class CountAxisValueFormatter() : AxisValueFormatter { - override fun formatValue( - value: Float, - chartValues: ChartValues - ): String { - return showCount(value.roundToInt()) - } -} - -@Stable -class AmountAxisValueFormatter() : AxisValueFormatter { - override fun formatValue( - value: Float, - chartValues: ChartValues - ): String { - return showAmountAxis(value.toBigDecimal()) - } -} - -var dfG: DecimalFormat = DecimalFormat("#G") -var dfM: DecimalFormat = DecimalFormat("#M") -var dfK: DecimalFormat = DecimalFormat("#k") -var dfN: DecimalFormat = DecimalFormat("#") - -fun showAmountAxis(amount: BigDecimal?): String { - if (amount == null) return "" - if (amount.abs() < BigDecimal(0.01)) return "" - - return when { - amount >= OneGiga -> dfG.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) - amount >= OneMega -> dfM.format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) - amount >= OneKilo -> dfK.format(amount.div(OneKilo).setScale(0, RoundingMode.HALF_UP)) - else -> dfN.format(amount) - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 3dd0c08ff..57cba335e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -38,13 +38,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreenByAuthor import com.vitorpamplona.amethyst.ui.screen.loggedIn.CommunityScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.CustomNotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.DiscoverScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsScreen @@ -157,7 +157,7 @@ fun AppNavigation( Route.Notification.let { route -> composable(route.route, route.arguments, content = { - NotificationScreen( + CustomNotificationScreen( notifFeedViewModel = notifFeedViewModel, userReactionsStatsModel = userReactionsStatsModel, accountViewModel = accountViewModel, diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt similarity index 98% rename from app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt rename to app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 6658af233..7f66ef265 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -32,12 +32,9 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.patrykandpatrick.vico.compose.axis.axisLabelComponent -import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis import com.patrykandpatrick.vico.compose.axis.horizontal.rememberBottomAxis -import com.patrykandpatrick.vico.compose.axis.vertical.endAxis import com.patrykandpatrick.vico.compose.axis.vertical.rememberEndAxis import com.patrykandpatrick.vico.compose.axis.vertical.rememberStartAxis -import com.patrykandpatrick.vico.compose.axis.vertical.startAxis import com.patrykandpatrick.vico.compose.chart.Chart import com.patrykandpatrick.vico.compose.chart.line.lineChart import com.patrykandpatrick.vico.compose.component.shape.shader.fromBrush diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt new file mode 100644 index 000000000..fd56ec9ae --- /dev/null +++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CustomNotificationScreen.kt @@ -0,0 +1,18 @@ +package com.vitorpamplona.amethyst.ui.screen.loggedIn + +import androidx.compose.runtime.Composable +import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel +import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel + +@Composable +fun CustomNotificationScreen( + notifFeedViewModel: NotificationViewModel, + userReactionsStatsModel: UserReactionsViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) = NotificationScreen( + notifFeedViewModel = notifFeedViewModel, + userReactionsStatsModel = userReactionsStatsModel, + accountViewModel = accountViewModel, + nav = nav +)