From 97a6ba6b5568150b62d299120e3cd5039362247f Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Sun, 17 May 2026 00:49:05 +0800 Subject: [PATCH] feat(ui): show build number in About dialog (v1.0.0-rc.8+) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add package_info_plus 8.3.1 (Flutter Community Plus, BSD-3, ~3M downloads) and resolve the displayed version string from the platform manifest at app init. The string shown in the About dialog is now formatted as 'v+' (e.g. 'v1.0.0-rc.8+60') where both halves come from the SAME pubspec.yaml 'version:' field that Flutter uses to populate iOS's CFBundleShortVersionString + CFBundleVersion and Android's versionName + versionCode. Motivation: during the iOS VoiceProcessingIO audio rollout the user is rebuilding repeatedly from Xcode. Without a build-number suffix there is no way to confirm from inside the app which commit produced the build under test — they all read 'v1.0.0-rc.8'. Every test commit going forward bumps the + field in pubspec.yaml so the About dialog uniquely identifies the build. Implementation: * pubspec.yaml: version 1.0.0-rc.8+59 -> +60 (this commit is a test build). Add package_info_plus: ^8.0.0. * main.dart: _kAppVersion changes from 'const String' to 'String' (resolved at runtime). Populated in main() before runApp() via new _resolveAppVersion() helper that calls PackageInfo.fromPlatform() and formats 'v+'. Falls back to the hardcoded 'v1.0.0-rc.8' baseline if the platform call fails (extremely unlikely; the platform channel is a constant lookup). The consumer site at the About dialog (l10n.aboutVersion(_kAppVersion)) is unchanged — the variable still resolves to a String. flutter analyze: clean. --- apps/chanora_flutter/lib/main.dart | 40 ++++++++++++++++++++++++++---- apps/chanora_flutter/pubspec.lock | 32 ++++++++++++++++++++++++ apps/chanora_flutter/pubspec.yaml | 13 +++++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index a5e376f..7fa5867 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -16,6 +16,7 @@ import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'l10n/generated/app_localizations.dart'; @@ -35,20 +36,49 @@ bool get _isTouchOnlyPttHost { return Platform.isIOS || Platform.isAndroid; } -/// Public version string shown in the About dialog. Aligned with -/// `pubspec.yaml` and the git tag for the MVP release candidate. -/// Bumped to rc.8 for the post-rc.7 v1 audio + PTT lifecycle work -/// (SDD-094..097, DEC-029..031). -const String _kAppVersion = 'v1.0.0-rc.8'; +/// Public version string shown in the About dialog. Resolved at +/// app init from `package_info_plus`, which reads the platform- +/// canonical version (iOS `CFBundleShortVersionString` + `CFBundleVersion`, +/// Android `versionName` + `versionCode`). Flutter populates both +/// from the SAME `pubspec.yaml` `version:` field, so whatever the +/// user sees in the About dialog is guaranteed to match the +/// commit that produced the build they're running. The "+build" +/// suffix is the post-`+` portion of the pubspec version and +/// distinguishes successive test builds during the iOS audio +/// rollout (every test commit bumps the build number). +/// +/// Initialised before `runApp` in `main()`. Falls back to the +/// hardcoded baseline if `PackageInfo.fromPlatform()` fails +/// (extremely unlikely — the platform channel call is a simple +/// constant lookup). +String _kAppVersion = 'v1.0.0-rc.8'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); + await _resolveAppVersion(); unawaited(_wireStorage()); unawaited(_wireConnectivity()); runApp(const ChanoraApp()); } +/// Populate `_kAppVersion` from the platform manifest. Format is +/// `v+` (e.g. `v1.0.0-rc.8+60`). The `+` is +/// the iOS `CFBundleVersion` / Android `versionCode`, kept in +/// sync with `pubspec.yaml`'s `version: +`. +Future _resolveAppVersion() async { + try { + final info = await PackageInfo.fromPlatform(); + // info.version = "1.0.0-rc.8" (CFBundleShortVersionString) + // info.buildNumber = "60" (CFBundleVersion) + final build = info.buildNumber.isEmpty ? '' : '+${info.buildNumber}'; + _kAppVersion = 'v${info.version}$build'; + } catch (_) { + // Keep the hardcoded fallback. Already-correct for the + // semver portion; only the build counter is lost. + } +} + Future _wireStorage() async { try { final dir = await getApplicationSupportDirectory(); diff --git a/apps/chanora_flutter/pubspec.lock b/apps/chanora_flutter/pubspec.lock index ab7a1bc..62e69ac 100644 --- a/apps/chanora_flutter/pubspec.lock +++ b/apps/chanora_flutter/pubspec.lock @@ -309,6 +309,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -469,6 +477,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.dev" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -730,6 +754,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: diff --git a/apps/chanora_flutter/pubspec.yaml b/apps/chanora_flutter/pubspec.yaml index cfdd87f..2c30fbc 100644 --- a/apps/chanora_flutter/pubspec.yaml +++ b/apps/chanora_flutter/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0-rc.8+59 +version: 1.0.0-rc.8+60 environment: sdk: ^3.11.5 @@ -63,6 +63,17 @@ dependencies: # wrong UI: only lists AirPlay output destinations, not the # speaker/receiver/Bluetooth choices we actually want. audio_session: ^0.2.3 + # package_info_plus 8.x (Flutter Community Plus, BSD-3, ~3M + # downloads) exposes the platform-canonical app version + build + # number at runtime so the About dialog (and any future + # diagnostic export) can display "v1.0.0-rc.8+60" sourced from + # the SAME pubspec.yaml field that drives CFBundleShortVersionString + # + CFBundleVersion on iOS and versionName + versionCode on + # Android. Single source of truth means the version on the + # iPhone screen always matches the commit that built it; no + # more "am I testing the right build?" question during the + # iOS audio test cycle. + package_info_plus: ^8.0.0 dev_dependencies: flutter_test: