diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 84ca3c4..d7b1caf 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -48,7 +48,8 @@ "Bash(flutter run)", "Bash(echo:*)", "Bash(pkill:*)", - "Bash(git restore:*)" + "Bash(git restore:*)", + "Bash(keytool:*)" ], "deny": [], "ask": [] diff --git a/.gitignore b/.gitignore index 7b55863..bc64bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,11 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Android signing +android/key.properties +*.jks +*.keystore *.zip *.ipa ios/Runner.app.dSYM.zip diff --git a/.osgrep/server.json b/.osgrep/server.json new file mode 100644 index 0000000..99f64e6 --- /dev/null +++ b/.osgrep/server.json @@ -0,0 +1,5 @@ +{ + "port": 4444, + "pid": 16846, + "authToken": "1c614f30-164f-4c79-9189-784b99f02ac4" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e59861f --- /dev/null +++ b/Makefile @@ -0,0 +1,118 @@ +# MeshCore SAR Makefile +# Version format: YYYY.MMDD.N+build (Flutter-compatible semver with date) +# Example: 2025.1205.1+1 + +# Configuration +APP_NAME := meshcore-sar +PUBSPEC := pubspec.yaml +BUILD_DIR := build/app/outputs/flutter-apk + +# Get current date parts +YEAR := $(shell date +%Y) +MMDD := $(shell date +%m%d) + +# Get current version info from pubspec.yaml +CURRENT_VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //') +CURRENT_BUILD_NUMBER := $(shell echo $(CURRENT_VERSION) | cut -d'+' -f2) + +# Extract current MMDD from version (middle part) +CURRENT_MMDD := $(shell echo $(CURRENT_VERSION) | cut -d'.' -f2) + +# Calculate new build number +# IMPORTANT: Build number (version code) must ALWAYS increment for Android updates +# Never reset to 1, even on date changes +NEW_BUILD_NUMBER := $(shell echo $$(($(CURRENT_BUILD_NUMBER) + 1))) + +# Calculate daily sequence number (resets each day for version name readability) +DAILY_BUILD := $(shell \ + if [ "$(CURRENT_MMDD)" = "$(MMDD)" ]; then \ + echo $(CURRENT_VERSION) | cut -d'.' -f3 | cut -d'+' -f1 | awk '{print $$1 + 1}'; \ + else \ + echo 1; \ + fi) + +# Version format: YYYY.MMDD.DAILY+BUILD +# DAILY resets each day (for readability), BUILD always increments (for Android) +NEW_VERSION := $(YEAR).$(MMDD).$(DAILY_BUILD)+$(NEW_BUILD_NUMBER) + +.PHONY: help version bump build release release-android release-ios clean deps analyze test icon + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +version: ## Show current and next version + @echo "Current version: $(CURRENT_VERSION)" + @echo "Current build: $(CURRENT_BUILD_NUMBER)" + @echo "---" + @echo "Next version: $(NEW_VERSION)" + +bump: ## Bump version in pubspec.yaml + @echo "Bumping version to $(NEW_VERSION)..." + @sed -i '' 's/^version: .*/version: $(NEW_VERSION)/' $(PUBSPEC) + @echo "Version bumped to $(NEW_VERSION)" + +deps: ## Install dependencies + flutter pub get + +analyze: ## Run Flutter analyze + flutter analyze + +test: ## Run tests + flutter test + +icon: ## Generate app icons from icon.png + dart run flutter_launcher_icons + +build: bump ## Build release APK (auto-bumps version) + @echo "Building release APK..." + flutter build apk --release + @echo "APK built: $(BUILD_DIR)/app-release.apk" + @ls -lh $(BUILD_DIR)/app-release.apk + +build-no-bump: ## Build release APK without bumping version + flutter build apk --release + @ls -lh $(BUILD_DIR)/app-release.apk + +release: build release-ios ## Build APK, create GitHub release, and upload iOS to TestFlight + $(eval VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //' | cut -d'+' -f1)) + @echo "Creating GitHub release v$(VERSION)..." + @if ! command -v gh &> /dev/null; then \ + echo "Error: GitHub CLI (gh) not installed. Install with: brew install gh"; \ + exit 1; \ + fi + @cp $(BUILD_DIR)/app-release.apk $(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk + gh release create "v$(VERSION)" \ + "$(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk#MeshCore SAR $(VERSION) APK" \ + --title "MeshCore SAR v$(VERSION)" \ + --notes "Release $(VERSION)" \ + --latest + @echo "Release v$(VERSION) created!" + +release-android: build ## Build APK and create GitHub release (Android only) + $(eval VERSION := $(shell grep '^version:' $(PUBSPEC) | sed 's/version: //' | cut -d'+' -f1)) + @echo "Creating GitHub release v$(VERSION)..." + @if ! command -v gh &> /dev/null; then \ + echo "Error: GitHub CLI (gh) not installed. Install with: brew install gh"; \ + exit 1; \ + fi + @cp $(BUILD_DIR)/app-release.apk $(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk + gh release create "v$(VERSION)" \ + "$(BUILD_DIR)/$(APP_NAME)-$(VERSION).apk#MeshCore SAR $(VERSION) APK" \ + --title "MeshCore SAR v$(VERSION)" \ + --notes "Release $(VERSION)" \ + --latest + @echo "Release v$(VERSION) created!" + +release-ios: ## Build iOS and upload to TestFlight + @echo "Building iOS and uploading to TestFlight..." + cd ios && fastlane release + @echo "iOS release uploaded!" + +clean: ## Clean build artifacts + flutter clean + rm -rf $(BUILD_DIR) + +# Build for all platforms +build-all: bump ## Build for Android and iOS + flutter build apk --release + flutter build ios --release --no-codesign diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1d84b5a..24c4cf9 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,3 +1,6 @@ +import java.util.Properties +import java.io.FileInputStream + plugins { id("com.android.application") id("kotlin-android") @@ -5,6 +8,13 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +// Load keystore properties from key.properties file +val keystorePropertiesFile = rootProject.file("key.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + android { namespace = "com.meshcore.sar.meshcore_sar_app" compileSdk = flutter.compileSdkVersion @@ -16,22 +26,17 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - // Required by some dependencies (e.g., flutter_local_notifications) - // to support Java 8+ APIs on older Android devices. isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.meshcore.sar.meshcore_sar_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 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode @@ -43,11 +48,20 @@ android { buildConfigField("String", "COMMIT_HASH", "\"$commitHash\"") } + signingConfigs { + create("release") { + if (keystorePropertiesFile.exists()) { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = file(keystoreProperties["storeFile"] as String) + storePassword = keystoreProperties["storePassword"] as String + } + } + } + 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") + signingConfig = signingConfigs.getByName("release") } } } @@ -57,6 +71,5 @@ flutter { } dependencies { - // Core library desugaring support library coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") } diff --git a/android/fastlane/Appfile b/android/fastlane/Appfile new file mode 100644 index 0000000..b596461 --- /dev/null +++ b/android/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("/Users/dz0ny/android-keystores/fastlane-480919-0cb30c62db50.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.meshcore.sar.meshcore_sar_app") # e.g. com.krausefx.app diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile new file mode 100644 index 0000000..cd0233a --- /dev/null +++ b/android/fastlane/Fastfile @@ -0,0 +1,59 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + desc "Runs all the tests" + lane :test do + gradle(task: "test") + end + + desc "Build release APK" + lane :beta do + # Get the project root directory (two levels up from android/fastlane/) + project_root = File.expand_path("../..", __dir__) + + # Build APK with Flutter + sh("cd #{project_root} && flutter build apk --release") + + apk_path = File.join(project_root, "build/app/outputs/flutter-apk/app-release.apk") + + # Uncomment to distribute via Firebase App Distribution: + # firebase_app_distribution( + # app: "YOUR_FIREBASE_APP_ID", + # apk_path: apk_path, + # groups: "testers" + # ) + + UI.success("APK built at: #{apk_path}") + end + + desc "Deploy a new version to the Google Play" + lane :deploy do + # Get the project root directory (two levels up from android/fastlane/) + project_root = File.expand_path("../..", __dir__) + + # Build AAB with Flutter (required for Play Store since 2021) + sh("cd #{project_root} && flutter build appbundle --release") + + aab_path = File.join(project_root, "build/app/outputs/bundle/release/app-release.aab") + + upload_to_play_store( + aab: aab_path, + track: "internal" + ) + end +end diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 73e1657..47360ef 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -16,10 +16,17 @@ default_platform(:ios) platform :ios do + desc "Push a new release build to the App Store" + lane :release do + increment_build_number(xcodeproj: "Runner.xcodeproj") + build_app(workspace: "Runner.xcworkspace", scheme: "Runner") + upload_to_app_store(skip_metadata: true, skip_screenshots: true) + end + desc "Push a new beta build to TestFlight" lane :beta do increment_build_number(xcodeproj: "Runner.xcodeproj") build_app(workspace: "Runner.xcworkspace", scheme: "Runner") - upload_to_testflight + upload_to_testflight(skip_waiting_for_build_processing: true) end end