Skip to content

Commit 1c0fa03

Browse files
author
LeanBitLab
committed
feat: presets system, better defaults, setup permissions, keep-alive decoupling
- Add 6 unique presets (Minimal/Neon/Cockpit/Sunset/Monochrome/Snowfall) with custom colors, fonts, elements, and arrangements under Appearance - Better defaults: outline off, battery 32sp bold, temp/storage/events off, opacity 85% - Decouple keep-alive from step counter permissions (keep-alive only needs POST_NOTIFICATIONS) - Add Data Usage, Screen Time, Weather permission cards to setup welcome screen - Remove duplicate welcome title, fix ViewFlipper animation direction (right-to-left) - Add custom slide animations (slide_in_right/slide_out_left) to replace private android resources
1 parent e258bab commit 1c0fa03

7 files changed

Lines changed: 460 additions & 42 deletions

File tree

app/src/main/java/com/leanbitlab/lwidget/AwidgetProvider.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,24 +197,24 @@ class AwidgetProvider : AppWidgetProvider() {
197197

198198
// --- Load Preferences ---
199199
val showTime = prefs.getBoolean("show_time", true)
200-
val sizeTime = prefs.getFloat("size_time", 64f)
200+
val sizeTime = prefs.getFloat("size_time", 56f)
201201

202202
val showDate = prefs.getBoolean("show_date", true)
203-
val sizeDate = prefs.getFloat("size_date", 14f)
203+
val sizeDate = prefs.getFloat("size_date", 16f)
204204

205205
val showBattery = prefs.getBoolean("show_battery", true)
206-
val sizeBattery = prefs.getFloat("size_battery", 24f)
207-
val boldBattery = prefs.getBoolean("bold_battery", false)
206+
val sizeBattery = prefs.getFloat("size_battery", 32f)
207+
val boldBattery = prefs.getBoolean("bold_battery", true)
208208

209-
val showTemp = prefs.getBoolean("show_temp", true)
209+
val showTemp = prefs.getBoolean("show_temp", false)
210210
val sizeTemp = prefs.getFloat("size_temp", 18f)
211211
val boldTemp = prefs.getBoolean("bold_temp", false)
212212

213213
val showWeatherCondition = prefs.getBoolean("show_weather_condition", false)
214214
val sizeWeather = prefs.getFloat("size_weather", 18f)
215215
val boldWeather = prefs.getBoolean("bold_weather", false)
216216

217-
var showEvents = prefs.getBoolean("show_events", true)
217+
var showEvents = prefs.getBoolean("show_events", false)
218218
if (showEvents && androidx.core.content.ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_CALENDAR) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
219219
showEvents = false
220220
}
@@ -250,7 +250,7 @@ class AwidgetProvider : AppWidgetProvider() {
250250
val sizeWorldClock = prefs.getFloat("size_world_clock", 18f)
251251
val worldClockZoneStr = prefs.getString("world_clock_zone_str", "UTC") ?: "UTC"
252252

253-
val showStorage = prefs.getBoolean("show_storage", true)
253+
val showStorage = prefs.getBoolean("show_storage", false)
254254
val sizeStorage = prefs.getFloat("size_storage", 14f)
255255

256256
var showTasks = prefs.getBoolean("show_tasks", false)
@@ -286,7 +286,7 @@ class AwidgetProvider : AppWidgetProvider() {
286286

287287
val fontStyle = prefs.getInt("font_style", 0)
288288

289-
val bgOpacity = prefs.getFloat("bg_opacity", 100f)
289+
val bgOpacity = prefs.getFloat("bg_opacity", 85f)
290290
val textColorPrimaryIdx = prefs.getInt("text_color_primary_idx", 0)
291291
val textColorSecondaryIdx = prefs.getInt("text_color_secondary_idx", 0)
292292
val bgColorIdx = prefs.getInt("bg_color_idx", 0)
@@ -366,7 +366,7 @@ class AwidgetProvider : AppWidgetProvider() {
366366
}
367367
}
368368

369-
val showOutline = prefs.getBoolean("show_outline", true)
369+
val showOutline = prefs.getBoolean("show_outline", false)
370370
val outlineColor = resolveOutlineColor(outlineColorIdx)
371371
views.setImageViewResource(R.id.widget_outline, R.drawable.widget_bg_outline)
372372
views.setViewVisibility(R.id.widget_outline, if (showOutline) android.view.View.VISIBLE else android.view.View.GONE)

app/src/main/java/com/leanbitlab/lwidget/MainActivity.kt

Lines changed: 183 additions & 19 deletions
Large diffs are not rendered by default.

app/src/main/java/com/leanbitlab/lwidget/SetupActivity.kt

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,9 @@ class SetupActivity : AppCompatActivity() {
5959
switchKeepAlive.isChecked = prefs.getBoolean("keep_alive", false)
6060
switchKeepAlive.setOnCheckedChangeListener { _, isChecked ->
6161
if (isChecked) {
62-
val neededPermissions = mutableListOf<String>()
63-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
64-
ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED) {
65-
neededPermissions.add(Manifest.permission.ACTIVITY_RECOGNITION)
66-
}
6762
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
6863
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
69-
neededPermissions.add(Manifest.permission.POST_NOTIFICATIONS)
70-
}
71-
if (neededPermissions.isNotEmpty()) {
72-
requestPermissionLauncher.launch(neededPermissions.toTypedArray())
64+
requestPermissionLauncher.launch(arrayOf(Manifest.permission.POST_NOTIFICATIONS))
7365
}
7466
}
7567
prefs.edit().putBoolean("keep_alive", isChecked).apply()
@@ -88,8 +80,43 @@ class SetupActivity : AppCompatActivity() {
8880
}
8981

9082
findViewById<MaterialButton>(R.id.btn_grant_steps).setOnClickListener {
91-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
92-
requestPermissionLauncher.launch(arrayOf(Manifest.permission.ACTIVITY_RECOGNITION))
83+
val neededPermissions = mutableListOf<String>()
84+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
85+
ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION) != PackageManager.PERMISSION_GRANTED) {
86+
neededPermissions.add(Manifest.permission.ACTIVITY_RECOGNITION)
87+
}
88+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
89+
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
90+
neededPermissions.add(Manifest.permission.POST_NOTIFICATIONS)
91+
}
92+
if (neededPermissions.isNotEmpty()) {
93+
requestPermissionLauncher.launch(neededPermissions.toTypedArray())
94+
}
95+
}
96+
97+
// Data Usage & Screen Time (both need Usage Stats)
98+
findViewById<MaterialButton>(R.id.btn_grant_data_usage).setOnClickListener {
99+
try {
100+
startActivity(Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS))
101+
} catch (e: Exception) {}
102+
}
103+
104+
findViewById<MaterialButton>(R.id.btn_grant_screen_time).setOnClickListener {
105+
try {
106+
startActivity(Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS))
107+
} catch (e: Exception) {}
108+
}
109+
110+
// Weather (Breezy Weather provider)
111+
findViewById<MaterialButton>(R.id.btn_grant_weather).setOnClickListener {
112+
if (packageManager.getLaunchIntentForPackage("org.breezyweather") != null) {
113+
requestPermissionLauncher.launch(arrayOf("org.breezyweather.READ_PROVIDER"))
114+
} else {
115+
com.google.android.material.snackbar.Snackbar.make(
116+
findViewById(R.id.setup_view_flipper),
117+
"Breezy Weather app is required.",
118+
com.google.android.material.snackbar.Snackbar.LENGTH_LONG
119+
).show()
93120
}
94121
}
95122

@@ -114,6 +141,9 @@ class SetupActivity : AppCompatActivity() {
114141
val btnCalendar = findViewById<MaterialButton>(R.id.btn_grant_calendar)
115142
val btnTasks = findViewById<MaterialButton>(R.id.btn_grant_tasks)
116143
val btnSteps = findViewById<MaterialButton>(R.id.btn_grant_steps)
144+
val btnDataUsage = findViewById<MaterialButton>(R.id.btn_grant_data_usage)
145+
val btnScreenTime = findViewById<MaterialButton>(R.id.btn_grant_screen_time)
146+
val btnWeather = findViewById<MaterialButton>(R.id.btn_grant_weather)
117147

118148
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
119149
btnCalendar.text = "Granted"
@@ -140,6 +170,35 @@ class SetupActivity : AppCompatActivity() {
140170
btnSteps.isEnabled = false
141171
prefs.edit().putBoolean("show_steps", true).apply()
142172
}
173+
174+
if (hasUsageStatsPermission()) {
175+
btnDataUsage.text = "Granted"
176+
btnDataUsage.isEnabled = false
177+
prefs.edit().putBoolean("show_data_usage", true).apply()
178+
179+
btnScreenTime.text = "Granted"
180+
btnScreenTime.isEnabled = false
181+
prefs.edit().putBoolean("show_screen_time", true).apply()
182+
}
183+
184+
if (ContextCompat.checkSelfPermission(this, "org.breezyweather.READ_PROVIDER") == PackageManager.PERMISSION_GRANTED) {
185+
btnWeather.text = "Granted"
186+
btnWeather.isEnabled = false
187+
prefs.edit().putBoolean("show_weather_condition", true).apply()
188+
}
189+
}
190+
191+
private fun hasUsageStatsPermission(): Boolean {
192+
val appOps = getSystemService(Context.APP_OPS_SERVICE) as android.app.AppOpsManager
193+
val opMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
194+
appOps.unsafeCheckOpNoThrow(android.app.AppOpsManager.OPSTR_GET_USAGE_STATS,
195+
android.os.Process.myUid(), packageName)
196+
} else {
197+
@Suppress("DEPRECATION")
198+
appOps.checkOpNoThrow(android.app.AppOpsManager.OPSTR_GET_USAGE_STATS,
199+
android.os.Process.myUid(), packageName)
200+
}
201+
return opMode == android.app.AppOpsManager.MODE_ALLOWED
143202
}
144203

145204
private fun finishSetup() {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
<translate
4+
android:fromXDelta="100%"
5+
android:toXDelta="0"
6+
android:duration="@android:integer/config_mediumAnimTime" />
7+
</set>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
<translate
4+
android:fromXDelta="0"
5+
android:toXDelta="-100%"
6+
android:duration="@android:integer/config_mediumAnimTime" />
7+
</set>

app/src/main/res/layout/activity_main.xml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,83 @@
10921092
android:paddingEnd="8dp"
10931093
android:paddingBottom="8dp">
10941094

1095+
<!-- Subsection: Presets -->
1096+
<com.google.android.material.card.MaterialCardView
1097+
android:id="@+id/card_appearance_presets"
1098+
style="@style/Widget.Material3.CardView.Outlined"
1099+
android:layout_width="match_parent"
1100+
android:layout_height="wrap_content"
1101+
android:layout_marginBottom="6dp"
1102+
app:cardBackgroundColor="?attr/colorSurfaceContainerLow">
1103+
1104+
<LinearLayout
1105+
android:layout_width="match_parent"
1106+
android:layout_height="wrap_content"
1107+
android:orientation="vertical">
1108+
1109+
<LinearLayout
1110+
android:id="@+id/header_appearance_presets"
1111+
android:layout_width="match_parent"
1112+
android:layout_height="wrap_content"
1113+
android:orientation="horizontal"
1114+
android:gravity="center_vertical"
1115+
android:paddingStart="14dp"
1116+
android:paddingEnd="14dp"
1117+
android:paddingTop="10dp"
1118+
android:paddingBottom="10dp"
1119+
android:background="?attr/selectableItemBackground"
1120+
android:clickable="true"
1121+
android:focusable="true">
1122+
1123+
<TextView
1124+
android:id="@+id/header_title_appearance_presets"
1125+
android:layout_width="0dp"
1126+
android:layout_height="wrap_content"
1127+
android:layout_weight="1"
1128+
android:text="PRESETS"
1129+
android:textColor="?attr/colorOnSurfaceVariant"
1130+
android:textSize="13sp"
1131+
android:textStyle="bold"
1132+
android:letterSpacing="0.05"/>
1133+
1134+
<ImageView
1135+
android:id="@+id/header_chevron_appearance_presets"
1136+
android:layout_width="20dp"
1137+
android:layout_height="20dp"
1138+
android:src="@drawable/ic_chevron_down"
1139+
android:rotation="0"
1140+
app:tint="?attr/colorOnSurfaceVariant"
1141+
android:contentDescription="Expand/collapse"/>
1142+
</LinearLayout>
1143+
1144+
<LinearLayout
1145+
android:id="@+id/content_appearance_presets"
1146+
android:layout_width="match_parent"
1147+
android:layout_height="wrap_content"
1148+
android:orientation="vertical"
1149+
android:visibility="gone"
1150+
android:paddingStart="8dp"
1151+
android:paddingEnd="8dp"
1152+
android:paddingBottom="12dp">
1153+
1154+
<HorizontalScrollView
1155+
android:layout_width="match_parent"
1156+
android:layout_height="wrap_content"
1157+
android:scrollbars="none"
1158+
android:layout_marginTop="4dp">
1159+
1160+
<com.google.android.material.chip.ChipGroup
1161+
android:id="@+id/preset_chip_group"
1162+
android:layout_width="wrap_content"
1163+
android:layout_height="wrap_content"
1164+
app:singleSelection="true"
1165+
app:chipSpacingHorizontal="8dp"
1166+
app:singleLine="true"/>
1167+
</HorizontalScrollView>
1168+
</LinearLayout>
1169+
</LinearLayout>
1170+
</com.google.android.material.card.MaterialCardView>
1171+
10951172
<!-- Subsection: Outline -->
10961173
<com.google.android.material.card.MaterialCardView
10971174
android:id="@+id/card_appearance_outline"

0 commit comments

Comments
 (0)