new icon, dep updates, new gradle daemon props, allow changing the push server
1
app/proguard-rules.pro
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
# keep classes used for Json deserializing
|
# keep classes used for Json deserializing
|
||||||
-keep class app.lonecloud.prism.api.data.** { *; }
|
-keep class app.lonecloud.prism.api.data.** { *; }
|
||||||
|
-keepnames class org.unifiedpush.** { *; }
|
||||||
|
|
||||||
# preserve line numbers for crash reporting
|
# preserve line numbers for crash reporting
|
||||||
-keepattributes SourceFile,LineNumberTable
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
@ -12,6 +13,7 @@
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".PrismApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
@ -19,6 +21,17 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@android:style/Theme.Material.NoActionBar"
|
android:theme="@android:style/Theme.Material.NoActionBar"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
<provider
|
||||||
|
android:name="androidx.startup.InitializationProvider"
|
||||||
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="merge">
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.work.WorkManagerInitializer"
|
||||||
|
android:value="androidx.startup"
|
||||||
|
tools:node="remove" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:process=":ui"
|
android:process=":ui"
|
||||||
|
|
@ -60,6 +73,7 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
<action android:name="app.lonecloud.prism.SET_PUSH_SERVICE_URL" />
|
||||||
<action android:name="app.lonecloud.prism.SET_PRISM_SERVER_URL" />
|
<action android:name="app.lonecloud.prism.SET_PRISM_SERVER_URL" />
|
||||||
<action android:name="app.lonecloud.prism.SET_PRISM_API_KEY" />
|
<action android:name="app.lonecloud.prism.SET_PRISM_API_KEY" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 136 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
package app.lonecloud.prism
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.work.Configuration
|
||||||
|
|
||||||
|
class PrismApplication : Application(), Configuration.Provider {
|
||||||
|
override val workManagerConfiguration: Configuration
|
||||||
|
get() = Configuration.Builder().build()
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ import org.json.JSONObject
|
||||||
|
|
||||||
object PrismServerClient {
|
object PrismServerClient {
|
||||||
private const val TAG = "PrismServerClient"
|
private const val TAG = "PrismServerClient"
|
||||||
private const val SUBSCRIPTION_DESC_PREFIX = "sid:"
|
|
||||||
private const val VAPID_PRIVATE_DESC_PREFIX = "vp:"
|
private const val VAPID_PRIVATE_DESC_PREFIX = "vp:"
|
||||||
|
|
||||||
private fun getAuthHeader(apiKey: String): String = "Bearer $apiKey"
|
private fun getAuthHeader(apiKey: String): String = "Bearer $apiKey"
|
||||||
|
|
@ -47,7 +46,7 @@ object PrismServerClient {
|
||||||
val existingSubscriptionId = store.getSubscriptionId(connectorToken)
|
val existingSubscriptionId = store.getSubscriptionId(connectorToken)
|
||||||
?: getSubscriptionIdFromDb(context, connectorToken)?.also {
|
?: getSubscriptionIdFromDb(context, connectorToken)?.also {
|
||||||
store.setSubscriptionId(connectorToken, it)
|
store.setSubscriptionId(connectorToken, it)
|
||||||
Log.d(TAG, "registerApp: restored subscriptionId from db for $connectorToken")
|
Log.d(TAG, "registerApp: restored subscriptionId from description for $connectorToken")
|
||||||
}
|
}
|
||||||
val knownEndpoint = store.getRegisteredEndpoint(connectorToken)
|
val knownEndpoint = store.getRegisteredEndpoint(connectorToken)
|
||||||
?: getEndpointFromDb(context, connectorToken)?.also {
|
?: getEndpointFromDb(context, connectorToken)?.also {
|
||||||
|
|
@ -134,7 +133,6 @@ object PrismServerClient {
|
||||||
|
|
||||||
store.setSubscriptionId(connectorToken, subscriptionId)
|
store.setSubscriptionId(connectorToken, subscriptionId)
|
||||||
store.setRegisteredEndpoint(connectorToken, webpushUrl)
|
store.setRegisteredEndpoint(connectorToken, webpushUrl)
|
||||||
persistSubscriptionIdInDb(context, connectorToken, subscriptionId)
|
|
||||||
|
|
||||||
Log.d(TAG, "Successfully registered app: $appName (ID: $subscriptionId)")
|
Log.d(TAG, "Successfully registered app: $appName (ID: $subscriptionId)")
|
||||||
withContext(Dispatchers.Main) { onSuccess() }
|
withContext(Dispatchers.Main) { onSuccess() }
|
||||||
|
|
@ -285,7 +283,7 @@ object PrismServerClient {
|
||||||
.find { it.connectorToken == connectorToken }
|
.find { it.connectorToken == connectorToken }
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
return DescriptionParser.extractValue(app.description, SUBSCRIPTION_DESC_PREFIX)
|
return DescriptionParser.extractValue(app.description, "sid:")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEndpointFromDb(context: Context, connectorToken: String): String? {
|
private fun getEndpointFromDb(context: Context, connectorToken: String): String? {
|
||||||
|
|
@ -297,34 +295,6 @@ object PrismServerClient {
|
||||||
return app.endpoint
|
return app.endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun persistSubscriptionIdInDb(
|
|
||||||
context: Context,
|
|
||||||
connectorToken: String,
|
|
||||||
subscriptionId: String
|
|
||||||
) {
|
|
||||||
val db = DatabaseFactory.getDb(context)
|
|
||||||
val app = db.listApps().find { it.connectorToken == connectorToken } ?: return
|
|
||||||
val channelId = db.listChannelIdVapid()
|
|
||||||
.find { (_, vapid) -> vapid == app.vapidKey }
|
|
||||||
?.first
|
|
||||||
?: return
|
|
||||||
|
|
||||||
val updatedDescription = DescriptionParser.updateValue(
|
|
||||||
app.description,
|
|
||||||
SUBSCRIPTION_DESC_PREFIX,
|
|
||||||
subscriptionId
|
|
||||||
)
|
|
||||||
|
|
||||||
db.registerApp(
|
|
||||||
app.packageName,
|
|
||||||
app.connectorToken,
|
|
||||||
channelId,
|
|
||||||
app.title ?: app.packageName,
|
|
||||||
app.vapidKey,
|
|
||||||
updatedDescription
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getVapidPrivateKeyFromDescription(description: String?): String? =
|
private fun getVapidPrivateKeyFromDescription(description: String?): String? =
|
||||||
DescriptionParser.extractValue(description, VAPID_PRIVATE_DESC_PREFIX)
|
DescriptionParser.extractValue(description, VAPID_PRIVATE_DESC_PREFIX)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class MainActivity : ComponentActivity() {
|
||||||
Log.d(TAG, "Destroy")
|
Log.d(TAG, "Destroy")
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
jobs += CoroutineScope(Dispatchers.Main + Job()).launch {
|
jobs += CoroutineScope(Dispatchers.Main + Job()).launch {
|
||||||
delay(10_000)
|
delay(5_000)
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import app.lonecloud.prism.PrismServerClient
|
||||||
import app.lonecloud.prism.activities.ui.InstalledApp
|
import app.lonecloud.prism.activities.ui.InstalledApp
|
||||||
import app.lonecloud.prism.activities.ui.MainUiState
|
import app.lonecloud.prism.activities.ui.MainUiState
|
||||||
import app.lonecloud.prism.utils.DescriptionParser
|
import app.lonecloud.prism.utils.DescriptionParser
|
||||||
|
import app.lonecloud.prism.utils.ManualAppNotifications
|
||||||
import app.lonecloud.prism.utils.TAG
|
import app.lonecloud.prism.utils.TAG
|
||||||
import app.lonecloud.prism.utils.VapidKeyGenerator
|
import app.lonecloud.prism.utils.VapidKeyGenerator
|
||||||
import app.lonecloud.prism.utils.WebPushEncryptionKeys
|
import app.lonecloud.prism.utils.WebPushEncryptionKeys
|
||||||
|
|
@ -69,6 +70,9 @@ class MainViewModel(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadInstalledApps()
|
loadInstalledApps()
|
||||||
|
application?.let { app ->
|
||||||
|
ManualAppNotifications.pruneOrphanedChannels(app)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closePermissionDialog() {
|
fun closePermissionDialog() {
|
||||||
|
|
@ -97,6 +101,7 @@ class MainViewModel(
|
||||||
selectedTokens.forEach { token ->
|
selectedTokens.forEach { token ->
|
||||||
if (token.startsWith("manual_app_")) {
|
if (token.startsWith("manual_app_")) {
|
||||||
PrismServerClient.deleteApp(app, token)
|
PrismServerClient.deleteApp(app, token)
|
||||||
|
ManualAppNotifications.deleteChannelForToken(app, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = android.content.Intent("org.unifiedpush.android.distributor.UNREGISTER")
|
val intent = android.content.Intent("org.unifiedpush.android.distributor.UNREGISTER")
|
||||||
|
|
@ -254,6 +259,7 @@ class MainViewModel(
|
||||||
application?.let { app ->
|
application?.let { app ->
|
||||||
if (token.startsWith("manual_app_")) {
|
if (token.startsWith("manual_app_")) {
|
||||||
PrismServerClient.deleteApp(app, token)
|
PrismServerClient.deleteApp(app, token)
|
||||||
|
ManualAppNotifications.deleteChannelForToken(app, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = Intent("org.unifiedpush.android.distributor.UNREGISTER")
|
val intent = Intent("org.unifiedpush.android.distributor.UNREGISTER")
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ 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.BuildConfig
|
||||||
import app.lonecloud.prism.PrismPreferences
|
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
|
||||||
|
import app.lonecloud.prism.utils.normalizeUrl
|
||||||
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
|
||||||
|
|
@ -36,6 +38,7 @@ class SettingsViewModel(
|
||||||
value: String
|
value: String
|
||||||
) {
|
) {
|
||||||
val extraKey = when (action) {
|
val extraKey = when (action) {
|
||||||
|
PrismConfigReceiver.ACTION_SET_PUSH_SERVICE_URL -> PrismConfigReceiver.EXTRA_URL
|
||||||
PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL -> PrismConfigReceiver.EXTRA_URL
|
PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL -> PrismConfigReceiver.EXTRA_URL
|
||||||
PrismConfigReceiver.ACTION_SET_PRISM_API_KEY -> PrismConfigReceiver.EXTRA_API_KEY
|
PrismConfigReceiver.ACTION_SET_PRISM_API_KEY -> PrismConfigReceiver.EXTRA_API_KEY
|
||||||
else -> return
|
else -> return
|
||||||
|
|
@ -57,6 +60,21 @@ class SettingsViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updatePushServiceUrl(url: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val normalizedUrl = if (url.isBlank()) {
|
||||||
|
BuildConfig.DEFAULT_API_URL
|
||||||
|
} else {
|
||||||
|
normalizeUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.copy(pushServiceUrl = normalizedUrl)
|
||||||
|
application?.let { app ->
|
||||||
|
sendConfigBroadcast(app, PrismConfigReceiver.ACTION_SET_PUSH_SERVICE_URL, if (url.isBlank()) "" else normalizedUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updatePrismServerUrl(url: String, sendAction: Boolean = true) {
|
fun updatePrismServerUrl(url: String, sendAction: Boolean = true) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val trimmedUrl = url.trim()
|
val trimmedUrl = url.trim()
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
|
||||||
SettingsViewModel(
|
SettingsViewModel(
|
||||||
SettingsState(
|
SettingsState(
|
||||||
showToasts = false,
|
showToasts = false,
|
||||||
|
pushServiceUrl = "",
|
||||||
prismServerUrl = "",
|
prismServerUrl = "",
|
||||||
prismApiKey = ""
|
prismApiKey = ""
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ private fun PrismAppListItem(prismApp: PrismServerApp, onClick: () -> Unit) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Image(
|
Image(
|
||||||
painter = androidx.compose.ui.res.painterResource(R.drawable.app_logo),
|
painter = androidx.compose.ui.res.painterResource(R.mipmap.ic_launcher_foreground),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ enum class AppScreen(@param:StringRes val title: Int) {
|
||||||
Intro(R.string.app_name),
|
Intro(R.string.app_name),
|
||||||
Main(R.string.app_name),
|
Main(R.string.app_name),
|
||||||
Settings(R.string.settings),
|
Settings(R.string.settings),
|
||||||
|
PushServiceConfig(R.string.push_service_title),
|
||||||
ServerConfig(R.string.configure_server),
|
ServerConfig(R.string.configure_server),
|
||||||
AddApp(R.string.add_custom_app_title),
|
AddApp(R.string.add_custom_app_title),
|
||||||
AppPicker(R.string.select_target_app_title),
|
AppPicker(R.string.select_target_app_title),
|
||||||
|
|
@ -241,11 +242,33 @@ fun App(
|
||||||
vm,
|
vm,
|
||||||
themeViewModel,
|
themeViewModel,
|
||||||
migrationViewModel,
|
migrationViewModel,
|
||||||
|
onNavigateToPushServiceConfig = {
|
||||||
|
navController.navigate(AppScreen.PushServiceConfig.name)
|
||||||
|
},
|
||||||
onNavigateToServerConfig = {
|
onNavigateToServerConfig = {
|
||||||
navController.navigate(AppScreen.ServerConfig.name)
|
navController.navigate(AppScreen.ServerConfig.name)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(
|
||||||
|
route = AppScreen.PushServiceConfig.name,
|
||||||
|
enterTransition = { slideInTo(Dir.Left) },
|
||||||
|
popEnterTransition = { slideInTo(Dir.Right) },
|
||||||
|
popExitTransition = { slideOutFrom(Dir.Left) }
|
||||||
|
) {
|
||||||
|
val settingsEntry = remember(it) {
|
||||||
|
navController.getBackStackEntry(AppScreen.Settings.name)
|
||||||
|
}
|
||||||
|
val vm = viewModel<SettingsViewModel>(
|
||||||
|
viewModelStoreOwner = settingsEntry,
|
||||||
|
factory = factory
|
||||||
|
)
|
||||||
|
PushServiceConfigScreen(
|
||||||
|
initialUrl = vm.state.pushServiceUrl,
|
||||||
|
onNavigateBack = { navController.navigateUp() },
|
||||||
|
onSave = { url -> vm.updatePushServiceUrl(url) }
|
||||||
|
)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = AppScreen.ServerConfig.name,
|
route = AppScreen.ServerConfig.name,
|
||||||
enterTransition = { slideInTo(Dir.Left) },
|
enterTransition = { slideInTo(Dir.Left) },
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ fun IntroScreen(onComplete: (url: String, apiKey: String) -> Unit, onSkip: () ->
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.drawable.app_logo),
|
painter = painterResource(R.mipmap.ic_launcher_foreground),
|
||||||
contentDescription = stringResource(R.string.app_name),
|
contentDescription = stringResource(R.string.app_name),
|
||||||
modifier = Modifier.size(120.dp)
|
modifier = Modifier.size(120.dp)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ fun PrismTogglePreference(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = it,
|
imageVector = it,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(28.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package app.lonecloud.prism.activities.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.lonecloud.prism.R
|
||||||
|
import app.lonecloud.prism.utils.normalizeUrl
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PushServiceConfigScreen(
|
||||||
|
initialUrl: String,
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
onSave: (url: String) -> Unit
|
||||||
|
) {
|
||||||
|
var url by remember { mutableStateOf(initialUrl) }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.push_service_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.push_service_crowd_blending_warning),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = url,
|
||||||
|
onValueChange = { url = it },
|
||||||
|
label = { Text(stringResource(R.string.push_service_url_label)) },
|
||||||
|
placeholder = { Text(stringResource(R.string.push_service_url_placeholder)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
onSave("")
|
||||||
|
onNavigateBack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.push_service_reset_button))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onSave(normalizeUrl(url))
|
||||||
|
onNavigateBack()
|
||||||
|
},
|
||||||
|
enabled = url.isNotBlank(),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.push_service_save_button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -84,7 +84,7 @@ fun RegistrationDetailsScreen(
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
)
|
)
|
||||||
} ?: Image(
|
} ?: Image(
|
||||||
painter = painterResource(R.drawable.app_logo),
|
painter = painterResource(R.mipmap.ic_launcher_foreground),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(52.dp)
|
.size(52.dp)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ 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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Cloud
|
import androidx.compose.material.icons.filled.Dns
|
||||||
import androidx.compose.material.icons.filled.Notifications
|
import androidx.compose.material.icons.filled.Notifications
|
||||||
import androidx.compose.material.icons.filled.Palette
|
import androidx.compose.material.icons.filled.Palette
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -20,6 +20,7 @@ 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.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -39,7 +40,8 @@ fun SettingsScreen(
|
||||||
viewModel: SettingsViewModel,
|
viewModel: SettingsViewModel,
|
||||||
themeViewModel: ThemeViewModel,
|
themeViewModel: ThemeViewModel,
|
||||||
migrationViewModel: DistribMigrationViewModel,
|
migrationViewModel: DistribMigrationViewModel,
|
||||||
onNavigateToServerConfig: () -> Unit = {}
|
onNavigateToServerConfig: () -> Unit = {},
|
||||||
|
onNavigateToPushServiceConfig: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -62,9 +64,9 @@ fun SettingsScreen(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Cloud,
|
painter = painterResource(R.drawable.ic_notification),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(28.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -89,6 +91,40 @@ fun SettingsScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
onClick = onNavigateToPushServiceConfig,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RectangleShape
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Dns,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.push_service_title),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = viewModel.state.pushServiceUrl,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PrismTogglePreference(
|
PrismTogglePreference(
|
||||||
title = stringResource(R.string.app_dropdown_show_toasts),
|
title = stringResource(R.string.app_dropdown_show_toasts),
|
||||||
description = stringResource(R.string.show_toasts_description),
|
description = stringResource(R.string.show_toasts_description),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import app.lonecloud.prism.PrismPreferences
|
||||||
|
|
||||||
data class SettingsState(
|
data class SettingsState(
|
||||||
val showToasts: Boolean,
|
val showToasts: Boolean,
|
||||||
|
val pushServiceUrl: String,
|
||||||
val prismServerUrl: String,
|
val prismServerUrl: String,
|
||||||
val prismApiKey: String
|
val prismApiKey: String
|
||||||
) {
|
) {
|
||||||
|
|
@ -13,6 +14,7 @@ data class SettingsState(
|
||||||
val store = PrismPreferences(context)
|
val store = PrismPreferences(context)
|
||||||
return SettingsState(
|
return SettingsState(
|
||||||
showToasts = store.showToasts,
|
showToasts = store.showToasts,
|
||||||
|
pushServiceUrl = store.apiUrl,
|
||||||
prismServerUrl = store.prismServerUrl ?: "",
|
prismServerUrl = store.prismServerUrl ?: "",
|
||||||
prismApiKey = store.prismApiKey ?: ""
|
prismApiKey = store.prismApiKey ?: ""
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ 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.BuildConfig
|
||||||
import app.lonecloud.prism.PrismPreferences
|
import app.lonecloud.prism.PrismPreferences
|
||||||
|
import app.lonecloud.prism.api.ApiUrlCandidate
|
||||||
|
import app.lonecloud.prism.services.RestartWorker
|
||||||
|
|
||||||
class PrismConfigReceiver : BroadcastReceiver() {
|
class PrismConfigReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
|
@ -19,12 +22,22 @@ class PrismConfigReceiver : BroadcastReceiver() {
|
||||||
val apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: ""
|
val apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: ""
|
||||||
store.prismApiKey = apiKey
|
store.prismApiKey = apiKey
|
||||||
}
|
}
|
||||||
|
ACTION_SET_PUSH_SERVICE_URL -> {
|
||||||
|
val url = intent.getStringExtra(EXTRA_URL) ?: ""
|
||||||
|
if (url.isBlank()) {
|
||||||
|
store.apiUrl = BuildConfig.DEFAULT_API_URL
|
||||||
|
RestartWorker.run(context, delay = 0)
|
||||||
|
} else {
|
||||||
|
ApiUrlCandidate.test(context, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_SET_PRISM_SERVER_URL = "app.lonecloud.prism.SET_PRISM_SERVER_URL"
|
const val ACTION_SET_PRISM_SERVER_URL = "app.lonecloud.prism.SET_PRISM_SERVER_URL"
|
||||||
const val ACTION_SET_PRISM_API_KEY = "app.lonecloud.prism.SET_PRISM_API_KEY"
|
const val ACTION_SET_PRISM_API_KEY = "app.lonecloud.prism.SET_PRISM_API_KEY"
|
||||||
|
const val ACTION_SET_PUSH_SERVICE_URL = "app.lonecloud.prism.SET_PUSH_SERVICE_URL"
|
||||||
const val EXTRA_URL = "url"
|
const val EXTRA_URL = "url"
|
||||||
const val EXTRA_API_KEY = "api_key"
|
const val EXTRA_API_KEY = "api_key"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ 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.BuildConfig
|
||||||
import app.lonecloud.prism.DatabaseFactory
|
import app.lonecloud.prism.DatabaseFactory
|
||||||
import app.lonecloud.prism.Distributor
|
import app.lonecloud.prism.Distributor
|
||||||
import app.lonecloud.prism.PrismPreferences
|
import app.lonecloud.prism.PrismPreferences
|
||||||
|
import app.lonecloud.prism.api.ApiUrlCandidate
|
||||||
import org.unifiedpush.android.distributor.Database
|
import org.unifiedpush.android.distributor.Database
|
||||||
import org.unifiedpush.android.distributor.MigrationManager
|
import org.unifiedpush.android.distributor.MigrationManager
|
||||||
import org.unifiedpush.android.distributor.SourceManager
|
import org.unifiedpush.android.distributor.SourceManager
|
||||||
|
|
@ -13,9 +15,11 @@ import org.unifiedpush.android.distributor.UnifiedPushDistributor
|
||||||
import org.unifiedpush.android.distributor.WorkerCompanion
|
import org.unifiedpush.android.distributor.WorkerCompanion
|
||||||
import org.unifiedpush.android.distributor.data.App
|
import org.unifiedpush.android.distributor.data.App
|
||||||
import org.unifiedpush.android.distributor.data.Description
|
import org.unifiedpush.android.distributor.data.Description
|
||||||
|
import org.unifiedpush.android.distributor.ipc.ACTION_REFRESH_API_URL
|
||||||
import org.unifiedpush.android.distributor.ipc.handler.IAccount
|
import org.unifiedpush.android.distributor.ipc.handler.IAccount
|
||||||
import org.unifiedpush.android.distributor.ipc.handler.IApi
|
import org.unifiedpush.android.distributor.ipc.handler.IApi
|
||||||
import org.unifiedpush.android.distributor.ipc.handler.IRegistrations
|
import org.unifiedpush.android.distributor.ipc.handler.IRegistrations
|
||||||
|
import org.unifiedpush.android.distributor.ipc.sendUiAction
|
||||||
import org.unifiedpush.android.distributor.service.ForegroundServiceFactory
|
import org.unifiedpush.android.distributor.service.ForegroundServiceFactory
|
||||||
import org.unifiedpush.android.distributor.service.InternalService
|
import org.unifiedpush.android.distributor.service.InternalService
|
||||||
import org.unifiedpush.android.distributor.utils.getApplicationIcon
|
import org.unifiedpush.android.distributor.utils.getApplicationIcon
|
||||||
|
|
@ -54,7 +58,16 @@ 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?) {
|
||||||
|
url?.let {
|
||||||
|
ApiUrlCandidate.test(context, url)
|
||||||
|
} ?: run {
|
||||||
|
PrismPreferences(context).apiUrl = BuildConfig.DEFAULT_API_URL
|
||||||
|
restartWorker().restart()
|
||||||
|
sendUiAction(context, ACTION_REFRESH_API_URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getUrl(): String = appStore.apiUrl
|
override fun getUrl(): String = appStore.apiUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
package app.lonecloud.prism.services
|
package app.lonecloud.prism.services
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.lonecloud.prism.utils.DisconnectedNotification
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okio.ByteString
|
import okio.ByteString
|
||||||
|
|
@ -20,7 +19,7 @@ object SourceManager : SManager<WebSocket>() {
|
||||||
override val foregroundService = FgService.service
|
override val foregroundService = FgService.service
|
||||||
override val migrationManager = MigrationManager()
|
override val migrationManager = MigrationManager()
|
||||||
|
|
||||||
override fun disconnectedNotification(context: Context): AppNotification = DisconnectedNotification(context)
|
override fun disconnectedNotification(context: Context): AppNotification? = null
|
||||||
|
|
||||||
override fun getDummySource(): WebSocket = object : WebSocket {
|
override fun getDummySource(): WebSocket = object : WebSocket {
|
||||||
override fun cancel() { /* Dummy implementation */ }
|
override fun cancel() { /* Dummy implementation */ }
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import app.lonecloud.prism.services.MainRegistrationCounter
|
||||||
import org.unifiedpush.android.distributor.Database
|
import org.unifiedpush.android.distributor.Database
|
||||||
|
|
||||||
private const val NOTIFICATION_BASE_ID = 52000
|
private const val NOTIFICATION_BASE_ID = 52000
|
||||||
|
private const val MANUAL_CHANNEL_PREFIX = "manual_app_"
|
||||||
|
|
||||||
object ManualAppNotifications {
|
object ManualAppNotifications {
|
||||||
|
|
||||||
|
|
@ -38,7 +39,7 @@ object ManualAppNotifications {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelId = "manual_app_${app.connectorToken}"
|
val channelId = channelIdForToken(app.connectorToken)
|
||||||
val appTitle = app.title ?: "Unknown App"
|
val appTitle = app.title ?: "Unknown App"
|
||||||
createNotificationChannel(context, channelId, appTitle)
|
createNotificationChannel(context, channelId, appTitle)
|
||||||
|
|
||||||
|
|
@ -158,6 +159,31 @@ object ManualAppNotifications {
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteChannelForToken(context: Context, connectorToken: String) {
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.deleteNotificationChannel(channelIdForToken(connectorToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pruneOrphanedChannels(context: Context) {
|
||||||
|
val db = DatabaseFactory.getDb(context)
|
||||||
|
val validManualChannelIds = db.listApps()
|
||||||
|
.asSequence()
|
||||||
|
.map { it.connectorToken }
|
||||||
|
.filter { it.startsWith(MANUAL_CHANNEL_PREFIX) }
|
||||||
|
.map { channelIdForToken(it) }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.notificationChannels
|
||||||
|
.asSequence()
|
||||||
|
.map { it.id }
|
||||||
|
.filter { it.startsWith(MANUAL_CHANNEL_PREFIX) }
|
||||||
|
.filterNot { it in validManualChannelIds }
|
||||||
|
.forEach { notificationManager.deleteNotificationChannel(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun channelIdForToken(connectorToken: String): String = "$MANUAL_CHANNEL_PREFIX$connectorToken"
|
||||||
|
|
||||||
private fun getNotificationId(tag: String): Int = notificationIds.getOrPut(tag) {
|
private fun getNotificationId(tag: String): Int = notificationIds.getOrPut(tag) {
|
||||||
nextNotificationId++
|
nextNotificationId++
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import org.unifiedpush.android.distributor.AppNotification
|
||||||
|
|
||||||
const val NOTIFICATION_ID_FOREGROUND = 51115
|
const val NOTIFICATION_ID_FOREGROUND = 51115
|
||||||
private const val NOTIFICATION_ID_WARNING = 51215
|
private const val NOTIFICATION_ID_WARNING = 51215
|
||||||
|
private const val WARNING_CHANNEL_ID = "WarningSilent"
|
||||||
|
private const val FOREGROUND_CHANNEL_ID = "ForegroundSilent"
|
||||||
|
|
||||||
class MainNotificationData(
|
class MainNotificationData(
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -36,9 +38,9 @@ class MainNotificationData(
|
||||||
|
|
||||||
private val Context.warningChannelData: AppNotification.ChannelData
|
private val Context.warningChannelData: AppNotification.ChannelData
|
||||||
get() = AppNotification.ChannelData(
|
get() = AppNotification.ChannelData(
|
||||||
"Warning",
|
WARNING_CHANNEL_ID,
|
||||||
this.getString(R.string.warning),
|
this.getString(R.string.warning),
|
||||||
NotificationManager.IMPORTANCE_HIGH,
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
this.resources.getString(R.string.warning_notif_description).format(this.getString(R.string.app_name))
|
this.resources.getString(R.string.warning_notif_description).format(this.getString(R.string.app_name))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -53,7 +55,7 @@ class DisconnectedNotification(context: Context) :
|
||||||
context.getString(R.string.app_name)
|
context.getString(R.string.app_name)
|
||||||
),
|
),
|
||||||
context.getString(R.string.warning),
|
context.getString(R.string.warning),
|
||||||
NotificationCompat.PRIORITY_HIGH,
|
NotificationCompat.PRIORITY_LOW,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
context.warningChannelData
|
context.warningChannelData
|
||||||
|
|
@ -78,7 +80,7 @@ class ForegroundNotification(context: Context) :
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
ChannelData(
|
ChannelData(
|
||||||
"Foreground",
|
FOREGROUND_CHANNEL_ID,
|
||||||
context.getString(R.string.foreground_service),
|
context.getString(R.string.foreground_service),
|
||||||
NotificationManager.IMPORTANCE_LOW,
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
context.getString(R.string.foreground_notif_description)
|
context.getString(R.string.foreground_notif_description)
|
||||||
|
|
@ -86,6 +88,6 @@ class ForegroundNotification(context: Context) :
|
||||||
)
|
)
|
||||||
|
|
||||||
private object Notifications {
|
private object Notifications {
|
||||||
val disconnectedShown = AtomicBoolean(false)
|
val disconnectedShown = AtomicBoolean(true)
|
||||||
val ignoreShown = AtomicBoolean(true)
|
val ignoreShown = AtomicBoolean(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1,006 B |
BIN
app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<vector android:height="24dp" android:tint="#000000"
|
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M12,2L22,8V16L12,22L2,16V8L12,2M12,4.5L4,9V15L12,19.5L20,15V9L12,4.5M12,7L17,10V14L12,17L7,14V10L12,7Z"/>
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -1,3 +0,0 @@
|
||||||
<resources>
|
|
||||||
<dimen name="fab_margin">16dp</dimen>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Preview strings for distributor-ui library -->
|
|
||||||
<string name="preview_app_name" translatable="false">Distrib name</string>
|
|
||||||
<string name="preview_privacy_policy" translatable="false">Distrib privacy policy\ </string>
|
|
||||||
<string name="preview_in_app_notif_link" translatable="false">on the Internet.</string>
|
|
||||||
|
|
||||||
<!-- Library strings referenced by Prism code -->
|
<!-- Library strings referenced by Prism code -->
|
||||||
<string name="toast_url_candidate_fail">Fail to use %1$s</string>
|
<string name="toast_url_candidate_fail">Fail to use %1$s</string>
|
||||||
<string name="toast_url_candidate_success">Successfully using %1$s</string>
|
<string name="toast_url_candidate_success">Successfully using %1$s</string>
|
||||||
|
|
@ -18,14 +13,6 @@
|
||||||
<string name="foreground_notif_ticker">Prism</string>
|
<string name="foreground_notif_ticker">Prism</string>
|
||||||
<string name="bar_unregister_title">%d selected</string>
|
<string name="bar_unregister_title">%d selected</string>
|
||||||
<string name="dialog_unregistering_content">Are you sure to unregister this app?</string>
|
<string name="dialog_unregistering_content">Are you sure to unregister this app?</string>
|
||||||
<plurals name="bar_unregister_title">
|
|
||||||
<item quantity="one">%d selected</item>
|
|
||||||
<item quantity="other">%d selected</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="dialog_unregistering_content">
|
|
||||||
<item quantity="one">Are you sure to unregister this app?</item>
|
|
||||||
<item quantity="other">Are you sure to unregister %d apps?</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="foreground_notif_content_with_reg">
|
<plurals name="foreground_notif_content_with_reg">
|
||||||
<item quantity="one">Connected for %d app</item>
|
<item quantity="one">Connected for %d app</item>
|
||||||
<item quantity="other">Connected for %d apps</item>
|
<item quantity="other">Connected for %d apps</item>
|
||||||
|
|
@ -34,6 +21,13 @@
|
||||||
<!-- Prism-specific strings -->
|
<!-- Prism-specific strings -->
|
||||||
<string name="app_name">Prism</string>
|
<string name="app_name">Prism</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
|
<string name="push_service_title">Push Service</string>
|
||||||
|
<string name="push_service_description">Set the push server URL. Default is Mozilla, or use your own compatible self-hosted service.</string>
|
||||||
|
<string name="push_service_crowd_blending_warning">Warning: changing from the default Mozilla server may reduce crowd blending.</string>
|
||||||
|
<string name="push_service_url_label">Push service URL</string>
|
||||||
|
<string name="push_service_url_placeholder">https://push.example.com</string>
|
||||||
|
<string name="push_service_save_button">Save</string>
|
||||||
|
<string name="push_service_reset_button">Use default</string>
|
||||||
<string name="add_custom_app_title">Register a New App</string>
|
<string name="add_custom_app_title">Register a New App</string>
|
||||||
<string name="add_app_description">Register an app to receive notifications from your Prism server. Optionally choose a target app to deliver notifications to.</string>
|
<string name="add_app_description">Register an app to receive notifications from your Prism server. Optionally choose a target app to deliver notifications to.</string>
|
||||||
<string name="app_name_label">App Name</string>
|
<string name="app_name_label">App Name</string>
|
||||||
|
|
@ -50,12 +44,9 @@
|
||||||
<string name="search_apps_placeholder">Search for apps</string>
|
<string name="search_apps_placeholder">Search for apps</string>
|
||||||
<string name="add_manual_app_content_description">Register a new app</string>
|
<string name="add_manual_app_content_description">Register a new app</string>
|
||||||
<string name="registered_apps_heading">Registered Apps</string>
|
<string name="registered_apps_heading">Registered Apps</string>
|
||||||
<string name="debug_title">Debug Information</string>
|
<string name="configure_server">Prism Server</string>
|
||||||
<string name="configure_server">Configure a Prism server</string>
|
|
||||||
<string name="prism_server_info">Prism server lets you receive notifications from custom services and must be self-hosted.</string>
|
<string name="prism_server_info">Prism server lets you receive notifications from custom services and must be self-hosted.</string>
|
||||||
<string name="prism_server_learn_more">Learn more about Prism</string>
|
<string name="prism_server_learn_more">Learn more about Prism</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">prism.example.com</string>
|
<string name="prism_server_url_placeholder">prism.example.com</string>
|
||||||
|
|
@ -69,8 +60,8 @@
|
||||||
<string name="clear_server_confirm_title">Remove Prism Server?</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_no_apps">This will remove your Prism server configuration.</string>
|
||||||
<string name="clear_server_confirm_message_with_apps">You have %d manual app(s). Clearing the server will unregister them from the server and remove the configuration.</string>
|
<string name="clear_server_confirm_message_with_apps">You have %d manual app(s). Clearing the server will unregister them from the server and remove the configuration.</string>
|
||||||
<string name="app_dropdown_show_toasts">Notify when apps register</string>
|
<string name="app_dropdown_show_toasts">Registration alerts</string>
|
||||||
<string name="show_toasts_description">Show a notification when apps register or unregister</string>
|
<string name="show_toasts_description">Show a notification when apps register</string>
|
||||||
<string name="dynamic_colors_title">Dynamic colors</string>
|
<string name="dynamic_colors_title">Dynamic colors</string>
|
||||||
<string name="dynamic_colors_description">Use colors from your wallpaper</string>
|
<string name="dynamic_colors_description">Use colors from your wallpaper</string>
|
||||||
<string name="registration_details_title">Registration Details</string>
|
<string name="registration_details_title">Registration Details</string>
|
||||||
|
|
@ -82,15 +73,12 @@
|
||||||
<string name="registration_details_type">Registration Type</string>
|
<string name="registration_details_type">Registration Type</string>
|
||||||
<string name="registration_details_type_manual">Manual</string>
|
<string name="registration_details_type_manual">Manual</string>
|
||||||
<string name="registration_details_type_standard">Standard</string>
|
<string name="registration_details_type_standard">Standard</string>
|
||||||
<string name="delete_registration">Delete registration</string>
|
|
||||||
<string name="delete_registration_title">Delete registration?</string>
|
<string name="delete_registration_title">Delete registration?</string>
|
||||||
<string name="delete_registration_message">This will unregister the app and remove it from your list.</string>
|
<string name="delete_registration_message">This will unregister the app and remove it from your list.</string>
|
||||||
<string name="delete_registration_confirm">Delete</string>
|
<string name="delete_registration_confirm">Delete</string>
|
||||||
|
|
||||||
<!-- Intro screen strings -->
|
<!-- Intro screen strings -->
|
||||||
<string name="intro_welcome_title">Welcome to Prism</string>
|
|
||||||
<string name="intro_welcome_message">Prism handles push notifications for your apps. Connect a Prism server to also receive notifications from custom services.</string>
|
<string name="intro_welcome_message">Prism handles push notifications for your apps. Connect a Prism server to also receive notifications from custom services.</string>
|
||||||
<string name="intro_server_optional">Configure a Prism server now or skip to set it up later in Settings.</string>
|
|
||||||
<string name="intro_continue_button">Continue</string>
|
<string name="intro_continue_button">Continue</string>
|
||||||
<string name="intro_skip_button">Skip for now</string>
|
<string name="intro_skip_button">Skip for now</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
13
gradle/gradle-daemon-jvm.properties
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#This file is generated by updateDaemonJvm
|
||||||
|
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||||
|
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
|
||||||
|
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||||
|
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
|
||||||
|
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect
|
||||||
|
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/9c55677aff3966382f3d853c0959bfb2/redirect
|
||||||
|
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
|
||||||
|
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
|
||||||
|
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/39846e8427e64a3824c13e399d7d813c/redirect
|
||||||
|
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ac151d55def6b6a9a159dc4cb4642851/redirect
|
||||||
|
toolchainVendor=JETBRAINS
|
||||||
|
toolchainVersion=21
|
||||||
|
|
@ -2,22 +2,23 @@
|
||||||
android-gradle-plugin = "8.13.2"
|
android-gradle-plugin = "8.13.2"
|
||||||
androidx-activityCompose = "1.12.1"
|
androidx-activityCompose = "1.12.1"
|
||||||
androidx-lifecycle = "2.10.0"
|
androidx-lifecycle = "2.10.0"
|
||||||
androidx-work = "2.11.0"
|
androidx-work = "2.11.1"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
unifiedpush_distributor = "0.7.2"
|
unifiedpush_distributor = "0.7.3"
|
||||||
unifiedpush_distributor_base = "0.7.0"
|
unifiedpush_distributor_base = "0.7.3"
|
||||||
accompanist_permissions = "0.37.3"
|
accompanist_permissions = "0.37.3"
|
||||||
tink = "1.15.0"
|
tink = "1.20.0"
|
||||||
kotlin = "2.2.20"
|
kotlin = "2.2.20"
|
||||||
kotlinx_serializationJson = "1.9.0"
|
kotlinx_serializationJson = "1.10.0"
|
||||||
ktlint = "14.0.1"
|
ktlint = "14.0.1"
|
||||||
detekt = "1.23.7"
|
detekt = "1.23.8"
|
||||||
material3Android = "1.4.0"
|
material3Android = "1.4.0"
|
||||||
materialIconsCore = "1.7.8"
|
materialIconsCore = "1.7.8"
|
||||||
okhttp = "5.3.2"
|
okhttp = "5.3.2"
|
||||||
uiTooling = "1.10.0"
|
uiTooling = "1.10.0"
|
||||||
runtimeAndroid = "1.10.0"
|
runtimeAndroid = "1.10.0"
|
||||||
navigationCompose = "2.9.6"
|
navigationCompose = "2.9.7"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ pluginManagement {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
plugins {
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
|
|
||||||