diff --git a/app/build.gradle b/app/build.gradle
index 8cf0ca9a8..b330bcb9d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -185,6 +185,9 @@ dependencies {
playImplementation platform('com.google.firebase:firebase-bom:32.3.1')
playImplementation 'com.google.firebase:firebase-messaging-ktx'
+ //PushNotifications(FDroid)
+ fdroidImplementation 'com.github.UnifiedPush:android-connector:2.2.0'
+
// Charts
implementation "com.patrykandpatrick.vico:core:${vico_version}"
implementation "com.patrykandpatrick.vico:compose:${vico_version}"
diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml
new file mode 100644
index 000000000..c4ef213f9
--- /dev/null
+++ b/app/src/fdroid/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushDistributorHandler.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushDistributorHandler.kt
new file mode 100644
index 000000000..0219c6c55
--- /dev/null
+++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushDistributorHandler.kt
@@ -0,0 +1,76 @@
+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 org.unifiedpush.android.connector.UnifiedPush
+
+interface PushDistributorActions {
+ fun getSavedDistributor(): String
+ fun getInstalledDistributors(): List
+ fun saveDistributor(distributor: String)
+ fun removeSavedDistributor()
+}
+object PushDistributorHandler : PushDistributorActions {
+ private val appContext = Amethyst.instance.applicationContext
+ private val unifiedPush: UnifiedPush = UnifiedPush
+
+ private var endpointInternal = ""
+ val endpoint = endpointInternal
+
+ fun getSavedEndpoint() = endpoint
+ fun setEndpoint(newEndpoint: String) {
+ endpointInternal = newEndpoint
+ Log.d("PushHandler", "New endpoint saved : $endpointInternal")
+ }
+
+ fun removeEndpoint() {
+ endpointInternal = ""
+ }
+
+ override fun getSavedDistributor(): String {
+ return unifiedPush.getDistributor(appContext)
+ }
+
+ fun savedDistributorExists(): Boolean = getSavedDistributor().isNotEmpty()
+
+ override fun getInstalledDistributors(): List {
+ return unifiedPush.getDistributors(appContext)
+ }
+
+ fun formattedDistributorNames(): List {
+ 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) {
+ unifiedPush.saveDistributor(appContext, distributor)
+ unifiedPush.registerApp(appContext)
+ }
+
+ override fun removeSavedDistributor() {
+ unifiedPush.safeRemoveDistributor(appContext)
+ }
+ fun forceRemoveDistributor(context: Context) {
+ unifiedPush.forceRemoveDistributor(context)
+ }
+}
diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt
new file mode 100644
index 000000000..0263c6fe4
--- /dev/null
+++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt
@@ -0,0 +1,92 @@
+package com.vitorpamplona.amethyst.service.notifications
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import android.util.LruCache
+import androidx.core.content.ContextCompat
+import com.vitorpamplona.amethyst.Amethyst
+import com.vitorpamplona.amethyst.LocalPreferences
+import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
+import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
+import com.vitorpamplona.quartz.events.Event
+import com.vitorpamplona.quartz.events.GiftWrapEvent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import org.unifiedpush.android.connector.MessagingReceiver
+
+class PushMessageReceiver : MessagingReceiver() {
+ private val TAG = "Amethyst-OSSPushReceiver"
+ private val appContext = Amethyst.instance.applicationContext
+ private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ private val eventCache = LruCache(100)
+ private val pushHandler = PushDistributorHandler
+
+ override fun onMessage(context: Context, message: ByteArray, instance: String) {
+ val messageStr = String(message)
+ Log.d(TAG, "New message ${message.decodeToString()} for Instance: $instance")
+ scope.launch {
+ try {
+ parseMessage(messageStr)?.let {
+ receiveIfNew(it)
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Message could not be parsed: ${e.message}")
+ }
+ }
+ }
+
+ private suspend fun parseMessage(message: String): GiftWrapEvent? {
+ (Event.fromJson(message) as? GiftWrapEvent)?.let {
+ return it
+ }
+ return null
+ }
+
+ private suspend fun receiveIfNew(event: GiftWrapEvent) {
+ if (eventCache.get(event.id) == null) {
+ eventCache.put(event.id, event.id)
+ EventNotificationConsumer(appContext).consume(event)
+ }
+ }
+
+ override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
+ Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance")
+ val sanitizedEndpoint = endpoint.dropLast(5)
+ pushHandler.setEndpoint(sanitizedEndpoint)
+ scope.launch(Dispatchers.IO) {
+ RegisterAccounts(LocalPreferences.allSavedAccounts()).go(sanitizedEndpoint)
+ notificationManager().getOrCreateZapChannel(appContext)
+ notificationManager().getOrCreateDMChannel(appContext)
+ }
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val intentData = intent.dataString
+ val intentAction = intent.action.toString()
+ Log.d(TAG, "Intent Data:- $intentData Intent Action: $intentAction")
+ super.onReceive(context, intent)
+ }
+
+ override fun onRegistrationFailed(context: Context, instance: String) {
+ Log.d(TAG, "Registration failed for Instance: $instance")
+ scope.cancel()
+ pushHandler.forceRemoveDistributor(context)
+ }
+
+ override fun onUnregistered(context: Context, instance: String) {
+ 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 {
+ return ContextCompat.getSystemService(appContext, NotificationManager::class.java) as NotificationManager
+ }
+}
diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
index 2855d5e5a..6e153d28f 100644
--- a/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
+++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
@@ -1,9 +1,21 @@
package com.vitorpamplona.amethyst.service.notifications
+import android.util.Log
import com.vitorpamplona.amethyst.AccountInfo
object PushNotificationUtils {
- var hasInit: Boolean = true
+ var hasInit: Boolean = false
+ private val pushHandler = PushDistributorHandler
suspend fun init(accounts: List) {
+ if (hasInit || pushHandler.savedDistributorExists()) {
+ return
+ }
+ try {
+ if (pushHandler.savedDistributorExists()) {
+ RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
+ }
+ } catch (e: Exception) {
+ Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.")
+ }
}
}
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/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt
index 9d023231b..d0394692c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/RegisterAccounts.kt
@@ -62,7 +62,12 @@ class RegisterAccounts(
it.isSuccessful
}
} 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)
}
}
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/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt
index ccdac5450..5e56c5f0f 100644
--- a/app/src/main/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
+)
diff --git a/settings.gradle b/settings.gradle
index 017aa10c6..d535b492f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,7 +3,12 @@ pluginManagement {
gradlePluginPortal()
google()
mavenCentral()
- maven { url "https://jitpack.io" }
+ maven {
+ url "https://jitpack.io"
+ content {
+ includeModule 'com.github.UnifiedPush', 'android-connector'
+ }
+ }
}
}
dependencyResolutionManagement {