Skip to content

Commit 258dd0d

Browse files
author
LeanBitLab
committed
Release v1.5: Features, Fixes, and UI Refinements
1 parent 7b08c80 commit 258dd0d

41 files changed

Lines changed: 2174 additions & 293 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@
2626

2727
- **Material You**: Full dynamic color support.
2828
- **Configurable**: Adjust text sizes and visibility for all elements.
29-
- **Essential Info**: Time, Date, Battery, Device Temp, and Calendar Events.
30-
- **Task Integration**: Manage your tasks directly from the widget.
29+
- **Time & Date**: Clear, customizable display.
30+
- **Battery & Temperature**: Real-time device status.
31+
- **Calendar Events**: Upcoming agenda at a glance.
32+
- **Task Integration**: Seamless integration with [Tasks.org](https://tasks.org/).
33+
- **World Clock**: Track time in another zone.
34+
- **Next Alarm**: Display your next scheduled alarm.
3135
- **Daily Data Usage**: Monitor your daily data consumption.
36+
- **Internal Storage**: Monitor available device storage.
3237
- **Custom Formats**: Choose your preferred Time and Date formats.
3338
- **Light/Dark Mode**: Optimized contrast for readability.
3439
- **Privacy Focused**: No internet permission required.

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.leanbitlab.lwidget"
1515
minSdk = 26
1616
targetSdk = 34
17-
versionCode = 5
18-
versionName = "1.4"
17+
versionCode = 6
18+
versionName = "1.5"
1919
}
2020

2121
signingConfigs {

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
66
<uses-permission android:name="android.permission.READ_CALENDAR" />
77
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
8+
<uses-permission android:name="org.tasks.permission.READ_TASKS" />
9+
<uses-permission android:name="com.todoroo.astrid.READ" />
810

911
<!-- Required for Android 11+ (API 30) to see these packages -->
1012
<queries>
@@ -19,6 +21,9 @@
1921
<package android:name="com.google.android.calendar" />
2022
<package android:name="com.simplemobiletools.calendar" />
2123
<package android:name="org.fossify.calendar" />
24+
25+
<!-- Task Apps -->
26+
<package android:name="org.tasks" />
2227
<intent>
2328
<action android:name="android.intent.action.MAIN" />
2429
<category android:name="android.intent.category.APP_CALENDAR" />

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

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,19 @@ class AwidgetProvider : AppWidgetProvider() {
146146
val showData = prefs.getBoolean("show_data_usage", false)
147147
val sizeData = prefs.getFloat("size_data", 14f)
148148

149+
val showWorldClock = prefs.getBoolean("show_world_clock", false)
150+
val sizeWorldClock = prefs.getFloat("size_world_clock", 18f)
151+
val worldClockZoneStr = prefs.getString("world_clock_zone_str", "UTC") ?: "UTC"
152+
153+
val showStorage = prefs.getBoolean("show_storage", false)
154+
val sizeStorage = prefs.getFloat("size_storage", 14f)
155+
149156
val showTasks = prefs.getBoolean("show_tasks", false)
150157
val sizeTasks = prefs.getFloat("size_tasks", 14f)
151158

159+
val showNextAlarm = prefs.getBoolean("show_next_alarm", true)
160+
val sizeNextAlarm = prefs.getFloat("size_next_alarm", 14f)
161+
152162
val fontStyle = prefs.getInt("font_style", 0)
153163

154164
// --- Theme & Font Setup ---
@@ -243,8 +253,15 @@ class AwidgetProvider : AppWidgetProvider() {
243253
else -> "h:mm" to "H:mm"
244254
}
245255
views.setCharSequence(R.id.clock_time, "setFormat12Hour", timeFormat12)
256+
views.setCharSequence(R.id.clock_time, "setFormat12Hour", timeFormat12)
246257
views.setCharSequence(R.id.clock_time, "setFormat24Hour", timeFormat24)
247258

259+
// --- World Clock ---
260+
views.setViewVisibility(R.id.text_world_clock, if (showWorldClock) android.view.View.VISIBLE else android.view.View.GONE)
261+
if (showWorldClock) {
262+
loadWorldClock(context, views, sizeWorldClock, secondaryColor, worldClockZoneStr, timeFormat12.contains("a"))
263+
}
264+
248265
// --- Apply Date ---
249266
views.setViewVisibility(R.id.clock_date, if (showDate) android.view.View.VISIBLE else android.view.View.GONE)
250267
views.setTextViewTextSize(R.id.clock_date, android.util.TypedValue.COMPLEX_UNIT_SP, sizeDate)
@@ -289,6 +306,14 @@ class AwidgetProvider : AppWidgetProvider() {
289306
views.setTextColor(R.id.text_data_usage, secondaryColor)
290307
updateDataUsage(context, views)
291308
}
309+
310+
// --- Storage ---
311+
views.setViewVisibility(R.id.text_storage, if (showStorage) android.view.View.VISIBLE else android.view.View.GONE)
312+
if (showStorage) {
313+
views.setTextViewTextSize(R.id.text_storage, android.util.TypedValue.COMPLEX_UNIT_SP, sizeStorage)
314+
views.setTextColor(R.id.text_storage, secondaryColor)
315+
updateStorageStats(context, views)
316+
}
292317

293318
// --- Click Actions ---
294319
val clockPackages = listOf("com.android.deskclock", "com.google.android.deskclock", "com.simplemobiletools.clock", "org.fossify.clock")
@@ -309,6 +334,10 @@ class AwidgetProvider : AppWidgetProvider() {
309334
val batteryPendingIntent = PendingIntent.getActivity(context, 2, batteryIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
310335
views.setOnClickPendingIntent(R.id.text_battery, batteryPendingIntent)
311336
views.setOnClickPendingIntent(R.id.text_temp, batteryPendingIntent)
337+
338+
val storageIntent = Intent(android.provider.Settings.ACTION_INTERNAL_STORAGE_SETTINGS)
339+
val storagePendingIntent = PendingIntent.getActivity(context, 3, storageIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
340+
views.setOnClickPendingIntent(R.id.text_storage, storagePendingIntent)
312341

313342
// --- Calendar Events OR Tasks ---
314343
views.setViewVisibility(R.id.events_container, if (showEvents || showTasks) android.view.View.VISIBLE else android.view.View.GONE)
@@ -319,11 +348,30 @@ class AwidgetProvider : AppWidgetProvider() {
319348
loadTasks(context, views, sizeTasks, primaryColor, secondaryColor)
320349
}
321350

351+
// --- Next Alarm ---
352+
views.setViewVisibility(R.id.text_next_alarm, if (showNextAlarm) android.view.View.VISIBLE else android.view.View.GONE)
353+
if (showNextAlarm) {
354+
loadNextAlarm(context, views, sizeNextAlarm, secondaryColor)
355+
}
356+
// Click action for Next Alarm (same as Clock)
357+
views.setOnClickPendingIntent(R.id.text_next_alarm, alarmPendingIntent)
358+
322359
val refreshIntent = Intent(context, AwidgetProvider::class.java).apply {
323360
action = ACTION_BATTERY_UPDATE
324361
}
325362
val refreshPendingIntent = PendingIntent.getBroadcast(context, 10, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
326-
views.setOnClickPendingIntent(R.id.events_container, refreshPendingIntent)
363+
364+
if (showTasks) {
365+
val tasksIntent = context.packageManager.getLaunchIntentForPackage("org.tasks")
366+
if (tasksIntent != null) {
367+
val tasksPendingIntent = PendingIntent.getActivity(context, 11, tasksIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
368+
views.setOnClickPendingIntent(R.id.events_container, tasksPendingIntent)
369+
} else {
370+
views.setOnClickPendingIntent(R.id.events_container, refreshPendingIntent)
371+
}
372+
} else {
373+
views.setOnClickPendingIntent(R.id.events_container, refreshPendingIntent)
374+
}
327375

328376
val settingsIntent = Intent(context, MainActivity::class.java)
329377
val settingsPendingIntent = PendingIntent.getActivity(context, 0, settingsIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
@@ -494,13 +542,35 @@ class AwidgetProvider : AppWidgetProvider() {
494542
R.id.text_event_10
495543
)
496544

545+
// Debugging: Check permission again contextually
546+
val hasPerm = context.checkSelfPermission("org.tasks.permission.READ_TASKS") == android.content.pm.PackageManager.PERMISSION_GRANTED ||
547+
context.checkSelfPermission("com.todoroo.astrid.READ") == android.content.pm.PackageManager.PERMISSION_GRANTED
548+
549+
if (!hasPerm) {
550+
views.setTextViewText(eventViews[0], "Missing Permission")
551+
views.setViewVisibility(eventViews[0], android.view.View.VISIBLE)
552+
return
553+
}
554+
497555
val taskUri = android.net.Uri.parse("content://org.tasks/tasks")
556+
// Try simpler selection or none to test
498557
val selection = "completed = 0"
499558

500559
try {
501560
context.contentResolver.query(taskUri, null, selection, null, "due ASC")?.use { cursor ->
502561
val titleIdx = cursor.getColumnIndex("title")
503562

563+
if (cursor.count == 0) {
564+
// views.setTextViewText(eventViews[0], "No active tasks found")
565+
// views.setViewVisibility(eventViews[0], android.view.View.VISIBLE)
566+
// views.setTextColor(eventViews[0], secondaryColor)
567+
// Just hide all
568+
for (viewId in eventViews) {
569+
views.setViewVisibility(viewId, android.view.View.GONE)
570+
}
571+
return
572+
}
573+
504574
var i = 0
505575
while (cursor.moveToNext() && i < eventViews.size) {
506576
if (titleIdx != -1) {
@@ -525,13 +595,76 @@ class AwidgetProvider : AppWidgetProvider() {
525595
return
526596
}
527597
} catch (e: Exception) {
598+
// Fail silently or log
599+
for (viewId in eventViews) {
600+
views.setViewVisibility(viewId, android.view.View.GONE)
601+
}
528602
}
529603

530-
for (viewId in eventViews) {
531-
views.setViewVisibility(viewId, android.view.View.GONE)
604+
// If we reached here (query null?), show generic message
605+
// views.setTextViewText(eventViews[0], "Query Failed")
606+
// views.setViewVisibility(eventViews[0], android.view.View.VISIBLE)
607+
}
608+
609+
private fun loadWorldClock(context: Context, views: RemoteViews, textSizeSp: Float, textColor: Int, zoneIdStr: String, is12Hour: Boolean) {
610+
try {
611+
val zoneId = ZoneId.of(zoneIdStr)
612+
val zdt = java.time.ZonedDateTime.now(zoneId)
613+
val pattern = if (is12Hour) "h:mm a" else "H:mm"
614+
val formatter = DateTimeFormatter.ofPattern(pattern, Locale.getDefault())
615+
val timeStr = zdt.format(formatter)
616+
617+
618+
619+
// If label is too long, maybe truncate? For now, let it be.
620+
// Format: "10:30 AM" (Time only, subtle)
621+
views.setTextViewText(R.id.text_world_clock, timeStr)
622+
views.setTextViewTextSize(R.id.text_world_clock, android.util.TypedValue.COMPLEX_UNIT_SP, textSizeSp)
623+
views.setTextColor(R.id.text_world_clock, textColor)
624+
views.setViewVisibility(R.id.text_world_clock, android.view.View.VISIBLE)
625+
626+
} catch (e: Exception) {
627+
views.setViewVisibility(R.id.text_world_clock, android.view.View.GONE)
628+
}
629+
}
630+
631+
private fun loadNextAlarm(context: Context, views: RemoteViews, textSizeSp: Float, textColor: Int) {
632+
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
633+
val nextAlarm = alarmManager.nextAlarmClock
634+
635+
if (nextAlarm != null) {
636+
val nextAlarmTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(nextAlarm.triggerTime), ZoneId.systemDefault())
637+
val timeFormatter = DateTimeFormatter.ofPattern("h:mm a", Locale.getDefault())
638+
val timeText = nextAlarmTime.format(timeFormatter)
639+
640+
// Format: "| ⏰ 7:00 AM"
641+
val fullText = "| ⏰ $timeText"
642+
views.setTextViewText(R.id.text_next_alarm, fullText)
643+
views.setTextViewTextSize(R.id.text_next_alarm, android.util.TypedValue.COMPLEX_UNIT_SP, textSizeSp)
644+
views.setTextColor(R.id.text_next_alarm, textColor)
645+
views.setViewVisibility(R.id.text_next_alarm, android.view.View.VISIBLE)
646+
} else {
647+
views.setViewVisibility(R.id.text_next_alarm, android.view.View.GONE)
532648
}
533649
}
534650

651+
private fun updateStorageStats(context: Context, views: RemoteViews) {
652+
try {
653+
val path = android.os.Environment.getDataDirectory()
654+
val stat = android.os.StatFs(path.path)
655+
val freeBytes = stat.availableBlocksLong * stat.blockSizeLong
656+
657+
val gb = freeBytes / (1024f * 1024f * 1024f)
658+
659+
// Concisely: "12GB"
660+
val text = String.format("%.0fGB", gb)
661+
662+
views.setTextViewText(R.id.text_storage, text)
663+
} catch (e: Exception) {
664+
views.setTextViewText(R.id.text_storage, "Err")
665+
}
666+
}
667+
535668
private fun getBestIntent(context: Context, packages: List<String>, fallback: Intent): Intent {
536669
val pm = context.packageManager
537670
for (pkg in packages) {

0 commit comments

Comments
 (0)