Skip to content

Commit 58cc860

Browse files
committed
savepoint
1 parent d5a0797 commit 58cc860

8 files changed

Lines changed: 110 additions & 162 deletions

File tree

mobile/src/main/java/net/activitywatch/android/widget/CategoryTimeWidgetProvider.kt

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,50 +77,29 @@ class CategoryTimeWidgetProvider : AppWidgetProvider() {
7777

7878
companion object {
7979
/**
80-
* Show loading state - hide refresh button and show spinner
80+
* Show loading state - show spinner
8181
*/
8282
private fun showLoadingState(
8383
context: Context,
8484
appWidgetManager: AppWidgetManager,
8585
appWidgetId: Int
8686
) {
8787
val views = RemoteViews(context.packageName, R.layout.widget_category_time)
88-
views.setViewVisibility(R.id.widget_refresh_button, View.GONE)
8988
views.setViewVisibility(R.id.widget_loading_indicator, View.VISIBLE)
9089
appWidgetManager.updateAppWidget(appWidgetId, views)
9190
Log.d(TAG, "Showing loading indicator for widget $appWidgetId")
9291
}
9392

9493
/**
95-
* Update widget and set up the refresh button click handler
94+
* Update widget data and set up tap-to-refresh on the whole widget
9695
*/
9796
fun updateWidgetWithRefreshButton(
9897
context: Context,
9998
appWidgetManager: AppWidgetManager,
10099
appWidgetId: Int
101100
) {
102-
// First update the widget data
101+
// updateSingleWidget handles data, click handler, and loading state
103102
CategoryTimeWidgetWorker.updateSingleWidget(context, appWidgetManager, appWidgetId)
104-
105-
// Then set up the refresh button click handler and hide loading
106-
val views = RemoteViews(context.packageName, R.layout.widget_category_time)
107-
108-
// Hide loading indicator and show refresh button
109-
views.setViewVisibility(R.id.widget_loading_indicator, View.GONE)
110-
views.setViewVisibility(R.id.widget_refresh_button, View.VISIBLE)
111-
112-
val refreshIntent = Intent(context, CategoryTimeWidgetProvider::class.java).apply {
113-
action = ACTION_REFRESH
114-
}
115-
val refreshPendingIntent = PendingIntent.getBroadcast(
116-
context,
117-
0,
118-
refreshIntent,
119-
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
120-
)
121-
views.setOnClickPendingIntent(R.id.widget_refresh_button, refreshPendingIntent)
122-
123-
appWidgetManager.updateAppWidget(appWidgetId, views)
124103
}
125104
}
126105
}

mobile/src/main/java/net/activitywatch/android/widget/CategoryTimeWidgetWorker.kt

Lines changed: 56 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package net.activitywatch.android.widget
22

3+
import android.app.PendingIntent
34
import android.appwidget.AppWidgetManager
45
import android.content.ComponentName
56
import android.content.Context
7+
import android.content.Intent
68
import android.graphics.Bitmap
79
import android.graphics.Canvas
810
import android.graphics.Color
@@ -11,6 +13,7 @@ import android.graphics.RectF
1113
import android.util.Log
1214
import android.view.View
1315
import android.widget.RemoteViews
16+
import androidx.core.content.ContextCompat
1417
import androidx.work.CoroutineWorker
1518
import androidx.work.ExistingPeriodicWorkPolicy
1619
import androidx.work.PeriodicWorkRequestBuilder
@@ -34,12 +37,11 @@ private const val BAR_WIDTH = 400
3437
private const val BAR_HEIGHT = 24
3538
private const val BAR_CORNER_RADIUS = 12f
3639

37-
// Category colors (matching the dots)
38-
private val CATEGORY_COLORS = intArrayOf(
40+
// Category accent colors (matching the dots) - these stay constant in both themes
41+
private val CATEGORY_ACCENT_COLORS = intArrayOf(
3942
Color.parseColor("#00BFA5"), // Teal - category 1
4043
Color.parseColor("#7986CB"), // Purple - category 2
41-
Color.parseColor("#42A5F5"), // Blue - category 3
42-
Color.parseColor("#424242") // Gray - others
44+
Color.parseColor("#42A5F5") // Blue - category 3
4345
)
4446

4547
/**
@@ -149,7 +151,7 @@ class CategoryTimeWidgetWorker(
149151
views.setTextViewText(R.id.widget_minutes, minutes.toString())
150152

151153
// Draw and set the bar chart
152-
val barChartBitmap = createBarChartBitmap(categoryData, totalMillis)
154+
val barChartBitmap = createBarChartBitmap(context, categoryData, totalMillis)
153155
views.setImageViewBitmap(R.id.widget_bar_chart, barChartBitmap)
154156

155157
// Update top 3 apps
@@ -178,130 +180,83 @@ class CategoryTimeWidgetWorker(
178180
views.setTextViewText(R.id.widget_minutes, "0")
179181
}
180182

183+
// Set up tap-to-refresh on the whole widget
184+
val refreshIntent = Intent(context, CategoryTimeWidgetProvider::class.java).apply {
185+
action = "net.activitywatch.android.widget.ACTION_REFRESH"
186+
}
187+
val refreshPendingIntent = PendingIntent.getBroadcast(
188+
context,
189+
0,
190+
refreshIntent,
191+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
192+
)
193+
views.setOnClickPendingIntent(R.id.widget_root, refreshPendingIntent)
194+
195+
// Ensure loading indicator is hidden
196+
views.setViewVisibility(R.id.widget_loading_indicator, View.GONE)
197+
181198
appWidgetManager.updateAppWidget(appWidgetId, views)
182199
}
183200

201+
/**
202+
* Get the category colors array, with the "others" color resolved from theme resources
203+
*/
204+
private fun getCategoryColors(context: Context): IntArray {
205+
val othersColor = ContextCompat.getColor(context, R.color.widget_bar_bg)
206+
return intArrayOf(
207+
CATEGORY_ACCENT_COLORS[0],
208+
CATEGORY_ACCENT_COLORS[1],
209+
CATEGORY_ACCENT_COLORS[2],
210+
othersColor
211+
)
212+
}
213+
184214
/**
185215
* Create a bitmap with the stacked bar chart
186216
*/
187217
private fun createBarChartBitmap(
218+
context: Context,
188219
categoryData: List<Pair<String, Long>>,
189220
totalMillis: Long
190221
): Bitmap {
222+
val categoryColors = getCategoryColors(context)
191223
val bitmap = Bitmap.createBitmap(BAR_WIDTH, BAR_HEIGHT, Bitmap.Config.ARGB_8888)
192224
val canvas = Canvas(bitmap)
193225
val paint = Paint().apply {
194226
isAntiAlias = true
195227
style = Paint.Style.FILL
196228
}
197229

230+
// Clip canvas to rounded rect so both ends are always perfectly rounded
231+
val barRect = RectF(0f, 0f, BAR_WIDTH.toFloat(), BAR_HEIGHT.toFloat())
232+
val path = android.graphics.Path().apply {
233+
addRoundRect(barRect, BAR_CORNER_RADIUS, BAR_CORNER_RADIUS, android.graphics.Path.Direction.CW)
234+
}
235+
canvas.clipPath(path)
236+
198237
if (totalMillis == 0L || categoryData.isEmpty()) {
199-
// Draw empty bar with gray background
200-
paint.color = CATEGORY_COLORS[3]
201-
canvas.drawRoundRect(
202-
RectF(0f, 0f, BAR_WIDTH.toFloat(), BAR_HEIGHT.toFloat()),
203-
BAR_CORNER_RADIUS,
204-
BAR_CORNER_RADIUS,
205-
paint
206-
)
238+
paint.color = categoryColors[3]
239+
canvas.drawRect(barRect, paint)
207240
return bitmap
208241
}
209242

210-
// Calculate proportions for top 3 + others
243+
// Draw segments as simple rects — the clip handles rounding
211244
val top3 = categoryData.take(3)
212245
val top3Total = top3.sumOf { it.second }
213246
val othersTotal = totalMillis - top3Total
214-
215-
// Draw segments
216247
var currentX = 0f
217-
248+
218249
for (i in top3.indices) {
219-
val proportion = top3[i].second.toFloat() / totalMillis
220-
val segmentWidth = proportion * BAR_WIDTH
221-
222-
paint.color = CATEGORY_COLORS[i]
223-
224-
// For first segment, include left rounded corners
225-
// For last segment (or if no others), include right rounded corners
226-
val isFirst = i == 0
227-
val isLast = i == top3.lastIndex && othersTotal <= 0
228-
229-
if (isFirst && isLast) {
230-
// Only segment - full rounded corners
231-
canvas.drawRoundRect(
232-
RectF(currentX, 0f, currentX + segmentWidth, BAR_HEIGHT.toFloat()),
233-
BAR_CORNER_RADIUS,
234-
BAR_CORNER_RADIUS,
235-
paint
236-
)
237-
} else if (isFirst) {
238-
// First segment - rounded left corners
239-
canvas.drawRoundRect(
240-
RectF(currentX, 0f, currentX + segmentWidth + BAR_CORNER_RADIUS, BAR_HEIGHT.toFloat()),
241-
BAR_CORNER_RADIUS,
242-
BAR_CORNER_RADIUS,
243-
paint
244-
)
245-
// Cover the right rounded corners with a rect
246-
canvas.drawRect(
247-
currentX + segmentWidth,
248-
0f,
249-
currentX + segmentWidth + BAR_CORNER_RADIUS,
250-
BAR_HEIGHT.toFloat(),
251-
paint
252-
)
253-
} else if (isLast) {
254-
// Last segment - rounded right corners
255-
canvas.drawRoundRect(
256-
RectF(currentX - BAR_CORNER_RADIUS, 0f, currentX + segmentWidth, BAR_HEIGHT.toFloat()),
257-
BAR_CORNER_RADIUS,
258-
BAR_CORNER_RADIUS,
259-
paint
260-
)
261-
// Cover the left rounded corners with rect
262-
canvas.drawRect(
263-
currentX - BAR_CORNER_RADIUS,
264-
0f,
265-
currentX,
266-
BAR_HEIGHT.toFloat(),
267-
paint
268-
)
269-
} else {
270-
// Middle segment - no rounded corners
271-
canvas.drawRect(
272-
currentX,
273-
0f,
274-
currentX + segmentWidth,
275-
BAR_HEIGHT.toFloat(),
276-
paint
277-
)
278-
}
279-
250+
val segmentWidth = top3[i].second.toFloat() / totalMillis * BAR_WIDTH
251+
paint.color = categoryColors[i]
252+
canvas.drawRect(currentX, 0f, currentX + segmentWidth, BAR_HEIGHT.toFloat(), paint)
280253
currentX += segmentWidth
281254
}
282-
283-
// Draw "others" segment if there's remaining time
284-
if (othersTotal > 0) {
285-
val proportion = othersTotal.toFloat() / totalMillis
286-
val segmentWidth = proportion * BAR_WIDTH
287-
288-
paint.color = CATEGORY_COLORS[3]
289-
290-
// Last segment with rounded right corners
291-
canvas.drawRoundRect(
292-
RectF(currentX - BAR_CORNER_RADIUS, 0f, currentX + segmentWidth, BAR_HEIGHT.toFloat()),
293-
BAR_CORNER_RADIUS,
294-
BAR_CORNER_RADIUS,
295-
paint
296-
)
297-
// Cover left rounded corners
298-
canvas.drawRect(
299-
currentX - BAR_CORNER_RADIUS,
300-
0f,
301-
currentX,
302-
BAR_HEIGHT.toFloat(),
303-
paint
304-
)
255+
256+
// Fill remaining width with "others" color to avoid gaps from float rounding
257+
if (othersTotal > 0 || currentX < BAR_WIDTH) {
258+
paint.color = categoryColors[3]
259+
canvas.drawRect(currentX, 0f, BAR_WIDTH.toFloat(), BAR_HEIGHT.toFloat(), paint)
305260
}
306261

307262
return bitmap

mobile/src/main/res/drawable/ic_refresh.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
android:height="24dp"
55
android:viewportWidth="24"
66
android:viewportHeight="24"
7-
android:tint="#888888">
7+
android:tint="@color/widget_icon_tint">
88
<path
99
android:fillColor="#FFFFFF"
10-
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
10+
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z" />
1111
</vector>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<shape xmlns:android="http://schemas.android.com/apk/res/android"
33
android:shape="rectangle">
4-
<solid android:color="#2A2A2A" />
4+
<solid android:color="@color/widget_background" />
55
<corners android:radius="24dp" />
6-
</shape>
6+
</shape>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<shape xmlns:android="http://schemas.android.com/apk/res/android"
33
android:shape="rectangle">
4-
<solid android:color="#424242" />
4+
<solid android:color="@color/widget_bar_bg" />
55
<corners android:radius="6dp" />
6-
</shape>
6+
</shape>

0 commit comments

Comments
 (0)