11package net.activitywatch.android.widget
22
3+ import android.app.PendingIntent
34import android.appwidget.AppWidgetManager
45import android.content.ComponentName
56import android.content.Context
7+ import android.content.Intent
68import android.graphics.Bitmap
79import android.graphics.Canvas
810import android.graphics.Color
@@ -11,6 +13,7 @@ import android.graphics.RectF
1113import android.util.Log
1214import android.view.View
1315import android.widget.RemoteViews
16+ import androidx.core.content.ContextCompat
1417import androidx.work.CoroutineWorker
1518import androidx.work.ExistingPeriodicWorkPolicy
1619import androidx.work.PeriodicWorkRequestBuilder
@@ -34,12 +37,11 @@ private const val BAR_WIDTH = 400
3437private const val BAR_HEIGHT = 24
3538private 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
0 commit comments