new icon, dep updates, new gradle daemon props, allow changing the push server

This commit is contained in:
Egor 2026-02-18 22:56:31 -08:00
parent 874444c6be
commit acb364155c
54 changed files with 310 additions and 117 deletions

View file

@ -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

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View file

@ -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()
}

View file

@ -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)

View file

@ -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)
} }
} }

View file

@ -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")

View file

@ -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()

View file

@ -52,6 +52,7 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
SettingsViewModel( SettingsViewModel(
SettingsState( SettingsState(
showToasts = false, showToasts = false,
pushServiceUrl = "",
prismServerUrl = "", prismServerUrl = "",
prismApiKey = "" prismApiKey = ""
), ),

View file

@ -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)
) )

View file

@ -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) },

View file

@ -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)
) )

View file

@ -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
) )
} }

View file

@ -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))
}
}
}
}

View file

@ -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)

View file

@ -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),

View file

@ -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 ?: ""
) )

View file

@ -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"
} }

View file

@ -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
} }

View file

@ -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 */ }

View file

@ -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++
} }

View file

@ -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)
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

View file

@ -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>

View 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

View file

@ -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" }

View file

@ -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)