Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bb3dfec

Browse files
ohucclaude
andcommitted
Release v2.0: widgets, notifications, Hindi translations, Foojay removal
- Add home screen widgets (caffeine now, quick add, combined) - Add notification support for daily reminders and caffeine alerts - Add Hindi (hi) translations - Add night-mode color values - Add What's New sheet - Rename Montserrat font asset, polish UI across all screens - Remove gradle/gradle-daemon-jvm.properties (Foojay API dependency) - Update README with dynamic badges and download buttons Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 73fb822 commit bb3dfec

76 files changed

Lines changed: 4806 additions & 691 deletions

File tree

Some content is hidden

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

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ local.properties
77
.idea/
88
.vscode/
99
.github/
10+
.claude/
11+
12+
CLAUDE.md
1013

1114
# Kotlin compiler errors
1215
.kotlin/
@@ -23,6 +26,9 @@ app/build/
2326
Thumbs.db
2427
desktop.ini
2528

29+
# Generated Gradle toolchain file (uses Foojay API URLs, not needed at build time)
30+
gradle/gradle-daemon-jvm.properties
31+
2632
# Temp / dev artifacts
2733
tmp_vico/
2834
artifacts/

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,26 @@
99
</p>
1010

1111
<p align="center">
12-
<img src="https://img.shields.io/badge/Platform-Android-3DDC84?logo=android&logoColor=white" alt="Platform" />
13-
<img src="https://img.shields.io/badge/Min%20SDK-31-brightgreen" alt="Min SDK" />
14-
<img src="https://img.shields.io/badge/Kotlin-2.3-7F52FF?logo=kotlin&logoColor=white" alt="Kotlin" />
15-
<img src="https://img.shields.io/badge/Jetpack%20Compose-Material%203-4285F4?logo=jetpack-compose&logoColor=white" alt="Compose" />
16-
<img src="https://img.shields.io/badge/License-GPL--3.0-blue" alt="License" />
12+
<a href="https://github.com/ohuc/CaffeineHealth/releases/latest">
13+
<img src="https://img.shields.io/github/v/release/ohuc/CaffeineHealth?logo=github&labelColor=1a1a1a" alt="GitHub release" />
14+
</a>
15+
<a href="https://f-droid.org/packages/com.uc.caffeine">
16+
<img src="https://img.shields.io/f-droid/v/com.uc.caffeine?logo=f-droid&labelColor=1a1a1a" alt="F-Droid" />
17+
</a>
18+
<a href="https://github.com/ohuc/CaffeineHealth/blob/master/LICENSE">
19+
<img src="https://img.shields.io/github/license/ohuc/CaffeineHealth?logo=gnu&color=blue&labelColor=1a1a1a" alt="License" />
20+
</a>
21+
<img src="https://img.shields.io/badge/Platform-Android-3DDC84?logo=android&logoColor=white&labelColor=1a1a1a" alt="Platform" />
22+
<img src="https://img.shields.io/badge/Min%20SDK-31-brightgreen&labelColor=1a1a1a" alt="Min SDK" />
23+
</p>
24+
25+
<p align="center">
26+
<a href="https://github.com/ohuc/CaffeineHealth/releases/latest">
27+
<img src="https://raw.githubusercontent.com/rubenpgrady/get-it-on-github/refs/heads/main/get-it-on-github.png" alt="Get it on GitHub" width="200" />
28+
</a>
29+
<a href="https://f-droid.org/packages/com.uc.caffeine">
30+
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" width="200" />
31+
</a>
1732
</p>
1833

1934
---

app/build.gradle.kts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ android {
1616
applicationId = "com.uc.caffeine"
1717
minSdk = 31
1818
targetSdk = 36
19-
versionCode = 2
20-
versionName = "1.5"
19+
versionCode = 3
20+
versionName = "2.0"
2121

2222
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2323
}
@@ -37,6 +37,7 @@ android {
3737
}
3838
buildFeatures {
3939
compose = true
40+
buildConfig = true
4041
}
4142
dependenciesInfo {
4243
includeInApk = false
@@ -97,6 +98,13 @@ dependencies {
9798
// Graphics Shapes — smooth shape morphing animations
9899
implementation(libs.androidx.graphics.shapes)
99100

101+
// Glance — Compose-style home screen widgets
102+
implementation(libs.glance.appwidget)
103+
implementation(libs.glance.material3)
104+
105+
// WorkManager — reliable periodic widget refresh
106+
implementation(libs.androidx.work.runtime.ktx)
107+
100108
testImplementation(libs.junit)
101109
androidTestImplementation(libs.androidx.junit)
102110
androidTestImplementation(libs.androidx.espresso.core)

app/src/main/AndroidManifest.xml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
<uses-permission android:name="android.permission.health.WRITE_NUTRITION"/>
66
<uses-permission android:name="android.permission.health.READ_SLEEP"/>
7+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
8+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
79

810
<queries>
911
<package android:name="com.google.android.apps.healthdata" />
@@ -16,13 +18,15 @@
1618
android:icon="@mipmap/ic_launcher"
1719
android:label="@string/app_name"
1820
android:enableOnBackInvokedCallback="true"
21+
android:localeConfig="@xml/locales_config"
1922
android:roundIcon="@mipmap/ic_launcher_round"
2023
android:supportsRtl="true"
2124
android:theme="@style/Theme.Caffeine">
2225
<activity
2326
android:name=".MainActivity"
2427
android:exported="true"
2528
android:label="@string/app_name"
29+
android:configChanges="locale|layoutDirection"
2630
android:theme="@style/Theme.Caffeine">
2731
<intent-filter>
2832
<action android:name="android.intent.action.MAIN" />
@@ -31,6 +35,59 @@
3135
</intent-filter>
3236
</activity>
3337

38+
<receiver
39+
android:name=".util.notifications.NotificationReceiver"
40+
android:exported="false">
41+
<intent-filter>
42+
<action android:name="com.uc.caffeine.NOTIFICATION_ALARM"/>
43+
</intent-filter>
44+
</receiver>
45+
46+
<receiver
47+
android:name=".util.notifications.BootReceiver"
48+
android:exported="true">
49+
<intent-filter>
50+
<action android:name="android.intent.action.BOOT_COMPLETED"/>
51+
</intent-filter>
52+
</receiver>
53+
54+
<!-- Widgets -->
55+
<receiver
56+
android:name=".widget.CaffeineNowWidgetReceiver"
57+
android:exported="true"
58+
android:label="@string/widget_caffeine_now_label">
59+
<intent-filter>
60+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
61+
</intent-filter>
62+
<meta-data
63+
android:name="android.appwidget.provider"
64+
android:resource="@xml/widget_caffeine_now_info" />
65+
</receiver>
66+
67+
<receiver
68+
android:name=".widget.QuickAddWidgetReceiver"
69+
android:exported="true"
70+
android:label="@string/widget_quick_add_label">
71+
<intent-filter>
72+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
73+
</intent-filter>
74+
<meta-data
75+
android:name="android.appwidget.provider"
76+
android:resource="@xml/widget_quick_add_info" />
77+
</receiver>
78+
79+
<receiver
80+
android:name=".widget.CombinedWidgetReceiver"
81+
android:exported="true"
82+
android:label="@string/widget_combined_label">
83+
<intent-filter>
84+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
85+
</intent-filter>
86+
<meta-data
87+
android:name="android.appwidget.provider"
88+
android:resource="@xml/widget_combined_info" />
89+
</receiver>
90+
3491
<activity-alias
3592
android:name="ViewPermissionUsageActivity"
3693
android:exported="true"

app/src/main/java/com/uc/caffeine/MainActivity.kt

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.uc.caffeine
22

3+
import android.Manifest
4+
import android.content.pm.PackageManager
5+
import android.os.Build
36
import android.os.Bundle
47
import androidx.activity.ComponentActivity
58
import androidx.activity.compose.setContent
69
import androidx.activity.enableEdgeToEdge
10+
import androidx.activity.result.contract.ActivityResultContracts
711
import androidx.compose.animation.AnimatedContent
812
import androidx.compose.animation.AnimatedVisibility
913
import androidx.compose.animation.SizeTransform
@@ -81,12 +85,16 @@ import androidx.compose.runtime.mutableIntStateOf
8185
import androidx.compose.runtime.mutableStateMapOf
8286
import androidx.compose.runtime.mutableStateOf
8387
import androidx.compose.runtime.setValue
88+
import androidx.lifecycle.compose.LifecycleResumeEffect
89+
import androidx.lifecycle.lifecycleScope
90+
import kotlinx.coroutines.launch
8491
import androidx.compose.ui.Alignment
8592
import androidx.compose.ui.Modifier
8693
import androidx.compose.ui.layout.boundsInParent
8794
import androidx.compose.ui.layout.onGloballyPositioned
8895
import androidx.compose.ui.platform.testTag
8996
import androidx.compose.ui.res.painterResource
97+
import androidx.compose.ui.res.stringResource
9098
import androidx.compose.ui.text.font.FontWeight
9199
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
92100
import androidx.compose.ui.unit.dp
@@ -109,11 +117,27 @@ import com.uc.caffeine.ui.screens.settings.SettingsScreen
109117
import com.uc.caffeine.ui.theme.CaffeineTheme
110118
import com.uc.caffeine.ui.theme.MontserratFamily
111119
import com.uc.caffeine.ui.viewmodel.CaffeineViewModel
120+
import com.uc.caffeine.widget.CaffeineWidgetUpdater
112121

113122
class MainActivity : ComponentActivity() {
123+
124+
private val requestNotificationPermission = registerForActivityResult(
125+
ActivityResultContracts.RequestPermission(),
126+
) { /* result handled by the system — no-op */ }
127+
114128
override fun onCreate(savedInstanceState: Bundle?) {
115129
super.onCreate(savedInstanceState)
116130
enableEdgeToEdge()
131+
com.uc.caffeine.util.notifications.NotificationChannels.createChannels(this)
132+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
133+
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
134+
requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
135+
}
136+
}
137+
CaffeineWidgetUpdater.schedulePeriodicRefresh(this)
138+
lifecycleScope.launch {
139+
CaffeineWidgetUpdater.publishWidgetPreviews(applicationContext)
140+
}
117141
setContent {
118142
CaffeineApp()
119143
}
@@ -137,6 +161,10 @@ fun CaffeineApp(
137161
val userSettings by viewModel.userSettings.collectAsStateWithLifecycle()
138162
val isUserSettingsLoaded by viewModel.isUserSettingsLoaded.collectAsStateWithLifecycle()
139163
val isConsumptionEntriesLoading by viewModel.isConsumptionEntriesLoading.collectAsStateWithLifecycle()
164+
LifecycleResumeEffect(Unit) {
165+
viewModel.onAppOpened()
166+
onPauseOrDispose {}
167+
}
140168
val hasExistingConsumptionHistory by viewModel.hasExistingConsumptionHistory.collectAsStateWithLifecycle()
141169
val darkTheme = when (userSettings.themeMode) {
142170
ThemeMode.SYSTEM -> isSystemInDarkTheme()
@@ -153,40 +181,47 @@ fun CaffeineApp(
153181
darkTheme = darkTheme,
154182
dynamicColor = userSettings.useDynamicColor,
155183
) {
156-
AnimatedContent(
157-
targetState = startupDestination,
184+
// Backdrop for the startup transition: the Onboarding→Main scaleIn from 0.92 leaves a
185+
// margin around the incoming shell, which would otherwise expose the windowBackground.
186+
Surface(
158187
modifier = Modifier.fillMaxSize(),
159-
transitionSpec = {
160-
when {
161-
initialState == StartupDestination.Onboarding &&
162-
targetState == StartupDestination.Main -> {
163-
(
164-
fadeIn(animationSpec = tween(durationMillis = 500)) +
165-
scaleIn(
166-
initialScale = 0.92f,
167-
animationSpec = tween(durationMillis = 600, easing = EaseOut),
168-
)
169-
) togetherWith (
170-
fadeOut(animationSpec = tween(durationMillis = 400)) +
171-
scaleOut(
172-
targetScale = 1.06f,
173-
animationSpec = tween(durationMillis = 400),
188+
color = MaterialTheme.colorScheme.background,
189+
) {
190+
AnimatedContent(
191+
targetState = startupDestination,
192+
modifier = Modifier.fillMaxSize(),
193+
transitionSpec = {
194+
when {
195+
initialState == StartupDestination.Onboarding &&
196+
targetState == StartupDestination.Main -> {
197+
(
198+
fadeIn(animationSpec = tween(durationMillis = 500)) +
199+
scaleIn(
200+
initialScale = 0.92f,
201+
animationSpec = tween(durationMillis = 600, easing = EaseOut),
202+
)
203+
) togetherWith (
204+
fadeOut(animationSpec = tween(durationMillis = 400)) +
205+
scaleOut(
206+
targetScale = 1.06f,
207+
animationSpec = tween(durationMillis = 400),
208+
)
174209
)
175-
)
176-
}
210+
}
177211

178-
else -> {
179-
fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 40)) togetherWith
180-
fadeOut(animationSpec = tween(durationMillis = 180))
181-
}
182-
}.using(SizeTransform(clip = false))
183-
},
184-
label = "startup_destination_transition",
185-
) { destination ->
186-
when (destination) {
187-
StartupDestination.Loading -> StartupLoadingScreen()
188-
StartupDestination.Onboarding -> OnboardingRoot(displaySettings = userSettings)
189-
StartupDestination.Main -> MainAppShell(userSettings = userSettings)
212+
else -> {
213+
fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 40)) togetherWith
214+
fadeOut(animationSpec = tween(durationMillis = 180))
215+
}
216+
}.using(SizeTransform(clip = false))
217+
},
218+
label = "startup_destination_transition",
219+
) { destination ->
220+
when (destination) {
221+
StartupDestination.Loading -> StartupLoadingScreen()
222+
StartupDestination.Onboarding -> OnboardingRoot(displaySettings = userSettings)
223+
StartupDestination.Main -> MainAppShell(userSettings = userSettings)
224+
}
190225
}
191226
}
192227
}
@@ -341,7 +376,7 @@ internal fun MainAppShell(
341376
),
342377
) {
343378
Text(
344-
text = destination.label,
379+
text = stringResource(destination.labelRes),
345380
modifier = Modifier.padding(start = ButtonDefaults.IconSpacing),
346381
style = MaterialTheme.typography.titleSmall.copy(
347382
fontWeight = FontWeight.Bold,
@@ -430,7 +465,7 @@ private fun DestinationIcon(
430465
if (vectorIcon != null) {
431466
Icon(
432467
imageVector = vectorIcon,
433-
contentDescription = destination.label,
468+
contentDescription = stringResource(destination.labelRes),
434469
modifier = Modifier.size(24.dp),
435470
)
436471
return
@@ -445,7 +480,7 @@ private fun DestinationIcon(
445480
if (iconRes != null) {
446481
Icon(
447482
painter = painterResource(iconRes),
448-
contentDescription = destination.label,
483+
contentDescription = stringResource(destination.labelRes),
449484
modifier = Modifier.size(24.dp),
450485
)
451486
}
@@ -489,7 +524,7 @@ private fun AddConsumptionButton(
489524
.background(Color.White),
490525
)
491526
Text(
492-
text = "Add Consumption",
527+
text = stringResource(R.string.main_add_consumption),
493528
style = MaterialTheme.typography.titleMedium.copy(
494529
fontFamily = MontserratFamily,
495530
fontWeight = FontWeight.Bold,

app/src/main/java/com/uc/caffeine/MainNavigation.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.uc.caffeine
22

3+
import androidx.annotation.StringRes
34
import androidx.compose.material.icons.Icons
45
import androidx.compose.material.icons.filled.Analytics
56
import androidx.compose.material.icons.filled.Settings
@@ -12,24 +13,24 @@ import java.io.Serializable
1213
sealed interface AppRoute : NavKey
1314

1415
enum class AppDestinations(
15-
val label: String,
16+
@param:StringRes val labelRes: Int,
1617
val iconOutlinedRes: Int? = null,
1718
val iconFilledRes: Int? = null,
1819
val iconOutlinedVector: ImageVector? = null,
1920
val iconFilledVector: ImageVector? = null,
2021
) : AppRoute {
2122
HOME(
22-
label = "Home",
23+
labelRes = R.string.nav_home,
2324
iconOutlinedRes = R.drawable.ic_home,
2425
iconFilledRes = R.drawable.ic_home_filled,
2526
),
2627
ANALYTICS(
27-
label = "Analytics",
28+
labelRes = R.string.nav_analytics,
2829
iconOutlinedVector = Icons.Outlined.Analytics,
2930
iconFilledVector = Icons.Filled.Analytics,
3031
),
3132
SETTINGS(
32-
label = "Settings",
33+
labelRes = R.string.nav_settings,
3334
iconOutlinedVector = Icons.Outlined.Settings,
3435
iconFilledVector = Icons.Filled.Settings,
3536
),

0 commit comments

Comments
 (0)