Voeg thema-selector toe (DayNight, Radiogroep, teal accent)
This commit is contained in:
parent
5a0dee77b3
commit
c977dc25a2
12 changed files with 201 additions and 24 deletions
|
|
@ -5,6 +5,9 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace 'com.eddy.atomrelay'
|
||||
// compileSdk/targetSdk stays on 34 (Android 14) by deliberate choice. The target audience
|
||||
// consists of physically disabled users who are very reluctant to update their phones. Staying
|
||||
// on API 34 maximises device compatibility. Re-evaluate when the user base has migrated.
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
android:allowBackup="false"
|
||||
android:label="Atom Relay"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppCompat.Light">
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
private var seq = 1
|
||||
private var hmacKey: ByteArray? = null
|
||||
private var lastTheme: String? = null
|
||||
|
||||
private lateinit var statusText: TextView
|
||||
private lateinit var relayButton: Button
|
||||
|
|
@ -144,6 +145,8 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
lastTheme = PrefsHelper.getPrefs(this).getString(PrefsHelper.PREF_THEME, "system") ?: "system"
|
||||
PrefsHelper.applyTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
|
|
@ -173,6 +176,12 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val current = PrefsHelper.getPrefs(this).getString(PrefsHelper.PREF_THEME, "system") ?: "system"
|
||||
if (current != lastTheme) {
|
||||
lastTheme = current
|
||||
recreate()
|
||||
return
|
||||
}
|
||||
loadState()
|
||||
if (hmacKey == null) {
|
||||
statusText.text = getString(R.string.status_no_key)
|
||||
|
|
@ -321,6 +330,13 @@ class MainActivity : AppCompatActivity() {
|
|||
val cmd: Byte = if (on) 0x01 else 0x00
|
||||
val packet = buildPacket(cmd, key)
|
||||
char.value = packet
|
||||
// WRITE_TYPE_NO_RESPONSE is intentional. Previously, WRITE_TYPE_DEFAULT (with ACK) was
|
||||
// used, but the BLE write acknowledgement took long enough that the user could release
|
||||
// the button before the ACK arrived. When ACTION_UP fired during a pending write, the
|
||||
// release was silently ignored and the relay (and app) got stuck in the ON state.
|
||||
// WRITE_TYPE_NO_RESPONSE removes that latency and allows immediate ON→OFF sequences.
|
||||
// This means the UI reflects the user's intent ("pressed" / "released") rather than
|
||||
// confirmed relay state — an acceptable trade-off for a hold-to-activate interaction.
|
||||
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
gatt.writeCharacteristic(char)
|
||||
seq++
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package com.eddy.atomrelay
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
|
||||
|
|
@ -13,8 +14,19 @@ object PrefsHelper {
|
|||
const val PREFS_NAME = "relay_prefs"
|
||||
const val PREF_KEY = "hmac_key"
|
||||
const val PREF_SEQ = "seq_counter"
|
||||
const val PREF_THEME = "theme"
|
||||
const val SEQ_MAX = 1_000_000
|
||||
|
||||
fun applyTheme(context: Context) {
|
||||
val prefs = getPrefs(context)
|
||||
val mode = when (prefs.getString(PREF_THEME, "system")) {
|
||||
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(mode)
|
||||
}
|
||||
|
||||
fun getPrefs(context: Context): SharedPreferences {
|
||||
return try {
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ import android.hardware.usb.UsbEndpoint
|
|||
import android.hardware.usb.UsbInterface
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
|
@ -52,6 +55,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
private var progressDialog: AlertDialog? = null
|
||||
private var dialog: AlertDialog? = null
|
||||
private var flasherJob: Job? = null
|
||||
private var lastTheme: String? = null
|
||||
|
||||
private val usbAttachedReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
|
@ -104,6 +108,8 @@ class SettingsActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
lastTheme = PrefsHelper.getPrefs(this).getString(PrefsHelper.PREF_THEME, "system") ?: "system"
|
||||
PrefsHelper.applyTheme(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_settings)
|
||||
|
||||
|
|
@ -123,6 +129,55 @@ class SettingsActivity : AppCompatActivity() {
|
|||
registerReceiver(usbAttachedReceiver, IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED), Context.RECEIVER_EXPORTED)
|
||||
registerReceiver(usbPermissionReceiver, IntentFilter(ACTION_USB_PERMISSION), Context.RECEIVER_NOT_EXPORTED)
|
||||
registerReceiver(usbDetachedReceiver, IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED), Context.RECEIVER_EXPORTED)
|
||||
|
||||
setupThemeButtons()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val current = PrefsHelper.getPrefs(this).getString(PrefsHelper.PREF_THEME, "system") ?: "system"
|
||||
if (current != lastTheme) {
|
||||
lastTheme = current
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupThemeButtons() {
|
||||
val current = PrefsHelper.getPrefs(this).getString(PrefsHelper.PREF_THEME, "system") ?: "system"
|
||||
|
||||
val group = findViewById<RadioGroup>(R.id.themeGroup)
|
||||
group.check(when (current) {
|
||||
"light" -> R.id.themeLight
|
||||
"dark" -> R.id.themeDark
|
||||
else -> R.id.themeSystem
|
||||
})
|
||||
|
||||
data class ThemeOpt(val id: Int, val key: String, val label: String)
|
||||
|
||||
val options = listOf(
|
||||
ThemeOpt(R.id.themeSystem, "system", getString(R.string.theme_system)),
|
||||
ThemeOpt(R.id.themeLight, "light", getString(R.string.theme_light)),
|
||||
ThemeOpt(R.id.themeDark, "dark", getString(R.string.theme_dark))
|
||||
)
|
||||
|
||||
options.forEach { (id, key, label) ->
|
||||
val rb = findViewById<android.widget.RadioButton>(id)
|
||||
val isActive = key == current
|
||||
rb.contentDescription = if (isActive)
|
||||
getString(R.string.theme_applied, label)
|
||||
else
|
||||
label
|
||||
rb.setOnClickListener {
|
||||
selectTheme(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectTheme(theme: String) {
|
||||
PrefsHelper.getPrefs(this).edit().putString(PrefsHelper.PREF_THEME, theme).apply()
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
PrefsHelper.applyTheme(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,59 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="Firmware Flashen" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/section_theme"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="3">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/themeGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/themeSystem"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/theme_system" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/themeLight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/theme_light" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/themeDark"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/theme_dark" />
|
||||
</RadioGroup>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
|
|
|||
6
android/app/src/main/res/values-night/colors.xml
Normal file
6
android/app/src/main/res/values-night/colors.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#4DB6AC</color>
|
||||
<color name="colorPrimaryDark">#00897B</color>
|
||||
<color name="colorAccent">#80CBC4</color>
|
||||
</resources>
|
||||
6
android/app/src/main/res/values/colors.xml
Normal file
6
android/app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#00695C</color>
|
||||
<color name="colorPrimaryDark">#004D40</color>
|
||||
<color name="colorAccent">#009688</color>
|
||||
</resources>
|
||||
|
|
@ -39,4 +39,11 @@
|
|||
<string name="msg_flash_complete">Firmware geflashed!\n\nOntkoppel de ESP en sluit opnieuw aan om de sleutel in te stellen.</string>
|
||||
<string name="button_close">Sluiten</string>
|
||||
<string name="button_cancel">Annuleren</string>
|
||||
|
||||
<!-- Theme -->
|
||||
<string name="section_theme">Weergave</string>
|
||||
<string name="theme_system">Systeem</string>
|
||||
<string name="theme_light">Licht</string>
|
||||
<string name="theme_dark">Donker</string>
|
||||
<string name="theme_applied">Thema: %s</string>
|
||||
</resources>
|
||||
|
|
|
|||
8
android/app/src/main/res/values/themes.xml
Normal file
8
android/app/src/main/res/values/themes.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue