Move NotificationScreen to different flavors to integrate Distributor dialog selection. Make some adjustments for handling changes to push distributors, and other fixes.
This commit is contained in:
+37
-4
@@ -1,5 +1,9 @@
|
|||||||
package com.vitorpamplona.amethyst.service.notifications
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import org.unifiedpush.android.connector.UnifiedPush
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
|
|
||||||
@@ -9,9 +13,9 @@ interface PushDistributorActions {
|
|||||||
fun saveDistributor(distributor: String)
|
fun saveDistributor(distributor: String)
|
||||||
fun removeSavedDistributor()
|
fun removeSavedDistributor()
|
||||||
}
|
}
|
||||||
class PushDistributorHandler() : PushDistributorActions {
|
object PushDistributorHandler : PushDistributorActions {
|
||||||
private val appContext = Amethyst.instance.applicationContext
|
private val appContext = Amethyst.instance.applicationContext
|
||||||
private val unifiedPush: UnifiedPush = UnifiedPush()
|
private val unifiedPush: UnifiedPush = UnifiedPush
|
||||||
|
|
||||||
private var endpointInternal = ""
|
private var endpointInternal = ""
|
||||||
val endpoint = endpointInternal
|
val endpoint = endpointInternal
|
||||||
@@ -19,6 +23,11 @@ class PushDistributorHandler() : PushDistributorActions {
|
|||||||
fun getSavedEndpoint() = endpoint
|
fun getSavedEndpoint() = endpoint
|
||||||
fun setEndpoint(newEndpoint: String) {
|
fun setEndpoint(newEndpoint: String) {
|
||||||
endpointInternal = newEndpoint
|
endpointInternal = newEndpoint
|
||||||
|
Log.d("PushHandler", "New endpoint saved : $endpointInternal")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeEndpoint() {
|
||||||
|
endpointInternal = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSavedDistributor(): String {
|
override fun getSavedDistributor(): String {
|
||||||
@@ -31,13 +40,37 @@ class PushDistributorHandler() : PushDistributorActions {
|
|||||||
return unifiedPush.getDistributors(appContext)
|
return unifiedPush.getDistributors(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formattedDistributorNames(): List<String> {
|
||||||
|
val distributorsArray = getInstalledDistributors().toTypedArray()
|
||||||
|
val distributorsNameArray = distributorsArray.map {
|
||||||
|
try {
|
||||||
|
val ai = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
appContext.packageManager.getApplicationInfo(
|
||||||
|
it,
|
||||||
|
PackageManager.ApplicationInfoFlags.of(
|
||||||
|
PackageManager.GET_META_DATA.toLong()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
appContext.packageManager.getApplicationInfo(it, 0)
|
||||||
|
}
|
||||||
|
appContext.packageManager.getApplicationLabel(ai)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
it
|
||||||
|
} as String
|
||||||
|
}.toTypedArray()
|
||||||
|
return distributorsNameArray.toList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun saveDistributor(distributor: String) {
|
override fun saveDistributor(distributor: String) {
|
||||||
unifiedPush.saveDistributor(appContext, distributor)
|
unifiedPush.saveDistributor(appContext, distributor)
|
||||||
|
unifiedPush.registerApp(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeSavedDistributor() {
|
override fun removeSavedDistributor() {
|
||||||
unifiedPush.safeRemoveDistributor(appContext)
|
unifiedPush.safeRemoveDistributor(appContext)
|
||||||
}
|
}
|
||||||
|
fun forceRemoveDistributor(context: Context) {
|
||||||
|
unifiedPush.forceRemoveDistributor(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UnifiedPush() = UnifiedPush
|
|
||||||
|
|||||||
+16
-6
@@ -20,18 +20,23 @@ import kotlinx.coroutines.launch
|
|||||||
import org.unifiedpush.android.connector.MessagingReceiver
|
import org.unifiedpush.android.connector.MessagingReceiver
|
||||||
|
|
||||||
class PushMessageReceiver : MessagingReceiver() {
|
class PushMessageReceiver : MessagingReceiver() {
|
||||||
|
private val TAG = "Amethyst-OSSPushReceiver"
|
||||||
private val appContext = Amethyst.instance.applicationContext
|
private val appContext = Amethyst.instance.applicationContext
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
private val eventCache = LruCache<String, String>(100)
|
private val eventCache = LruCache<String, String>(100)
|
||||||
private val pushHandler = PushDistributorHandler()
|
private val pushHandler = PushDistributorHandler
|
||||||
|
|
||||||
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||||
val messageStr = String(message)
|
val messageStr = String(message)
|
||||||
Log.d("Amethyst-OSSPush", "New message ${message.decodeToString()} for Instance: $instance")
|
Log.d(TAG, "New message ${message.decodeToString()} for Instance: $instance")
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
try {
|
||||||
parseMessage(messageStr)?.let {
|
parseMessage(messageStr)?.let {
|
||||||
receiveIfNew(it)
|
receiveIfNew(it)
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(TAG, "Message could not be parsed: ${e.message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +55,7 @@ class PushMessageReceiver : MessagingReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||||
Log.d("Amethyst-OSSPush", "New endpoint provided:- $endpoint for Instance: $instance")
|
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance")
|
||||||
pushHandler.setEndpoint(endpoint)
|
pushHandler.setEndpoint(endpoint)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(endpoint)
|
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(endpoint)
|
||||||
@@ -62,17 +67,22 @@ class PushMessageReceiver : MessagingReceiver() {
|
|||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val intentData = intent.dataString
|
val intentData = intent.dataString
|
||||||
val intentAction = intent.action.toString()
|
val intentAction = intent.action.toString()
|
||||||
Log.d("Amethyst-OSSPush", "Intent Data:- $intentData Intent Action: $intentAction")
|
Log.d(TAG, "Intent Data:- $intentData Intent Action: $intentAction")
|
||||||
super.onReceive(context, intent)
|
super.onReceive(context, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRegistrationFailed(context: Context, instance: String) {
|
override fun onRegistrationFailed(context: Context, instance: String) {
|
||||||
|
Log.d(TAG, "Registration failed for Instance: $instance")
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
super.onRegistrationFailed(context, instance)
|
pushHandler.forceRemoveDistributor(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnregistered(context: Context, instance: String) {
|
override fun onUnregistered(context: Context, instance: String) {
|
||||||
super.onUnregistered(context, instance)
|
val removedEndpoint = pushHandler.endpoint
|
||||||
|
Log.d(TAG, "Endpoint: $removedEndpoint removed for Instance: $instance")
|
||||||
|
Log.d(TAG, "App is unregistered. ")
|
||||||
|
pushHandler.forceRemoveDistributor(context)
|
||||||
|
pushHandler.removeEndpoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notificationManager(): NotificationManager {
|
fun notificationManager(): NotificationManager {
|
||||||
|
|||||||
+5
-4
@@ -2,17 +2,18 @@ package com.vitorpamplona.amethyst.service.notifications
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.AccountInfo
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
|
|
||||||
object PushNotificationUtils {
|
object PushNotificationUtils {
|
||||||
var hasInit: Boolean = false
|
var hasInit: Boolean = false
|
||||||
private val pushHandler = PushDistributorHandler()
|
private val pushHandler = PushDistributorHandler
|
||||||
suspend fun init(accounts: List<AccountInfo>) = with(Dispatchers.IO) {
|
suspend fun init(accounts: List<AccountInfo>) {
|
||||||
if (hasInit || pushHandler.savedDistributorExists()) {
|
if (hasInit || pushHandler.savedDistributorExists()) {
|
||||||
return@with
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (pushHandler.savedDistributorExists()) {
|
||||||
RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
|
RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.")
|
Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.")
|
||||||
}
|
}
|
||||||
|
|||||||
+370
@@ -0,0 +1,370 @@
|
|||||||
|
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.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<String>,
|
||||||
|
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 = Modifier.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<String>) : AxisValueFormatter<AxisPosition.Horizontal.Bottom> {
|
||||||
|
override fun formatValue(
|
||||||
|
value: Float,
|
||||||
|
chartValues: ChartValues
|
||||||
|
): String {
|
||||||
|
return axisLabels[value.roundToInt()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class CountAxisValueFormatter() : AxisValueFormatter<AxisPosition.Vertical.Start> {
|
||||||
|
override fun formatValue(
|
||||||
|
value: Float,
|
||||||
|
chartValues: ChartValues
|
||||||
|
): String {
|
||||||
|
return showCount(value.roundToInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class AmountAxisValueFormatter() : AxisValueFormatter<AxisPosition.Vertical.End> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -62,7 +62,12 @@ class RegisterAccounts(
|
|||||||
it.isSuccessful
|
it.isSuccessful
|
||||||
}
|
}
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
Log.e("FirebaseMsgService", "Unable to register with push server", e)
|
val tag = if (BuildConfig.FLAVOR == "play") {
|
||||||
|
"FirebaseMsgService"
|
||||||
|
} else {
|
||||||
|
"UnifiedPushService"
|
||||||
|
}
|
||||||
|
Log.e(tag, "Unable to register with push server", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user