Migrate main activity to jetpack
This commit is contained in:
parent
1d206c92f8
commit
9ac56626cb
|
@ -1,13 +1,15 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
}
|
alias(libs.plugins.compose.compiler)
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(17)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
@ -18,6 +20,10 @@ android {
|
||||||
versionName = "1.9.0"
|
versionName = "1.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
resValue("string", "app_name", "NextPush")
|
resValue("string", "app_name", "NextPush")
|
||||||
|
@ -57,10 +63,11 @@ if (project.hasProperty("sign")) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.androidx.coordinatorlayout)
|
implementation(libs.androidx.coordinatorlayout)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.work.runtime.ktx)
|
implementation(libs.androidx.work.runtime.ktx)
|
||||||
implementation(libs.appcompat)
|
implementation(libs.appcompat)
|
||||||
implementation(libs.kotlin.stdlib)
|
implementation(libs.kotlin.stdlib)
|
||||||
|
@ -72,4 +79,7 @@ dependencies {
|
||||||
implementation(libs.retrofit.retrofit)
|
implementation(libs.retrofit.retrofit)
|
||||||
implementation(libs.rxjava3.rxandroid)
|
implementation(libs.rxjava3.rxandroid)
|
||||||
implementation(libs.rxjava3.rxjava)
|
implementation(libs.rxjava3.rxjava)
|
||||||
|
implementation(libs.androidx.material3.android)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling.preview.android)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,11 +34,8 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity android:name=".activities.MainActivity">
|
||||||
android:name=".activities.MainActivity"
|
|
||||||
android:theme="@style/Theme.NextPush.NoActionBar">
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".activities.LinkActivity"
|
<activity android:name=".activities.LinkActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
|
||||||
|
object EventBus {
|
||||||
|
val mutEvents: MutableSharedFlow<Any> = MutableSharedFlow()
|
||||||
|
val events = mutEvents.asSharedFlow()
|
||||||
|
|
||||||
|
suspend inline fun <reified T : Any> publish(event: T) {
|
||||||
|
if (mutEvents.subscriptionCount.value > 0) {
|
||||||
|
mutEvents.emit(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <reified T> subscribe(crossinline onEvent: (T) -> Unit) {
|
||||||
|
events.filterIsInstance<T>()
|
||||||
|
.collectLatest { event ->
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
onEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.unifiedpush.distributor.nextpush.AppStore
|
||||||
|
import org.unifiedpush.distributor.nextpush.EventBus
|
||||||
|
import org.unifiedpush.distributor.nextpush.LocalNotification
|
||||||
|
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
||||||
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor
|
||||||
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
|
||||||
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteDevice
|
||||||
|
import org.unifiedpush.distributor.nextpush.services.FailureHandler
|
||||||
|
import org.unifiedpush.distributor.nextpush.services.RestartWorker
|
||||||
|
import org.unifiedpush.distributor.nextpush.services.StartService
|
||||||
|
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||||
|
|
||||||
|
class AppAction(private val type: Type, private val argv: Map<String, Any>? = null) {
|
||||||
|
enum class Type {
|
||||||
|
RestartService,
|
||||||
|
Logout,
|
||||||
|
AddChannel,
|
||||||
|
DisableBatteryOptimisation,
|
||||||
|
CopyEndpoint,
|
||||||
|
DeleteRegistration,
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handle(context: Context) {
|
||||||
|
when (type) {
|
||||||
|
Type.RestartService -> restartService(context)
|
||||||
|
Type.Logout -> logout(context)
|
||||||
|
Type.AddChannel -> addChannel(context, argv)
|
||||||
|
Type.DisableBatteryOptimisation -> disableBatteryOptimisation(context)
|
||||||
|
Type.CopyEndpoint -> copyEndpoint(context, argv)
|
||||||
|
Type.DeleteRegistration -> deleteRegistration(context, argv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restartService(context: Context) {
|
||||||
|
Log.d(TAG, "Restarting the Listener")
|
||||||
|
FailureHandler.clearFails()
|
||||||
|
StartService.stopService {
|
||||||
|
RestartWorker.run(context, delay = 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logout(context: Context) {
|
||||||
|
deleteDevice(context) {
|
||||||
|
StartService.stopService()
|
||||||
|
FailureHandler.clearFails()
|
||||||
|
}
|
||||||
|
AccountFactory.logout(context)
|
||||||
|
AppStore(context).wipe()
|
||||||
|
UiAction.publish(UiAction.Type.Logout)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addChannel(context: Context, argv: Map<String, Any>?) {
|
||||||
|
(argv?.get(ARG_NEW_CHANNEL_TITLE) as String?)?.let {
|
||||||
|
LocalNotification.createChannel(
|
||||||
|
context,
|
||||||
|
it
|
||||||
|
) {
|
||||||
|
Log.d(TAG, "Channel \"$it\" created")
|
||||||
|
UiAction.publish(UiAction.Type.UpdateRegistrations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
private fun disableBatteryOptimisation(context: Context) {
|
||||||
|
Log.d(TAG, "Disabling battery optimization")
|
||||||
|
try {
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||||
|
Uri.parse("package:${context.packageName}")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
try {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
|
||||||
|
} catch (e2: ActivityNotFoundException) {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyEndpoint(context: Context, argv: Map<String, Any>?) {
|
||||||
|
val token = argv?.get(ARG_TOKEN) as String? ?: return
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip: ClipData = ClipData.newPlainText(
|
||||||
|
"Endpoint",
|
||||||
|
Distributor.getEndpoint(context, token)
|
||||||
|
)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteRegistration(context: Context, argv: Map<String, Any>?) {
|
||||||
|
val registrations = argv?.get(ARG_REGISTRATIONS) as List<String>? ?: return
|
||||||
|
registrations?.forEach {
|
||||||
|
deleteApp(context, it) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ARG_NEW_CHANNEL_TITLE = "title"
|
||||||
|
const val ARG_TOKEN = "token"
|
||||||
|
const val ARG_REGISTRATIONS = "registrations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ViewModel.publishAction(action: AppAction) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
EventBus.publish(action)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,95 +0,0 @@
|
||||||
package org.unifiedpush.distributor.nextpush.activities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.SparseBooleanArray
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
|
||||||
import org.unifiedpush.distributor.nextpush.R
|
|
||||||
import org.unifiedpush.distributor.nextpush.utils.getApplicationName
|
|
||||||
|
|
||||||
data class App(
|
|
||||||
val token: String,
|
|
||||||
val packageId: String
|
|
||||||
)
|
|
||||||
|
|
||||||
class AppListAdapter(context: Context, private val resource: Int, apps: List<App>) : ArrayAdapter<App>(context, resource, apps) {
|
|
||||||
private var selectedItemsIds = SparseBooleanArray()
|
|
||||||
private val inflater = LayoutInflater.from(context)
|
|
||||||
private val db = getDb(context)
|
|
||||||
|
|
||||||
private class ViewHolder {
|
|
||||||
var name: TextView? = null
|
|
||||||
var description: TextView? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getView(position: Int, pConvertView: View?, parent: ViewGroup): View {
|
|
||||||
var viewHolder: ViewHolder? = null
|
|
||||||
val convertView = pConvertView?.apply {
|
|
||||||
viewHolder = tag as ViewHolder
|
|
||||||
} ?: run {
|
|
||||||
val rConvertView = inflater.inflate(resource, parent, false)
|
|
||||||
viewHolder = ViewHolder().apply {
|
|
||||||
this.name = rConvertView.findViewById(R.id.item_app_name) as TextView
|
|
||||||
this.description = rConvertView.findViewById(R.id.item_description) as TextView
|
|
||||||
}
|
|
||||||
rConvertView.apply {
|
|
||||||
tag = viewHolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getItem(position)?.let {
|
|
||||||
if (it.packageId == context.packageName) {
|
|
||||||
setViewHolderForLocalChannel(viewHolder, it)
|
|
||||||
} else {
|
|
||||||
setViewHolderForUnifiedPushApp(viewHolder, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedItemsIds.get(position)) {
|
|
||||||
convertView?.setBackgroundColor(
|
|
||||||
MaterialColors.getColor(convertView, com.google.android.material.R.attr.colorOnTertiary)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
convertView?.setBackgroundResource(0)
|
|
||||||
}
|
|
||||||
return convertView
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setViewHolderForUnifiedPushApp(viewHolder: ViewHolder?, app: App) {
|
|
||||||
context.getApplicationName(app.packageId)?.let {
|
|
||||||
viewHolder?.name?.text = it
|
|
||||||
viewHolder?.description?.text = app.packageId
|
|
||||||
} ?: run {
|
|
||||||
viewHolder?.name?.text = app.packageId
|
|
||||||
viewHolder?.description?.isGone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setViewHolderForLocalChannel(viewHolder: ViewHolder?, app: App) {
|
|
||||||
val title = db.getNotificationTitle(app.token)
|
|
||||||
viewHolder?.name?.text = context.getString(R.string.local_notif_title).format(title)
|
|
||||||
viewHolder?.description?.text = context.getString(R.string.local_notif_description)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleSelection(position: Int) {
|
|
||||||
selectView(position, !selectedItemsIds.get(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeSelection() {
|
|
||||||
selectedItemsIds = SparseBooleanArray()
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun selectView(position: Int, value: Boolean) {
|
|
||||||
selectedItemsIds.put(position, value)
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSelectedIds(): SparseBooleanArray {
|
|
||||||
return selectedItemsIds
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,366 +1,82 @@
|
||||||
package org.unifiedpush.distributor.nextpush.activities
|
package org.unifiedpush.distributor.nextpush.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.text.Html
|
|
||||||
import android.text.InputType
|
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import androidx.activity.ComponentActivity
|
||||||
import android.view.ActionMode
|
import androidx.activity.compose.setContent
|
||||||
import android.view.Menu
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import android.view.MenuItem
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import android.view.View
|
import kotlinx.coroutines.Dispatchers
|
||||||
import android.widget.* // ktlint-disable no-wildcard-imports
|
import kotlinx.coroutines.Job
|
||||||
import android.widget.AbsListView.MultiChoiceModeListener
|
import kotlinx.coroutines.launch
|
||||||
import androidx.appcompat.app.AlertDialog
|
import org.unifiedpush.distributor.nextpush.EventBus
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import org.unifiedpush.distributor.nextpush.activities.ui.MainUi
|
||||||
import androidx.core.util.size
|
import org.unifiedpush.distributor.nextpush.activities.ui.theme.AppTheme
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.setPadding
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.unifiedpush.distributor.nextpush.AppStore
|
|
||||||
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
|
||||||
import org.unifiedpush.distributor.nextpush.LocalNotification
|
|
||||||
import org.unifiedpush.distributor.nextpush.R
|
|
||||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
|
||||||
import org.unifiedpush.distributor.nextpush.activities.PermissionsRequest.requestAppPermissions
|
|
||||||
import org.unifiedpush.distributor.nextpush.activities.StartActivity.Companion.goToStartActivity
|
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor
|
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
|
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteDevice
|
|
||||||
import org.unifiedpush.distributor.nextpush.services.FailureHandler
|
|
||||||
import org.unifiedpush.distributor.nextpush.services.RestartWorker
|
import org.unifiedpush.distributor.nextpush.services.RestartWorker
|
||||||
import org.unifiedpush.distributor.nextpush.services.StartService
|
|
||||||
import org.unifiedpush.distributor.nextpush.utils.TAG
|
import org.unifiedpush.distributor.nextpush.utils.TAG
|
||||||
import org.unifiedpush.distributor.nextpush.utils.copyToClipboard
|
|
||||||
import org.unifiedpush.distributor.nextpush.utils.getDebugInfo
|
|
||||||
import java.lang.String.format
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
private var viewModel: MainViewModel? = null
|
||||||
private lateinit var listView: ListView
|
private var jobs: MutableList<Job> = emptyList<Job>().toMutableList()
|
||||||
|
|
||||||
// if the unregister dialog is shown, we prevent the list to be reset
|
|
||||||
private var preventListReset = false
|
|
||||||
private var lastClickTime = 0L
|
|
||||||
private var clickCount = 0
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
setSupportActionBar(findViewById(R.id.toolbar))
|
|
||||||
this.requestAppPermissions()
|
|
||||||
if (AccountFactory.getAccount(this)?.connected != true) {
|
|
||||||
Log.d(TAG, "Not connected: going to StartActivity.")
|
|
||||||
goToStartActivity(this)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
findViewById<TextView>(R.id.main_account_desc).text =
|
|
||||||
format(getString(R.string.main_account_desc), AccountFactory.getAccount(this)?.name)
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
RestartWorker.startPeriodic(this)
|
RestartWorker.startPeriodic(this)
|
||||||
setDebugInformationListener()
|
|
||||||
findViewById<View>(android.R.id.content)?.setOnApplyWindowInsetsListener { _, insets ->
|
setContent {
|
||||||
val statusBarSize = insets.systemWindowInsetTop
|
val viewModel =
|
||||||
findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar).setPadding(0, statusBarSize , 0, 0)
|
viewModel {
|
||||||
return@setOnApplyWindowInsetsListener insets
|
MainViewModel(this@MainActivity)
|
||||||
|
}.also {
|
||||||
|
viewModel = it
|
||||||
|
}
|
||||||
|
AppTheme {
|
||||||
|
MainUi(viewModel)
|
||||||
|
}
|
||||||
|
subscribeActions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
private fun subscribeActions() {
|
||||||
super.onStart()
|
Log.d(TAG, "Subscribing to actions")
|
||||||
showOptimisationWarning()
|
jobs += CoroutineScope(Dispatchers.IO).launch {
|
||||||
}
|
EventBus.subscribe<AppAction> { it.handle(this@MainActivity) }
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
|
||||||
super.onWindowFocusChanged(hasFocus)
|
|
||||||
if (hasFocus) {
|
|
||||||
if (preventListReset) {
|
|
||||||
preventListReset = false
|
|
||||||
} else {
|
|
||||||
setListView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
jobs += CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
EventBus.subscribe<UiAction> {
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
it.handle { type ->
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
when (type) {
|
||||||
return true
|
UiAction.Type.UpdateRegistrations -> viewModel?.updateRegistrations(
|
||||||
}
|
this@MainActivity
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
return when (item.itemId) {
|
|
||||||
R.id.action_restart -> {
|
|
||||||
restart()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.action_logout -> {
|
|
||||||
logout()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.action_add_local_channel -> {
|
|
||||||
addChannel()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
private fun showOptimisationWarning() {
|
|
||||||
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
||||||
if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
|
|
||||||
findViewById<MaterialCardView>(R.id.card_battery_optimization)?.isGone = false
|
|
||||||
findViewById<Button>(R.id.button_disable_optimisation)?.setOnClickListener {
|
|
||||||
try {
|
|
||||||
startActivity(
|
|
||||||
Intent(
|
|
||||||
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
|
||||||
Uri.parse("package:$packageName")
|
|
||||||
)
|
)
|
||||||
)
|
UiAction.Type.Logout -> {
|
||||||
} catch (e: ActivityNotFoundException) {
|
StartActivity.goToStartActivity(this@MainActivity)
|
||||||
try {
|
finish()
|
||||||
startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
|
}
|
||||||
} catch (e2: ActivityNotFoundException) {
|
|
||||||
startActivity(Intent(Settings.ACTION_SETTINGS))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findViewById<MaterialCardView>(R.id.card_battery_optimization)?.isGone = true
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
findViewById<MaterialCardView>(R.id.card_battery_optimization)?.isGone = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restart() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Restarting the Listener")
|
Log.d(TAG, "Destroy")
|
||||||
FailureHandler.clearFails()
|
jobs.removeAll {
|
||||||
StartService.stopService {
|
it.cancel()
|
||||||
RestartWorker.run(this, delay = 0)
|
true
|
||||||
}
|
}
|
||||||
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logout() {
|
|
||||||
val alert = AlertDialog.Builder(
|
|
||||||
this
|
|
||||||
)
|
|
||||||
alert.setTitle(getString(R.string.logout_alert_title))
|
|
||||||
alert.setMessage(R.string.logout_alert_content)
|
|
||||||
alert.setPositiveButton(R.string.ok) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
deleteDevice(this) {
|
|
||||||
StartService.stopService()
|
|
||||||
FailureHandler.clearFails()
|
|
||||||
}
|
|
||||||
AccountFactory.logout(this)
|
|
||||||
AppStore(this).wipe()
|
|
||||||
finish()
|
|
||||||
goToStartActivity(this)
|
|
||||||
}
|
|
||||||
alert.setNegativeButton(getString(R.string.discard)) { dialog, _ -> dialog.dismiss() }
|
|
||||||
alert.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addChannel() {
|
|
||||||
val builder = AlertDialog.Builder(
|
|
||||||
this
|
|
||||||
)
|
|
||||||
val input = EditText(this)
|
|
||||||
input.inputType = InputType.TYPE_CLASS_TEXT
|
|
||||||
input.hint = "My Title"
|
|
||||||
val pad32 = TypedValue.applyDimension(
|
|
||||||
TypedValue.COMPLEX_UNIT_PX,
|
|
||||||
32F,
|
|
||||||
resources.displayMetrics
|
|
||||||
).toInt()
|
|
||||||
input.setPadding(pad32)
|
|
||||||
builder.setView(input)
|
|
||||||
builder.setTitle("Notification Channel")
|
|
||||||
builder.setMessage(Html.fromHtml(getString(R.string.add_channel_dialog_content), Html.FROM_HTML_MODE_LEGACY))
|
|
||||||
builder.setPositiveButton(R.string.ok) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
Log.d(TAG, "title: ${input.text}")
|
|
||||||
LocalNotification.createChannel(
|
|
||||||
this,
|
|
||||||
input.text.toString()
|
|
||||||
) {
|
|
||||||
setListView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.setNegativeButton(getString(R.string.discard)) { dialog, _ -> dialog.dismiss() }
|
|
||||||
builder.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldShowCopyItem(listView: ListView, appListAdapter: AppListAdapter): Boolean {
|
|
||||||
if (listView.checkedItemCount == 1) {
|
|
||||||
val selected = appListAdapter.getSelectedIds()
|
|
||||||
var i = selected.size - 1
|
|
||||||
while (i >= 0) {
|
|
||||||
if (selected.valueAt(i)) {
|
|
||||||
return appListAdapter.getItem(selected.keyAt(i))?.packageId == packageName
|
|
||||||
}
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setListView() {
|
|
||||||
listView = findViewById(R.id.applications_list)
|
|
||||||
|
|
||||||
val appList = emptyList<App>().toMutableList()
|
|
||||||
|
|
||||||
getDb(this).let { db ->
|
|
||||||
db.listTokens().forEach {
|
|
||||||
appList.add(
|
|
||||||
App(token = it, packageId = db.getPackageName(it) ?: it)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val editListAdapter = AppListAdapter(
|
|
||||||
this,
|
|
||||||
R.layout.item_app,
|
|
||||||
appList
|
|
||||||
)
|
|
||||||
|
|
||||||
listView.adapter = editListAdapter
|
|
||||||
listView.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
|
|
||||||
|
|
||||||
var copyEndpointItem: MenuItem? = null
|
|
||||||
|
|
||||||
listView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
|
|
||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
|
||||||
editListAdapter.removeSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemCheckedStateChanged(
|
|
||||||
mode: ActionMode,
|
|
||||||
position: Int,
|
|
||||||
id: Long,
|
|
||||||
checked: Boolean
|
|
||||||
) {
|
|
||||||
val checkedCount = listView.checkedItemCount
|
|
||||||
mode.title = "$checkedCount selected"
|
|
||||||
editListAdapter.toggleSelection(position)
|
|
||||||
copyEndpointItem?.isVisible = shouldShowCopyItem(listView, editListAdapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
|
|
||||||
mode.menuInflater.inflate(R.menu.menu_delete, menu)
|
|
||||||
copyEndpointItem = menu?.findItem(R.id.action_copy_endpoint)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
|
||||||
Log.d(TAG, "Action clicked")
|
|
||||||
return when (item.itemId) {
|
|
||||||
R.id.action_delete -> {
|
|
||||||
Log.d(TAG, "deleting")
|
|
||||||
val selected = editListAdapter.getSelectedIds()
|
|
||||||
val alert = MaterialAlertDialogBuilder(this@MainActivity)
|
|
||||||
alert.setTitle(getString(R.string.dialog_unregistering_title))
|
|
||||||
alert.setMessage(getString(R.string.dialog_unregistering_content).format(selected.size))
|
|
||||||
alert.setPositiveButton(getString(R.string.dialog_yes)) { dialog, _ ->
|
|
||||||
var i = selected.size - 1
|
|
||||||
while (i >= 0) {
|
|
||||||
if (selected.valueAt(i)) {
|
|
||||||
editListAdapter.getItem(selected.keyAt(i))?.let {
|
|
||||||
deleteApp(this@MainActivity, it.token) {
|
|
||||||
Log.d(TAG, "${it.packageId} unregistered")
|
|
||||||
editListAdapter.remove(it)
|
|
||||||
this@MainActivity.runOnUiThread {
|
|
||||||
setListView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preventListReset = false
|
|
||||||
dialog.dismiss()
|
|
||||||
mode.finish()
|
|
||||||
}
|
|
||||||
alert.setNegativeButton(getString(R.string.dialog_no)) { dialog, _ -> dialog.dismiss() }
|
|
||||||
alert.setOnCancelListener {
|
|
||||||
Log.d(TAG, "Canceled")
|
|
||||||
}
|
|
||||||
preventListReset = true
|
|
||||||
alert.show()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.action_copy_endpoint -> {
|
|
||||||
Log.d(TAG, "Copying endpoint")
|
|
||||||
val selected = editListAdapter.getSelectedIds()
|
|
||||||
if (selected.size > 1) {
|
|
||||||
Log.e(TAG, "Copying endpoint for more than an app")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
editListAdapter.getItem(selected.keyAt(0))?.let {
|
|
||||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
val clip: ClipData = ClipData.newPlainText("simple text", Distributor.getEndpoint(this@MainActivity, it.token))
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
mode.finish()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDebugInformationListener() {
|
|
||||||
findViewById<TextView>(R.id.main_account_title).setOnClickListener {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastClickTime < 500) {
|
|
||||||
clickCount++
|
|
||||||
if (clickCount == 5) {
|
|
||||||
val msg = SpannableStringBuilder(getDebugInfo())
|
|
||||||
MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle("Debug information")
|
|
||||||
.setMessage(msg)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(android.R.string.ok) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.setNeutralButton(android.R.string.copy) { dialog, _ ->
|
|
||||||
copyToClipboard(this, "Debug Information", msg.toString())
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
|
|
||||||
clickCount = 0 // Reset count after showing the dialog
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clickCount = 1
|
|
||||||
}
|
|
||||||
lastClickTime = currentTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
companion object {
|
companion object {
|
||||||
fun goToMainActivity(context: Context) {
|
fun goToMainActivity(context: Context) {
|
||||||
val intent = Intent(
|
val intent =
|
||||||
context,
|
Intent(
|
||||||
MainActivity::class.java
|
context,
|
||||||
)
|
MainActivity::class.java
|
||||||
|
)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.ui.MainUiState
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.ui.RegistrationListState
|
||||||
|
|
||||||
|
class MainViewModel(
|
||||||
|
mainUiState: MainUiState,
|
||||||
|
// We don't need liveData for this little list
|
||||||
|
registrationsState: RegistrationListState
|
||||||
|
) : ViewModel() {
|
||||||
|
constructor(context: Context) : this(
|
||||||
|
mainUiState = MainUiState(context),
|
||||||
|
registrationsState = RegistrationListState(context)
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainUiState by mutableStateOf(mainUiState)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var registrationsState by mutableStateOf(registrationsState)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun disableBatteryOptimization() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
mainUiState = mainUiState.copy(requireBatteryOptimization = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleSelection(token: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val newList =
|
||||||
|
registrationsState.list.toMutableList().apply {
|
||||||
|
replaceAll {
|
||||||
|
if (it.token == token) {
|
||||||
|
it.copy(selected = !it.selected)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registrationsState = RegistrationListState(
|
||||||
|
list = newList,
|
||||||
|
hasSelection = newList.any {
|
||||||
|
it.selected
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateRegistrations(context: Context) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
registrationsState = RegistrationListState(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unselectAll() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val newList =
|
||||||
|
registrationsState.list.toMutableList().apply {
|
||||||
|
replaceAll {
|
||||||
|
it.copy(selected = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registrationsState = RegistrationListState(list = newList, hasSelection = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSelection() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val tokenList = registrationsState.list.filter { it.selected }.map { it.token }
|
||||||
|
publishAction(
|
||||||
|
AppAction(
|
||||||
|
AppAction.Type.DeleteRegistration,
|
||||||
|
mapOf(
|
||||||
|
AppAction.ARG_REGISTRATIONS to tokenList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
registrationsState = RegistrationListState(
|
||||||
|
list = registrationsState.list.filter {
|
||||||
|
!it.selected
|
||||||
|
},
|
||||||
|
hasSelection = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.unifiedpush.distributor.nextpush.EventBus
|
||||||
|
|
||||||
|
class UiAction(val type: Type) {
|
||||||
|
enum class Type {
|
||||||
|
UpdateRegistrations,
|
||||||
|
Logout,
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handle(action: (Type) -> Unit) {
|
||||||
|
action(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun publish(type: Type) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
EventBus.publish(UiAction(type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import org.unifiedpush.distributor.nextpush.R
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.AppAction
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.publishAction
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppBarUi(viewModel: ViewModel) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
var showNotificationDialog by remember { mutableStateOf(false) }
|
||||||
|
TopAppBar(
|
||||||
|
colors = TopAppBarDefaults
|
||||||
|
.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.app_name)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
expanded = !expanded
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MoreVert,
|
||||||
|
contentDescription = "Actions"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Dropdown(
|
||||||
|
expanded,
|
||||||
|
actionHandler = { a -> viewModel.publishAction(a) },
|
||||||
|
onDismiss = {
|
||||||
|
expanded = false
|
||||||
|
},
|
||||||
|
onNewChannel = {
|
||||||
|
showNotificationDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (showNotificationDialog) {
|
||||||
|
AddChannelDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showNotificationDialog = false
|
||||||
|
},
|
||||||
|
onConfirmation = {
|
||||||
|
viewModel.publishAction(
|
||||||
|
AppAction(
|
||||||
|
AppAction.Type.AddChannel,
|
||||||
|
mapOf(
|
||||||
|
AppAction.ARG_NEW_CHANNEL_TITLE to it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
showNotificationDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Dropdown(
|
||||||
|
expanded: Boolean,
|
||||||
|
actionHandler: (AppAction) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onNewChannel: () -> Unit
|
||||||
|
) {
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = onDismiss
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
actionHandler(AppAction(AppAction.Type.RestartService, null))
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.app_dropdown_restart))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
actionHandler(AppAction(AppAction.Type.Logout, null))
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.app_dropdown_logout)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
onNewChannel()
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.app_dropdown_add_notification_channel)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AppBarPreview() {
|
||||||
|
AppBarUi(object : ViewModel() {})
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.unifiedpush.distributor.nextpush.R
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.AppAction
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.MainViewModel
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.publishAction
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainUi(viewModel: MainViewModel) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
if (viewModel.registrationsState.hasSelection) {
|
||||||
|
SelectToDeleteBarUi(
|
||||||
|
count = viewModel.registrationsState.list.count { it.selected },
|
||||||
|
onBack = { viewModel.unselectAll() },
|
||||||
|
onDelete = { viewModel.deleteSelection() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AppBarUi(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
MainUiContent(viewModel, innerPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun MainUiContent(viewModel: MainViewModel, innerPadding: PaddingValues) {
|
||||||
|
val mainUiState = viewModel.mainUiState
|
||||||
|
val registrationsState = viewModel.registrationsState
|
||||||
|
val haptics = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
0.dp,
|
||||||
|
innerPadding.calculateTopPadding(),
|
||||||
|
0.dp,
|
||||||
|
innerPadding.calculateBottomPadding()
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
16.dp,
|
||||||
|
0.dp,
|
||||||
|
16.dp,
|
||||||
|
0.dp
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Spacer(Modifier)
|
||||||
|
// ToolBar ?
|
||||||
|
if (mainUiState.requireBatteryOptimization) {
|
||||||
|
Card {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.card_disable_optimisation_description),
|
||||||
|
modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)
|
||||||
|
)
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
onClick = {
|
||||||
|
viewModel.publishAction(
|
||||||
|
AppAction(AppAction.Type.DisableBatteryOptimisation)
|
||||||
|
)
|
||||||
|
viewModel.disableBatteryOptimization()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.button_disable_optimisation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.main_account_desc, mainUiState.accountName),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.main_applications_title),
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
registrationsState.list.forEach { app ->
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.background(
|
||||||
|
if (app.selected) {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.background
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
if (registrationsState.hasSelection) {
|
||||||
|
viewModel.toggleSelection(app.token)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
viewModel.toggleSelection(app.token)
|
||||||
|
},
|
||||||
|
onLongClickLabel = stringResource(
|
||||||
|
R.string.list_registrations_elt_long_click_label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(Modifier.padding(8.dp, 4.dp)) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = app.title ?: app.description,
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = app.title?.let { app.description } ?: "",
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
if (app.inApp) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
viewModel.publishAction(
|
||||||
|
AppAction(
|
||||||
|
AppAction.Type.CopyEndpoint,
|
||||||
|
mapOf(
|
||||||
|
AppAction.ARG_TOKEN to app.token
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
painter = painterResource(R.drawable.ic_content_copy_24),
|
||||||
|
contentDescription = stringResource(
|
||||||
|
R.string.button_copy_endpoint_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 0.5.dp,
|
||||||
|
color = Color.LightGray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun MainPreview() {
|
||||||
|
val regList =
|
||||||
|
listOf(
|
||||||
|
RegistrationState(
|
||||||
|
title = "Application 1",
|
||||||
|
token = "tok1",
|
||||||
|
description = "tld.app.1",
|
||||||
|
inApp = false
|
||||||
|
),
|
||||||
|
RegistrationState(
|
||||||
|
title = "My Channel",
|
||||||
|
token = "tok2",
|
||||||
|
description = stringResource(R.string.list_registrations_local_description),
|
||||||
|
inApp = true,
|
||||||
|
selected = true
|
||||||
|
),
|
||||||
|
RegistrationState(
|
||||||
|
title = null,
|
||||||
|
token = "tok3",
|
||||||
|
description = "tld.app.3",
|
||||||
|
inApp = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
MainUi(
|
||||||
|
MainViewModel(
|
||||||
|
MainUiState(
|
||||||
|
requireBatteryOptimization = true,
|
||||||
|
accountName = "account@domain.tld"
|
||||||
|
),
|
||||||
|
RegistrationListState(list = regList)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
|
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
||||||
|
|
||||||
|
data class MainUiState(
|
||||||
|
val requireBatteryOptimization: Boolean,
|
||||||
|
val accountName: String
|
||||||
|
) {
|
||||||
|
constructor(context: Context) : this(
|
||||||
|
requireBatteryOptimization = !(
|
||||||
|
context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
).isIgnoringBatteryOptimizations(context.packageName),
|
||||||
|
accountName = AccountFactory.getAccount(context)?.name ?: ""
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.unifiedpush.distributor.nextpush.R
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NotificationChannelUi(value: String = "", onValueChanged: (String) -> Unit = {}) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
|
Text(
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(stringResource(R.string.dialog_add_channel_content))
|
||||||
|
append(" ")
|
||||||
|
withLink(
|
||||||
|
LinkAnnotation.Url(url = stringResource(R.string.hyperlink_to_channel_example))
|
||||||
|
) {
|
||||||
|
withStyle(
|
||||||
|
style =
|
||||||
|
SpanStyle(
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append(stringResource(R.string.hyperlink_to_channel_example_text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChanged,
|
||||||
|
label = { Text(stringResource(R.string.dialog_add_channel_input_label)) },
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AddChannelDialog(onDismissRequest: () -> Unit = {}, onConfirmation: (String) -> Unit = {}) {
|
||||||
|
var value by remember { mutableStateOf("") }
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.dialog_add_channel_title))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
NotificationChannelUi(value) {
|
||||||
|
value = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirmation(value)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
||||||
|
|
||||||
|
data class RegistrationListState(
|
||||||
|
val hasSelection: Boolean = false,
|
||||||
|
val list: List<RegistrationState>
|
||||||
|
) {
|
||||||
|
constructor(context: Context) : this(
|
||||||
|
list = emptyList<RegistrationState?>()
|
||||||
|
.toMutableList().also { appList ->
|
||||||
|
getDb(context).let { db ->
|
||||||
|
db.listTokens().forEach {
|
||||||
|
appList.add(
|
||||||
|
RegistrationState.get(context, db, it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.filterNotNull()
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.unifiedpush.distributor.nextpush.Database
|
||||||
|
import org.unifiedpush.distributor.nextpush.R
|
||||||
|
import org.unifiedpush.distributor.nextpush.utils.getApplicationName
|
||||||
|
|
||||||
|
data class RegistrationState(
|
||||||
|
val title: String?,
|
||||||
|
val token: String,
|
||||||
|
val description: String,
|
||||||
|
val inApp: Boolean,
|
||||||
|
val selected: Boolean = false
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun get(context: Context, db: Database, token: String): RegistrationState? {
|
||||||
|
val packageId = db.getPackageName(token) ?: return null
|
||||||
|
val inApp = packageId == context.packageName
|
||||||
|
val title =
|
||||||
|
if (inApp) {
|
||||||
|
db.getNotificationTitle(token)?.let {
|
||||||
|
context.getString(R.string.list_registrations_local_title, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.getApplicationName(packageId)
|
||||||
|
} ?: packageId
|
||||||
|
val description =
|
||||||
|
if (inApp) {
|
||||||
|
context.getString(R.string.list_registrations_local_description)
|
||||||
|
} else {
|
||||||
|
if (title == packageId) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
packageId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RegistrationState(
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
token = token,
|
||||||
|
inApp = inApp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.unifiedpush.distributor.nextpush.R
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SelectToDeleteBarUi(count: Int = 1, onBack: () -> Unit = {}, onDelete: () -> Unit = {}) {
|
||||||
|
var showUnregisterDialog by remember { mutableStateOf(false) }
|
||||||
|
TopAppBar(
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
text = stringResource(R.string.bar_unregister_title, count)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
onBack()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.bar_unregister_back_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showUnregisterDialog = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Delete,
|
||||||
|
contentDescription = stringResource(R.string.bar_unregister_delete_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (showUnregisterDialog) {
|
||||||
|
UnregisterConfirmationDialog(
|
||||||
|
count,
|
||||||
|
onConfirmation = onDelete,
|
||||||
|
onDismissRequest = {
|
||||||
|
showUnregisterDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun UnregisterConfirmationDialog(
|
||||||
|
count: Int = 1,
|
||||||
|
onDismissRequest: () -> Unit = {},
|
||||||
|
onConfirmation: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.dialog_unregistering_title))
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.dialog_unregistering_content, count)) },
|
||||||
|
onDismissRequest = {
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirmation()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(android.R.string.cancel)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val nextcloud = Color(0xFF0F9AE6)
|
||||||
|
|
||||||
|
val primaryLight = Color(0xFF2C638B)
|
||||||
|
val onPrimaryLight = Color(0xFFFFFFFF)
|
||||||
|
val primaryContainerLight = Color(0xFFCCE5FF)
|
||||||
|
val onPrimaryContainerLight = Color(0xFF001D31)
|
||||||
|
val secondaryLight = Color(0xFF51606F)
|
||||||
|
val onSecondaryLight = Color(0xFFFFFFFF)
|
||||||
|
val secondaryContainerLight = Color(0xFFD4E4F6)
|
||||||
|
val onSecondaryContainerLight = Color(0xFF0D1D2A)
|
||||||
|
val tertiaryLight = Color(0xFF67587A)
|
||||||
|
val onTertiaryLight = Color(0xFFFFFFFF)
|
||||||
|
val tertiaryContainerLight = Color(0xFFEDDCFF)
|
||||||
|
val onTertiaryContainerLight = Color(0xFF221534)
|
||||||
|
val errorLight = Color(0xFFBA1A1A)
|
||||||
|
val onErrorLight = Color(0xFFFFFFFF)
|
||||||
|
val errorContainerLight = Color(0xFFFFDAD6)
|
||||||
|
val onErrorContainerLight = Color(0xFF410002)
|
||||||
|
val backgroundLight = Color(0xFFF7F9FF)
|
||||||
|
val onBackgroundLight = Color(0xFF181C20)
|
||||||
|
val surfaceLight = Color(0xFFF7F9FF)
|
||||||
|
val onSurfaceLight = Color(0xFF181C20)
|
||||||
|
val surfaceVariantLight = Color(0xFFDEE3EB)
|
||||||
|
val onSurfaceVariantLight = Color(0xFF42474E)
|
||||||
|
val outlineLight = Color(0xFF72787E)
|
||||||
|
val outlineVariantLight = Color(0xFFC2C7CE)
|
||||||
|
val scrimLight = Color(0xFF000000)
|
||||||
|
val inverseSurfaceLight = Color(0xFF2D3135)
|
||||||
|
val inverseOnSurfaceLight = Color(0xFFEEF1F6)
|
||||||
|
val inversePrimaryLight = Color(0xFF99CCFA)
|
||||||
|
val surfaceDimLight = Color(0xFFD7DADF)
|
||||||
|
val surfaceBrightLight = Color(0xFFF7F9FF)
|
||||||
|
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
|
||||||
|
val surfaceContainerLowLight = Color(0xFFF1F4F9)
|
||||||
|
val surfaceContainerLight = Color(0xFFEBEEF3)
|
||||||
|
val surfaceContainerHighLight = Color(0xFFE6E8EE)
|
||||||
|
val surfaceContainerHighestLight = Color(0xFFE0E2E8)
|
||||||
|
|
||||||
|
val primaryLightMediumContrast = Color(0xFF00476D)
|
||||||
|
val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val primaryContainerLightMediumContrast = Color(0xFF4579A3)
|
||||||
|
val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val secondaryLightMediumContrast = Color(0xFF354453)
|
||||||
|
val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val secondaryContainerLightMediumContrast = Color(0xFF677686)
|
||||||
|
val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val tertiaryLightMediumContrast = Color(0xFF4A3D5D)
|
||||||
|
val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val tertiaryContainerLightMediumContrast = Color(0xFF7D6E92)
|
||||||
|
val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val errorLightMediumContrast = Color(0xFF8C0009)
|
||||||
|
val onErrorLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val errorContainerLightMediumContrast = Color(0xFFDA342E)
|
||||||
|
val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val backgroundLightMediumContrast = Color(0xFFF7F9FF)
|
||||||
|
val onBackgroundLightMediumContrast = Color(0xFF181C20)
|
||||||
|
val surfaceLightMediumContrast = Color(0xFFF7F9FF)
|
||||||
|
val onSurfaceLightMediumContrast = Color(0xFF181C20)
|
||||||
|
val surfaceVariantLightMediumContrast = Color(0xFFDEE3EB)
|
||||||
|
val onSurfaceVariantLightMediumContrast = Color(0xFF3E434A)
|
||||||
|
val outlineLightMediumContrast = Color(0xFF5A6066)
|
||||||
|
val outlineVariantLightMediumContrast = Color(0xFF767B82)
|
||||||
|
val scrimLightMediumContrast = Color(0xFF000000)
|
||||||
|
val inverseSurfaceLightMediumContrast = Color(0xFF2D3135)
|
||||||
|
val inverseOnSurfaceLightMediumContrast = Color(0xFFEEF1F6)
|
||||||
|
val inversePrimaryLightMediumContrast = Color(0xFF99CCFA)
|
||||||
|
val surfaceDimLightMediumContrast = Color(0xFFD7DADF)
|
||||||
|
val surfaceBrightLightMediumContrast = Color(0xFFF7F9FF)
|
||||||
|
val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
|
||||||
|
val surfaceContainerLowLightMediumContrast = Color(0xFFF1F4F9)
|
||||||
|
val surfaceContainerLightMediumContrast = Color(0xFFEBEEF3)
|
||||||
|
val surfaceContainerHighLightMediumContrast = Color(0xFFE6E8EE)
|
||||||
|
val surfaceContainerHighestLightMediumContrast = Color(0xFFE0E2E8)
|
||||||
|
|
||||||
|
val primaryLightHighContrast = Color(0xFF00243B)
|
||||||
|
val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val primaryContainerLightHighContrast = Color(0xFF00476D)
|
||||||
|
val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val secondaryLightHighContrast = Color(0xFF142431)
|
||||||
|
val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val secondaryContainerLightHighContrast = Color(0xFF354453)
|
||||||
|
val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val tertiaryLightHighContrast = Color(0xFF291C3B)
|
||||||
|
val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val tertiaryContainerLightHighContrast = Color(0xFF4A3D5D)
|
||||||
|
val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val errorLightHighContrast = Color(0xFF4E0002)
|
||||||
|
val onErrorLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val errorContainerLightHighContrast = Color(0xFF8C0009)
|
||||||
|
val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val backgroundLightHighContrast = Color(0xFFF7F9FF)
|
||||||
|
val onBackgroundLightHighContrast = Color(0xFF181C20)
|
||||||
|
val surfaceLightHighContrast = Color(0xFFF7F9FF)
|
||||||
|
val onSurfaceLightHighContrast = Color(0xFF000000)
|
||||||
|
val surfaceVariantLightHighContrast = Color(0xFFDEE3EB)
|
||||||
|
val onSurfaceVariantLightHighContrast = Color(0xFF1F242A)
|
||||||
|
val outlineLightHighContrast = Color(0xFF3E434A)
|
||||||
|
val outlineVariantLightHighContrast = Color(0xFF3E434A)
|
||||||
|
val scrimLightHighContrast = Color(0xFF000000)
|
||||||
|
val inverseSurfaceLightHighContrast = Color(0xFF2D3135)
|
||||||
|
val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val inversePrimaryLightHighContrast = Color(0xFFDEEEFF)
|
||||||
|
val surfaceDimLightHighContrast = Color(0xFFD7DADF)
|
||||||
|
val surfaceBrightLightHighContrast = Color(0xFFF7F9FF)
|
||||||
|
val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val surfaceContainerLowLightHighContrast = Color(0xFFF1F4F9)
|
||||||
|
val surfaceContainerLightHighContrast = Color(0xFFEBEEF3)
|
||||||
|
val surfaceContainerHighLightHighContrast = Color(0xFFE6E8EE)
|
||||||
|
val surfaceContainerHighestLightHighContrast = Color(0xFFE0E2E8)
|
||||||
|
|
||||||
|
val primaryDark = Color(0xFF99CCFA)
|
||||||
|
val onPrimaryDark = Color(0xFF003351)
|
||||||
|
val primaryContainerDark = Color(0xFF074B72)
|
||||||
|
val onPrimaryContainerDark = Color(0xFFCCE5FF)
|
||||||
|
val secondaryDark = Color(0xFFB8C8DA)
|
||||||
|
val onSecondaryDark = Color(0xFF23323F)
|
||||||
|
val secondaryContainerDark = Color(0xFF394857)
|
||||||
|
val onSecondaryContainerDark = Color(0xFFD4E4F6)
|
||||||
|
val tertiaryDark = Color(0xFFD1BFE7)
|
||||||
|
val onTertiaryDark = Color(0xFF372A4A)
|
||||||
|
val tertiaryContainerDark = Color(0xFF4E4161)
|
||||||
|
val onTertiaryContainerDark = Color(0xFFEDDCFF)
|
||||||
|
val errorDark = Color(0xFFFFB4AB)
|
||||||
|
val onErrorDark = Color(0xFF690005)
|
||||||
|
val errorContainerDark = Color(0xFF93000A)
|
||||||
|
val onErrorContainerDark = Color(0xFFFFDAD6)
|
||||||
|
val backgroundDark = Color(0xFF101418)
|
||||||
|
val onBackgroundDark = Color(0xFFE0E2E8)
|
||||||
|
val surfaceDark = Color(0xFF101418)
|
||||||
|
val onSurfaceDark = Color(0xFFE0E2E8)
|
||||||
|
val surfaceVariantDark = Color(0xFF42474E)
|
||||||
|
val onSurfaceVariantDark = Color(0xFFC2C7CE)
|
||||||
|
val outlineDark = Color(0xFF8C9198)
|
||||||
|
val outlineVariantDark = Color(0xFF42474E)
|
||||||
|
val scrimDark = Color(0xFF000000)
|
||||||
|
val inverseSurfaceDark = Color(0xFFE0E2E8)
|
||||||
|
val inverseOnSurfaceDark = Color(0xFF2D3135)
|
||||||
|
val inversePrimaryDark = Color(0xFF2C638B)
|
||||||
|
val surfaceDimDark = Color(0xFF101418)
|
||||||
|
val surfaceBrightDark = Color(0xFF36393E)
|
||||||
|
val surfaceContainerLowestDark = Color(0xFF0B0F12)
|
||||||
|
val surfaceContainerLowDark = Color(0xFF181C20)
|
||||||
|
val surfaceContainerDark = Color(0xFF1C2024)
|
||||||
|
val surfaceContainerHighDark = Color(0xFF272A2E)
|
||||||
|
val surfaceContainerHighestDark = Color(0xFF313539)
|
||||||
|
|
||||||
|
val primaryDarkMediumContrast = Color(0xFF9DD0FE)
|
||||||
|
val onPrimaryDarkMediumContrast = Color(0xFF001829)
|
||||||
|
val primaryContainerDarkMediumContrast = Color(0xFF6396C1)
|
||||||
|
val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||||
|
val secondaryDarkMediumContrast = Color(0xFFBCCCDE)
|
||||||
|
val onSecondaryDarkMediumContrast = Color(0xFF071824)
|
||||||
|
val secondaryContainerDarkMediumContrast = Color(0xFF8392A3)
|
||||||
|
val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||||
|
val tertiaryDarkMediumContrast = Color(0xFFD6C3EB)
|
||||||
|
val onTertiaryDarkMediumContrast = Color(0xFF1C102E)
|
||||||
|
val tertiaryContainerDarkMediumContrast = Color(0xFF9A8AAF)
|
||||||
|
val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||||
|
val errorDarkMediumContrast = Color(0xFFFFBAB1)
|
||||||
|
val onErrorDarkMediumContrast = Color(0xFF370001)
|
||||||
|
val errorContainerDarkMediumContrast = Color(0xFFFF5449)
|
||||||
|
val onErrorContainerDarkMediumContrast = Color(0xFF000000)
|
||||||
|
val backgroundDarkMediumContrast = Color(0xFF101418)
|
||||||
|
val onBackgroundDarkMediumContrast = Color(0xFFE0E2E8)
|
||||||
|
val surfaceDarkMediumContrast = Color(0xFF101418)
|
||||||
|
val onSurfaceDarkMediumContrast = Color(0xFFF9FBFF)
|
||||||
|
val surfaceVariantDarkMediumContrast = Color(0xFF42474E)
|
||||||
|
val onSurfaceVariantDarkMediumContrast = Color(0xFFC6CBD3)
|
||||||
|
val outlineDarkMediumContrast = Color(0xFF9EA3AB)
|
||||||
|
val outlineVariantDarkMediumContrast = Color(0xFF7E848B)
|
||||||
|
val scrimDarkMediumContrast = Color(0xFF000000)
|
||||||
|
val inverseSurfaceDarkMediumContrast = Color(0xFFE0E2E8)
|
||||||
|
val inverseOnSurfaceDarkMediumContrast = Color(0xFF272A2E)
|
||||||
|
val inversePrimaryDarkMediumContrast = Color(0xFF0A4C73)
|
||||||
|
val surfaceDimDarkMediumContrast = Color(0xFF101418)
|
||||||
|
val surfaceBrightDarkMediumContrast = Color(0xFF36393E)
|
||||||
|
val surfaceContainerLowestDarkMediumContrast = Color(0xFF0B0F12)
|
||||||
|
val surfaceContainerLowDarkMediumContrast = Color(0xFF181C20)
|
||||||
|
val surfaceContainerDarkMediumContrast = Color(0xFF1C2024)
|
||||||
|
val surfaceContainerHighDarkMediumContrast = Color(0xFF272A2E)
|
||||||
|
val surfaceContainerHighestDarkMediumContrast = Color(0xFF313539)
|
||||||
|
|
||||||
|
val primaryDarkHighContrast = Color(0xFFF9FBFF)
|
||||||
|
val onPrimaryDarkHighContrast = Color(0xFF000000)
|
||||||
|
val primaryContainerDarkHighContrast = Color(0xFF9DD0FE)
|
||||||
|
val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
|
||||||
|
val secondaryDarkHighContrast = Color(0xFFF9FBFF)
|
||||||
|
val onSecondaryDarkHighContrast = Color(0xFF000000)
|
||||||
|
val secondaryContainerDarkHighContrast = Color(0xFFBCCCDE)
|
||||||
|
val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
|
||||||
|
val tertiaryDarkHighContrast = Color(0xFFFFF9FD)
|
||||||
|
val onTertiaryDarkHighContrast = Color(0xFF000000)
|
||||||
|
val tertiaryContainerDarkHighContrast = Color(0xFFD6C3EB)
|
||||||
|
val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
|
||||||
|
val errorDarkHighContrast = Color(0xFFFFF9F9)
|
||||||
|
val onErrorDarkHighContrast = Color(0xFF000000)
|
||||||
|
val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
|
||||||
|
val onErrorContainerDarkHighContrast = Color(0xFF000000)
|
||||||
|
val backgroundDarkHighContrast = Color(0xFF101418)
|
||||||
|
val onBackgroundDarkHighContrast = Color(0xFFE0E2E8)
|
||||||
|
val surfaceDarkHighContrast = Color(0xFF101418)
|
||||||
|
val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
|
||||||
|
val surfaceVariantDarkHighContrast = Color(0xFF42474E)
|
||||||
|
val onSurfaceVariantDarkHighContrast = Color(0xFFF9FBFF)
|
||||||
|
val outlineDarkHighContrast = Color(0xFFC6CBD3)
|
||||||
|
val outlineVariantDarkHighContrast = Color(0xFFC6CBD3)
|
||||||
|
val scrimDarkHighContrast = Color(0xFF000000)
|
||||||
|
val inverseSurfaceDarkHighContrast = Color(0xFFE0E2E8)
|
||||||
|
val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
|
||||||
|
val inversePrimaryDarkHighContrast = Color(0xFF002D47)
|
||||||
|
val surfaceDimDarkHighContrast = Color(0xFF101418)
|
||||||
|
val surfaceBrightDarkHighContrast = Color(0xFF36393E)
|
||||||
|
val surfaceContainerLowestDarkHighContrast = Color(0xFF0B0F12)
|
||||||
|
val surfaceContainerLowDarkHighContrast = Color(0xFF181C20)
|
||||||
|
val surfaceContainerDarkHighContrast = Color(0xFF1C2024)
|
||||||
|
val surfaceContainerHighDarkHighContrast = Color(0xFF272A2E)
|
||||||
|
val surfaceContainerHighestDarkHighContrast = Color(0xFF313539)
|
|
@ -0,0 +1,280 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui.theme
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val lightScheme =
|
||||||
|
lightColorScheme(
|
||||||
|
primary = primaryLight,
|
||||||
|
onPrimary = onPrimaryLight,
|
||||||
|
primaryContainer = primaryContainerLight,
|
||||||
|
onPrimaryContainer = onPrimaryContainerLight,
|
||||||
|
secondary = secondaryLight,
|
||||||
|
onSecondary = onSecondaryLight,
|
||||||
|
secondaryContainer = secondaryContainerLight,
|
||||||
|
onSecondaryContainer = onSecondaryContainerLight,
|
||||||
|
tertiary = tertiaryLight,
|
||||||
|
onTertiary = onTertiaryLight,
|
||||||
|
tertiaryContainer = tertiaryContainerLight,
|
||||||
|
onTertiaryContainer = onTertiaryContainerLight,
|
||||||
|
error = errorLight,
|
||||||
|
onError = onErrorLight,
|
||||||
|
errorContainer = errorContainerLight,
|
||||||
|
onErrorContainer = onErrorContainerLight,
|
||||||
|
background = backgroundLight,
|
||||||
|
onBackground = onBackgroundLight,
|
||||||
|
surface = surfaceLight,
|
||||||
|
onSurface = onSurfaceLight,
|
||||||
|
surfaceVariant = surfaceVariantLight,
|
||||||
|
onSurfaceVariant = onSurfaceVariantLight,
|
||||||
|
outline = outlineLight,
|
||||||
|
outlineVariant = outlineVariantLight,
|
||||||
|
scrim = scrimLight,
|
||||||
|
inverseSurface = inverseSurfaceLight,
|
||||||
|
inverseOnSurface = inverseOnSurfaceLight,
|
||||||
|
inversePrimary = inversePrimaryLight,
|
||||||
|
surfaceDim = surfaceDimLight,
|
||||||
|
surfaceBright = surfaceBrightLight,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestLight,
|
||||||
|
surfaceContainerLow = surfaceContainerLowLight,
|
||||||
|
surfaceContainer = surfaceContainerLight,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighLight,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestLight
|
||||||
|
)
|
||||||
|
|
||||||
|
private val darkScheme =
|
||||||
|
darkColorScheme(
|
||||||
|
primary = primaryDark,
|
||||||
|
onPrimary = onPrimaryDark,
|
||||||
|
primaryContainer = primaryContainerDark,
|
||||||
|
onPrimaryContainer = onPrimaryContainerDark,
|
||||||
|
secondary = secondaryDark,
|
||||||
|
onSecondary = onSecondaryDark,
|
||||||
|
secondaryContainer = secondaryContainerDark,
|
||||||
|
onSecondaryContainer = onSecondaryContainerDark,
|
||||||
|
tertiary = tertiaryDark,
|
||||||
|
onTertiary = onTertiaryDark,
|
||||||
|
tertiaryContainer = tertiaryContainerDark,
|
||||||
|
onTertiaryContainer = onTertiaryContainerDark,
|
||||||
|
error = errorDark,
|
||||||
|
onError = onErrorDark,
|
||||||
|
errorContainer = errorContainerDark,
|
||||||
|
onErrorContainer = onErrorContainerDark,
|
||||||
|
background = backgroundDark,
|
||||||
|
onBackground = onBackgroundDark,
|
||||||
|
surface = surfaceDark,
|
||||||
|
onSurface = onSurfaceDark,
|
||||||
|
surfaceVariant = surfaceVariantDark,
|
||||||
|
onSurfaceVariant = onSurfaceVariantDark,
|
||||||
|
outline = outlineDark,
|
||||||
|
outlineVariant = outlineVariantDark,
|
||||||
|
scrim = scrimDark,
|
||||||
|
inverseSurface = inverseSurfaceDark,
|
||||||
|
inverseOnSurface = inverseOnSurfaceDark,
|
||||||
|
inversePrimary = inversePrimaryDark,
|
||||||
|
surfaceDim = surfaceDimDark,
|
||||||
|
surfaceBright = surfaceBrightDark,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestDark,
|
||||||
|
surfaceContainerLow = surfaceContainerLowDark,
|
||||||
|
surfaceContainer = surfaceContainerDark,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighDark,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestDark
|
||||||
|
)
|
||||||
|
|
||||||
|
private val mediumContrastLightColorScheme =
|
||||||
|
lightColorScheme(
|
||||||
|
primary = primaryLightMediumContrast,
|
||||||
|
onPrimary = onPrimaryLightMediumContrast,
|
||||||
|
primaryContainer = primaryContainerLightMediumContrast,
|
||||||
|
onPrimaryContainer = onPrimaryContainerLightMediumContrast,
|
||||||
|
secondary = secondaryLightMediumContrast,
|
||||||
|
onSecondary = onSecondaryLightMediumContrast,
|
||||||
|
secondaryContainer = secondaryContainerLightMediumContrast,
|
||||||
|
onSecondaryContainer = onSecondaryContainerLightMediumContrast,
|
||||||
|
tertiary = tertiaryLightMediumContrast,
|
||||||
|
onTertiary = onTertiaryLightMediumContrast,
|
||||||
|
tertiaryContainer = tertiaryContainerLightMediumContrast,
|
||||||
|
onTertiaryContainer = onTertiaryContainerLightMediumContrast,
|
||||||
|
error = errorLightMediumContrast,
|
||||||
|
onError = onErrorLightMediumContrast,
|
||||||
|
errorContainer = errorContainerLightMediumContrast,
|
||||||
|
onErrorContainer = onErrorContainerLightMediumContrast,
|
||||||
|
background = backgroundLightMediumContrast,
|
||||||
|
onBackground = onBackgroundLightMediumContrast,
|
||||||
|
surface = surfaceLightMediumContrast,
|
||||||
|
onSurface = onSurfaceLightMediumContrast,
|
||||||
|
surfaceVariant = surfaceVariantLightMediumContrast,
|
||||||
|
onSurfaceVariant = onSurfaceVariantLightMediumContrast,
|
||||||
|
outline = outlineLightMediumContrast,
|
||||||
|
outlineVariant = outlineVariantLightMediumContrast,
|
||||||
|
scrim = scrimLightMediumContrast,
|
||||||
|
inverseSurface = inverseSurfaceLightMediumContrast,
|
||||||
|
inverseOnSurface = inverseOnSurfaceLightMediumContrast,
|
||||||
|
inversePrimary = inversePrimaryLightMediumContrast,
|
||||||
|
surfaceDim = surfaceDimLightMediumContrast,
|
||||||
|
surfaceBright = surfaceBrightLightMediumContrast,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
|
||||||
|
surfaceContainerLow = surfaceContainerLowLightMediumContrast,
|
||||||
|
surfaceContainer = surfaceContainerLightMediumContrast,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestLightMediumContrast
|
||||||
|
)
|
||||||
|
|
||||||
|
private val highContrastLightColorScheme =
|
||||||
|
lightColorScheme(
|
||||||
|
primary = primaryLightHighContrast,
|
||||||
|
onPrimary = onPrimaryLightHighContrast,
|
||||||
|
primaryContainer = primaryContainerLightHighContrast,
|
||||||
|
onPrimaryContainer = onPrimaryContainerLightHighContrast,
|
||||||
|
secondary = secondaryLightHighContrast,
|
||||||
|
onSecondary = onSecondaryLightHighContrast,
|
||||||
|
secondaryContainer = secondaryContainerLightHighContrast,
|
||||||
|
onSecondaryContainer = onSecondaryContainerLightHighContrast,
|
||||||
|
tertiary = tertiaryLightHighContrast,
|
||||||
|
onTertiary = onTertiaryLightHighContrast,
|
||||||
|
tertiaryContainer = tertiaryContainerLightHighContrast,
|
||||||
|
onTertiaryContainer = onTertiaryContainerLightHighContrast,
|
||||||
|
error = errorLightHighContrast,
|
||||||
|
onError = onErrorLightHighContrast,
|
||||||
|
errorContainer = errorContainerLightHighContrast,
|
||||||
|
onErrorContainer = onErrorContainerLightHighContrast,
|
||||||
|
background = backgroundLightHighContrast,
|
||||||
|
onBackground = onBackgroundLightHighContrast,
|
||||||
|
surface = surfaceLightHighContrast,
|
||||||
|
onSurface = onSurfaceLightHighContrast,
|
||||||
|
surfaceVariant = surfaceVariantLightHighContrast,
|
||||||
|
onSurfaceVariant = onSurfaceVariantLightHighContrast,
|
||||||
|
outline = outlineLightHighContrast,
|
||||||
|
outlineVariant = outlineVariantLightHighContrast,
|
||||||
|
scrim = scrimLightHighContrast,
|
||||||
|
inverseSurface = inverseSurfaceLightHighContrast,
|
||||||
|
inverseOnSurface = inverseOnSurfaceLightHighContrast,
|
||||||
|
inversePrimary = inversePrimaryLightHighContrast,
|
||||||
|
surfaceDim = surfaceDimLightHighContrast,
|
||||||
|
surfaceBright = surfaceBrightLightHighContrast,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
|
||||||
|
surfaceContainerLow = surfaceContainerLowLightHighContrast,
|
||||||
|
surfaceContainer = surfaceContainerLightHighContrast,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighLightHighContrast,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestLightHighContrast
|
||||||
|
)
|
||||||
|
|
||||||
|
private val mediumContrastDarkColorScheme =
|
||||||
|
darkColorScheme(
|
||||||
|
primary = primaryDarkMediumContrast,
|
||||||
|
onPrimary = onPrimaryDarkMediumContrast,
|
||||||
|
primaryContainer = primaryContainerDarkMediumContrast,
|
||||||
|
onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
|
||||||
|
secondary = secondaryDarkMediumContrast,
|
||||||
|
onSecondary = onSecondaryDarkMediumContrast,
|
||||||
|
secondaryContainer = secondaryContainerDarkMediumContrast,
|
||||||
|
onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
|
||||||
|
tertiary = tertiaryDarkMediumContrast,
|
||||||
|
onTertiary = onTertiaryDarkMediumContrast,
|
||||||
|
tertiaryContainer = tertiaryContainerDarkMediumContrast,
|
||||||
|
onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
|
||||||
|
error = errorDarkMediumContrast,
|
||||||
|
onError = onErrorDarkMediumContrast,
|
||||||
|
errorContainer = errorContainerDarkMediumContrast,
|
||||||
|
onErrorContainer = onErrorContainerDarkMediumContrast,
|
||||||
|
background = backgroundDarkMediumContrast,
|
||||||
|
onBackground = onBackgroundDarkMediumContrast,
|
||||||
|
surface = surfaceDarkMediumContrast,
|
||||||
|
onSurface = onSurfaceDarkMediumContrast,
|
||||||
|
surfaceVariant = surfaceVariantDarkMediumContrast,
|
||||||
|
onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
|
||||||
|
outline = outlineDarkMediumContrast,
|
||||||
|
outlineVariant = outlineVariantDarkMediumContrast,
|
||||||
|
scrim = scrimDarkMediumContrast,
|
||||||
|
inverseSurface = inverseSurfaceDarkMediumContrast,
|
||||||
|
inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
|
||||||
|
inversePrimary = inversePrimaryDarkMediumContrast,
|
||||||
|
surfaceDim = surfaceDimDarkMediumContrast,
|
||||||
|
surfaceBright = surfaceBrightDarkMediumContrast,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
|
||||||
|
surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
|
||||||
|
surfaceContainer = surfaceContainerDarkMediumContrast,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast
|
||||||
|
)
|
||||||
|
|
||||||
|
private val highContrastDarkColorScheme =
|
||||||
|
darkColorScheme(
|
||||||
|
primary = primaryDarkHighContrast,
|
||||||
|
onPrimary = onPrimaryDarkHighContrast,
|
||||||
|
primaryContainer = primaryContainerDarkHighContrast,
|
||||||
|
onPrimaryContainer = onPrimaryContainerDarkHighContrast,
|
||||||
|
secondary = secondaryDarkHighContrast,
|
||||||
|
onSecondary = onSecondaryDarkHighContrast,
|
||||||
|
secondaryContainer = secondaryContainerDarkHighContrast,
|
||||||
|
onSecondaryContainer = onSecondaryContainerDarkHighContrast,
|
||||||
|
tertiary = tertiaryDarkHighContrast,
|
||||||
|
onTertiary = onTertiaryDarkHighContrast,
|
||||||
|
tertiaryContainer = tertiaryContainerDarkHighContrast,
|
||||||
|
onTertiaryContainer = onTertiaryContainerDarkHighContrast,
|
||||||
|
error = errorDarkHighContrast,
|
||||||
|
onError = onErrorDarkHighContrast,
|
||||||
|
errorContainer = errorContainerDarkHighContrast,
|
||||||
|
onErrorContainer = onErrorContainerDarkHighContrast,
|
||||||
|
background = backgroundDarkHighContrast,
|
||||||
|
onBackground = onBackgroundDarkHighContrast,
|
||||||
|
surface = surfaceDarkHighContrast,
|
||||||
|
onSurface = onSurfaceDarkHighContrast,
|
||||||
|
surfaceVariant = surfaceVariantDarkHighContrast,
|
||||||
|
onSurfaceVariant = onSurfaceVariantDarkHighContrast,
|
||||||
|
outline = outlineDarkHighContrast,
|
||||||
|
outlineVariant = outlineVariantDarkHighContrast,
|
||||||
|
scrim = scrimDarkHighContrast,
|
||||||
|
inverseSurface = inverseSurfaceDarkHighContrast,
|
||||||
|
inverseOnSurface = inverseOnSurfaceDarkHighContrast,
|
||||||
|
inversePrimary = inversePrimaryDarkHighContrast,
|
||||||
|
surfaceDim = surfaceDimDarkHighContrast,
|
||||||
|
surfaceBright = surfaceBrightDarkHighContrast,
|
||||||
|
surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
|
||||||
|
surfaceContainerLow = surfaceContainerLowDarkHighContrast,
|
||||||
|
surfaceContainer = surfaceContainerDarkHighContrast,
|
||||||
|
surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
|
||||||
|
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ColorFamily(
|
||||||
|
val color: Color,
|
||||||
|
val onColor: Color,
|
||||||
|
val colorContainer: Color,
|
||||||
|
val onColorContainer: Color
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = false,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme =
|
||||||
|
when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> darkScheme
|
||||||
|
else -> lightScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = AppTypography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.unifiedpush.distributor.nextpush.activities.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
|
||||||
|
val AppTypography = Typography()
|
|
@ -12,6 +12,7 @@ import org.unifiedpush.distributor.nextpush.AppCompanion
|
||||||
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
||||||
import org.unifiedpush.distributor.nextpush.WakeLock
|
import org.unifiedpush.distributor.nextpush.WakeLock
|
||||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
||||||
|
import org.unifiedpush.distributor.nextpush.activities.UiAction
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
|
import org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkRegistration
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkRegistration
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.createApp
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor.createApp
|
||||||
|
@ -169,7 +170,7 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||||
private fun onRegisterUpdatedToken(context: Context, connectorToken: String, application: String, vapid: String?) {
|
private fun onRegisterUpdatedToken(context: Context, connectorToken: String, application: String, vapid: String?) {
|
||||||
Log.d(TAG, "Updating registration for $application")
|
Log.d(TAG, "Updating registration for $application")
|
||||||
deleteApp(context, connectorToken, false) {
|
deleteApp(context, connectorToken, false) {
|
||||||
onRegisterNewToken(context, connectorToken, application, vapid, toastOnSuccess = false)
|
saveApplication(context, connectorToken, application, vapid, newRegistration = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +179,25 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||||
*
|
*
|
||||||
* If we are not connected to Nextcloud, we send registration failed with [FailedReason.ACTION_REQUIRED]
|
* If we are not connected to Nextcloud, we send registration failed with [FailedReason.ACTION_REQUIRED]
|
||||||
*/
|
*/
|
||||||
private fun onRegisterNewToken(context: Context, connectorToken: String, application: String, vapid: String?, toastOnSuccess: Boolean = true) {
|
private fun onRegisterNewToken(context: Context, connectorToken: String, application: String, vapid: String?) {
|
||||||
|
Log.d(TAG, "New registration for $application")
|
||||||
|
saveApplication(context, connectorToken, application, vapid, newRegistration = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the registration, consumed by [onRegisterNewToken] and [onRegisterUpdatedToken]
|
||||||
|
*
|
||||||
|
* If we are not connected to Nextcloud, we send registration failed with [FailedReason.ACTION_REQUIRED]
|
||||||
|
*
|
||||||
|
* @param newRegistration if this is a new registration, we inform user with a toast and update registrationsUi
|
||||||
|
*/
|
||||||
|
private fun saveApplication(
|
||||||
|
context: Context,
|
||||||
|
connectorToken: String,
|
||||||
|
application: String,
|
||||||
|
vapid: String?,
|
||||||
|
newRegistration: Boolean = true,
|
||||||
|
) {
|
||||||
val appName = context.getApplicationName(application) ?: application
|
val appName = context.getApplicationName(application) ?: application
|
||||||
when {
|
when {
|
||||||
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
||||||
|
@ -204,7 +223,8 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||||
when (success) {
|
when (success) {
|
||||||
true -> {
|
true -> {
|
||||||
sendEndpoint(context, connectorToken)
|
sendEndpoint(context, connectorToken)
|
||||||
if (toastOnSuccess) {
|
if (newRegistration) {
|
||||||
|
UiAction.publish(UiAction.Type.UpdateRegistrations)
|
||||||
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -251,6 +271,7 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||||
try {
|
try {
|
||||||
deleteApp(context, connectorToken) {
|
deleteApp(context, connectorToken) {
|
||||||
Log.d(TAG, "Unregistration is finished")
|
Log.d(TAG, "Unregistration is finished")
|
||||||
|
UiAction.publish(UiAction.Type.UpdateRegistrations)
|
||||||
AppCompanion.delQueue.removeToken(connectorToken)
|
AppCompanion.delQueue.removeToken(connectorToken)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package org.unifiedpush.distributor.nextpush.utils
|
|
||||||
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
|
||||||
|
|
||||||
fun copyToClipboard(context: Context, label: String, text: String) {
|
|
||||||
val clipboard: ClipboardManager? = getSystemService(context, ClipboardManager::class.java)
|
|
||||||
val clip = ClipData.newPlainText(label, text)
|
|
||||||
clipboard?.setPrimaryClip(clip)
|
|
||||||
}
|
|
|
@ -286,7 +286,7 @@ class FromPushNotification(context: Context, title: String, content: String) : A
|
||||||
"${context.getString(R.string.app_name)}.Push.$title",
|
"${context.getString(R.string.app_name)}.Push.$title",
|
||||||
"(Push) $title",
|
"(Push) $title",
|
||||||
NotificationManager.IMPORTANCE_HIGH,
|
NotificationManager.IMPORTANCE_HIGH,
|
||||||
context.getString(R.string.local_notif_description)
|
context.getString(R.string.list_registrations_local_description)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context="org.unifiedpush.distributor.nextpush.activities.MainActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:theme="@style/Theme.NextPush.AppBarOverlay">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:popupTheme="@style/Theme.NextPush.PopupOverlay" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/sub_main"
|
|
||||||
layout="@layout/content_main" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
|
@ -1,94 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/card_battery_optimization"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:checkable="true"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:text="@string/card_disable_optimisation_description" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/button_disable_optimisation"
|
|
||||||
style="?attr/borderlessButtonStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:text="@string/button_disable_optimisation" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/main_account_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:text="@string/main_account_title"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/card_battery_optimization" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/main_account_desc"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:text="@string/main_account_desc"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/main_account_title" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/main_applications_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/main_applications_title"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/main_account_desc" />
|
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:id="@+id/applications_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/main_applications_title" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
|
||||||
android:minHeight="?android:attr/listPreferredItemHeightSmall" >
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/item_app_name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
|
||||||
android:gravity="center_vertical"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/item_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:gravity="center_vertical" />
|
|
||||||
</LinearLayout>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_copy_endpoint"
|
|
||||||
android:icon="@drawable/ic_content_copy_24"
|
|
||||||
android:title="@string/copy_endpoint"
|
|
||||||
android:iconTint="@color/white" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_delete"
|
|
||||||
android:icon="@drawable/ic_delete_24"
|
|
||||||
android:title="@string/action_delete"
|
|
||||||
android:iconTint="@color/white" />
|
|
||||||
</menu>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
tools:context="org.unifiedpush.distributor.nextpush.activities.MainActivity">
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_restart"
|
|
||||||
android:orderInCategory="100"
|
|
||||||
android:title="@string/action_restart"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_logout"
|
|
||||||
android:orderInCategory="105"
|
|
||||||
android:title="@string/action_logout"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_add_local_channel"
|
|
||||||
android:orderInCategory="110"
|
|
||||||
android:title="Add notification channel"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
</menu>
|
|
|
@ -1,22 +1,15 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="action_settings">Settings</string>
|
|
||||||
|
|
||||||
<!-- Strings used on the main page -->
|
|
||||||
<string name="main_applications_title">Registered applications</string>
|
<string name="main_applications_title">Registered applications</string>
|
||||||
|
|
||||||
<string name="help"></string>
|
|
||||||
<string name="foreground_notif_description">Notification to run in the foreground</string>
|
<string name="foreground_notif_description">Notification to run in the foreground</string>
|
||||||
<string name="foreground_notif_content_no_reg">Waiting for registration to connect</string>
|
<string name="foreground_notif_content_no_reg">Waiting for registration to connect</string>
|
||||||
<string name="foreground_notif_content_with_reg">Connected for %s registration(s)</string>
|
<string name="foreground_notif_content_with_reg">Connected for %s registration(s)</string>
|
||||||
<string name="sso_connection_button">Login with the Nextcloud File application</string>
|
<string name="sso_connection_button">Login with the Nextcloud File application</string>
|
||||||
<string name="action_logout">Logout</string>
|
|
||||||
<string name="logout_alert_title">Logout</string>
|
<string name="logout_alert_title">Logout</string>
|
||||||
<string name="logout_alert_content">Confirm to logout</string>
|
<string name="logout_alert_content">Confirm to logout</string>
|
||||||
<string name="ok">OK</string>
|
<string name="main_account_desc">You are connected as %s</string>
|
||||||
<string name="discard">Discard</string>
|
<string name="app_dropdown_logout">Logout</string>
|
||||||
<string name="main_account_title">Account</string>
|
<string name="app_dropdown_restart">Restart Service</string>
|
||||||
<string name="main_account_desc">You are connected as: %s</string>
|
<string name="app_dropdown_add_notification_channel">Add notification channel</string>
|
||||||
<string name="action_restart">Restart Service</string>
|
|
||||||
<string name="warning_notif_content">NextPush is disconnected</string>
|
<string name="warning_notif_content">NextPush is disconnected</string>
|
||||||
<string name="warning_notif_description">Warn when NextPush is disconnected or an issue occurred.</string>
|
<string name="warning_notif_description">Warn when NextPush is disconnected or an issue occurred.</string>
|
||||||
<string name="warning_notif_ticker">Warning</string>
|
<string name="warning_notif_ticker">Warning</string>
|
||||||
|
@ -42,13 +35,18 @@
|
||||||
<string name="login_show_password_img_description">Show password</string>
|
<string name="login_show_password_img_description">Show password</string>
|
||||||
<string name="button_disable_optimisation">Disable optimization</string>
|
<string name="button_disable_optimisation">Disable optimization</string>
|
||||||
<string name="card_disable_optimisation_description">To ensure the app functions properly, it is important to disable battery optimization. This will prevent the app from being put to sleep and causing delays in notifications.</string>
|
<string name="card_disable_optimisation_description">To ensure the app functions properly, it is important to disable battery optimization. This will prevent the app from being put to sleep and causing delays in notifications.</string>
|
||||||
<string name="action_delete">Delete</string>
|
|
||||||
<string name="dialog_yes">YES</string>
|
|
||||||
<string name="dialog_no">NO</string>
|
|
||||||
<string name="dialog_unregistering_title">Unregistering</string>
|
<string name="dialog_unregistering_title">Unregistering</string>
|
||||||
<string name="dialog_unregistering_content">Are you sure to unregister %d app(s)?</string>
|
<string name="dialog_unregistering_content">Are you sure to unregister %d app(s)?</string>
|
||||||
<string name="add_channel_dialog_content"><![CDATA[Notification channels are an endpoint that you can use to create custom push notifications for services outside of UnifiedPush, such as a script failures or ssh logins. After creating a channel, long tap it to copy its endpoint URL. Example scripts are provided <a href="https://codeberg.org/NextPush/nextpush-android/src/branch/main/docs/notification_channel_examples.md"here</a><br><br><h4>Give your notification a Title:</h4>]]></string>
|
<string name="dialog_add_channel_title">Notification Channel</string>
|
||||||
<string name="local_notif_title">(Notif) %s</string>
|
<string name="dialog_add_channel_content">Notification channels are an endpoint you can use to create customized push notifications for services outside UnifiedPush, such as script failures or ssh connections. After creating a channel, long-press it to copy its endpoint URL. Sample scripts are provided</string>
|
||||||
<string name="local_notif_description">Push to be notified. Not related to UnifiedPush: messages aren\'t forwarded to any other app.</string>
|
<string name="dialog_add_channel_input_label">Channel title</string>
|
||||||
<string name="copy_endpoint">Copy Endpoint</string>
|
<string name="hyperlink_to_channel_example" translatable="false">https://codeberg.org/NextPush/nextpush-android/src/branch/main/docs/notification_channel_examples.md</string>
|
||||||
|
<string name="hyperlink_to_channel_example_text">on Codeberg.</string>
|
||||||
|
<string name="list_registrations_local_title">(Channel) %s</string>
|
||||||
|
<string name="list_registrations_local_description">Local channel. Not related to UnifiedPush.</string>
|
||||||
|
<string name="button_copy_endpoint_description">Copy Endpoint</string>
|
||||||
|
<string name="list_registrations_elt_long_click_label">Select registration to delete</string>
|
||||||
|
<string name="bar_unregister_title">%d selected</string>
|
||||||
|
<string name="bar_unregister_back_description">Unselect all</string>
|
||||||
|
<string name="bar_unregister_delete_description">Unregister selection</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -16,7 +16,8 @@ buildscript {
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.ktlint)
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
|
alias(libs.plugins.ktlint) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
[versions]
|
[versions]
|
||||||
android-gradle-plugin = "8.7.2"
|
android-gradle-plugin = "8.7.2"
|
||||||
|
androidx-activityCompose = "1.9.3"
|
||||||
androidx-constraintlayout = "2.2.0"
|
androidx-constraintlayout = "2.2.0"
|
||||||
androidx-coordinatorlayout = "1.2.0"
|
androidx-coordinatorlayout = "1.2.0"
|
||||||
|
androidx-lifecycle = "2.8.7"
|
||||||
androidx-work = "2.10.0"
|
androidx-work = "2.10.0"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
ktlint = "12.1.1"
|
ktlint = "12.1.1"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
nextcloud-sso = "1.3.2"
|
#nextcloud-sso = "1.3.2.patch3"
|
||||||
|
nextcloud-sso = "1.3.2.nodes1"
|
||||||
|
#nextcloud-sso = "1.3.2"
|
||||||
okhttp-sse = "5.0.0.SSEPATCH1-SNAPSHOT"
|
okhttp-sse = "5.0.0.SSEPATCH1-SNAPSHOT"
|
||||||
retrofit = "2.11.0"
|
retrofit = "2.11.0"
|
||||||
rxjava3-rxandroid = "3.0.2"
|
rxjava3-rxandroid = "3.0.2"
|
||||||
rxjava3-rxjava = "3.1.9"
|
rxjava3-rxjava = "3.1.9"
|
||||||
|
material3Android = "1.3.1"
|
||||||
|
uiTooling = "1.7.5"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
||||||
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
|
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
|
||||||
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorlayout" }
|
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorlayout" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
||||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
|
@ -29,8 +37,12 @@ retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", ve
|
||||||
retrofit-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
retrofit-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxjava3-rxandroid" }
|
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxjava3-rxandroid" }
|
||||||
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3-rxjava" }
|
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3-rxjava" }
|
||||||
|
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
|
||||||
|
androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiTooling" }
|
||||||
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
|
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
@ -25,6 +25,7 @@ dependencyResolutionManagement {
|
||||||
includeModule("com.squareup.okhttp3", "okhttp-sse")
|
includeModule("com.squareup.okhttp3", "okhttp-sse")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mavenLocal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue