improving copy, better prism registration, allow clearing prism registration

This commit is contained in:
Egor 2026-02-13 23:09:28 -08:00
parent 05607cb9f1
commit e767e89ee9
18 changed files with 194 additions and 68 deletions

26
.github/copilot-instructions.md vendored Normal file
View file

@ -0,0 +1,26 @@
# GitHub Copilot Instructions
## Code Style
- Do NOT add obvious comments that just describe what the code does
- Only add comments for complex logic, non-obvious behavior, or important context
- Prefer self-documenting code with clear variable and function names over comments
- Avoid redundant comments like "// Create button" or "// Set text color"
## Examples of BAD comments to avoid:
```kotlin
// Description with clickable link
val description = stringResource(R.string.prism_server_description)
// Get the URI handler
val uriHandler = LocalUriHandler.current
```
## Examples of GOOD comments:
```kotlin
// Retry with exponential backoff, max 5 attempts
for (attempt in 1..5) { ... }
// WorkAround: Android 12+ requires explicit mutation flag
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE)
```

View file

@ -6,4 +6,4 @@
</div> </div>
A notification provider to use with [Prism](https://github.com/lone-cloud/prism) A notification provider for [Prism](https://github.com/lone-cloud/prism) and [UnifiedPush](https://unifiedpush.org/) applications.

View file

@ -5,7 +5,7 @@ import androidx.core.content.edit
import org.unifiedpush.android.distributor.MigrationManager import org.unifiedpush.android.distributor.MigrationManager
import org.unifiedpush.android.distributor.Store import org.unifiedpush.android.distributor.Store
class AppStore(context: Context) : class PrismPreferences(context: Context) :
Store(context, PREF_NAME), Store(context, PREF_NAME),
MigrationManager.MigrationStore { MigrationManager.MigrationStore {
var uaid: String? var uaid: String?

View file

@ -3,7 +3,7 @@ package app.lonecloud.prism
import android.content.Context import android.content.Context
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.DatabaseFactory import app.lonecloud.prism.DatabaseFactory
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -23,11 +23,7 @@ object PrismServerClient {
.readTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS)
.build() .build()
private fun getAuthHeader(apiKey: String): String { private fun getAuthHeader(apiKey: String): String = "Bearer $apiKey"
val credentials = ":$apiKey"
val encoded = Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)
return "Basic $encoded"
}
fun registerApp( fun registerApp(
context: Context, context: Context,
@ -39,7 +35,7 @@ object PrismServerClient {
onSuccess: () -> Unit = {}, onSuccess: () -> Unit = {},
onError: (String) -> Unit = {} onError: (String) -> Unit = {}
) { ) {
val store = AppStore(context) val store = PrismPreferences(context)
val serverUrl = store.prismServerUrl val serverUrl = store.prismServerUrl
val apiKey = store.prismApiKey val apiKey = store.prismApiKey
@ -86,7 +82,7 @@ object PrismServerClient {
} }
fun registerAllApps(context: Context) { fun registerAllApps(context: Context) {
val store = AppStore(context) val store = PrismPreferences(context)
val serverUrl = store.prismServerUrl val serverUrl = store.prismServerUrl
val apiKey = store.prismApiKey val apiKey = store.prismApiKey
@ -128,7 +124,7 @@ object PrismServerClient {
onSuccess: () -> Unit = {}, onSuccess: () -> Unit = {},
onError: (String) -> Unit = {} onError: (String) -> Unit = {}
) { ) {
val store = AppStore(context) val store = PrismPreferences(context)
val url = serverUrl ?: store.prismServerUrl val url = serverUrl ?: store.prismServerUrl
val key = apiKey ?: store.prismApiKey val key = apiKey ?: store.prismApiKey
@ -170,7 +166,7 @@ object PrismServerClient {
serverUrl: String? = null, serverUrl: String? = null,
apiKey: String? = null apiKey: String? = null
) { ) {
val store = AppStore(context) val store = PrismPreferences(context)
val url = serverUrl ?: store.prismServerUrl val url = serverUrl ?: store.prismServerUrl
val key = apiKey ?: store.prismApiKey val key = apiKey ?: store.prismApiKey
@ -206,8 +202,9 @@ object PrismServerClient {
) { ) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val healthUrl = "$serverUrl/api/health"
val request = Request.Builder() val request = Request.Builder()
.url(serverUrl) .url(healthUrl)
.addHeader("Authorization", getAuthHeader(apiKey)) .addHeader("Authorization", getAuthHeader(apiKey))
.get() .get()
.build() .build()

View file

@ -12,8 +12,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.DatabaseFactory import app.lonecloud.prism.DatabaseFactory
import app.lonecloud.prism.EncryptionKeyStore import app.lonecloud.prism.EncryptionKeyStore
import app.lonecloud.prism.PrismServerClient import app.lonecloud.prism.PrismServerClient
import app.lonecloud.prism.activities.ui.InstalledApp import app.lonecloud.prism.activities.ui.InstalledApp
@ -41,8 +41,8 @@ class MainViewModel(
) : ViewModel() { ) : ViewModel() {
constructor(requireBatteryOpt: Boolean, messenger: InternalMessenger?, application: Application) : this( constructor(requireBatteryOpt: Boolean, messenger: InternalMessenger?, application: Application) : this(
mainUiState = MainUiState( mainUiState = MainUiState(
prismServerConfigured = !AppStore(application).prismServerUrl.isNullOrBlank() && prismServerConfigured = !PrismPreferences(application).prismServerUrl.isNullOrBlank() &&
!AppStore(application).prismApiKey.isNullOrBlank() !PrismPreferences(application).prismApiKey.isNullOrBlank()
), ),
batteryOptimisationViewModel = BatteryOptimisationViewModel(requireBatteryOpt, messenger), batteryOptimisationViewModel = BatteryOptimisationViewModel(requireBatteryOpt, messenger),
registrationsViewModel = RegistrationsViewModel( registrationsViewModel = RegistrationsViewModel(

View file

@ -7,7 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.PrismServerClient import app.lonecloud.prism.PrismServerClient
import app.lonecloud.prism.activities.ui.SettingsState import app.lonecloud.prism.activities.ui.SettingsState
import app.lonecloud.prism.receivers.PrismConfigReceiver import app.lonecloud.prism.receivers.PrismConfigReceiver
@ -33,7 +33,7 @@ class SettingsViewModel(
fun toggleShowToasts() { fun toggleShowToasts() {
viewModelScope.launch { viewModelScope.launch {
state = state.copy(showToasts = !state.showToasts) state = state.copy(showToasts = !state.showToasts)
application?.let { AppStore(it).showToasts = state.showToasts } application?.let { PrismPreferences(it).showToasts = state.showToasts }
messenger?.sendIMessage(InternalOpcode.SHOW_TOASTS_SET, if (state.showToasts) 1 else 0) messenger?.sendIMessage(InternalOpcode.SHOW_TOASTS_SET, if (state.showToasts) 1 else 0)
} }
} }
@ -43,7 +43,7 @@ class SettingsViewModel(
val trimmedUrl = url.trim() val trimmedUrl = url.trim()
state = state.copy(prismServerUrl = trimmedUrl) state = state.copy(prismServerUrl = trimmedUrl)
application?.let { application?.let {
AppStore(it).prismServerUrl = trimmedUrl.ifBlank { null } PrismPreferences(it).prismServerUrl = trimmedUrl.ifBlank { null }
val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL).apply { val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL).apply {
putExtra(PrismConfigReceiver.EXTRA_URL, trimmedUrl) putExtra(PrismConfigReceiver.EXTRA_URL, trimmedUrl)
@ -65,7 +65,7 @@ class SettingsViewModel(
val trimmedKey = apiKey.trim() val trimmedKey = apiKey.trim()
state = state.copy(prismApiKey = trimmedKey) state = state.copy(prismApiKey = trimmedKey)
application?.let { application?.let {
AppStore(it).prismApiKey = trimmedKey.ifBlank { null } PrismPreferences(it).prismApiKey = trimmedKey.ifBlank { null }
val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_API_KEY).apply { val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_API_KEY).apply {
putExtra(PrismConfigReceiver.EXTRA_API_KEY, trimmedKey) putExtra(PrismConfigReceiver.EXTRA_API_KEY, trimmedKey)

View file

@ -6,18 +6,18 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.unifiedpush.android.distributor.ipc.InternalMessenger import org.unifiedpush.android.distributor.ipc.InternalMessenger
import org.unifiedpush.android.distributor.ipc.InternalOpcode import org.unifiedpush.android.distributor.ipc.InternalOpcode
class ThemeViewModel(val messenger: InternalMessenger?, val application: Application?) : ViewModel() { class ThemeViewModel(val messenger: InternalMessenger?, val application: Application?) : ViewModel() {
var dynamicColors by mutableStateOf(application?.let { AppStore(it).dynamicColors } ?: false) var dynamicColors by mutableStateOf(application?.let { PrismPreferences(it).dynamicColors } ?: false)
fun toggleDynamicColors() { fun toggleDynamicColors() {
viewModelScope.launch { viewModelScope.launch {
dynamicColors = !dynamicColors dynamicColors = !dynamicColors
application?.let { AppStore(it).dynamicColors = dynamicColors } application?.let { PrismPreferences(it).dynamicColors = dynamicColors }
messenger?.sendIMessage(InternalOpcode.THEME_DYN_SET, if (dynamicColors) 1 else 0) messenger?.sendIMessage(InternalOpcode.THEME_DYN_SET, if (dynamicColors) 1 else 0)
} }
} }

View file

@ -24,7 +24,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.R import app.lonecloud.prism.R
import app.lonecloud.prism.activities.MainViewModel import app.lonecloud.prism.activities.MainViewModel
import app.lonecloud.prism.activities.PreviewFactory import app.lonecloud.prism.activities.PreviewFactory
@ -89,7 +89,7 @@ fun MainScreen(
"RefreshRegistrations" -> viewModel.refreshRegistrations() "RefreshRegistrations" -> viewModel.refreshRegistrations()
"UpdatePrismServerConfigured" -> { "UpdatePrismServerConfigured" -> {
viewModel.application?.let { app -> viewModel.application?.let { app ->
val store = AppStore(app) val store = PrismPreferences(app)
viewModel.updatePrismServerConfigured( viewModel.updatePrismServerConfigured(
!store.prismServerUrl.isNullOrBlank() && !store.prismServerUrl.isNullOrBlank() &&
!store.prismApiKey.isNullOrBlank() !store.prismApiKey.isNullOrBlank()

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -24,7 +25,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.lonecloud.prism.R import app.lonecloud.prism.R
@ -52,7 +58,7 @@ fun PrismServerConfigButton(
) )
Text( Text(
text = if (currentUrl.isNotBlank()) { text = if (currentUrl.isNotBlank()) {
stringResource(R.string.prism_server_configured, currentUrl) currentUrl
} else { } else {
stringResource(R.string.prism_server_not_configured) stringResource(R.string.prism_server_not_configured)
}, },
@ -87,7 +93,9 @@ fun PrismServerConfigDialog(
var isTesting by remember { mutableStateOf(false) } var isTesting by remember { mutableStateOf(false) }
var testResult by remember { mutableStateOf<String?>(null) } var testResult by remember { mutableStateOf<String?>(null) }
var showServerChangeWarning by remember { mutableStateOf(false) } var showServerChangeWarning by remember { mutableStateOf(false) }
var showClearConfirmation by remember { mutableStateOf(false) }
val context = LocalContext.current val context = LocalContext.current
val uriHandler = LocalUriHandler.current
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
@ -97,6 +105,46 @@ fun PrismServerConfigDialog(
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
val description = stringResource(R.string.prism_server_description)
val repoUrl = stringResource(R.string.prism_server_repo_link)
val fullText = "$description\n\n$repoUrl"
val annotatedString = buildAnnotatedString {
append(description)
append("\n\n")
val linkStart = length
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline
)
) {
append(repoUrl)
}
addStringAnnotation(
tag = "URL",
annotation = repoUrl,
start = linkStart,
end = length
)
}
ClickableText(
text = annotatedString,
style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
),
onClick = { offset ->
annotatedString.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
).firstOrNull()?.let { annotation ->
uriHandler.openUri(annotation.item)
}
}
)
OutlinedTextField( OutlinedTextField(
value = url, value = url,
onValueChange = { onValueChange = {
@ -163,7 +211,7 @@ fun PrismServerConfigDialog(
.count { it.description?.startsWith("target:") == true } .count { it.description?.startsWith("target:") == true }
if (manualAppsCount > 0) { if (manualAppsCount > 0) {
val oldUrl = initialUrl val oldUrl = initialUrl
val oldKey = app.lonecloud.prism.AppStore(context).prismApiKey val oldKey = app.lonecloud.prism.PrismPreferences(context).prismApiKey
if (!oldUrl.isNullOrBlank() && !oldKey.isNullOrBlank()) { if (!oldUrl.isNullOrBlank() && !oldKey.isNullOrBlank()) {
app.lonecloud.prism.PrismServerClient.deleteAllApps( app.lonecloud.prism.PrismServerClient.deleteAllApps(
context, context,
@ -197,12 +245,76 @@ fun PrismServerConfigDialog(
} }
}, },
dismissButton = { dismissButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
if (initialUrl.isNotBlank()) {
TextButton(
onClick = { showClearConfirmation = true },
enabled = !isTesting
) {
Text(
text = stringResource(R.string.clear_server_button),
color = MaterialTheme.colorScheme.error
)
}
}
TextButton(onClick = onDismiss, enabled = !isTesting) { TextButton(onClick = onDismiss, enabled = !isTesting) {
Text(stringResource(R.string.cancel_button)) Text(stringResource(R.string.cancel_button))
} }
} }
}
) )
if (showClearConfirmation) {
val db = app.lonecloud.prism.DatabaseFactory.getDb(context)
val manualAppsCount = db.listApps()
.count { it.description?.startsWith("target:") == true }
AlertDialog(
onDismissRequest = { showClearConfirmation = false },
title = { Text(stringResource(R.string.clear_server_confirm_title)) },
text = {
Text(
if (manualAppsCount > 0) {
stringResource(R.string.clear_server_confirm_message_with_apps, manualAppsCount)
} else {
stringResource(R.string.clear_server_confirm_message_no_apps)
}
)
},
confirmButton = {
Button(
onClick = {
if (initialUrl.isNotBlank()) {
val oldKey = app.lonecloud.prism.PrismPreferences(context).prismApiKey
if (!oldKey.isNullOrBlank() && manualAppsCount > 0) {
app.lonecloud.prism.PrismServerClient.deleteAllApps(
context,
serverUrl = initialUrl,
apiKey = oldKey
)
}
}
onSave("", "")
showClearConfirmation = false
onDismiss()
},
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text(stringResource(R.string.clear_server_button))
}
},
dismissButton = {
TextButton(onClick = { showClearConfirmation = false }) {
Text(stringResource(R.string.cancel_button))
}
}
)
}
if (showServerChangeWarning) { if (showServerChangeWarning) {
val db = app.lonecloud.prism.DatabaseFactory.getDb(context) val db = app.lonecloud.prism.DatabaseFactory.getDb(context)
val manualAppsCount = db.listApps() val manualAppsCount = db.listApps()

View file

@ -28,7 +28,7 @@ fun RestartServicesPreference(onClick: () -> Unit) {
verticalArrangement = Arrangement.spacedBy(4.dp) verticalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
Text( Text(
text = stringResource(R.string.restart_service_button), text = stringResource(R.string.reset_connection_button),
style = MaterialTheme.typography.bodyLarge style = MaterialTheme.typography.bodyLarge
) )
} }

View file

@ -1,7 +1,7 @@
package app.lonecloud.prism.activities.ui package app.lonecloud.prism.activities.ui
import android.content.Context import android.content.Context
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
data class SettingsState( data class SettingsState(
val showToasts: Boolean, val showToasts: Boolean,
@ -10,7 +10,7 @@ data class SettingsState(
) { ) {
companion object { companion object {
fun from(context: Context): SettingsState { fun from(context: Context): SettingsState {
val store = AppStore(context) val store = PrismPreferences(context)
return SettingsState( return SettingsState(
showToasts = store.showToasts, showToasts = store.showToasts,
prismServerUrl = store.prismServerUrl ?: "", prismServerUrl = store.prismServerUrl ?: "",

View file

@ -6,7 +6,7 @@ import android.os.Looper
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.DatabaseFactory import app.lonecloud.prism.DatabaseFactory
import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor
import app.lonecloud.prism.Distributor.sendMessage import app.lonecloud.prism.Distributor.sendMessage
@ -32,7 +32,7 @@ import org.unifiedpush.android.distributor.ChannelCreationStatus
class ServerConnection(private val context: Context, private val releaseLock: () -> Unit) : WebSocketListener() { class ServerConnection(private val context: Context, private val releaseLock: () -> Unit) : WebSocketListener() {
private val store = AppStore(context) private val store = PrismPreferences(context)
fun start(): WebSocket { fun start(): WebSocket {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
@ -107,8 +107,6 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
} }
db.deleteDisabledApps() db.deleteDisabledApps()
} else { } else {
// We remove pending unregistrations
// and register pending registrations
db.listDisabledChannelIds().forEach { db.listDisabledChannelIds().forEach {
Log.d(TAG, "Hello, unregistering $it") Log.d(TAG, "Hello, unregistering $it")
ClientMessage.Unregister(channelID = it).send(webSocket) ClientMessage.Unregister(channelID = it).send(webSocket)
@ -226,7 +224,6 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
if (failToUseUrlCandidate(context)) return if (failToUseUrlCandidate(context)) return
if (!shouldRestart()) return if (!shouldRestart()) return
if (SourceManager.addFail(context, webSocket)) { if (SourceManager.addFail(context, webSocket)) {
// If null, we keep the worker with its 16min
val delay = SourceManager.getTimeout() ?: return val delay = SourceManager.getTimeout() ?: return
Log.d(TAG, "Retrying in $delay ms") Log.d(TAG, "Retrying in $delay ms")
RestartWorker.run(context, delay = delay) RestartWorker.run(context, delay = delay)
@ -262,7 +259,6 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
} }
if (!NetworkCallbackFactory.hasInternet()) { if (!NetworkCallbackFactory.hasInternet()) {
Log.d(TAG, "No Internet: do not restart") Log.d(TAG, "No Internet: do not restart")
// It will be restarted when Internet is back
return false return false
} }
return true return true

View file

@ -3,12 +3,12 @@ package app.lonecloud.prism.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
class PrismConfigReceiver : BroadcastReceiver() { class PrismConfigReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val store = AppStore(context) val store = PrismPreferences(context)
when (intent.action) { when (intent.action) {
ACTION_SET_PRISM_SERVER_URL -> { ACTION_SET_PRISM_SERVER_URL -> {

View file

@ -3,7 +3,7 @@
package app.lonecloud.prism.receivers package app.lonecloud.prism.receivers
import android.content.Context import android.content.Context
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor
import app.lonecloud.prism.callback.NetworkCallbackFactory import app.lonecloud.prism.callback.NetworkCallbackFactory
import org.unifiedpush.android.distributor.receiver.DistributorReceiver import org.unifiedpush.android.distributor.receiver.DistributorReceiver
@ -16,5 +16,5 @@ class RegisterBroadcastReceiver : DistributorReceiver() {
override fun hasInternet(context: Context): Boolean = NetworkCallbackFactory.hasInternet() override fun hasInternet(context: Context): Boolean = NetworkCallbackFactory.hasInternet()
override fun showToasts(context: Context): Boolean = AppStore(context).showToasts override fun showToasts(context: Context): Boolean = PrismPreferences(context).showToasts
} }

View file

@ -1,11 +1,11 @@
package app.lonecloud.prism.services package app.lonecloud.prism.services
import android.content.Context import android.content.Context
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor
import org.unifiedpush.android.distributor.MigrationManager as MManager import org.unifiedpush.android.distributor.MigrationManager as MManager
class MigrationManager : MManager() { class MigrationManager : MManager() {
override val distrib = Distributor override val distrib = Distributor
override fun getStore(context: Context): MigrationStore = AppStore(context) override fun getStore(context: Context): MigrationStore = PrismPreferences(context)
} }

View file

@ -3,7 +3,7 @@ package app.lonecloud.prism.services
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.DatabaseFactory import app.lonecloud.prism.DatabaseFactory
import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor
import org.unifiedpush.android.distributor.Database import org.unifiedpush.android.distributor.Database
@ -28,7 +28,7 @@ class PrismInternalService : InternalService() {
override val distributor: UnifiedPushDistributor = Distributor override val distributor: UnifiedPushDistributor = Distributor
override val db: Database by lazy { DatabaseFactory.getDb(this) } override val db: Database by lazy { DatabaseFactory.getDb(this) }
private val appStore by lazy { AppStore(this) } private val appStore by lazy { PrismPreferences(this) }
override var themeDynamicColors: Boolean override var themeDynamicColors: Boolean
get() = appStore.dynamicColors get() = appStore.dynamicColors
@ -44,9 +44,7 @@ class PrismInternalService : InternalService() {
override fun getDebugInfo(): String = "Prism Distributor" override fun getDebugInfo(): String = "Prism Distributor"
override fun runAppMigration() { override fun runAppMigration() {}
// No app migration needed for Prism currently
}
override fun account(): IAccount = object : IAccount { override fun account(): IAccount = object : IAccount {
override fun get(): String? = null override fun get(): String? = null
@ -55,22 +53,20 @@ class PrismInternalService : InternalService() {
} }
override fun api(): IApi = object : IApi { override fun api(): IApi = object : IApi {
override fun newPushServer(url: String?) { override fun newPushServer(url: String?) {}
// Prism uses fixed Mozilla server, but can be customized
}
override fun getUrl(): String = appStore.apiUrl override fun getUrl(): String = appStore.apiUrl
} }
override fun registrations() = object : IRegistrations { override fun registrations() = object : IRegistrations {
override fun delete(registrations: List<String>) { override fun delete(registrations: List<String>) {
registrations.forEach { token -> registrations.forEach { token ->
distributor.deleteApp(context, token) distributor.deleteApp(this@PrismInternalService, token)
} }
} }
override fun list(): List<App> = db override fun list(): List<App> = db
.listApps().map { .listApps().map {
val pm = context.packageManager val pm = this@PrismInternalService.packageManager
val isManualApp = it.description?.startsWith("target:") == true val isManualApp = it.description?.startsWith("target:") == true
val targetPackage = if (isManualApp) { val targetPackage = if (isManualApp) {
@ -96,22 +92,20 @@ class PrismInternalService : InternalService() {
vapidKey = it.vapidKey, vapidKey = it.vapidKey,
title = displayTitle, title = displayTitle,
msgCount = it.msgCount, msgCount = it.msgCount,
description = if (it.packageName == context.packageName) { description = if (it.packageName == this@PrismInternalService.packageName) {
Description.LocalChannel Description.LocalChannel
} else { } else {
Description.StringDescription(packageToResolve) Description.StringDescription(packageToResolve)
}, },
icon = getApplicationIcon(packageToResolve)?.toBitmap(), icon = getApplicationIcon(packageToResolve)?.toBitmap(),
isLocal = it.packageName == context.packageName isLocal = it.packageName == this@PrismInternalService.packageName
) )
} }
override fun copyEndpoint(token: String?) { override fun copyEndpoint(token: String?) {
super@PrismInternalService.registrations().copyEndpoint(token)
} }
override fun addLocal(title: String) { override fun addLocal(title: String) {
super@PrismInternalService.registrations().addLocal(title)
} }
} }
} }

View file

@ -5,7 +5,7 @@ package app.lonecloud.prism.services
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.work.* import androidx.work.*
import app.lonecloud.prism.AppStore import app.lonecloud.prism.PrismPreferences
import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor
import app.lonecloud.prism.api.MessageSender import app.lonecloud.prism.api.MessageSender
import app.lonecloud.prism.callback.NetworkCallbackFactory import app.lonecloud.prism.callback.NetworkCallbackFactory
@ -20,7 +20,6 @@ class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params
*/ */
@Suppress("ReturnCount") @Suppress("ReturnCount")
override fun doWork(): Result { override fun doWork(): Result {
// We avoid running twice at the same time
synchronized(lock) { synchronized(lock) {
Log.d(TAG, "Working [$id]") Log.d(TAG, "Working [$id]")
if (!NetworkCallbackFactory.hasInternet()) { if (!NetworkCallbackFactory.hasInternet()) {
@ -29,8 +28,6 @@ class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params
} }
if (SourceManager.isRunningWithoutFailure) { if (SourceManager.isRunningWithoutFailure) {
Log.d(TAG, "Running without failure") Log.d(TAG, "Running without failure")
// We send a ping, if it fails it will restart this worker, and wont
// pass this check
MessageSender.ping(applicationContext) MessageSender.ping(applicationContext)
return Result.success() return Result.success()
} }
@ -44,10 +41,7 @@ class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params
private val lock = Object() private val lock = Object()
override fun canRun(context: Context): Boolean { override fun canRun(context: Context): Boolean {
// We don't have any credential requirement, if we don't have return !PrismPreferences(context).migrated
// a uaid yet, it will be created during the initial sync
// So, as soon as the user hasn't migrated, we can run
return !AppStore(context).migrated
} }
override fun isServiceStarted(context: Context): Boolean = FgService.isServiceStarted() override fun isServiceStarted(context: Context): Boolean = FgService.isServiceStarted()

View file

@ -47,7 +47,10 @@
<string name="add_manual_app_content_description">Add manual app</string> <string name="add_manual_app_content_description">Add manual app</string>
<string name="debug_title">Debug Information</string> <string name="debug_title">Debug Information</string>
<string name="configure_server">Configure Prism Server</string> <string name="configure_server">Configure Prism Server</string>
<string name="prism_server_description">Self-host a Prism server to unlock manual app registrations. Otherwise, this distributor works normally with UnifiedPush apps.</string>
<string name="prism_server_repo_link">https://github.com/lone-cloud/prism</string>
<string name="prism_server_configured">Prism server configured</string> <string name="prism_server_configured">Prism server configured</string>
<string name="prism_server_configured_with_version">%1$s (v%2$s)</string>
<string name="prism_server_not_configured">Prism server not configured</string> <string name="prism_server_not_configured">Prism server not configured</string>
<string name="prism_server_url_label">Server URL</string> <string name="prism_server_url_label">Server URL</string>
<string name="prism_server_url_placeholder">https://prism.example.com</string> <string name="prism_server_url_placeholder">https://prism.example.com</string>
@ -57,7 +60,11 @@
<string name="connection_successful">Connection successful</string> <string name="connection_successful">Connection successful</string>
<string name="connection_failed">Connection failed</string> <string name="connection_failed">Connection failed</string>
<string name="test_and_save_button">Test and Save</string> <string name="test_and_save_button">Test and Save</string>
<string name="restart_service_button">Restart Service</string> <string name="clear_server_button">Clear</string>
<string name="clear_server_confirm_title">Remove Prism Server?</string>
<string name="clear_server_confirm_message_no_apps">This will remove your Prism server configuration.</string>
<string name="clear_server_confirm_message_with_apps">You have %d manual app registration(s). Clearing the server will delete them from the server and remove the configuration.</string>
<string name="reset_connection_button">Reset Connection</string>
<string name="app_dropdown_show_toasts">Notify when apps register</string> <string name="app_dropdown_show_toasts">Notify when apps register</string>
<string name="dynamic_colors_title">Dynamic Colors</string> <string name="dynamic_colors_title">Dynamic Colors</string>
</resources> </resources>