feat(ui): add vertical brightness fills, dark mode, and websocket toggle
All checks were successful
Update Wiki Documentation / generate-docs (push) Successful in 2m26s
All checks were successful
Update Wiki Documentation / generate-docs (push) Successful in 2m26s
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 182 KiB |
@@ -1,15 +1,20 @@
|
||||
package com.example.retroha
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import com.example.retroha.data.Prefs
|
||||
import com.example.retroha.i18n.AndroidStrings
|
||||
import com.example.retroha.i18n.StringKey
|
||||
import com.example.retroha.network.HaClient
|
||||
import com.example.retroha.network.HaState
|
||||
import com.example.retroha.theme.Colors
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
/**
|
||||
* Activity for configuring the connection to the Home Assistant server.
|
||||
* Handles server URL, authentication token, and refresh interval settings.
|
||||
@@ -19,37 +24,47 @@ class ConnectionSettingsActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_connection_settings)
|
||||
|
||||
applyTheme()
|
||||
|
||||
val etUrl = findViewById<EditText>(R.id.etUrl)
|
||||
val etToken = findViewById<EditText>(R.id.etToken)
|
||||
val etRefreshInterval = findViewById<EditText>(R.id.etRefreshInterval)
|
||||
val cbWebSocket = findViewById<com.example.retroha.ui.BauhausCheckbox>(R.id.cbWebSocket)
|
||||
val btnSave = findViewById<Button>(R.id.btnTestAndSave)
|
||||
val tvStatus = findViewById<TextView>(R.id.tvStatus)
|
||||
|
||||
etUrl.setText(Prefs.getUrl(this))
|
||||
etToken.setText(Prefs.getToken(this))
|
||||
etRefreshInterval.setText((Prefs.getRefreshInterval(this) / 1000).toString())
|
||||
cbWebSocket.isChecked = Prefs.isWebSocketEnabled(this)
|
||||
cbWebSocket.setOnClickListener { cbWebSocket.isChecked = !cbWebSocket.isChecked }
|
||||
|
||||
btnSave.setOnClickListener {
|
||||
val url = etUrl.text.toString()
|
||||
val token = etToken.text.toString()
|
||||
val intervalSec = etRefreshInterval.text.toString().toLongOrNull() ?: 30L
|
||||
val wsEnabled = cbWebSocket.isChecked
|
||||
val stringsProvider = AndroidStrings(this)
|
||||
android.app.AlertDialog.Builder(this)
|
||||
.setTitle(stringsProvider.get(StringKey.DIALOG_WARNING))
|
||||
.setMessage(stringsProvider.get(StringKey.CONFIRM_CHANGE_CONN))
|
||||
.setPositiveButton(stringsProvider.get(StringKey.DIALOG_YES_CHANGE)) { _, _ ->
|
||||
performTestAndSave(url, token, intervalSec, tvStatus)
|
||||
performTestAndSave(url, token, intervalSec, wsEnabled, tvStatus)
|
||||
}
|
||||
.setNegativeButton(stringsProvider.get(StringKey.DIALOG_CANCEL), null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to HA with the provided credentials.
|
||||
* If successful, saves the configuration and clears the widget cache.
|
||||
*/
|
||||
private fun performTestAndSave(url: String, token: String, intervalSec: Long, tvStatus: TextView) {
|
||||
private fun performTestAndSave(url: String, token: String, intervalSec: Long, wsEnabled: Boolean, tvStatus: TextView) {
|
||||
val stringsProvider = AndroidStrings(this)
|
||||
tvStatus.text = stringsProvider.get(StringKey.STATUS_CONNECTING)
|
||||
tvStatus.setTextColor(0xFF000000.toInt())
|
||||
tvStatus.setTextColor(Colors.getTextColor(this))
|
||||
val testClient = HaClient.getServiceForTest(url, token)
|
||||
testClient.getStates().enqueue(object : Callback<List<HaState>> {
|
||||
override fun onResponse(call: Call<List<HaState>>, response: Response<List<HaState>>) {
|
||||
@@ -57,27 +72,52 @@ class ConnectionSettingsActivity : BaseActivity() {
|
||||
Prefs.setUrl(this@ConnectionSettingsActivity, url)
|
||||
Prefs.setToken(this@ConnectionSettingsActivity, token)
|
||||
Prefs.setRefreshInterval(this@ConnectionSettingsActivity, intervalSec * 1000L)
|
||||
Prefs.setWebSocketEnabled(this@ConnectionSettingsActivity, wsEnabled)
|
||||
Prefs.setSelectedEntities(this@ConnectionSettingsActivity, emptySet())
|
||||
HaClient.clearCache()
|
||||
runOnUiThread {
|
||||
tvStatus.text = stringsProvider.get(StringKey.STATUS_SUCCESS)
|
||||
tvStatus.setTextColor(0xFF0056B3.toInt())
|
||||
tvStatus.setTextColor(if (Prefs.isDarkMode(this@ConnectionSettingsActivity)) Colors.YELLOW else Colors.BLUE)
|
||||
Toast.makeText(this@ConnectionSettingsActivity, stringsProvider.get(StringKey.TOAST_SAVED_CLEARED), Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
tvStatus.text = "${stringsProvider.get(StringKey.STATUS_ERROR)}: ${response.code()}"
|
||||
tvStatus.setTextColor(0xFFE23A24.toInt())
|
||||
tvStatus.setTextColor(Colors.RED)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<List<HaState>>, t: Throwable) {
|
||||
runOnUiThread {
|
||||
tvStatus.text = "${stringsProvider.get(StringKey.STATUS_ERROR_NETWORK)}: ${t.message}"
|
||||
tvStatus.setTextColor(0xFFE23A24.toInt())
|
||||
tvStatus.setTextColor(Colors.RED)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun applyTheme() {
|
||||
val root = findViewById<View>(android.R.id.content)
|
||||
root.setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
val textColor = Colors.getTextColor(this)
|
||||
val borderColor = Colors.getBorderColor(this)
|
||||
|
||||
findViewById<TextView>(R.id.tvConnectionTitle).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.labelUrl).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.labelToken).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.labelRefresh).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.labelWebSocket).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvStatus).setTextColor(textColor)
|
||||
|
||||
findViewById<EditText>(R.id.etUrl).setTextColor(textColor)
|
||||
findViewById<EditText>(R.id.etToken).setTextColor(textColor)
|
||||
findViewById<EditText>(R.id.etRefreshInterval).setTextColor(textColor)
|
||||
|
||||
findViewById<View>(R.id.vBorderTitle).setBackgroundColor(borderColor)
|
||||
findViewById<View>(R.id.vBorderUrl).setBackgroundColor(borderColor)
|
||||
findViewById<View>(R.id.vBorderToken).setBackgroundColor(borderColor)
|
||||
findViewById<View>(R.id.vBorderRefresh).setBackgroundColor(borderColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package com.example.retroha
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.example.retroha.data.Prefs
|
||||
import com.example.retroha.i18n.AndroidStrings
|
||||
import com.example.retroha.i18n.StringKey
|
||||
import com.example.retroha.network.HaClient
|
||||
import com.example.retroha.network.HaState
|
||||
import com.example.retroha.theme.Colors
|
||||
import com.example.retroha.ui.EntitySelectionAdapter
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
/**
|
||||
* Activity for browsing and selecting Home Assistant entities to be displayed as widgets.
|
||||
* Features real-time filtering and persistent selection storage.
|
||||
@@ -22,51 +26,62 @@ class EntitySelectionActivity : BaseActivity() {
|
||||
private var allEntities = mutableListOf<HaState>()
|
||||
private var filteredEntities = mutableListOf<HaState>()
|
||||
private var selectedEntities = mutableSetOf<String>()
|
||||
private var adapter: EntitySelectionAdapter? = null
|
||||
private lateinit var adapter: EntitySelectionAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_entity_selection)
|
||||
|
||||
applyTheme()
|
||||
|
||||
val stringsProvider = AndroidStrings(this)
|
||||
etSearch = findViewById(R.id.etSearch)
|
||||
lvEntities = findViewById(R.id.lvEntities)
|
||||
val btnSave = findViewById<Button>(R.id.btnSave)
|
||||
|
||||
selectedEntities.addAll(Prefs.getSelectedEntities(this))
|
||||
|
||||
btnSave.setOnClickListener {
|
||||
Prefs.setSelectedEntities(this, selectedEntities)
|
||||
Toast.makeText(this, stringsProvider.get(StringKey.TOAST_ENTITIES_SAVED), Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
etSearch.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) { filterEntities(s.toString()) }
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
lvEntities.onItemClickListener = AdapterView.OnItemClickListener { _, view, position, _ ->
|
||||
val entityId = filteredEntities[position].entity_id
|
||||
if (selectedEntities.contains(entityId)) {
|
||||
selectedEntities.remove(entityId)
|
||||
|
||||
lvEntities.setOnItemClickListener { _, view, position, _ ->
|
||||
val item = filteredEntities[position]
|
||||
val checkbox = view.findViewById<com.example.retroha.ui.BauhausCheckbox>(R.id.cbEntity)
|
||||
if (selectedEntities.contains(item.entity_id)) {
|
||||
selectedEntities.remove(item.entity_id)
|
||||
checkbox.isChecked = false
|
||||
} else {
|
||||
selectedEntities.add(entityId)
|
||||
selectedEntities.add(item.entity_id)
|
||||
checkbox.isChecked = true
|
||||
}
|
||||
val layout = view as? LinearLayout
|
||||
val checkbox = layout?.getChildAt(0) as? com.example.retroha.ui.BauhausCheckbox
|
||||
checkbox?.isChecked = selectedEntities.contains(entityId)
|
||||
}
|
||||
|
||||
fetchEntities()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the complete list of available entities from the HA server.
|
||||
*/
|
||||
private fun fetchEntities() {
|
||||
val url = Prefs.getUrl(this)
|
||||
val token = Prefs.getToken(this)
|
||||
if (token.isEmpty()) return
|
||||
HaClient.getService(this).getStates().enqueue(object : Callback<List<HaState>> {
|
||||
override fun onResponse(call: Call<List<HaState>>, response: Response<List<HaState>>) {
|
||||
if (response.isSuccessful) {
|
||||
val list = response.body() ?: emptyList()
|
||||
allEntities.clear()
|
||||
allEntities.addAll(response.body() ?: emptyList())
|
||||
allEntities.sortBy { it.entity_id }
|
||||
runOnUiThread { filterEntities(etSearch.text.toString()) }
|
||||
allEntities.addAll(list)
|
||||
filterEntities("")
|
||||
} else {
|
||||
val stringsProvider = AndroidStrings(this@EntitySelectionActivity)
|
||||
runOnUiThread { Toast.makeText(this@EntitySelectionActivity, "${stringsProvider.get(StringKey.STATUS_ERROR_HA)}: ${response.code()}", Toast.LENGTH_SHORT).show() }
|
||||
@@ -80,6 +95,7 @@ class EntitySelectionActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the [allEntities] list based on the user's search query.
|
||||
*/
|
||||
@@ -89,15 +105,37 @@ class EntitySelectionActivity : BaseActivity() {
|
||||
filteredEntities.addAll(allEntities)
|
||||
} else {
|
||||
val q = query.lowercase()
|
||||
allEntities.filterTo(filteredEntities) {
|
||||
it.entity_id.lowercase().contains(q) ||
|
||||
(it.attributes.friendly_name?.lowercase()?.contains(q) == true)
|
||||
allEntities.forEach { item ->
|
||||
if (item.entity_id.lowercase().contains(q) ||
|
||||
item.attributes.friendly_name?.lowercase()?.contains(q) == true) {
|
||||
filteredEntities.add(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateList()
|
||||
}
|
||||
|
||||
private fun updateList() {
|
||||
adapter = EntitySelectionAdapter(this, filteredEntities, selectedEntities)
|
||||
lvEntities.adapter = adapter
|
||||
}
|
||||
|
||||
private fun applyTheme() {
|
||||
val root = findViewById<View>(R.id.rootLayout)
|
||||
root.setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
val textColor = Colors.getTextColor(this)
|
||||
val borderColor = Colors.getBorderColor(this)
|
||||
|
||||
findViewById<TextView>(R.id.tvSelectionTitle).setTextColor(textColor)
|
||||
findViewById<EditText>(R.id.etSearch).setTextColor(textColor)
|
||||
findViewById<EditText>(R.id.etSearch).setHintTextColor(Colors.GRAY_MID)
|
||||
|
||||
findViewById<View>(R.id.vBorderTitle).setBackgroundColor(borderColor)
|
||||
findViewById<View>(R.id.vBorderSearch).setBackgroundColor(borderColor)
|
||||
|
||||
lvEntities = findViewById(R.id.lvEntities)
|
||||
lvEntities.divider = android.graphics.drawable.ColorDrawable(borderColor)
|
||||
lvEntities.dividerHeight = (1 * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.example.retroha
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.example.retroha.theme.Colors
|
||||
|
||||
/**
|
||||
* Activity for displaying the user manual and basic operation instructions.
|
||||
*/
|
||||
@@ -7,5 +11,24 @@ class InstructionsActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_instructions)
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
private fun applyTheme() {
|
||||
val root = findViewById<View>(R.id.scrollView)
|
||||
root.setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
val textColor = Colors.getTextColor(this)
|
||||
val borderColor = Colors.getBorderColor(this)
|
||||
|
||||
findViewById<TextView>(R.id.tvInstructionsTitle).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvHeader1).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvBody1).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvHeader2).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvBody2).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvHeader3).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.tvBody3).setTextColor(textColor)
|
||||
|
||||
findViewById<View>(R.id.vBorderTitle).setBackgroundColor(borderColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class LanguageActivity : BaseActivity() {
|
||||
val root = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundColor(Colors.WHITE)
|
||||
setBackgroundColor(Colors.getBgColor(this@LanguageActivity))
|
||||
setPadding(dp(32), dp(32), dp(32), dp(32))
|
||||
}
|
||||
val stringsProvider = AndroidStrings(this)
|
||||
@@ -40,7 +40,7 @@ class LanguageActivity : BaseActivity() {
|
||||
text = stringsProvider.get(StringKey.TITLE_SELECT_LANGUAGE)
|
||||
typeface = android.graphics.Typeface.MONOSPACE
|
||||
textSize = 18f
|
||||
setTextColor(Colors.BLACK)
|
||||
setTextColor(Colors.getTextColor(this@LanguageActivity))
|
||||
gravity = Gravity.CENTER
|
||||
setPadding(0, 0, 0, dp(64))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.example.retroha
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
@@ -21,6 +22,8 @@ import com.example.retroha.network.HaClient
|
||||
import com.example.retroha.network.HaState
|
||||
import com.example.retroha.network.ToggleRequest
|
||||
import com.example.retroha.network.HaWebSocketManager
|
||||
import com.example.retroha.ui.BauhausControlOverlay
|
||||
import com.example.retroha.theme.Colors
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
@@ -31,6 +34,7 @@ import android.view.HapticFeedbackConstants
|
||||
import android.view.WindowManager
|
||||
import android.graphics.Color
|
||||
import android.view.Gravity
|
||||
|
||||
/**
|
||||
* The primary dashboard activity.
|
||||
* Displays a responsive grid of Home Assistant widgets and handles real-time updates.
|
||||
@@ -46,6 +50,7 @@ class MainActivity : BaseActivity() {
|
||||
private lateinit var tvStatusIndicator: TextView
|
||||
private lateinit var tabContainer: LinearLayout
|
||||
private var webSocketManager: HaWebSocketManager? = null
|
||||
private lateinit var controlOverlay: BauhausControlOverlay
|
||||
|
||||
private val refreshRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
@@ -54,13 +59,19 @@ class MainActivity : BaseActivity() {
|
||||
mainHandler.postDelayed(this, interval)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
applyTheme()
|
||||
|
||||
stringsProvider = AndroidStrings(this)
|
||||
tvStatusIndicator = findViewById(R.id.tvStatusIndicator)
|
||||
tabContainer = findViewById(R.id.tabContainer)
|
||||
controlOverlay = findViewById(R.id.controlOverlay)
|
||||
|
||||
val gridView = findViewById<GridView>(R.id.gridView)
|
||||
gridView.numColumns = resolveColumns()
|
||||
adapter = WidgetAdapter(this, displayedEntities)
|
||||
@@ -73,27 +84,48 @@ class MainActivity : BaseActivity() {
|
||||
handleLongToggle(cfg)
|
||||
}
|
||||
gridView.adapter = adapter
|
||||
|
||||
findViewById<android.view.View>(R.id.btnSettings).setOnClickListener {
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
findViewById<TextView>(R.id.btnSettings).apply {
|
||||
val bg = android.graphics.drawable.GradientDrawable().apply {
|
||||
shape = android.graphics.drawable.GradientDrawable.RECTANGLE
|
||||
setColor(com.example.retroha.theme.Colors.BLUE)
|
||||
setStroke(dp(2), com.example.retroha.theme.Colors.BLACK)
|
||||
setColor(Colors.BLUE)
|
||||
setStroke(dp(2), Colors.BLACK)
|
||||
}
|
||||
background = bg
|
||||
}
|
||||
|
||||
findViewById<android.view.View>(R.id.tvTitle).setOnClickListener {
|
||||
fetchHaStates()
|
||||
Toast.makeText(this, stringsProvider.get(StringKey.STATUS_REFRESHING), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
setupTabs()
|
||||
|
||||
webSocketManager = HaWebSocketManager(this) { haState ->
|
||||
handleStateUpdate(haState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyTheme() {
|
||||
val root = findViewById<View>(R.id.mainLayout)
|
||||
root.setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
val textColor = Colors.getTextColor(this)
|
||||
val borderColor = Colors.getBorderColor(this)
|
||||
|
||||
findViewById<TextView>(R.id.tvTitle).setTextColor(textColor)
|
||||
findViewById<View>(R.id.topBar).setBackgroundColor(Colors.getBgColor(this))
|
||||
findViewById<View>(R.id.tabScrollView).setBackgroundColor(Colors.getBgColor(this))
|
||||
findViewById<View>(R.id.vTopBarBorder).setBackgroundColor(borderColor)
|
||||
findViewById<View>(R.id.gridView).setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
findViewById<TextView>(R.id.btnSettingsShadow).setTextColor(Colors.BLACK) // Shadow is always black
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the category filtering tabs.
|
||||
* Tabs are dynamically generated based on supported domains and current selection.
|
||||
@@ -118,7 +150,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
val shadow = View(this).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
setBackgroundColor(com.example.retroha.theme.Colors.BLACK)
|
||||
setBackgroundColor(Colors.getShadowColor(this@MainActivity))
|
||||
translationX = dp(2).toFloat()
|
||||
translationY = dp(2).toFloat()
|
||||
}
|
||||
@@ -131,11 +163,11 @@ class MainActivity : BaseActivity() {
|
||||
gravity = Gravity.CENTER
|
||||
val bgDrawable = android.graphics.drawable.GradientDrawable().apply {
|
||||
shape = android.graphics.drawable.GradientDrawable.RECTANGLE
|
||||
setColor(if (isSelected) com.example.retroha.theme.Colors.YELLOW else com.example.retroha.theme.Colors.WHITE)
|
||||
setStroke(dp(2), com.example.retroha.theme.Colors.BLACK)
|
||||
setColor(if (isSelected) Colors.YELLOW else Colors.getStatusOff(this@MainActivity))
|
||||
setStroke(dp(2), Colors.getBorderColor(this@MainActivity))
|
||||
}
|
||||
background = bgDrawable
|
||||
setTextColor(com.example.retroha.theme.Colors.BLACK)
|
||||
setTextColor(Colors.getTextColor(this@MainActivity))
|
||||
setOnClickListener {
|
||||
if (currentCategory == cat) return@setOnClickListener
|
||||
currentCategory = cat
|
||||
@@ -150,7 +182,9 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dp(v: Int) = (v * resources.displayMetrics.density + 0.5f).toInt()
|
||||
|
||||
/**
|
||||
* Filters the list of all entities based on the [currentCategory] and updates the grid.
|
||||
*/
|
||||
@@ -167,20 +201,29 @@ class MainActivity : BaseActivity() {
|
||||
else -> allEntities
|
||||
}
|
||||
runOnUiThread {
|
||||
adapter.updateItems(filtered)
|
||||
displayedEntities.clear()
|
||||
displayedEntities.addAll(filtered)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
applyTheme()
|
||||
setupTabs()
|
||||
mainHandler.removeCallbacks(refreshRunnable)
|
||||
mainHandler.post(refreshRunnable)
|
||||
webSocketManager?.connect()
|
||||
if (Prefs.isWebSocketEnabled(this)) {
|
||||
webSocketManager?.connect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mainHandler.removeCallbacks(refreshRunnable)
|
||||
webSocketManager?.disconnect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the latest states from Home Assistant and updates the UI.
|
||||
* Handles authentication errors and network failures.
|
||||
@@ -189,7 +232,7 @@ class MainActivity : BaseActivity() {
|
||||
val token = Prefs.getToken(this)
|
||||
if (token.isEmpty()) {
|
||||
tvStatusIndicator.text = stringsProvider.get(StringKey.STATUS_NO_TOKEN)
|
||||
tvStatusIndicator.setTextColor(com.example.retroha.theme.Colors.RED)
|
||||
tvStatusIndicator.setTextColor(Colors.RED)
|
||||
return
|
||||
}
|
||||
HaClient.getService(this).getStates().enqueue(object : Callback<List<HaState>> {
|
||||
@@ -199,23 +242,24 @@ class MainActivity : BaseActivity() {
|
||||
updateEntities(states)
|
||||
runOnUiThread {
|
||||
tvStatusIndicator.text = stringsProvider.get(StringKey.STATUS_CONNECTED)
|
||||
tvStatusIndicator.setTextColor(com.example.retroha.theme.Colors.BLUE)
|
||||
tvStatusIndicator.setTextColor(if (Prefs.isDarkMode(this@MainActivity)) Colors.YELLOW else Colors.BLUE)
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
tvStatusIndicator.text = "${stringsProvider.get(StringKey.STATUS_ERROR_HA)}: ${response.code()}"
|
||||
tvStatusIndicator.setTextColor(com.example.retroha.theme.Colors.RED)
|
||||
tvStatusIndicator.setTextColor(Colors.RED)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<List<HaState>>, t: Throwable) {
|
||||
runOnUiThread {
|
||||
tvStatusIndicator.text = stringsProvider.get(StringKey.STATUS_OFFLINE)
|
||||
tvStatusIndicator.setTextColor(com.example.retroha.theme.Colors.RED)
|
||||
tvStatusIndicator.setTextColor(Colors.RED)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a single entity state update from WebSocket.
|
||||
*/
|
||||
@@ -248,9 +292,7 @@ class MainActivity : BaseActivity() {
|
||||
} else {
|
||||
allEntities.add(updatedConfig)
|
||||
}
|
||||
runOnUiThread {
|
||||
filterEntities()
|
||||
}
|
||||
filterEntities()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,21 +323,17 @@ class MainActivity : BaseActivity() {
|
||||
brightness = ha.attributes.brightness
|
||||
))
|
||||
}
|
||||
runOnUiThread {
|
||||
filterEntities()
|
||||
filterEntities()
|
||||
}
|
||||
|
||||
private fun handleLongToggle(cfg: WidgetConfig) {
|
||||
// Light control is now available even if state is OFF
|
||||
if (cfg.domain != "light") return
|
||||
controlOverlay.showBrightness(cfg.label, cfg.brightness ?: 0) { brightness ->
|
||||
setLightBrightness(cfg.entityId, brightness)
|
||||
}
|
||||
}
|
||||
private fun handleLongToggle(cfg: WidgetConfig) {
|
||||
if (cfg.domain != "light") return
|
||||
com.example.retroha.ui.LightControlDialog(
|
||||
this,
|
||||
cfg.label,
|
||||
cfg.brightness ?: 0,
|
||||
onBrightnessChanged = { brightness ->
|
||||
setLightBrightness(cfg.entityId, brightness)
|
||||
}
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun setLightBrightness(entityId: String, brightness: Int) {
|
||||
HaClient.getService(this).setBrightness(com.example.retroha.network.BrightnessRequest(entityId, brightness))
|
||||
.enqueue(object : Callback<List<HaState>> {
|
||||
@@ -305,6 +343,7 @@ class MainActivity : BaseActivity() {
|
||||
override fun onFailure(call: Call<List<HaState>>, t: Throwable) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleToggle(cfg: WidgetConfig) {
|
||||
val idx = allEntities.indexOfFirst { it.entityId == cfg.entityId }
|
||||
if (idx < 0) return
|
||||
@@ -314,6 +353,7 @@ class MainActivity : BaseActivity() {
|
||||
WidgetInteraction.READ_ONLY -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun doToggle(idx: Int, cfg: WidgetConfig) {
|
||||
val domain = cfg.entityId.split(".")[0]
|
||||
if (idx >= 0 && idx < allEntities.size) {
|
||||
@@ -336,6 +376,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun doExecute(idx: Int, cfg: WidgetConfig) {
|
||||
val domain = cfg.entityId.split(".")[0]
|
||||
if (idx >= 0 && idx < allEntities.size) {
|
||||
@@ -358,12 +399,22 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (controlOverlay.visibility == View.VISIBLE) {
|
||||
controlOverlay.visibility = View.GONE
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mainHandler.removeCallbacks(refreshRunnable)
|
||||
mainHandler.removeCallbacksAndMessages(null)
|
||||
webSocketManager?.disconnect()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
|
||||
@@ -374,6 +425,7 @@ class MainActivity : BaseActivity() {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the optimal number of columns based on screen width and orientation.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.example.retroha
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.example.retroha.data.Prefs
|
||||
import com.example.retroha.i18n.AndroidStrings
|
||||
import com.example.retroha.i18n.StringKey
|
||||
import com.example.retroha.theme.Colors
|
||||
|
||||
/**
|
||||
* Main settings menu activity.
|
||||
* Provides navigation to connection configuration, entity selection,
|
||||
@@ -15,6 +20,9 @@ class SettingsActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_settings)
|
||||
|
||||
applyTheme()
|
||||
|
||||
findViewById<Button>(R.id.btnEntitySelection).setOnClickListener {
|
||||
startActivity(Intent(this, EntitySelectionActivity::class.java))
|
||||
}
|
||||
@@ -29,6 +37,16 @@ class SettingsActivity : BaseActivity() {
|
||||
intent.putExtra("from_settings", true)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
val cbDarkMode = findViewById<com.example.retroha.ui.BauhausCheckbox>(R.id.cbDarkMode)
|
||||
cbDarkMode.isChecked = Prefs.isDarkMode(this)
|
||||
cbDarkMode.setOnClickListener {
|
||||
val newState = !cbDarkMode.isChecked
|
||||
cbDarkMode.isChecked = newState
|
||||
Prefs.setDarkMode(this, newState)
|
||||
applyTheme() // Immediate visual feedback
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.btnDeleteAll).setOnClickListener {
|
||||
val stringsProvider = AndroidStrings(this)
|
||||
android.app.AlertDialog.Builder(this)
|
||||
@@ -42,4 +60,13 @@ class SettingsActivity : BaseActivity() {
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyTheme() {
|
||||
val root = findViewById<View>(android.R.id.content)
|
||||
root.setBackgroundColor(Colors.getBgColor(this))
|
||||
|
||||
val textColor = Colors.getTextColor(this)
|
||||
findViewById<TextView>(R.id.tvSettingsTitle).setTextColor(textColor)
|
||||
findViewById<TextView>(R.id.labelDarkMode).setTextColor(textColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ object Prefs {
|
||||
private const val KEY_SELECTED_ENTITIES = "selected_entities"
|
||||
private const val KEY_REFRESH_INTERVAL = "refresh_interval"
|
||||
private const val KEY_LANGUAGE = "app_language"
|
||||
private const val KEY_WEBSOCKET_ENABLED = "websocket_enabled"
|
||||
private const val KEY_DARK_MODE = "dark_mode"
|
||||
|
||||
private fun getPrefs(context: Context): SharedPreferences =
|
||||
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
@@ -43,4 +45,14 @@ object Prefs {
|
||||
/** Saves the set of selected HA entity IDs. */
|
||||
fun setSelectedEntities(context: Context, entities: Set<String>) =
|
||||
getPrefs(context).edit().putStringSet(KEY_SELECTED_ENTITIES, entities).apply()
|
||||
|
||||
/** Checks if WebSocket real-time updates are enabled. Defaults to true. */
|
||||
fun isWebSocketEnabled(context: Context): Boolean = getPrefs(context).getBoolean(KEY_WEBSOCKET_ENABLED, true)
|
||||
/** Sets the WebSocket enabled status. */
|
||||
fun setWebSocketEnabled(context: Context, enabled: Boolean) = getPrefs(context).edit().putBoolean(KEY_WEBSOCKET_ENABLED, enabled).apply()
|
||||
|
||||
/** Checks if Dark Mode is enabled. Defaults to false. */
|
||||
fun isDarkMode(context: Context): Boolean = getPrefs(context).getBoolean(KEY_DARK_MODE, false)
|
||||
/** Sets the Dark Mode status. */
|
||||
fun setDarkMode(context: Context, enabled: Boolean) = getPrefs(context).edit().putBoolean(KEY_DARK_MODE, enabled).apply()
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ class AndroidStrings(private val context: Context) : Strings {
|
||||
StringKey.LABEL_URL -> R.string.label_url
|
||||
StringKey.LABEL_TOKEN -> R.string.label_token
|
||||
StringKey.LABEL_REFRESH -> R.string.label_refresh
|
||||
StringKey.LABEL_WEBSOCKET_ENABLE -> R.string.label_websocket_enable
|
||||
StringKey.LABEL_DARK_MODE -> R.string.label_dark_mode
|
||||
StringKey.BTN_TEST_SAVE -> R.string.btn_test_save
|
||||
StringKey.BTN_DELETE_ALL -> R.string.btn_delete_all
|
||||
StringKey.BTN_SAVE_SELECTED -> R.string.btn_save_selected
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.example.retroha.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import com.example.retroha.theme.Colors
|
||||
|
||||
/**
|
||||
* A full-screen overlay view that provides entity controls (like brightness).
|
||||
* Replaces Dialogs to avoid window surface bugs (EGL_BAD_MATCH) on old hardware.
|
||||
*/
|
||||
class BauhausControlOverlay @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val density = resources.displayMetrics.density
|
||||
private fun dp(v: Int) = (v * density + 0.5f).toInt()
|
||||
|
||||
private var onBrightnessChanged: ((Int) -> Unit)? = null
|
||||
|
||||
init {
|
||||
// Semi-transparent background
|
||||
setBackgroundColor(0xAA000000.toInt())
|
||||
visibility = View.GONE
|
||||
|
||||
// Close on click outside the center box
|
||||
setOnClickListener {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the brightness control UI for a specific entity.
|
||||
*/
|
||||
fun showBrightness(entityName: String, initialBrightness: Int, callback: (Int) -> Unit) {
|
||||
this.onBrightnessChanged = callback
|
||||
removeAllViews()
|
||||
|
||||
val bgColor = Colors.getBgColor(context)
|
||||
val textColor = Colors.getTextColor(context)
|
||||
val borderColor = Colors.getBorderColor(context)
|
||||
|
||||
val root = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setBackgroundColor(bgColor)
|
||||
setPadding(dp(24), dp(24), dp(24), dp(24))
|
||||
layoutParams = LayoutParams(dp(300), ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
|
||||
// Prevent click events from reaching the overlay's background click listener
|
||||
setOnClickListener { /* Consume */ }
|
||||
}
|
||||
|
||||
// Title
|
||||
root.addView(TextView(context).apply {
|
||||
text = entityName.uppercase()
|
||||
typeface = Typeface.MONOSPACE
|
||||
textSize = 16f
|
||||
setTextColor(textColor)
|
||||
setPadding(0, 0, 0, dp(16))
|
||||
})
|
||||
|
||||
// Border line
|
||||
root.addView(View(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(2))
|
||||
setBackgroundColor(borderColor)
|
||||
})
|
||||
|
||||
val brightnessLabel = context.getString(com.example.retroha.R.string.dialog_brightness)
|
||||
|
||||
val tvBrightness = TextView(context).apply {
|
||||
text = "$brightnessLabel: ${(initialBrightness * 100 / 255)}%"
|
||||
typeface = Typeface.MONOSPACE
|
||||
textSize = 14f
|
||||
setTextColor(textColor)
|
||||
setPadding(0, dp(24), 0, dp(8))
|
||||
}
|
||||
root.addView(tvBrightness)
|
||||
|
||||
// SeekBar
|
||||
val seekBar = SeekBar(context).apply {
|
||||
max = 255
|
||||
progress = initialBrightness
|
||||
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(s: SeekBar?, p: Int, fromUser: Boolean) {
|
||||
tvBrightness.text = "$brightnessLabel: ${(p * 100 / 255)}%"
|
||||
}
|
||||
override fun onStartTrackingTouch(s: SeekBar?) {}
|
||||
override fun onStopTrackingTouch(s: SeekBar?) {
|
||||
onBrightnessChanged?.invoke(progress)
|
||||
}
|
||||
})
|
||||
}
|
||||
root.addView(seekBar)
|
||||
|
||||
// Padding
|
||||
root.addView(View(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(1, dp(16))
|
||||
})
|
||||
|
||||
addView(root)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,18 @@ class EntitySelectionAdapter(
|
||||
val textContainer = layout.getChildAt(1) as LinearLayout
|
||||
val tvName = textContainer.getChildAt(0) as TextView
|
||||
val tvId = textContainer.getChildAt(1) as TextView
|
||||
|
||||
val item = items[position]
|
||||
checkbox.isChecked = selectedEntities.contains(item.entity_id)
|
||||
tvName.text = item.attributes.friendly_name ?: item.entity_id
|
||||
tvId.text = item.entity_id
|
||||
|
||||
val textColor = Colors.getTextColor(context)
|
||||
tvName.setTextColor(textColor)
|
||||
tvId.setTextColor(Colors.GRAY_MID)
|
||||
|
||||
return layout
|
||||
}
|
||||
}
|
||||
private fun createLayout(): LinearLayout {
|
||||
return LinearLayout(context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
@@ -31,18 +31,21 @@ class LightControlDialog(
|
||||
private val entityName: String,
|
||||
private val initialBrightness: Int,
|
||||
private val onBrightnessChanged: (Int) -> Unit
|
||||
) : Dialog(context) {
|
||||
) : Dialog(context, android.R.style.Theme_Holo_Light_Dialog_NoActionBar) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
// Disable hardware acceleration for this dialog window to avoid EGL errors on old hardware
|
||||
window?.setFlags(
|
||||
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
|
||||
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
|
||||
)
|
||||
|
||||
// Force OPAQUE format and disable hardware acceleration to fix EGL_BAD_MATCH on old drivers
|
||||
window?.let { win ->
|
||||
win.setFormat(android.graphics.PixelFormat.RGB_565)
|
||||
win.setFlags(
|
||||
0, // Remove all flags that might trigger HW acceleration bugs
|
||||
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
|
||||
)
|
||||
win.setBackgroundDrawable(ColorDrawable(Colors.WHITE))
|
||||
}
|
||||
|
||||
val density = context.resources.displayMetrics.density
|
||||
fun dp(v: Int) = (v * density + 0.5f).toInt()
|
||||
|
||||
@@ -50,7 +53,10 @@ class LightControlDialog(
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setBackgroundColor(Colors.WHITE)
|
||||
setPadding(dp(24), dp(24), dp(24), dp(24))
|
||||
layoutParams = LinearLayout.LayoutParams(dp(320), ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
layoutParams = LinearLayout.LayoutParams(dp(300), ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
// Force software rendering for the content of the dialog
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
|
||||
root.addView(TextView(context).apply {
|
||||
|
||||
@@ -39,13 +39,13 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
private val execIconPath = Path()
|
||||
private val paintFill = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
||||
private val tpLabel = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Colors.GRAY_MID; textSize = sp(11); typeface = Fonts.REGULAR
|
||||
typeface = Fonts.REGULAR
|
||||
}
|
||||
private val tpValue = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Colors.BLACK; textSize = sp(18); typeface = Fonts.BOLD
|
||||
typeface = Fonts.BOLD
|
||||
}
|
||||
private val tpSecondary = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Colors.GRAY_MID; textSize = sp(10); typeface = Fonts.REGULAR
|
||||
typeface = Fonts.REGULAR
|
||||
}
|
||||
private val pulseInterpolator = DecelerateInterpolator()
|
||||
|
||||
@@ -99,8 +99,11 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
fun bind(cfg: WidgetConfig) {
|
||||
config = cfg
|
||||
currentInteraction = cfg.domain.toWidgetInteraction()
|
||||
isClickable = currentInteraction != WidgetInteraction.READ_ONLY
|
||||
&& cfg.state != EntityState.UNAVAILABLE
|
||||
|
||||
// Ensure interactive widgets are clickable even when OFF.
|
||||
// Only UNAVAILABLE state blocks interaction.
|
||||
isClickable = cfg.state != EntityState.UNAVAILABLE && currentInteraction != WidgetInteraction.READ_ONLY
|
||||
isLongClickable = cfg.domain == "light" && cfg.state != EntityState.UNAVAILABLE
|
||||
|
||||
if (cfg.state == EntityState.TOGGLING) {
|
||||
startPulse()
|
||||
@@ -136,17 +139,21 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
|
||||
val labelUp = if (labelChanged) cfg.label.uppercase() else cachedLabel
|
||||
|
||||
// For sensors/numbers, append unit to value to prevent line wrapping
|
||||
val valueText = if (cfg.secondary.length <= 3 && cfg.secondary.isNotEmpty()) {
|
||||
"${cfg.value} ${cfg.secondary}"
|
||||
// Use non-breaking space to keep value and unit together
|
||||
val valueText = if (cfg.secondary.length <= 4 && cfg.secondary.isNotEmpty()) {
|
||||
"${cfg.value}\u00A0${cfg.secondary}"
|
||||
} else {
|
||||
cfg.value
|
||||
}
|
||||
|
||||
val secondaryText = if (cfg.secondary.length <= 3) "" else cfg.secondary
|
||||
val secondaryText = if (cfg.secondary.length <= 4) "" else cfg.secondary
|
||||
|
||||
lLabel = makeLayout(labelUp, tpLabel, textW)
|
||||
lValue = makeLayout(valueText, tpValue, textW)
|
||||
|
||||
// Forced single line: manually ellipsize before making layout
|
||||
val truncatedValue = TextUtils.ellipsize(valueText, tpValue, textW.toFloat(), TextUtils.TruncateAt.END)
|
||||
lValue = makeLayout(truncatedValue.toString(), tpValue, textW)
|
||||
|
||||
lSecondary = makeLayout(secondaryText, tpSecondary, textW)
|
||||
|
||||
cachedLabel = cfg.label
|
||||
@@ -162,27 +169,50 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
val b = borderPx.toFloat()
|
||||
val s = shadowPx.toFloat()
|
||||
|
||||
// Update paint colors based on theme
|
||||
tpLabel.color = Colors.GRAY_MID
|
||||
tpValue.color = Colors.getTextColor(context)
|
||||
tpSecondary.color = Colors.GRAY_MID
|
||||
tpLabel.textSize = sp(11)
|
||||
tpValue.textSize = sp(18)
|
||||
tpSecondary.textSize = sp(10)
|
||||
|
||||
// Shadow
|
||||
paintFill.color = Colors.BLACK
|
||||
paintFill.color = Colors.getShadowColor(context)
|
||||
canvas.drawRect(s, s, width.toFloat(), cardH + s, paintFill)
|
||||
|
||||
// Border
|
||||
paintFill.color = when (cfg.state) {
|
||||
EntityState.TOGGLING -> Colors.BORDER_TOGGLING
|
||||
EntityState.UNAVAILABLE -> Colors.BORDER_UNAVAILABLE
|
||||
else -> Colors.BORDER_DEFAULT
|
||||
EntityState.TOGGLING -> Colors.BLUE
|
||||
EntityState.UNAVAILABLE -> Colors.GRAY_MID
|
||||
else -> Colors.getBorderColor(context)
|
||||
}
|
||||
canvas.drawRect(0f, 0f, cardW, cardH, paintFill)
|
||||
|
||||
// Background
|
||||
paintFill.color = when (cfg.state) {
|
||||
EntityState.ON -> Colors.STATUS_ON
|
||||
EntityState.OFF -> Colors.STATUS_OFF
|
||||
val baseBgColor = when (cfg.state) {
|
||||
EntityState.UNAVAILABLE -> Colors.STATUS_UNAVAILABLE
|
||||
EntityState.TOGGLING -> Colors.STATUS_TOGGLING
|
||||
else -> Colors.getStatusOff(context)
|
||||
}
|
||||
paintFill.color = baseBgColor
|
||||
canvas.drawRect(b, b, cardW - b, cardH - b, paintFill)
|
||||
|
||||
// Brightness fill for lights that are ON
|
||||
if (cfg.domain == "light" && cfg.state == EntityState.ON) {
|
||||
paintFill.color = Colors.STATUS_ON
|
||||
val brightness = cfg.brightness ?: 255
|
||||
val fillPercent = brightness / 255f
|
||||
val fillTop = cardH - b - ((cardH - 2 * b) * fillPercent)
|
||||
canvas.drawRect(b, fillTop, cardW - b, cardH - b, paintFill)
|
||||
} else if (cfg.state == EntityState.ON) {
|
||||
// Full fill for other ON entities (switches, etc)
|
||||
paintFill.color = Colors.STATUS_ON
|
||||
canvas.drawRect(b, b, cardW - b, cardH - b, paintFill)
|
||||
} else if (cfg.state == EntityState.TOGGLING) {
|
||||
paintFill.color = Colors.getStatusToggling(context)
|
||||
canvas.drawRect(b, b, cardW - b, cardH - b, paintFill)
|
||||
}
|
||||
|
||||
// Domain Stripe
|
||||
paintFill.color = stripeColor(cfg.domain)
|
||||
canvas.drawRect(b, b, b + stripePx, cardH - b, paintFill)
|
||||
@@ -228,7 +258,6 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
|
||||
private fun startPulse() {
|
||||
if (pulseAnim?.isRunning == true) return
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
pulseAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f, 0.35f).apply {
|
||||
duration = 550
|
||||
repeatMode = ObjectAnimator.REVERSE
|
||||
@@ -242,7 +271,6 @@ class WidgetCardView(context: Context) : View(context) {
|
||||
pulseAnim?.cancel()
|
||||
pulseAnim = null
|
||||
alpha = 1f
|
||||
setLayerType(LAYER_TYPE_NONE, null)
|
||||
}
|
||||
|
||||
private fun stripeColor(domain: String): Int = when (domain) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvConnectionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_connection"
|
||||
@@ -16,6 +17,7 @@
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/ha_black"
|
||||
@@ -23,6 +25,7 @@
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelUrl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/label_url"
|
||||
@@ -43,12 +46,14 @@
|
||||
android:textSize="16sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelToken"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/label_token"
|
||||
@@ -69,12 +74,14 @@
|
||||
android:textSize="16sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderToken"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelRefresh"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/label_refresh"
|
||||
@@ -95,10 +102,35 @@
|
||||
android:textSize="16sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="24dp" />
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<com.example.retroha.ui.BauhausCheckbox
|
||||
android:id="@+id/cbWebSocket"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelWebSocket"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/label_websocket_enable"
|
||||
android:typeface="monospace"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/ha_black"
|
||||
android:layout_marginLeft="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
@@ -7,6 +8,7 @@
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSelectionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_entity_selection"
|
||||
@@ -16,6 +18,7 @@
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/ha_black"
|
||||
@@ -26,20 +29,27 @@
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Szukaj encji..."
|
||||
android:inputType="text"
|
||||
android:background="@null"
|
||||
android:padding="8dp"
|
||||
android:hint="Szukaj / Search..."
|
||||
android:typeface="monospace"
|
||||
android:textSize="14sp"
|
||||
android:drawableLeft="@android:drawable/ic_menu_search" />
|
||||
android:textSize="14sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/lvEntities"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="8dp"
|
||||
android:divider="@color/ha_black"
|
||||
android:dividerHeight="1dp" />
|
||||
android:dividerHeight="1dp"
|
||||
android:listSelector="@android:color/transparent" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/ha_white">
|
||||
@@ -11,6 +12,7 @@
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInstructionsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_instructions"
|
||||
@@ -21,12 +23,14 @@
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vBorderTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeader1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_header_1"
|
||||
@@ -37,6 +41,7 @@
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBody1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_body_1"
|
||||
@@ -47,6 +52,7 @@
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeader2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_header_2"
|
||||
@@ -57,6 +63,7 @@
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBody2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_body_2"
|
||||
@@ -67,6 +74,7 @@
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeader3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_header_3"
|
||||
@@ -77,6 +85,7 @@
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBody3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/inst_body_3"
|
||||
|
||||
@@ -1,131 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/mainRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/ha_white">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<LinearLayout
|
||||
android:id="@+id/mainLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/ha_white">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="54dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_title"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatusIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ONLINE"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_blue" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btnSettingsContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<!-- hard shadow -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_settings"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:layout_marginLeft="3dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="@color/ha_black"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<!-- button -->
|
||||
<TextView
|
||||
android:id="@+id/btnSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_settings"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:background="@color/ha_blue"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_white"
|
||||
android:clickable="true" />
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- TABS -->
|
||||
<HorizontalScrollView
|
||||
<!-- TOP BAR -->
|
||||
<LinearLayout
|
||||
android:id="@+id/topBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/ha_white">
|
||||
<LinearLayout
|
||||
android:id="@+id/tabContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
<!-- Tabs will be added programmatically -->
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<!-- 2dp bottom border -->
|
||||
<View
|
||||
<RelativeLayout
|
||||
android:id="@+id/topBarContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="54dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_title"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatusIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ONLINE"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_blue" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btnSettingsContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<!-- hard shadow -->
|
||||
<TextView
|
||||
android:id="@+id/btnSettingsShadow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_settings"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:layout_marginLeft="3dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:background="@color/ha_black"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_black" />
|
||||
|
||||
<!-- button -->
|
||||
<TextView
|
||||
android:id="@+id/btnSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_settings"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:background="@color/ha_blue"
|
||||
android:textSize="12sp"
|
||||
android:typeface="monospace"
|
||||
android:textColor="@color/ha_white"
|
||||
android:clickable="true" />
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- TABS -->
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/tabScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:background="@color/ha_white">
|
||||
<LinearLayout
|
||||
android:id="@+id/tabContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
<!-- Tabs will be added programmatically -->
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<!-- 2dp bottom border -->
|
||||
<View
|
||||
android:id="@+id/vTopBarBorder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/ha_black" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- WIDGET GRID -->
|
||||
<GridView
|
||||
android:id="@+id/gridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/ha_black" />
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:numColumns="2"
|
||||
android:horizontalSpacing="8dp"
|
||||
android:verticalSpacing="8dp"
|
||||
android:padding="8dp"
|
||||
android:background="@color/ha_white"
|
||||
android:listSelector="@android:color/transparent"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- WIDGET GRID -->
|
||||
<GridView
|
||||
android:id="@+id/gridView"
|
||||
<!-- CONTROL OVERLAY (Hidden by default) -->
|
||||
<com.example.retroha.ui.BauhausControlOverlay
|
||||
android:id="@+id/controlOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:numColumns="2"
|
||||
android:horizontalSpacing="8dp"
|
||||
android:verticalSpacing="8dp"
|
||||
android:padding="8dp"
|
||||
android:background="@color/ha_white"
|
||||
android:listSelector="@android:color/transparent"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:scrollbars="vertical"
|
||||
android:clipToPadding="false" />
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSettingsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_settings"
|
||||
@@ -109,6 +110,38 @@
|
||||
android:textStyle="bold" />
|
||||
</FrameLayout>
|
||||
|
||||
<!-- DARK MODE TOGGLE -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.example.retroha.ui.BauhausCheckbox
|
||||
android:id="@+id/cbDarkMode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelDarkMode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/label_dark_mode"
|
||||
android:typeface="monospace"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/ha_black"
|
||||
android:layout_marginLeft="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/ha_black"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- DELETE ALL BUTTON -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<string name="label_url">URL ADDRESS</string>
|
||||
<string name="label_token">ACCESS TOKEN</string>
|
||||
<string name="label_refresh">REFRESH (SECONDS)</string>
|
||||
<string name="label_websocket_enable">ENABLE WEBSOCKET (REAL-TIME)</string>
|
||||
<string name="label_dark_mode">DARK MODE</string>
|
||||
<string name="btn_test_save">TEST AND SAVE</string>
|
||||
<string name="btn_delete_all">DELETE ALL WIDGETS</string>
|
||||
<string name="btn_save_selected">SAVE SELECTED</string>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<string name="label_url">ADRES URL</string>
|
||||
<string name="label_token">TOKEN DOSTĘPU</string>
|
||||
<string name="label_refresh">ODŚWIEŻANIE (SEKUNDY)</string>
|
||||
<string name="label_websocket_enable">WŁĄCZ WEBSOCKET (REAL-TIME)</string>
|
||||
<string name="label_dark_mode">TRYB CIEMNY</string>
|
||||
<string name="btn_test_save">TESTUJ I ZAPISZ</string>
|
||||
<string name="btn_delete_all">USUŃ WSZYSTKIE WIDŻETY</string>
|
||||
<string name="btn_save_selected">ZAPISZ WYBRANE</string>
|
||||
|
||||
Reference in New Issue
Block a user