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
42 changes: 0 additions & 42 deletions .github/workflows/dart.yml

This file was deleted.

79 changes: 79 additions & 0 deletions .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Flutter

on:
push:
branches: ["dev", "main"]
paths-ignore:
- "landingPage/**"
- "bookbridge-rust-core/**"
- "supabase/**"
- "**.md"
pull_request:
branches: ["dev", "main"]
paths-ignore:
- "landingPage/**"
- "bookbridge-rust-core/**"
- "supabase/**"
- "**.md"

permissions:
contents: read

concurrency:
group: flutter-${{ github.ref }}
cancel-in-progress: true

env:
# Pinned deliberately: an unpinned SDK let Flutter 3.47 silently break the
# Android build (AGP/NDK minimums, final IconData). Bump in its own PR.
FLUTTER_VERSION: "3.47.4"
ANDROID_NDK_VERSION: "28.2.13676358"
EXPECTED_APPLICATION_ID: "com.bookbridge.app"

jobs:
android:
name: Analyze, test, build Android
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- uses: actions/checkout@v7

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: "17"
cache: gradle

- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true

- name: Install Android NDK
timeout-minutes: 10
run: |
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;${ANDROID_NDK_VERSION}"
test -f "$ANDROID_HOME/ndk/${ANDROID_NDK_VERSION}/source.properties"

- name: Install dependencies (must match pubspec.lock)
run: flutter pub get --enforce-lockfile

# Fails on errors only; tighten to --fatal-warnings once existing warnings are fixed.
- name: Analyze
run: flutter analyze --no-fatal-infos --no-fatal-warnings

- name: Test
run: flutter test

- name: Build debug APK
run: flutter build apk --debug

- name: Verify application ID
run: |
AAPT2="$(ls -d "$ANDROID_HOME"/build-tools/*/ | sort -V | tail -1)aapt2"
PACKAGE="$("$AAPT2" dump badging build/app/outputs/flutter-apk/app-debug.apk | sed -n "s/^package: name='\([^']*\)'.*/\1/p")"
echo "APK package: $PACKAGE"
test "$PACKAGE" = "$EXPECTED_APPLICATION_ID"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ supabase.json
build/
*.log
*.lock
# Applications must commit their lockfile so every machine resolves the same versions
!/pubspec.lock
analysis_output.txt

# OS
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ flutter run \
--dart-define="FAPSHI_API_KEY=$(grep FAPSHI_API_KEY .env | cut -d'=' -f2)"
```

### Release builds (Android)
Release builds are signed with an upload key that is read from `android/key.properties` (this file is gitignored).

1. Generate the upload keystore **once**. Keep it outside the repository:
```bash
keytool -genkeypair -v -keystore ~/keys/bookbridge-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
```
2. Copy `android/key.properties.example` to `android/key.properties` and fill in the real values.
3. Build the bundle for Play Console with `flutter build appbundle --release`, adding the same `--dart-define` flags as above.

> [!CAUTION]
> Back up the keystore and its passwords in at least two durable places outside your laptop, and share them with a second maintainer. If the key is lost, you can't update the Play Store listing until Google approves an upload-key reset. That only works if Play App Signing is enabled, so enable it when you create the app.

Without `key.properties`, release builds fall back to debug keys and print a warning. Play Console rejects bundles signed that way.

Register the upload key's SHA-1 in Firebase and in the Google Cloud OAuth client so that Google Sign-In works. To print it, run `keytool -list -v -keystore ~/keys/bookbridge-upload.jks -alias upload`.

### Running Landing Page (SvelteKit)
```bash
cd landingPage
Expand Down
56 changes: 49 additions & 7 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import java.io.FileInputStream
import java.util.Properties

plugins {
id("com.android.application")
// START: FlutterFire Configuration
Expand All @@ -8,10 +11,39 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}

// Release signing is read from android/key.properties (gitignored).
// See android/key.properties.example and the "Release builds" section of README.md.
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties().apply {
if (keystorePropertiesFile.exists()) {
FileInputStream(keystorePropertiesFile).use { load(it) }
}
}
val hasReleaseKeystore = keystorePropertiesFile.exists()

if (hasReleaseKeystore) {
val requiredKeys = listOf("storeFile", "storePassword", "keyAlias", "keyPassword")
val missingKeys = requiredKeys.filter { keystoreProperties.getProperty(it).isNullOrBlank() }
if (missingKeys.isNotEmpty()) {
throw GradleException("android/key.properties is missing: ${missingKeys.joinToString()}")
}
}

if (!hasReleaseKeystore) {
gradle.taskGraph.whenReady {
if (allTasks.any { it.name.contains("Release") }) {
System.err.println(
"WARNING: android/key.properties not found; release build is signed with DEBUG keys " +
"and cannot be uploaded to Play Console."
)
}
}
}

android {
namespace = "com.example.book_bridge"
namespace = "com.bookbridge.app"
compileSdk = flutter.compileSdkVersion
// ndkVersion = flutter.ndkVersion
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
Expand All @@ -24,8 +56,7 @@ android {
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.book_bridge"
applicationId = "com.bookbridge.app"
// 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
Expand All @@ -34,11 +65,22 @@ android {
versionName = flutter.versionName
}

signingConfigs {
if (hasReleaseKeystore) {
create("release") {
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
}

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.getByName("debug")
// Without key.properties, fall back to debug keys so `flutter run --release`
// still works locally. Play Console rejects debug-signed uploads.
signingConfig = signingConfigs.getByName(if (hasReleaseKeystore) "release" else "debug")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion android/app/google-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"client_info": {
"mobilesdk_app_id": "1:243422993063:android:97feef1bd71c5194d91982",
"android_client_info": {
"package_name": "com.example.book_bridge"
"package_name": "com.bookbridge.app"
}
},
"oauth_client": [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.book_bridge
package com.bookbridge.app

import io.flutter.embedding.android.FlutterActivity

Expand Down
5 changes: 4 additions & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
org.gradle.java.home=/opt/android-studio/jbr
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
7 changes: 7 additions & 0 deletions android/key.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copy to android/key.properties (gitignored) and fill in real values.
# Keep the keystore itself OUTSIDE the repository and back it up.
# storeFile is resolved relative to android/app/, so prefer an absolute path.
storeFile=/home/you/keys/bookbridge-upload.jks
storePassword=change-me
keyAlias=upload
keyPassword=change-me
2 changes: 1 addition & 1 deletion android/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pluginManagement {

plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("com.android.application") version "8.13.2" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.10") apply false
// END: FlutterFire Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# BookBridge v2.0 — Implementation Plan
# BookBridge v2.0 — Monetization & Backend Plan
*Rust/Axum migration + Power Seller subscription tier*
*July 2026*

> [!NOTE]
> **Scope:** this document covers only the monetization and backend slice of v2.0: the Power Seller subscription, the free-tier listing cap, and the Rust/Axum migration. It is not the whole v2.0 plan.
>
> - The secondary-school feature set (filters, meetup zones, verification, parent-as-buyer, Discover, push, and more) is tracked as GitHub issues #29–#38.
> - The subscription work in this document is tracked in #39.
> - The fee model in #30 (the buyer pays 6% on top, and the seller receives the full price) supersedes the "5% commission" wording below.

---

## 1. One clarification first: Axum, Railway, and Render are not alternatives to each other
Expand Down
12 changes: 6 additions & 6 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
Expand All @@ -384,7 +384,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -401,7 +401,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
Expand All @@ -416,7 +416,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
Expand Down Expand Up @@ -547,7 +547,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -569,7 +569,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.bookBridge;
PRODUCT_BUNDLE_IDENTIFIER = com.bookbridge.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class ContactUsScreen extends StatelessWidget {
const SizedBox(height: 40),
_buildContactCard(
context,
icon: FontAwesomeIcons.whatsapp,
icon: FontAwesomeIcons.whatsapp.data,
title: AppLocalizations.of(context)!.whatsappSupport,
subtitle: AppLocalizations.of(context)!.chatDirectly,
color: Colors.green,
Expand All @@ -81,7 +81,7 @@ class ContactUsScreen extends StatelessWidget {
const SizedBox(height: 16),
_buildContactCard(
context,
icon: FontAwesomeIcons.linkedinIn,
icon: FontAwesomeIcons.linkedinIn.data,
title: 'LinkedIn',
subtitle: AppLocalizations.of(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
}

Widget _buildSocialIcon(
IconData icon,
FaIconData icon,
Color color,
String label,
VoidCallback onTap,
Expand Down
2 changes: 1 addition & 1 deletion lib/firebase_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ class DefaultFirebaseOptions {
messagingSenderId: '243422993063',
projectId: 'bookbridge-c12fa',
storageBucket: 'bookbridge-c12fa.firebasestorage.app',
iosBundleId: 'com.example.book_bridge',
iosBundleId: 'com.bookbridge.app',
);
}
2 changes: 1 addition & 1 deletion linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "book_bridge")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.book_bridge")
set(APPLICATION_ID "com.bookbridge.app")

# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
Expand Down
Loading
Loading