Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions activity_main_patch.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- app/src/main/res/layout/activity_main.xml
+++ app/src/main/res/layout/activity_main.xml
@@ -1246,6 +1246,16 @@
android:layout_height="wrap_content"/>

<include layout="@layout/settings_selector_row"
+ android:id="@+id/row_date_color"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <include layout="@layout/settings_color_row"
+ android:id="@+id/row_date_color_custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <include layout="@layout/settings_selector_row"
android:id="@+id/row_outline_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
14 changes: 14 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ android {
versionName = "2.0"
}

testOptions {
unitTests.isReturnDefaultValues = true
}

signingConfigs {
create("release") {
val keystorePropertiesFile = rootProject.file("keystore.properties")
Expand Down Expand Up @@ -55,10 +59,20 @@ android {
}

dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.11.1")
testImplementation("androidx.test:core:1.5.0")
testImplementation("androidx.test.ext:junit:1.1.5")
testImplementation("org.mockito:mockito-core:5.3.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.work:work-runtime-ktx:2.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")

testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.8.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</queries>

<application
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
Expand Down
363 changes: 177 additions & 186 deletions app/src/main/java/com/leanbitlab/lwidget/AwidgetProvider.kt

Large diffs are not rendered by default.

1,272 changes: 1,272 additions & 0 deletions app/src/main/java/com/leanbitlab/lwidget/AwidgetProvider.kt.orig

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions app/src/main/java/com/leanbitlab/lwidget/ColorResolver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.leanbitlab.lwidget

import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build

object ColorResolver {
fun resolveColor(
context: Context,
prefs: SharedPreferences,
useDynamicColors: Boolean,
idx: Int,
isPrimary: Boolean,
isLight: Boolean,
sdkInt: Int = Build.VERSION.SDK_INT
): Int {
// When dynamic colors is on, always use dynamic palette regardless of saved color index
if (useDynamicColors && sdkInt >= Build.VERSION_CODES.S) {
return if (isPrimary) {
// High contrast accent for time & battery
context.getColor(if (isLight) android.R.color.system_accent1_800 else android.R.color.system_accent1_50)
} else {
// Muted neutral for secondary items (temp, data, storage, steps)
context.getColor(if (isLight) android.R.color.system_neutral2_600 else android.R.color.system_neutral2_300)
}
}
return when (idx) {
0 -> { // Default
if (isPrimary) {
if (isLight) context.getColor(R.color.widget_text_light) else Color.WHITE
} else {
if (isLight) context.getColor(R.color.widget_text_secondary_light) else Color.parseColor("#CCFFFFFF")
}
}
1 -> if (sdkInt >= Build.VERSION_CODES.S) {
context.getColor(android.R.color.system_accent1_500)
} else {
Color.CYAN
}
2 -> {
val prefix = if (isPrimary) "text_color_primary" else "text_color_secondary"
val r = prefs.getInt("${prefix}_r", 255)
val g = prefs.getInt("${prefix}_g", 255)
val b = prefs.getInt("${prefix}_b", 255)
Color.rgb(r, g, b)
}
else -> if (isPrimary) Color.WHITE else Color.parseColor("#CCFFFFFF")
}
}
}
80 changes: 60 additions & 20 deletions app/src/main/java/com/leanbitlab/lwidget/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class MainActivity : AppCompatActivity() {
}

// Check Tasks
if (prefs.getBoolean("show_tasks", false) && ContextCompat.checkSelfPermission(this, "org.tasks.permission.READ_TASKS") != PackageManager.PERMISSION_GRANTED) {
if (prefs.getBoolean("show_tasks", false) && ContextCompat.checkSelfPermission(this, AwidgetProvider.PERMISSION_READ_TASKS_ORG) != PackageManager.PERMISSION_GRANTED) {
prefs.edit().putBoolean("show_tasks", false).apply()
findViewById<View>(R.id.row_tasks_toggle).findViewById<com.google.android.material.switchmaterial.SwitchMaterial>(R.id.row_switch).isChecked = false
findViewById<View>(R.id.row_tasks_size).visibility = View.GONE
Expand Down Expand Up @@ -739,16 +739,34 @@ class MainActivity : AppCompatActivity() {
val timeFormatOptions = listOf(getString(R.string.format_12h), getString(R.string.format_24h))
val colorOptions = listOf(getString(R.string.color_default), getString(R.string.color_system_accent), getString(R.string.color_custom))

setupTimeSection(timeFormatOptions)
setupNextAlarmSection()
setupWorldClockSection(zoneIds)
setupDateSection(dateFormatOptions)
setupBatterySection()
setupTempSection()
setupWeatherSection()
setupDataUsageSection()
setupStorageSection()
setupStepsSection()
setupScreenTimeSection()
setupKeepAliveSection()
setupEventsAndTasksSections()
setupThemeSection(colorOptions)
}

private fun setupTimeSection(timeFormatOptions: List<String>) {
// Time
val timeSwitch = bindFoldedSection(
bindFoldedSection(
R.id.header_time, R.drawable.ic_time, getString(R.string.section_time),
R.id.content_time, R.id.row_time_toggle,
"show_time", true,
sizeRowId = R.id.row_time_size, prefSizeKey = "size_time", defSize = 64f, minSize = 12f, maxSize = 120f,
selectorRowId = R.id.row_time_format, selectorOptions = timeFormatOptions, prefSelectorKey = "time_format_idx", defSelectorIdx = 0,
isContent = true
)

}
private fun setupNextAlarmSection() {
// Next Alarm
bindFoldedSection(
R.id.header_next_alarm, R.drawable.ic_alarm, getString(R.string.section_next_alarm),
Expand All @@ -757,17 +775,19 @@ class MainActivity : AppCompatActivity() {
sizeRowId = R.id.row_next_alarm_size, prefSizeKey = "size_next_alarm", defSize = 14f, minSize = 10f, maxSize = 24f,
isContent = true
)

}
private fun setupWorldClockSection(zoneIds: List<String>) {
// World Clock
val worldClockSwitch = bindFoldedSection(
bindFoldedSection(
R.id.header_world_clock, R.drawable.ic_world, getString(R.string.section_world_clock),
R.id.content_world_clock, R.id.row_world_clock_toggle,
"show_world_clock", false,
sizeRowId = R.id.row_world_clock_size, prefSizeKey = "size_world_clock", defSize = 18f, minSize = 10f, maxSize = 32f,
isContent = true
)
bindTimezoneSearch(R.id.row_world_clock_zone, zoneIds, "world_clock_zone_str", "UTC")

}
private fun setupDateSection(dateFormatOptions: List<String>) {
// Date
bindFoldedSection(
R.id.header_date, R.drawable.ic_date, getString(R.string.section_date),
Expand All @@ -777,17 +797,19 @@ class MainActivity : AppCompatActivity() {
selectorRowId = R.id.row_date_format, selectorOptions = dateFormatOptions, prefSelectorKey = "date_format_idx", defSelectorIdx = 0,
isContent = true
)

}
private fun setupBatterySection() {
// Battery
val batterySwitch = bindFoldedSection(
bindFoldedSection(
R.id.header_battery, R.drawable.ic_battery, getString(R.string.section_battery),
R.id.content_battery, R.id.row_battery_toggle,
"show_battery", true,
sizeRowId = R.id.row_battery_size, prefSizeKey = "size_battery", defSize = 24f, minSize = 10f, maxSize = 74f,
isContent = true
).also { it.tag = "battery" }
bindToggle(R.id.row_battery_bold, "Bold Text", "bold_battery", false)

}
private fun setupTempSection() {
// Temp
bindFoldedSection(
R.id.header_temp, R.drawable.ic_temp, getString(R.string.section_temp),
Expand All @@ -797,7 +819,8 @@ class MainActivity : AppCompatActivity() {
isContent = true
).also { it.tag = "temp" }
bindToggle(R.id.row_temp_bold, "Bold Text", "bold_temp", false)

}
private fun setupWeatherSection() {
// Weather
val weatherSwitch = bindFoldedSection(
R.id.header_weather, R.drawable.ic_weather, getString(R.string.section_weather_condition),
Expand Down Expand Up @@ -853,7 +876,8 @@ class MainActivity : AppCompatActivity() {
.show()
}
}

}
private fun setupDataUsageSection() {
// Data Usage
val dataSwitch = bindFoldedSection(
R.id.header_data, R.drawable.ic_data, getString(R.string.section_data_usage),
Expand Down Expand Up @@ -889,7 +913,8 @@ class MainActivity : AppCompatActivity() {
updateToggleAvailability()
checkAllPermissions()
}

}
private fun setupStorageSection() {
// Storage
bindFoldedSection(
R.id.header_storage, R.drawable.ic_storage, getString(R.string.section_storage),
Expand All @@ -899,7 +924,8 @@ class MainActivity : AppCompatActivity() {
isContent = true
).also { it.tag = "storage" }
bindToggle(R.id.row_storage_bold, "Bold Text", "bold_storage", false)

}
private fun setupStepsSection() {
// Steps
val stepsSwitch = bindFoldedSection(
R.id.header_steps, R.drawable.ic_steps, getString(R.string.section_steps),
Expand Down Expand Up @@ -953,7 +979,8 @@ class MainActivity : AppCompatActivity() {
updateToggleAvailability()
checkAllPermissions()
}

}
private fun setupScreenTimeSection() {
// Screen Time
val screenTimeSwitch = bindFoldedSection(
R.id.header_screen_time, R.drawable.ic_time, getString(R.string.section_screen_time),
Expand Down Expand Up @@ -989,7 +1016,8 @@ class MainActivity : AppCompatActivity() {
updateToggleAvailability()
checkAllPermissions()
}

}
private fun setupKeepAliveSection() {
// Keep Alive
val keepAliveSwitch = bindFoldedSection(
R.id.header_keep_alive, R.drawable.ic_alarm, getString(R.string.section_keep_alive),
Expand Down Expand Up @@ -1022,7 +1050,8 @@ class MainActivity : AppCompatActivity() {
else { stopService(serviceIntent) }
updateWidget()
}

}
private fun setupEventsAndTasksSections() {
// Events
val eventsSwitch = bindFoldedSection(
R.id.header_events, R.drawable.ic_events, getString(R.string.section_events),
Expand Down Expand Up @@ -1065,7 +1094,6 @@ class MainActivity : AppCompatActivity() {
checkAllPermissions()
}
}

tasksSwitch.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
if (!isAppInstalled("org.tasks")) {
Expand All @@ -1083,8 +1111,8 @@ class MainActivity : AppCompatActivity() {
}.show()
return@setOnCheckedChangeListener
}
if (ContextCompat.checkSelfPermission(this, "org.tasks.permission.READ_TASKS") != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf("org.tasks.permission.READ_TASKS"), 101)
if (ContextCompat.checkSelfPermission(this, AwidgetProvider.PERMISSION_READ_TASKS_ORG) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(AwidgetProvider.PERMISSION_READ_TASKS_ORG), 101)
}
if (checkLimit()) {
eventsSwitch.isChecked = false
Expand All @@ -1102,7 +1130,8 @@ class MainActivity : AppCompatActivity() {
checkAllPermissions()
}
}

}
private fun setupThemeSection(colorOptions: List<String>) {
// ===== THEME =====
// Use bindFoldedSection for the main card (top-level accordion), not bindNestedCard
accordionViews["section_appearance_expanded"] = findViewById(R.id.content_appearance)
Expand Down Expand Up @@ -1162,6 +1191,7 @@ class MainActivity : AppCompatActivity() {
prefs.edit()
.putInt("text_color_primary_idx", 0)
.putInt("text_color_secondary_idx", 0)
.putInt("date_color_idx", 0)
.putInt("outline_color_idx", 0)
.putInt("bg_color_idx", 0)
.apply()
Expand Down Expand Up @@ -1206,6 +1236,15 @@ class MainActivity : AppCompatActivity() {
bindColorSliders(R.id.row_text_color_secondary_custom, "text_color_secondary")
secondarySliderRow.visibility = if (prefs.getInt("text_color_secondary_idx", 0) == 2) View.VISIBLE else View.GONE

// Date Color
val dateSliderRow = findViewById<View>(R.id.row_date_color_custom)
bindSelector(R.id.row_date_color, getString(R.string.section_date_color), "date_color_idx", colorOptions, 0) { idx ->
dateSliderRow.visibility = if (idx == 2) View.VISIBLE else View.GONE
if (idx != 2) updateWidget()
}
bindColorSliders(R.id.row_date_color_custom, "date_color")
dateSliderRow.visibility = if (prefs.getInt("date_color_idx", 0) == 2) View.VISIBLE else View.GONE

// Outline Color
val outlineSliderRow = findViewById<View>(R.id.row_outline_color_custom)
bindSelector(R.id.row_outline_color, getString(R.string.section_outline_color), "outline_color_idx", colorOptions, 0) { idx ->
Expand Down Expand Up @@ -1368,6 +1407,7 @@ class MainActivity : AppCompatActivity() {
R.id.row_bg_color, R.id.row_bg_color_custom,
R.id.row_text_color_primary, R.id.row_text_color_primary_custom,
R.id.row_text_color_secondary, R.id.row_text_color_secondary_custom,
R.id.row_date_color, R.id.row_date_color_custom,
R.id.row_outline_color, R.id.row_outline_color_custom
)
manualColorIds.forEach { id ->
Expand Down
Loading