forked from firka/firka
Compare commits
6 Commits
7531e58114
...
23f7f7cd48
| Author | SHA1 | Date | |
|---|---|---|---|
| 23f7f7cd48 | |||
| c2879766eb | |||
| 01cc08d5f3 | |||
| c386e1194b | |||
| 67ed4e03eb | |||
| c7d1f80e79 |
22
build.sh
22
build.sh
@@ -1,12 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build firka and/or firka_wear with version from pubspec + short git SHA.
|
||||
# Usage: ./build.sh [firka|firka_wear|all]
|
||||
# Default (no args) builds both.
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||
SHA=$(git -C "$ROOT" rev-parse --short HEAD)
|
||||
COMMIT_COUNT=$(git -C "$ROOT" rev-list --count HEAD)
|
||||
|
||||
build_app() {
|
||||
local APP="$1"
|
||||
@@ -16,22 +13,21 @@ build_app() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local VERSION_LINE BUILD_NUMBER BASE_VERSION BUILD_NAME
|
||||
local VERSION_LINE BASE_VERSION BUILD_NAME VERSION_CODE
|
||||
VERSION_LINE=$(grep -E '^\s*version:\s*' "$PUBSPEC" | head -1)
|
||||
BASE_VERSION=$(echo "$VERSION_LINE" | sed -E 's/^[[:space:]]*version:[[:space:]]*([^+]+).*/\1/' | tr -d ' ')
|
||||
BUILD_NUMBER=""
|
||||
if [[ "$VERSION_LINE" == *+* ]]; then
|
||||
BUILD_NUMBER=$(echo "$VERSION_LINE" | sed -E 's/^[[:space:]]*version:[[:space:]]*[^+]+\+([0-9]+).*/\1/')
|
||||
fi
|
||||
BUILD_NAME="${BASE_VERSION}-${SHA}"
|
||||
|
||||
echo "Building $APP: version $BUILD_NAME (build number: ${BUILD_NUMBER:-none})"
|
||||
VERSION_CODE=$((2000 + COMMIT_COUNT))
|
||||
[[ "$APP" == "firka_wear" ]] && VERSION_CODE=$((VERSION_CODE + 1))
|
||||
|
||||
echo "Building $APP: version $BUILD_NAME (version code: $VERSION_CODE)"
|
||||
cd "$ROOT/$APP"
|
||||
|
||||
local FLUTTER_ARGS=(build appbundle --build-name="$BUILD_NAME" --verbose)
|
||||
[[ -n "${BUILD_NUMBER:-}" ]] && FLUTTER_ARGS+=(--build-number="$BUILD_NUMBER")
|
||||
flutter pub get
|
||||
dart run scripts/codegen.dart
|
||||
|
||||
flutter "${FLUTTER_ARGS[@]}"
|
||||
flutter build appbundle --build-name="$BUILD_NAME" --build-number="$VERSION_CODE" --verbose
|
||||
}
|
||||
|
||||
case "${1:-all}" in
|
||||
|
||||
Submodule firka/android/app/src/main/java/org/brotli deleted from da8f329432
@@ -31,11 +31,11 @@ void main() async {
|
||||
ran = true;
|
||||
}
|
||||
|
||||
if (_isarOutOfDate(root)) {
|
||||
if (_isarOutOfDate(root) || _isarGeneratedFilesMissing(root)) {
|
||||
final inputs = _isarInputs(root);
|
||||
final hashes = _computeHashes(root, inputs);
|
||||
stdout.writeln(
|
||||
'Isar generated dart files out of date, running build_runner...',
|
||||
'Isar generated dart files out of date or missing, running build_runner...',
|
||||
);
|
||||
await _run('dart', ['run', 'build_runner', 'build'], root);
|
||||
_updateLockWithHashes(root, 'isar', hashes);
|
||||
@@ -240,6 +240,18 @@ bool _isarOutOfDate(String root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isarGeneratedFilesMissing(String root) {
|
||||
final inputs = _isarInputs(root);
|
||||
if (inputs.isEmpty) return false;
|
||||
final modelsDir = p.join(root, 'lib/data/models');
|
||||
for (final dartFile in inputs) {
|
||||
final baseName = p.basenameWithoutExtension(dartFile.path);
|
||||
final gFile = File(p.join(modelsDir, '$baseName.g.dart'));
|
||||
if (!gFile.existsSync()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<File> _splashInputs(String root) {
|
||||
final config = File(p.join(root, 'flutter_native_splash.yaml'));
|
||||
final splashImage = File(p.join(root, 'assets/images/logos/splash.png'));
|
||||
|
||||
@@ -9,6 +9,10 @@ import 'package:firka_wear/services/wear_sync_store.dart';
|
||||
late final Logger logger;
|
||||
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
/// When non-null, the app should show [WearErrorScreen] with this error.
|
||||
final ValueNotifier<FlutterErrorDetails?> globalErrorNotifier =
|
||||
ValueNotifier<FlutterErrorDetails?>(null);
|
||||
late WearAppInitialization initData;
|
||||
bool initDone = false;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:wear_plus/wear_plus.dart';
|
||||
|
||||
import 'package:firka_wear/app/app_state.dart';
|
||||
import 'package:firka_wear/app/initialization.dart';
|
||||
import 'package:firka_wear/core/bloc/wear_sync_cubit.dart';
|
||||
import 'package:firka_wear/l10n/app_localizations.dart';
|
||||
import 'package:firka_wear/ui/theme/style.dart';
|
||||
import 'package:firka_wear/ui/wear/screens/error/error_screen.dart';
|
||||
import 'package:firka_wear/ui/wear/screens/home/home_screen.dart';
|
||||
import 'package:firka_wear/ui/wear/screens/login/login_screen.dart';
|
||||
|
||||
@@ -25,25 +25,7 @@ class WearInitializationScreen extends StatelessWidget {
|
||||
if (snapshot.hasError) {
|
||||
return MaterialApp(
|
||||
key: ValueKey('firkaErrorPage'),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: WatchShape(
|
||||
builder: (context, shape, child) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Error initializing app: ${snapshot.error}',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
child!,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
home: WearErrorScreen(exception: snapshot.error!),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firka_wear/app/app_state.dart';
|
||||
import 'package:firka_wear/app/initialization_screen.dart';
|
||||
import 'package:firka_wear/ui/wear/screens/error/error_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -26,5 +28,49 @@ void main() async {
|
||||
|
||||
await ScreenUtil.ensureScreenSize();
|
||||
|
||||
runApp(WearInitializationScreen());
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
FlutterError.presentError(details);
|
||||
if (_isFatalError(details.exception)) {
|
||||
globalErrorNotifier.value = details;
|
||||
}
|
||||
};
|
||||
|
||||
runZonedGuarded(() => runApp(const _WearAppWrapper()), (
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
if (_isFatalError(error)) {
|
||||
globalErrorNotifier.value = FlutterErrorDetails(
|
||||
exception: error,
|
||||
stack: stackTrace,
|
||||
library: 'firka_wear',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _isFatalError(Object error) {
|
||||
return error is! AssertionError;
|
||||
}
|
||||
|
||||
class _WearAppWrapper extends StatelessWidget {
|
||||
const _WearAppWrapper();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<FlutterErrorDetails?>(
|
||||
valueListenable: globalErrorNotifier,
|
||||
builder: (context, error, _) {
|
||||
if (error != null) {
|
||||
return MaterialApp(
|
||||
home: WearErrorScreen(
|
||||
exception: error.exception,
|
||||
stackTrace: error.stack,
|
||||
),
|
||||
);
|
||||
}
|
||||
return WearInitializationScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
65
firka_wear/lib/ui/wear/screens/error/error_screen.dart
Normal file
65
firka_wear/lib/ui/wear/screens/error/error_screen.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:wear_plus/wear_plus.dart';
|
||||
|
||||
import 'package:firka_wear/ui/theme/style.dart';
|
||||
|
||||
final int _kMaxQrPayloadChars = 410;
|
||||
|
||||
String errorPayload(Object exception, [StackTrace? stackTrace]) {
|
||||
final buffer = StringBuffer();
|
||||
buffer.writeln(exception.toString());
|
||||
if (stackTrace != null) buffer.write(stackTrace.toString());
|
||||
final s = buffer.toString();
|
||||
return s.length > _kMaxQrPayloadChars
|
||||
? s.substring(0, _kMaxQrPayloadChars)
|
||||
: s;
|
||||
}
|
||||
|
||||
/// Full-screen error UI: encodes [exception] (and [stackTrace]) into a QR code
|
||||
/// scaled to fit the watch's circular display so it is not clipped.
|
||||
class WearErrorScreen extends StatelessWidget {
|
||||
final Object exception;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
const WearErrorScreen({super.key, required this.exception, this.stackTrace});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ScreenUtil.init(context);
|
||||
|
||||
final payload = errorPayload(exception, stackTrace);
|
||||
return Scaffold(
|
||||
backgroundColor: wearStyle.colors.background,
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Center(
|
||||
child: WatchShape(
|
||||
builder: (context, shape, child) {
|
||||
return SizedBox(
|
||||
width: 350.w,
|
||||
height: 350.h,
|
||||
child: QrImageView(
|
||||
data: payload,
|
||||
version: 13,
|
||||
backgroundColor: wearStyle.colors.background,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: wearStyle.colors.textPrimary,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: wearStyle.colors.textPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ dependencies:
|
||||
flutter_svg: ^1.1.6
|
||||
logging: ^1.3.0
|
||||
flutter_bloc: ^9.0.0
|
||||
qr_flutter: ^4.1.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: any
|
||||
|
||||
@@ -30,11 +30,11 @@ void main() async {
|
||||
ran = true;
|
||||
}
|
||||
|
||||
if (_isarOutOfDate(root)) {
|
||||
if (_isarOutOfDate(root) || _isarGeneratedFilesMissing(root)) {
|
||||
final inputs = _isarInputs(root);
|
||||
final hashes = _computeHashes(root, inputs);
|
||||
stdout.writeln(
|
||||
'Isar generated dart files out of date, running build_runner...',
|
||||
'Isar generated dart files out of date or missing, running build_runner...',
|
||||
);
|
||||
await _run('dart', ['run', 'build_runner', 'build'], root);
|
||||
_updateLockWithHashes(root, 'isar', hashes);
|
||||
@@ -228,6 +228,18 @@ bool _isarOutOfDate(String root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isarGeneratedFilesMissing(String root) {
|
||||
final inputs = _isarInputs(root);
|
||||
if (inputs.isEmpty) return false;
|
||||
final modelsDir = p.join(root, 'lib/data/models');
|
||||
for (final dartFile in inputs) {
|
||||
final baseName = p.basenameWithoutExtension(dartFile.path);
|
||||
final gFile = File(p.join(modelsDir, '$baseName.g.dart'));
|
||||
if (!gFile.existsSync()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _run(
|
||||
String executable,
|
||||
List<String> args,
|
||||
|
||||
Reference in New Issue
Block a user