Migrate main activity to jetpack
This commit is contained in:
parent
1d206c92f8
commit
9ac56626cb
|
@ -1,13 +1,15 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
|
@ -18,6 +20,10 @@ android {
|
|||
versionName = "1.9.0"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
resValue("string", "app_name", "NextPush")
|
||||
|
@ -57,10 +63,11 @@ if (project.hasProperty("sign")) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.coordinatorlayout)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.kotlin.stdlib)
|
||||
|
@ -72,4 +79,7 @@ dependencies {
|
|||
implementation(libs.retrofit.retrofit)
|
||||
implementation(libs.rxjava3.rxandroid)
|
||||
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" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:theme="@style/Theme.NextPush.NoActionBar">
|
||||
<activity android:name=".activities.MainActivity">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activities.LinkActivity"
|
||||
android:exported="true">
|
||||
<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
|
||||
|
||||
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.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.TypedValue
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.* // ktlint-disable no-wildcard-imports
|
||||
import android.widget.AbsListView.MultiChoiceModeListener
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.util.size
|
||||
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 androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.distributor.nextpush.EventBus
|
||||
import org.unifiedpush.distributor.nextpush.activities.ui.MainUi
|
||||
import org.unifiedpush.distributor.nextpush.activities.ui.theme.AppTheme
|
||||
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.copyToClipboard
|
||||
import org.unifiedpush.distributor.nextpush.utils.getDebugInfo
|
||||
import java.lang.String.format
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var listView: ListView
|
||||
|
||||
// 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
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var viewModel: MainViewModel? = null
|
||||
private var jobs: MutableList<Job> = emptyList<Job>().toMutableList()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
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)
|
||||
setDebugInformationListener()
|
||||
findViewById<View>(android.R.id.content)?.setOnApplyWindowInsetsListener { _, insets ->
|
||||
val statusBarSize = insets.systemWindowInsetTop
|
||||
findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar).setPadding(0, statusBarSize , 0, 0)
|
||||
return@setOnApplyWindowInsetsListener insets
|
||||
|
||||
setContent {
|
||||
val viewModel =
|
||||
viewModel {
|
||||
MainViewModel(this@MainActivity)
|
||||
}.also {
|
||||
viewModel = it
|
||||
}
|
||||
AppTheme {
|
||||
MainUi(viewModel)
|
||||
}
|
||||
subscribeActions()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
showOptimisationWarning()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
if (preventListReset) {
|
||||
preventListReset = false
|
||||
} else {
|
||||
setListView()
|
||||
}
|
||||
private fun subscribeActions() {
|
||||
Log.d(TAG, "Subscribing to actions")
|
||||
jobs += CoroutineScope(Dispatchers.IO).launch {
|
||||
EventBus.subscribe<AppAction> { it.handle(this@MainActivity) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
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")
|
||||
jobs += CoroutineScope(Dispatchers.IO).launch {
|
||||
EventBus.subscribe<UiAction> {
|
||||
it.handle { type ->
|
||||
when (type) {
|
||||
UiAction.Type.UpdateRegistrations -> viewModel?.updateRegistrations(
|
||||
this@MainActivity
|
||||
)
|
||||
)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
try {
|
||||
startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
|
||||
} catch (e2: ActivityNotFoundException) {
|
||||
startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
UiAction.Type.Logout -> {
|
||||
StartActivity.goToStartActivity(this@MainActivity)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<MaterialCardView>(R.id.card_battery_optimization)?.isGone = true
|
||||
}
|
||||
} else {
|
||||
findViewById<MaterialCardView>(R.id.card_battery_optimization)?.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun restart() {
|
||||
Log.d(TAG, "Restarting the Listener")
|
||||
FailureHandler.clearFails()
|
||||
StartService.stopService {
|
||||
RestartWorker.run(this, delay = 0)
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy")
|
||||
jobs.removeAll {
|
||||
it.cancel()
|
||||
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 {
|
||||
fun goToMainActivity(context: Context) {
|
||||
val intent = Intent(
|
||||
context,
|
||||
MainActivity::class.java
|
||||
)
|
||||
val intent =
|
||||
Intent(
|
||||
context,
|
||||
MainActivity::class.java
|
||||
)
|
||||
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.WakeLock
|
||||
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.Distributor.checkRegistration
|
||||
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?) {
|
||||
Log.d(TAG, "Updating registration for $application")
|
||||
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]
|
||||
*/
|
||||
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
|
||||
when {
|
||||
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
||||
|
@ -204,7 +223,8 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
|||
when (success) {
|
||||
true -> {
|
||||
sendEndpoint(context, connectorToken)
|
||||
if (toastOnSuccess) {
|
||||
if (newRegistration) {
|
||||
UiAction.publish(UiAction.Type.UpdateRegistrations)
|
||||
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
|
@ -251,6 +271,7 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
|||
try {
|
||||
deleteApp(context, connectorToken) {
|
||||
Log.d(TAG, "Unregistration is finished")
|
||||
UiAction.publish(UiAction.Type.UpdateRegistrations)
|
||||
AppCompanion.delQueue.removeToken(connectorToken)
|
||||
}
|
||||
} 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",
|
||||
"(Push) $title",
|
||||
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>
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
<!-- Strings used on the main page -->
|
||||
<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_content_no_reg">Waiting for registration to connect</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="action_logout">Logout</string>
|
||||
<string name="logout_alert_title">Logout</string>
|
||||
<string name="logout_alert_content">Confirm to logout</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="discard">Discard</string>
|
||||
<string name="main_account_title">Account</string>
|
||||
<string name="main_account_desc">You are connected as: %s</string>
|
||||
<string name="action_restart">Restart Service</string>
|
||||
<string name="main_account_desc">You are connected as %s</string>
|
||||
<string name="app_dropdown_logout">Logout</string>
|
||||
<string name="app_dropdown_restart">Restart Service</string>
|
||||
<string name="app_dropdown_add_notification_channel">Add notification channel</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_ticker">Warning</string>
|
||||
|
@ -42,13 +35,18 @@
|
|||
<string name="login_show_password_img_description">Show password</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="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_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="local_notif_title">(Notif) %s</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="copy_endpoint">Copy Endpoint</string>
|
||||
<string name="dialog_add_channel_title">Notification Channel</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="dialog_add_channel_input_label">Channel title</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>
|
||||
|
|
|
@ -16,7 +16,8 @@ buildscript {
|
|||
plugins {
|
||||
alias(libs.plugins.android.application) 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") {
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
[versions]
|
||||
android-gradle-plugin = "8.7.2"
|
||||
androidx-activityCompose = "1.9.3"
|
||||
androidx-constraintlayout = "2.2.0"
|
||||
androidx-coordinatorlayout = "1.2.0"
|
||||
androidx-lifecycle = "2.8.7"
|
||||
androidx-work = "2.10.0"
|
||||
appcompat = "1.7.0"
|
||||
kotlin = "2.0.21"
|
||||
ktlint = "12.1.1"
|
||||
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"
|
||||
retrofit = "2.11.0"
|
||||
rxjava3-rxandroid = "3.0.2"
|
||||
rxjava3-rxjava = "3.1.9"
|
||||
material3Android = "1.3.1"
|
||||
uiTooling = "1.7.5"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
|
||||
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
|
||||
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" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||
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" }
|
||||
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxjava3-rxandroid" }
|
||||
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]
|
||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||
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")
|
||||
}
|
||||
}
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue