Skip to content

Commit 4f7cb6c

Browse files
committed
Merge remote-tracking branch 'origin/main' into codexd/issue-394-dependency-refresh
# Conflicts: # lib/core/services/notification_service.dart
2 parents 51043b8 + 398ae2b commit 4f7cb6c

31 files changed

Lines changed: 1136 additions & 293 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Android Release Verification
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
build_release:
11+
runs-on: ubuntu-latest
12+
environment: release
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- uses: actions/setup-java@v4
17+
with:
18+
distribution: temurin
19+
java-version: "17"
20+
21+
- uses: subosito/flutter-action@v2
22+
with:
23+
flutter-version: "3.32.6"
24+
channel: "stable"
25+
26+
- name: Decode Android Firebase config
27+
env:
28+
ANDROID_GOOGLE_SERVICES_JSON_B64: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON_B64 }}
29+
run: |
30+
if [ -z "$ANDROID_GOOGLE_SERVICES_JSON_B64" ]; then
31+
echo "ANDROID_GOOGLE_SERVICES_JSON_B64 secret is required for Android release builds." >&2
32+
exit 1
33+
fi
34+
mkdir -p android/app/src/release
35+
printf '%s' "$ANDROID_GOOGLE_SERVICES_JSON_B64" | base64 --decode > android/app/src/release/google-services.json
36+
test -s android/app/src/release/google-services.json
37+
38+
- run: flutter pub get
39+
40+
- run: dart run build_runner build -d
41+
42+
- name: Build Android app bundle
43+
env:
44+
REST_API_URL: ${{ vars.REST_API_URL }}
45+
run: flutter build appbundle --release --dart-define=REST_API_URL=$REST_API_URL

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ app.*.map.json
5353
ios/Flutter/Dart-Defines.xcconfig
5454
ios/GoogleService-Info.plist
5555
android/app/google-services.json
56+
android/app/src/debug/google-services.json
57+
android/app/src/release/google-services.json
58+
android/app/src/google-services.json
5659

5760
.cursor
5861
.codex/

README.md

Lines changed: 287 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,290 @@
1-
# on_time_front
1+
# OnTime Front
22

3-
# widgetbook
3+
OnTime is a Flutter app for schedule preparation, alarms, and arrival-time planning. The app is organized with clean architecture: presentation widgets and BLoCs depend on domain use cases, while data sources, Drift DAOs, and repository implementations live behind domain contracts.
4+
5+
Primary supported targets are web, Android, and iOS. Desktop platform folders are present in the Flutter project, but Firebase options are currently configured only for web, Android, and iOS.
6+
7+
## Contents
8+
9+
- [Requirements](#requirements)
10+
- [Project Structure](#project-structure)
11+
- [First-Time Setup](#first-time-setup)
12+
- [Runtime Configuration](#runtime-configuration)
13+
- [Firebase Configuration](#firebase-configuration)
14+
- [Development Commands](#development-commands)
15+
- [Build and Release](#build-and-release)
16+
- [Widgetbook](#widgetbook)
17+
- [Troubleshooting](#troubleshooting)
18+
- [Further Reading](#further-reading)
19+
20+
## Requirements
21+
22+
- Flutter stable. CI uses Flutter `3.32.6`; use that version for release parity when possible.
23+
- Dart SDK compatible with `pubspec.yaml` (`^3.5.4`).
24+
- Java and Android SDK for Android builds.
25+
- Xcode and CocoaPods for iOS builds.
26+
- Firebase CLI and FlutterFire CLI when regenerating Firebase config.
27+
- Access to the project secrets/configuration values listed below.
28+
29+
Check your local toolchain with:
30+
31+
```sh
32+
flutter doctor
33+
flutter --version
34+
dart --version
35+
```
36+
37+
## Project Structure
38+
39+
```text
40+
lib/
41+
core/ platform services, database, dependency injection, Dio, utilities
42+
data/ data sources, DAOs, models, tables, repository implementations
43+
domain/ entities, repository contracts, use cases
44+
l10n/ localization files
45+
presentation/ screens, blocs, cubits, shared widgets, routing, theme
46+
test/ tests mirroring app paths
47+
docs/ architecture and feature documentation
48+
assets/ shared assets and fonts
49+
widgetbook/ component catalog app
50+
```
51+
52+
Generated Dart files such as `*.g.dart`, `*.freezed.dart`, `*.config.dart`, and `*.mocks.dart` are not committed. Regenerate them locally after dependency changes or code-generator changes.
53+
54+
## First-Time Setup
55+
56+
1. Install dependencies:
57+
58+
```sh
59+
flutter pub get
60+
```
61+
62+
2. Generate code:
63+
64+
```sh
65+
dart run build_runner build --delete-conflicting-outputs
66+
```
67+
68+
3. Provide environment values and platform config. At minimum, local app runs need `REST_API_URL`; some flows may also require Firebase and native sign-in config. See [Runtime Configuration](#runtime-configuration) and [Firebase Configuration](#firebase-configuration).
69+
70+
4. Run the app:
71+
72+
```sh
73+
flutter run -d chrome --dart-define=REST_API_URL=<api-base-url>
74+
```
75+
76+
For Android or iOS, replace `chrome` with the device id from `flutter devices`.
77+
78+
## Runtime Configuration
79+
80+
The app reads compile-time values from Dart defines in `lib/core/constants/environment_variable.dart`.
81+
82+
| Define | Required | Used for |
83+
| --- | --- | --- |
84+
| `REST_API_URL` | Yes | Base URL for Dio API calls. |
85+
| `REST_AUTH_TOKEN` | Optional | Static auth token override for development/test scenarios. |
86+
| `GOOGLE_RESERVED_CLIENT_ID_IOS` | iOS release/archive only | Reversed iOS Google client ID used as the iOS URL scheme for Google Sign-In. |
87+
88+
Pass defines directly on Flutter commands:
89+
90+
```sh
91+
flutter run -d chrome \
92+
--dart-define=REST_API_URL=<api-base-url>
93+
94+
flutter build web --release \
95+
--dart-define=REST_API_URL=<api-base-url>
96+
```
97+
98+
For iOS release and archive builds, include the Google URL scheme define:
99+
100+
```sh
101+
flutter build ios --release \
102+
--dart-define=REST_API_URL=<api-base-url> \
103+
--dart-define=GOOGLE_RESERVED_CLIENT_ID_IOS=<reversed-ios-client-id>
104+
```
105+
106+
The iOS Xcode scheme decodes `DART_DEFINES` into `ios/Flutter/Dart-Defines.xcconfig`. Release builds fail if `GOOGLE_RESERVED_CLIENT_ID_IOS` is missing or if the built `Info.plist` does not contain the Google Sign-In URL scheme.
107+
108+
## Firebase Configuration
109+
110+
`lib/firebase_options.dart` is generated by FlutterFire and currently supports web, Android, and iOS. macOS, Windows, and Linux throw `UnsupportedError` unless Firebase is reconfigured for those targets.
111+
112+
Tracked Firebase-related files:
113+
114+
- `lib/firebase_options.dart`
115+
- `firebase.json`
116+
- `web/firebase-messaging-sw.js`
117+
118+
Native Firebase files are intentionally not committed and must come from project secret/config access:
119+
120+
- Android: `android/app/google-services.json`, or a variant-specific file under `android/app/src/debug/` or `android/app/src/release/`.
121+
- iOS: `ios/Runner/GoogleService-Info.plist`.
122+
123+
Android local builds skip the Google Services Gradle plugin when no `google-services.json` file is present, which is useful for non-Firebase development. Firebase-dependent features and production builds should use the correct native config files.
124+
125+
To regenerate Firebase configuration, use the FlutterFire CLI with the `ontime-c63f1` Firebase project and keep the outputs aligned with `firebase.json`.
126+
127+
## Development Commands
128+
129+
Install dependencies:
130+
131+
```sh
132+
flutter pub get
133+
```
134+
135+
Regenerate Drift, JSON, Injectable, Freezed, Mockito, and Widgetbook code:
136+
137+
```sh
138+
dart run build_runner build --delete-conflicting-outputs
139+
```
140+
141+
Format Dart code:
142+
143+
```sh
144+
dart format lib test
145+
```
146+
147+
Run analyzer:
148+
149+
```sh
150+
flutter analyze
151+
```
152+
153+
Run tests:
154+
155+
```sh
156+
flutter test
157+
```
158+
159+
Run tests with coverage:
160+
161+
```sh
162+
flutter test --coverage
163+
```
164+
165+
Run the web app locally:
166+
167+
```sh
168+
flutter run -d chrome --dart-define=REST_API_URL=<api-base-url>
169+
```
170+
171+
Do not use `npm test`; the root `package.json` script is a placeholder that intentionally fails.
172+
173+
## Build and Release
174+
175+
### Web
176+
177+
Preview and production web deployments are handled by GitHub Actions and Firebase Hosting. CI uses the `REST_API_URL` GitHub environment variable and builds with:
178+
179+
```sh
180+
flutter build web --release --dart-define=REST_API_URL=<api-base-url>
181+
```
182+
183+
Firebase Hosting deploys `build/web` to project `ontime-c63f1`.
184+
185+
### Android
186+
187+
Build a release APK:
188+
189+
```sh
190+
flutter build apk --release \
191+
--dart-define=REST_API_URL=<api-base-url>
192+
```
193+
194+
Build an app bundle for Play Console upload:
195+
196+
```sh
197+
flutter build appbundle --release \
198+
--dart-define=REST_API_URL=<api-base-url>
199+
```
200+
201+
Before production upload, confirm the release `google-services.json`, signing configuration, version name, and version code. The current Gradle release block uses the debug signing config as a local-build fallback, so production signing must be configured before store release.
202+
203+
### iOS
204+
205+
Build locally:
206+
207+
```sh
208+
flutter build ios --release \
209+
--dart-define=REST_API_URL=<api-base-url> \
210+
--dart-define=GOOGLE_RESERVED_CLIENT_ID_IOS=<reversed-ios-client-id>
211+
```
212+
213+
Create an App Store archive from Xcode or CI with the same Dart defines. See [docs/iOS-Release-Configuration.md](docs/iOS-Release-Configuration.md) for the release-only validation flow.
214+
215+
Before production upload, confirm `ios/Runner/GoogleService-Info.plist`, Apple signing, bundle id, app capabilities, push notification entitlement, build number, and App Store Connect metadata.
216+
217+
## Widgetbook
218+
219+
Widgetbook is a separate Flutter project under `widgetbook/`.
220+
221+
Run locally:
222+
223+
```sh
224+
cd widgetbook
225+
flutter pub get
226+
dart run build_runner build --delete-conflicting-outputs
227+
flutter run -d chrome
228+
```
229+
230+
Build for Firebase Hosting:
231+
232+
```sh
233+
cd widgetbook
234+
flutter build web --release
235+
```
236+
237+
Published Widgetbook:
4238

5239
https://on-time-front-widgetbook.web.app/
240+
241+
## Troubleshooting
242+
243+
### Generated files are missing or stale
244+
245+
Run:
246+
247+
```sh
248+
dart run build_runner build --delete-conflicting-outputs
249+
```
250+
251+
If generation still fails, clean generated state and fetch dependencies again:
252+
253+
```sh
254+
flutter clean
255+
flutter pub get
256+
dart run build_runner build --delete-conflicting-outputs
257+
```
258+
259+
### API calls fail immediately
260+
261+
Confirm `REST_API_URL` was passed to the `flutter run` or `flutter build` command. `String.fromEnvironment` values are compile-time constants, so changing a shell variable after launch does not update the app.
262+
263+
### Firebase initialization fails on desktop
264+
265+
Firebase options are not configured for macOS, Windows, or Linux. Use web, Android, or iOS, or regenerate Firebase options for desktop platforms before enabling those targets.
266+
267+
### Android Firebase features do not work locally
268+
269+
Add the appropriate `google-services.json` file under `android/app/` or a variant directory. Without that file, the Gradle build intentionally skips the Google Services plugin.
270+
271+
### iOS release fails with `GOOGLE_RESERVED_CLIENT_ID_IOS`
272+
273+
Pass the reversed iOS Google client ID with `--dart-define=GOOGLE_RESERVED_CLIENT_ID_IOS=<value>`. The release build validates both the Dart define extraction and the final `Info.plist` URL scheme.
274+
275+
### Web push notifications do not arrive
276+
277+
Confirm the browser has notification permission, the Firebase web config in `web/firebase-messaging-sw.js` matches the active Firebase project, and the app is served over HTTPS or localhost.
278+
279+
### SQLite or Drift tests fail in CI-like environments
280+
281+
Install SQLite system libraries before running tests. The GitHub Actions workflow installs `sqlite3` and `libsqlite3-dev` before `flutter test`.
282+
283+
## Further Reading
284+
285+
- [Architecture](docs/Architecture.md)
286+
- [Error handling and Result system](docs/Error-Handling-Result-System.md)
287+
- [Logging policy](docs/Logging-Policy.md)
288+
- [iOS release configuration](docs/iOS-Release-Configuration.md)
289+
- [Schedule timer system](docs/Schedule-Timer-System.md)
290+
- [Git workflow](docs/Git.md)

android/app/build.gradle

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,32 @@ plugins {
55
id "dev.flutter.flutter-gradle-plugin"
66
}
77

8+
def isReleaseBuild = gradle.startParameter.taskNames.any {
9+
it.toLowerCase().contains("release")
10+
}
11+
def googleServicesBuildType = isReleaseBuild ? "release" : "debug"
812
def googleServicesConfigFiles = [
13+
file("src/${googleServicesBuildType}/google-services.json"),
14+
file("google-services.json"),
15+
file("src/google-services.json"),
16+
]
17+
def releaseGoogleServicesConfigFiles = [
18+
file("src/release/google-services.json"),
919
file("google-services.json"),
10-
file("src/${gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') } ? 'release' : 'debug'}/google-services.json"),
1120
file("src/google-services.json"),
1221
]
22+
def hasReleaseGoogleServicesConfig = releaseGoogleServicesConfigFiles.any {
23+
it.exists()
24+
}
25+
26+
if (isReleaseBuild && !hasReleaseGoogleServicesConfig) {
27+
throw new GradleException(
28+
"Android release builds require Firebase config. " +
29+
"Provide android/app/src/release/google-services.json, usually by " +
30+
"decoding the ANDROID_GOOGLE_SERVICES_JSON_B64 CI secret before " +
31+
"running a release build."
32+
)
33+
}
1334

1435
if (googleServicesConfigFiles.any { it.exists() }) {
1536
apply plugin: "com.google.gms.google-services"
@@ -81,6 +102,10 @@ android {
81102
jvmTarget = "17"
82103
}
83104

105+
buildFeatures {
106+
buildConfig = true
107+
}
108+
84109
defaultConfig {
85110
applicationId = "club.devkor.ontime"
86111
minSdk = 23

0 commit comments

Comments
 (0)