Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 61 additions & 6 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,51 @@ if (googleServicesConfigFiles.any { it.exists() }) {
logger.lifecycle("google-services.json not found; skipping Google Services plugin for this local build.")
}

def isReleaseTask = gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") }
def releaseSigningProperties = new Properties()
def releaseSigningPropertiesFile = rootProject.file("key.properties")

if (releaseSigningPropertiesFile.exists()) {
releaseSigningPropertiesFile.withInputStream { releaseSigningProperties.load(it) }
}

def releaseSigningValue = { propertyName, environmentName, legacyEnvironmentName ->
releaseSigningProperties.getProperty(propertyName)
?: System.getenv(environmentName)
?: System.getenv(legacyEnvironmentName)
}

def releaseKeystorePath = releaseSigningValue("storeFile", "ANDROID_KEYSTORE_PATH", "ONTIME_ANDROID_KEYSTORE_PATH")
def releaseKeystorePassword = releaseSigningValue("storePassword", "ANDROID_KEYSTORE_PASSWORD", "ONTIME_ANDROID_KEYSTORE_PASSWORD")
def releaseKeyAlias = releaseSigningValue("keyAlias", "ANDROID_KEY_ALIAS", "ONTIME_ANDROID_KEY_ALIAS")
def releaseKeyPassword = releaseSigningValue("keyPassword", "ANDROID_KEY_PASSWORD", "ONTIME_ANDROID_KEY_PASSWORD")
def releaseKeystoreFile = releaseKeystorePath ? file(releaseKeystorePath) : null

if (isReleaseTask) {
def missingReleaseSigningInputs = []

if (!releaseKeystorePath) {
missingReleaseSigningInputs << "storeFile, ANDROID_KEYSTORE_PATH, or ONTIME_ANDROID_KEYSTORE_PATH"
}
if (!releaseKeystorePassword) {
missingReleaseSigningInputs << "storePassword, ANDROID_KEYSTORE_PASSWORD, or ONTIME_ANDROID_KEYSTORE_PASSWORD"
}
if (!releaseKeyAlias) {
missingReleaseSigningInputs << "keyAlias, ANDROID_KEY_ALIAS, or ONTIME_ANDROID_KEY_ALIAS"
}
if (!releaseKeyPassword) {
missingReleaseSigningInputs << "keyPassword, ANDROID_KEY_PASSWORD, or ONTIME_ANDROID_KEY_PASSWORD"
}

if (releaseKeystoreFile && !releaseKeystoreFile.exists()) {
missingReleaseSigningInputs << "existing keystore file at ${releaseKeystoreFile}"
}

if (!missingReleaseSigningInputs.empty) {
throw new GradleException("Android release signing is required for release builds. Missing: ${missingReleaseSigningInputs.join(', ')}. Configure android/key.properties or Android release signing environment variables.")
}
}

android {
namespace = "club.devkor.ontime"
compileSdk = flutter.compileSdkVersion
Expand All @@ -28,15 +73,16 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8
}

buildFeatures {
buildConfig = true
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "club.devkor.ontime"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
Expand All @@ -47,11 +93,20 @@ android {
]
}

signingConfigs {
release {
if (releaseKeystoreFile) {
storeFile = releaseKeystoreFile
storePassword = releaseKeystorePassword
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
}
}
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
signingConfig = signingConfigs.release
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions docs/Android-Release-Signing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Android Release Signing

Android release builds must be signed with the production release key. Debug signing is only for debug and profile builds.

## Required Signing Inputs

Release builds require all of these values:

- `storeFile`: path to the release keystore file
- `storePassword`: keystore password
- `keyAlias`: release key alias
- `keyPassword`: release key password

The Gradle build reads these values from `android/key.properties` first. If that file is absent or a value is missing, it falls back to environment variables:

- `ANDROID_KEYSTORE_PATH`
- `ANDROID_KEYSTORE_PASSWORD`
- `ANDROID_KEY_ALIAS`
- `ANDROID_KEY_PASSWORD`

For compatibility with existing release metadata work, Gradle also accepts the legacy `ONTIME_ANDROID_KEYSTORE_PATH`, `ONTIME_ANDROID_KEYSTORE_PASSWORD`, `ONTIME_ANDROID_KEY_ALIAS`, and `ONTIME_ANDROID_KEY_PASSWORD` variable names.

## Local Release Build

Create `android/key.properties` on your machine:

```properties
storeFile=/absolute/path/to/release-keystore.jks
storePassword=<keystore-password>
keyAlias=<release-key-alias>
keyPassword=<release-key-password>
```

Then build the release artifact:

```sh
flutter build appbundle --release
```

`android/key.properties`, `*.jks`, and `*.keystore` files are ignored by git. Do not commit keystores or signing passwords.

## CI Release Build

CI should materialize the release keystore from a secure secret store, then provide the required environment variables to the Flutter build step:

```sh
export ANDROID_KEYSTORE_PATH=/secure/path/release-keystore.jks
export ANDROID_KEYSTORE_PASSWORD=<keystore-password>
export ANDROID_KEY_ALIAS=<release-key-alias>
export ANDROID_KEY_PASSWORD=<release-key-password>

flutter build appbundle --release
```

## How Validation Works

Gradle checks release signing inputs only when a release task is requested. Debug and profile builds do not require release signing credentials.

Release builds fail before packaging if any required value is absent or if the keystore file path does not exist. The error message lists the missing inputs so local and CI setup failures are explicit.
107 changes: 107 additions & 0 deletions docs/Android-Signing-Setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Android Signing Setup

Use this guide when preparing OnTime for Google Play release builds.

## Key Concepts

- Google Play releases should use Play App Signing.
- The local team keeps an upload key and uses it to sign app bundles before
uploading them to Play Console.
- Google Play uses the upload key to verify the upload, then signs distributed
APKs with the app signing key.
- Do not commit keystores, passwords, `android/key.properties`, or generated
release artifacts.

## Create an Upload Key

Create and store the keystore somewhere outside the repository:

```sh
mkdir -p ~/secure

keytool -genkeypair -v \
-keystore ~/secure/ontime-upload.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias ontime
```

Save the keystore file and passwords in the team password manager or secret
manager. Losing the upload key requires a Play Console upload-key reset.

## Configure Local Release Signing

Create `android/key.properties` locally:

```properties
storeFile=/absolute/path/to/ontime-upload.jks
storePassword=<keystore-password>
keyAlias=ontime
keyPassword=<key-password>
```

`android/key.properties` is ignored by git and must stay local.

Alternatively, set environment variables:

```sh
export ANDROID_KEYSTORE_PATH=/absolute/path/to/ontime-upload.jks
export ANDROID_KEYSTORE_PASSWORD='<keystore-password>'
export ANDROID_KEY_ALIAS=ontime
export ANDROID_KEY_PASSWORD='<key-password>'
```

The Gradle build also accepts the legacy `ONTIME_ANDROID_KEYSTORE_PATH`,
`ONTIME_ANDROID_KEYSTORE_PASSWORD`, `ONTIME_ANDROID_KEY_ALIAS`, and
`ONTIME_ANDROID_KEY_PASSWORD` variable names for compatibility.

## Build a Signed Release

Google Play prefers Android App Bundles:

```sh
flutter build appbundle --release
```

For APK validation outside Play:

```sh
flutter build apk --release
```

The Gradle config intentionally fails release builds when signing secrets are
missing. Debug builds do not require release signing secrets.

## First Play Console Release

1. Create or open the app in Google Play Console.
2. Create a release on an internal testing track first.
3. Configure Play App Signing when prompted.
4. Use Google-generated app signing key unless the team needs to share the same
signing key across multiple stores or related apps.
5. Upload the `.aab` from `build/app/outputs/bundle/release/`.
6. After Play App Signing is active, continue signing future uploads with the
same local upload key.

## Existing Play Console App

- If the app already has an upload key, use that existing key.
- If the upload key is lost but Play App Signing is enabled, request an upload
key reset in Play Console.
- If Play App Signing is not enabled and the app signing key is lost, the app
generally cannot be updated under the same package name.

## Verification

Run these checks before handing a release build to QA or Play Console:

```sh
flutter analyze
flutter test
flutter build appbundle --release
```

Confirm that `pubspec.yaml` has the intended `version` and that the build number
is greater than every previous Play Console upload for `club.devkor.ontime`.
1 change: 1 addition & 0 deletions docs/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Welcome to the OnTime-front project documentation! This wiki contains everything
- [Wiki Management Guide](./Wiki-Management.md) - How to modify and upload wiki content
- [Project Architecture](./Architecture.md) - Understanding the project structure
- [Git Workflow](./Git.md) - Git strategy and commit message formats
- [Android Release Signing](./Android-Release-Signing.md) - Required keystore inputs and release signing validation
- [iOS Release Configuration](./iOS-Release-Configuration.md) - Required Dart defines and archive validation

### Technical Deep Dives
Expand Down
56 changes: 56 additions & 0 deletions docs/Release-Checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Release Checklist

Use this checklist before producing store or production distribution builds for
OnTime.

## Versioning

- Keep `pubspec.yaml` as the source of truth for the app version.
- Use semantic versioning for the build name and a monotonically increasing
build number, formatted as `major.minor.patch+build`.
- The first production release is `1.0.0+1`.
- For each store submission, bump the build number even if the public version
name is unchanged.

## Android

- Confirm the application ID is `club.devkor.ontime`.
- Confirm the app label is `OnTime`.
- Review launcher icons in `android/app/src/main/res/mipmap-*`.
- Provide release signing through one of the following secret-free paths:
- `android/key.properties` with `storeFile`, `storePassword`, `keyAlias`, and
`keyPassword`.
- Environment variables `ANDROID_KEYSTORE_PATH`,
`ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, and
`ANDROID_KEY_PASSWORD`.
- Legacy environment variables `ONTIME_ANDROID_KEYSTORE_PATH`,
`ONTIME_ANDROID_KEYSTORE_PASSWORD`, `ONTIME_ANDROID_KEY_ALIAS`, and
`ONTIME_ANDROID_KEY_PASSWORD`.
- Follow `docs/Android-Signing-Setup.md` when creating or configuring the
upload key.
- Keep keystores, passwords, and `key.properties` out of git.
- Verify the release build fails clearly when signing secrets are missing.

## iOS

- Confirm the bundle identifier is `club.devkor.ontime`.
- Confirm the display name and bundle name are `OnTime`.
- Review the app icon set in `ios/Runner/Assets.xcassets/AppIcon.appiconset`.
- Confirm the launch screen, supported orientations, background modes,
entitlements, and AlarmKit capability declarations match the release target.
- Pass `GOOGLE_RESERVED_CLIENT_ID_IOS` for release/archive builds as documented
in `docs/iOS-Release-Configuration.md`.

## Web

- Confirm `web/manifest.json` uses the production app name, short name,
description, icons, theme color, and orientation.
- Confirm `web/index.html` uses the production description, app title, favicon,
apple mobile web app title, and touch icon.
- Rebuild the web app before deployment.

## Store Metadata

- Confirm the app name, short description, full description, screenshots,
support contact, privacy policy, and category are ready for the target stores.
- Track any brand, icon, screenshot, or store copy gaps before submission.
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: on_time_front
description: "A new Flutter project."
description: "OnTime helps you prepare for schedules with timely alarms and reminders."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
Expand Down
6 changes: 3 additions & 3 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A new Flutter project." />
<meta name="description" content="OnTime helps you prepare for schedules with timely alarms and reminders." />

<meta name="theme-color" content="transparent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
Expand All @@ -42,7 +42,7 @@
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<link rel="icon" href="favicon.ico" type="image/x-icon">

<title>on_time_front</title>
<title>OnTime</title>
<link rel="manifest" href="manifest.json" />

<style>
Expand Down Expand Up @@ -72,4 +72,4 @@
<script src="flutter_bootstrap.js" async></script>
</body>

</html>
</html>
2 changes: 1 addition & 1 deletion web/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"description": "OnTime helps you prepare for schedules with timely alarms and reminders.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
Expand Down
Loading