1
0
forked from firka/firka

22 Commits

Author SHA1 Message Date
23f7f7cd48 firka_wear: add error screen
Closes #12
2026-03-04 15:20:15 +01:00
c2879766eb build: run pub get and codegen 2026-03-04 10:55:40 +01:00
01cc08d5f3 codegen: generate missing .g.dart files 2026-03-04 09:56:52 +01:00
c386e1194b auto generate version code 2026-03-04 09:35:17 +01:00
67ed4e03eb firka: remov brotli again 2026-03-04 06:38:01 +01:00
c7d1f80e79 Merge pull request 'IOS Build fix' (#10) from devbeni/firka:dev into dev
Reviewed-on: firka/firka#10
2026-03-04 06:29:07 +01:00
7531e58114 Update project version to 1102 in Info.plist and project.pbxproj 2026-03-04 00:23:15 +01:00
c1e329cb5a Update subproject commit reference in l10n directory 2026-03-03 23:49:35 +01:00
dad52bf20e Merge branch 'dev' of https://git.firka.app/devbeni/firka into dev 2026-03-03 23:46:38 +01:00
5fb6d03d9c Remove Brotli subproject from Android app directory 2026-03-03 23:46:37 +01:00
1ef757d10f merge upstream 2026-03-03 23:46:10 +01:00
444abb83c2 Remove fmb_dart subproject from vendor directory 2026-03-03 23:44:24 +01:00
e835dcf6b1 firka: fix app icon changing
Closes: #9
2026-03-03 23:26:02 +01:00
e61a19fbbf Merge branch 'dev' of https://git.firka.app/devbeni/firka into dev 2026-03-03 23:23:30 +01:00
a937b854cd Update localization files and enhance build script for native assets handling 2026-03-03 23:22:43 +01:00
6d8f17ac00 Update object version and set development team for RunnerTests configuration 2026-03-03 23:11:25 +01:00
78dd4239cc merge upstream 2026-03-03 23:10:56 +01:00
39c5ca357e firka: remove package: 'firka'
Closes: #5
2026-03-03 23:08:45 +01:00
8249dbf03e remove dependency on brotli 2026-03-03 23:05:40 +01:00
e79de0326c bump version codes for firka and firka_wear 2026-03-03 21:43:20 +01:00
de335af3c1 add build script 2026-03-03 20:29:56 +01:00
4be0bcd813 bump version to 1.1.0 and version code to 1100 2026-03-03 20:28:31 +01:00
27 changed files with 264 additions and 899 deletions

3
.gitmodules vendored
View File

@@ -1,9 +1,6 @@
[submodule "firka/lib/l10n"]
path = firka/lib/l10n
url = https://github.com/QwIT-Development/firka-localization
[submodule "firka/android/app/src/main/java/org/brotli"]
path = firka/android/app/src/main/java/org/brotli
url = https://git.firka.app/firka/org_brotli
[submodule "firka_wear/lib/l10n"]
path = firka_wear/lib/l10n
url = https://github.com/qwit-development/firka-localization

41
build.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
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"
local PUBSPEC="$ROOT/$APP/pubspec.yaml"
if [[ ! -f "$PUBSPEC" ]]; then
echo "Not found: $PUBSPEC" >&2
return 1
fi
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_NAME="${BASE_VERSION}-${SHA}"
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"
flutter pub get
dart run scripts/codegen.dart
flutter build appbundle --build-name="$BUILD_NAME" --build-number="$VERSION_CODE" --verbose
}
case "${1:-all}" in
firka) build_app firka ;;
firka_wear) build_app firka_wear ;;
all) build_app firka && build_app firka_wear ;;
*)
echo "Usage: $0 [firka|firka_wear|all]" >&2
exit 1
;;
esac

View File

@@ -1,2 +1 @@
-keep class org.brotli.** { *; }
-keep class app.firka.naplo.glance.** { *; }

View File

@@ -11,7 +11,6 @@
android:name=".AppMain"
android:icon="@mipmap/launcher_icon">
<service
android:name=".WearSyncForegroundService"
android:exported="false"
@@ -41,7 +40,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_ace"
android:roundIcon="@mipmap/ic_ace_round" >
<intent-filter>
@@ -55,7 +54,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_ace_f"
android:roundIcon="@mipmap/ic_ace_f_round" >
<intent-filter>
@@ -69,7 +68,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_bi"
android:roundIcon="@mipmap/ic_bi_round" >
<intent-filter>
@@ -83,7 +82,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_bi_f"
android:roundIcon="@mipmap/ic_bi_f_round" >
<intent-filter>
@@ -97,7 +96,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_cactus"
android:roundIcon="@mipmap/ic_cactus_round" >
<intent-filter>
@@ -111,7 +110,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_cc"
android:roundIcon="@mipmap/ic_cc_round" >
<intent-filter>
@@ -125,7 +124,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_enby"
android:roundIcon="@mipmap/ic_enby_round" >
<intent-filter>
@@ -139,7 +138,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_enby_f"
android:roundIcon="@mipmap/ic_enby_f_round" >
<intent-filter>
@@ -153,7 +152,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_fidesz"
android:roundIcon="@mipmap/ic_fidesz_round" >
<intent-filter>
@@ -167,7 +166,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_filc"
android:roundIcon="@mipmap/ic_filc_round" >
<intent-filter>
@@ -181,7 +180,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_filco"
android:roundIcon="@mipmap/ic_filco_round" >
<intent-filter>
@@ -195,7 +194,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_galaxy"
android:roundIcon="@mipmap/ic_galaxy_round" >
<intent-filter>
@@ -209,7 +208,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_gay"
android:roundIcon="@mipmap/ic_gay_round" >
<intent-filter>
@@ -223,7 +222,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_gay_f"
android:roundIcon="@mipmap/ic_gay_f_round" >
<intent-filter>
@@ -237,7 +236,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_half_firka_2"
android:roundIcon="@mipmap/ic_half_firka_2_round" >
<intent-filter>
@@ -251,7 +250,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_kreta"
android:roundIcon="@mipmap/ic_kreta_round" >
<intent-filter>
@@ -265,7 +264,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lesb"
android:roundIcon="@mipmap/ic_lesb_round" >
<intent-filter>
@@ -279,7 +278,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lesb_f"
android:roundIcon="@mipmap/ic_lesb_f_round" >
<intent-filter>
@@ -293,7 +292,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lgbtq"
android:roundIcon="@mipmap/ic_lgbtq_round" >
<intent-filter>
@@ -307,7 +306,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lgbtq_f"
android:roundIcon="@mipmap/ic_lgbtq_f_round" >
<intent-filter>
@@ -321,7 +320,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lgbtqp"
android:roundIcon="@mipmap/ic_lgbtqp_round" >
<intent-filter>
@@ -335,7 +334,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lgbtqp_f"
android:roundIcon="@mipmap/ic_lgbtqp_f_round" >
<intent-filter>
@@ -349,7 +348,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_lidl"
android:roundIcon="@mipmap/ic_lidl_round" >
<intent-filter>
@@ -363,7 +362,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_mkkp"
android:roundIcon="@mipmap/ic_mkkp_round" >
<intent-filter>
@@ -377,7 +376,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_nuke"
android:roundIcon="@mipmap/ic_nuke_round" >
<intent-filter>
@@ -391,7 +390,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_modern"
android:roundIcon="@mipmap/ic_modern_round" >
<intent-filter>
@@ -405,7 +404,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_o1g"
android:roundIcon="@mipmap/ic_o1g_round" >
<intent-filter>
@@ -419,7 +418,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_old"
android:roundIcon="@mipmap/ic_old_round" >
<intent-filter>
@@ -433,7 +432,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_paper"
android:roundIcon="@mipmap/ic_paper_round" >
<intent-filter>
@@ -447,7 +446,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_pear"
android:roundIcon="@mipmap/ic_pear_round" >
<intent-filter>
@@ -461,7 +460,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_pixel"
android:roundIcon="@mipmap/ic_pixel_round" >
<intent-filter>
@@ -475,7 +474,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_pixelized"
android:roundIcon="@mipmap/ic_pixelized_round" >
<intent-filter>
@@ -489,7 +488,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_pride"
android:roundIcon="@mipmap/ic_pride_round" >
<intent-filter>
@@ -503,7 +502,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_proto"
android:roundIcon="@mipmap/ic_proto_round" >
<intent-filter>
@@ -517,7 +516,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_refilc"
android:roundIcon="@mipmap/ic_refilc_round" >
<intent-filter>
@@ -531,7 +530,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_refulc"
android:roundIcon="@mipmap/ic_refulc_round" >
<intent-filter>
@@ -545,7 +544,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_repont"
android:roundIcon="@mipmap/ic_repont_round" >
<intent-filter>
@@ -559,7 +558,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_szivacs"
android:roundIcon="@mipmap/ic_szivacs_round" >
<intent-filter>
@@ -573,7 +572,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_tisza"
android:roundIcon="@mipmap/ic_tisza_round" >
<intent-filter>
@@ -587,7 +586,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_trans"
android:roundIcon="@mipmap/ic_trans_round" >
<intent-filter>
@@ -601,7 +600,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_trans_f"
android:roundIcon="@mipmap/ic_trans_f_round" >
<intent-filter>
@@ -615,7 +614,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_void_icon"
android:roundIcon="@mipmap/ic_void_icon_round" >
<intent-filter>
@@ -629,7 +628,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_xmas1"
android:roundIcon="@mipmap/ic_xmas1_round" >
<intent-filter>
@@ -643,7 +642,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_xmas2"
android:roundIcon="@mipmap/ic_xmas2_round" >
<intent-filter>
@@ -657,7 +656,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:enabled="false"
android:icon="@mipmap/launcher_icon"
android:icon="@mipmap/ic_xmas3"
android:roundIcon="@mipmap/ic_xmas3_round" >
<intent-filter>

View File

@@ -7,9 +7,9 @@ icons:
"assets/images/logos/colored_logo_only_mustache.png": "bad6220c11bdfb1dfe04e5173bd2ebedd3999689d4b3a68fc63dc520c96dd33b"
l10n:
"l10n.yml": "a57bc304cac4a2b0235593586f17f400a5165d67fc9aadeaa11893cfa36ee082"
"lib/l10n/app_hu.arb": "a7f61bf4452a639d61c350f6674fdb5fd424f9ab31a195a200d446763fa8b396"
"lib/l10n/app_de.arb": "55f030b312cc07ff05cdc3d6ee10ef9bdec3243b507225e9a47196444518d955"
"lib/l10n/app_en.arb": "efac3f14d8ecc3e278f80a3e5aff599a88e408d2e30ff9e30f889978f465823a"
"lib/l10n/app_de.arb": "9cd5913be1e3bc3ed6c088ef448d5ce2924a6290b7dd6006d1af624c5e9a2503"
"lib/l10n/app_hu.arb": "17077ec76b68ed03796a264b99e4dba9e6ddd532e27a92d8fb237ea6f211f757"
"lib/l10n/app_en.arb": "cbad6dd2485a983e399cce97371c19089b9110d30536488c14a7ea709c7b6ead"
isar:
"lib/data/models/app_settings_model.dart": "5eb5af345f1347f104257f0999763650fe2673f9da1754bd12d3f756fe5c9723"
"lib/data/models/generic_cache_model.dart": "79151d0467fb5d40c532eaaa08ad7c7e24a34304199280fbf49cf6e5adcce6bc"

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -816,7 +816,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
shellScript = "set -e\nNATIVE_ASSETS_DIR=\"$FLUTTER_APPLICATION_PATH/$FLUTTER_BUILD_DIR/native_assets\"\ncase \"$FLUTTER_BUILD_DIR\" in\n /*) NATIVE_ASSETS_DIR=\"$FLUTTER_BUILD_DIR/native_assets\" ;;\nesac\nif [ -d \"$FLUTTER_APPLICATION_PATH/.dart_tool/hooks_runner\" ]; then\n find \"$FLUTTER_APPLICATION_PATH/.dart_tool/hooks_runner\" -exec xattr -c {} \\; 2>/dev/null || true\nfi\nif [ -d \"$NATIVE_ASSETS_DIR\" ]; then\n find \"$NATIVE_ASSETS_DIR\" -exec xattr -c {} \\; 2>/dev/null || true\nfi\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
D576F90540C8E625A9A12317 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@@ -1076,7 +1076,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -1108,7 +1108,8 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1127,7 +1128,8 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1144,7 +1146,8 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1171,7 +1174,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1222,7 +1225,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1270,7 +1273,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1317,7 +1320,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1370,7 +1373,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1420,7 +1423,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1471,7 +1474,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1522,7 +1525,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1569,7 +1572,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1877,7 +1880,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -1913,7 +1916,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1101;
CURRENT_PROJECT_VERSION = 1102;
DEVELOPMENT_TEAM = R9PZGUCNJ3;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -46,7 +46,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1101</string>
<string>1102</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@@ -1,62 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:brotli/brotli.dart';
import 'package:firka/app/app_state.dart';
import 'package:flutter/services.dart';
class FirkaBundle extends CachingAssetBundle {
// final bool _compressedBundle = !kDebugMode && Platform.isAndroid;
final bool _compressedBundle = false;
Map<String, dynamic>? index;
Future<Map<String, dynamic>> loadIndex() async {
var indexBrotli = await rootBundle.load("assets/firka.i");
var indexStr = brotli.decodeToString(indexBrotli.buffer.asInt8List());
return Future.value(jsonDecode(indexStr));
}
ByteData decode(Codec<List<int>, List<int>> codec, ByteData data) {
var dec = codec.decode(data.buffer.asInt8List());
var b = ByteData(dec.length);
var l = b.buffer.asInt8List();
for (var i = 0; i < dec.length; i++) {
l[i] = dec[i];
}
return b;
}
@override
Future<ByteData> load(String key) async {
if (!_compressedBundle) {
logger.finest(
"Loading asset from root bundle: assets/flutter_assets/$key",
);
return rootBundle.load(key);
} else {
index ??= await loadIndex();
final gzip = GZipCodec();
logger.finest(
"Loading asset from firka bundle: assets/flutter_assets/$key",
);
switch (index!["assets/flutter_assets/$key"]!) {
case "b": // brotli
return decode(brotli, await rootBundle.load(key));
case "g": // gzip
return decode(gzip, await rootBundle.load(key));
case "r": // raw
return rootBundle.load(key);
default:
logger.shout("Unknown file format: ${index![key]!}");
throw "Unknown file format: ${index![key]!}";
}
}
logger.finest(
"Loading asset from root bundle: assets/flutter_assets/$key",
);
return rootBundle.load(key);
}
}

View File

@@ -608,7 +608,6 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
"dropdownLeft",
size: 24,
color: appStyle.colors.accent,
package: 'firka',
),
),
onTap: () async {
@@ -673,7 +672,6 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
"dropdownRight",
size: 24,
color: appStyle.colors.accent,
package: 'firka',
),
),
onTap: () async {

View File

@@ -438,7 +438,6 @@ class _HomeTimetableMonthlyScreen
"dropdownLeft",
size: 24,
color: appStyle.colors.accent,
package: 'firka',
),
),
onTap: () async {
@@ -468,7 +467,6 @@ class _HomeTimetableMonthlyScreen
"dropdownRight",
size: 24,
color: appStyle.colors.accent,
package: 'firka',
),
onTap: () async {
var newNow = DateTime(now!.year, now!.month + 1);

View File

@@ -948,7 +948,6 @@ class _SettingsScreenState extends FirkaState<SettingsScreen> {
FirkaIconType.icons,
"group",
color: appStyle.colors.accent,
package: 'firka',
),
SizedBox(width: 8),
Text(

View File

@@ -56,14 +56,12 @@ class _WelcomeWidgetState extends State<WelcomeWidget> {
FirkaIconType.majesticonsLocal,
"sunSolid",
color: appStyle.colors.accent,
package: 'firka',
);
case Cycle.day:
return FirkaIconWidget(
FirkaIconType.majesticonsLocal,
"parkSolidSchool",
color: appStyle.colors.accent,
package: 'firka',
);
case Cycle.afternoon:
return FirkaIconWidget(

View File

@@ -34,7 +34,6 @@ class HomeworkWidget extends StatelessWidget {
"homeWithMark",
color: appStyle.colors.accent,
size: 24,
package: 'firka',
)
: FirkaIconWidget(
FirkaIconType.majesticons,

View File

@@ -370,7 +370,6 @@ class LessonWidget extends StatelessWidget {
'cupFilled',
color: appStyle.colors.accent,
size: 24,
package: 'firka',
),
),
),

View File

@@ -245,7 +245,6 @@ class LessonBigWidget extends StatelessWidget {
'cupFilled',
color: appStyle.colors.accent,
size: 24,
package: 'firka',
),
),
),
@@ -488,7 +487,6 @@ class LessonBigWidget extends StatelessWidget {
'cupFilled',
color: appStyle.colors.accent,
size: 24,
package: 'firka',
),
),
),

View File

@@ -2,7 +2,7 @@ name: firka
description: "Firka, Alternatív e-Kréta kliens."
publish_to: 'none'
version: 1.1.0+1040
version: 1.1.0+2001
environment:
sdk: ^3.11.0
@@ -38,7 +38,6 @@ dependencies:
flutter_arc_text: ^0.6.0
flutter_svg: ^1.1.6
home_widget: ^0.8.0
brotli: ^0.6.0
crypto: ^3.0.6
transparent_pointer: ^1.0.1
flutter_staggered_grid_view: ^0.7.0
@@ -90,7 +89,6 @@ flutter:
- assets/images/bubble.svg
- assets/icons/
- assets/majesticons/
- assets/firka.i
- assets/swears/DirtyWords.xml
- assets/images/cactus_error_screen.png
- assets/images/logos/dave_error.png

View File

@@ -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'));

Submodule firka/vendor/fmb_dart deleted from fb711c8c40

View File

@@ -1,15 +1,5 @@
import org.apache.commons.io.FileUtils
import java.io.FileInputStream
import java.security.MessageDigest
import java.util.Properties
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import java.util.zip.ZipOutputStream.DEFLATED
import java.util.zip.ZipOutputStream.STORED
plugins {
id("com.android.application")
@@ -114,724 +104,3 @@ fun checkReleaseKey() {
throw Exception("Release keystore not found!")
}
}
fun transformApks(debug: Boolean, i : Int = 0) {
try {
_transformApks(debug)
} catch (e: Exception) {
if (i < 5) {
e.printStackTrace()
println("Retrying: ${i + 1}")
transformApks(debug, i + 1)
} else {
throw e
}
}
}
fun _transformApks(debug: Boolean) {
println("Starting APK transformation process...")
val buildDir = project.buildDir
val apkDir = File(buildDir, "outputs/flutter-apk")
val apks = getApks(debug)
var c = 0
apks
.forEach { c++; transformAndSignApk(apkDir, it.nameWithoutExtension, debug) }
println("Transformed: $c apks")
}
fun transformAndSignApk(apkDir: File, name: String, debug: Boolean) {
val originalApk = File(apkDir, "$name.apk")
val transformedApk = File(apkDir, "$name-transformed.apk")
val finalApk = File(apkDir, "$name-resigned.apk")
val finalIdsig = File(apkDir, "$name-resigned.apk.idsig")
if (!originalApk.exists()) {
throw GradleException("Original APK not found at: ${originalApk.absolutePath}")
}
if (transformedApk.exists()) transformedApk.delete()
if (finalApk.exists()) finalApk.delete()
println("Original APK: ${originalApk.absolutePath}")
try {
println("Transforming APK...")
transformApk(originalApk, transformedApk, if (debug) { "6" } else {"Z"})
if (debug) {
println("Signing with debug key...")
signWithDebugKey(transformedApk, finalApk)
} else {
println("Signing with release key...")
signWithReleaseKey(transformedApk, finalApk)
}
if (finalApk.exists()) {
originalApk.delete()
finalIdsig.delete()
finalApk.renameTo(originalApk)
println("APK successfully transformed")
println("Final APK: ${originalApk.absolutePath}")
}
transformedApk.delete()
} catch (e: Exception) {
throw GradleException("Failed to transform and resign APK: ${e.message}", e)
}
}
fun transformApk(input: File, output: File, compressionLevel: String = "Z") {
val tempDir = File(project.buildDir, "tmp/apk-transform")
val cacheDir = File(project.buildDir, "cache")
val optipngCacheDir = File(cacheDir, "optipng")
val assetCompressionDir = File(cacheDir, "assets")
tempDir.deleteRecursively()
tempDir.mkdirs()
if (!optipngCacheDir.exists()) optipngCacheDir.mkdirs()
if (!assetCompressionDir.exists()) assetCompressionDir.mkdirs()
val brotli = findToolInPath("brotli")
?: throw Exception("Brotli not found in path")
val optipng = findToolInPath("optipng")
if (optipng == null || optipng.isEmpty()) {
println("Optipng was not found in PATH, optimizing images will be skipped.")
}
copy {
from(zipTree(input))
into(tempDir)
}
val metaInf = File(tempDir, "META-INF")
val metaInfFiles = metaInf.listFiles()
for (file in metaInfFiles!!) {
if (file.name.endsWith("MF") || file.name.endsWith("SF")
|| file.name.endsWith("RSA")) {
file.delete()
}
}
val arches = File(tempDir, "lib").listFiles()
val compressedLibs = mutableMapOf<String, String>()
for (arch in arches!!) {
val libFlutter = File(arch, "libflutter.so")
if (!libFlutter.exists()) continue
val compressedFlutter = File(arch, "libflutter-br.so")
compressedLibs["libflutter.so"] = libFlutter.sha256()
println("Compressing ${arch.name}/libflutter.so with brotli")
exec {
commandLine(
brotli,
"-$compressionLevel",
libFlutter.absolutePath,
"-o", compressedFlutter.absolutePath
)
}
libFlutter.delete()
val json = groovy.json.JsonBuilder(compressedLibs)
File(arch, "index.so").writeText(json.toString())
}
val topDirL = tempDir.absolutePath.length + 1
val zos = ZipOutputStream(output.outputStream())
val coreCount = Runtime.getRuntime().availableProcessors()
val flutterResources = tempDir.walkTopDown().filter{f -> f.absolutePath.contains("flutter_assets")}
val pngFiles = tempDir.walkTopDown().filter{f -> f.name.endsWith(".png")}
val assetIndex = mutableMapOf<String, String>()
val indexReadWriteLock = ReentrantReadWriteLock()
if (compressionLevel == "Z") {
if (optipng != null) {
val executor = Executors.newFixedThreadPool(coreCount)
val futures = mutableListOf<Future<*>>()
pngFiles.forEach { pngFile ->
val cacheFile = File(optipngCacheDir, pngFile.sha256())
if (cacheFile.exists()) {
cacheFile.copyTo(pngFile, true)
} else {
val future = executor.submit {
exec {
commandLine(
optipng,
"-zm", "9",
"-zw", "32k",
"-o9",
pngFile.absolutePath
)
}
pngFile.copyTo(cacheFile, true)
}
futures.add(future)
}
}
futures.forEach { it.get() }
executor.shutdown()
}
val executor = Executors.newFixedThreadPool(coreCount)
val futures = mutableListOf<Future<*>>()
val blacklist = listOf(
// "AssetManifest.bin",
"AssetManifest.json",
"FontManifest.json",
"isolate_snapshot_data",
"kernel_blob.bin",
"NativeAssetsManifest.json",
"NOTICES.Z",
"vm_snapshot_data",
"fonts",
"shaders"
)
flutterResources.forEach { f ->
val relName = f.absolutePath.substring(topDirL).replace("\\", "/")
if (f.isDirectory) return@forEach
val cacheFileRaw = File(assetCompressionDir, f.sha256()+".r")
val cacheFileGz = File(assetCompressionDir, f.sha256()+".gz")
val cacheFileBr = File(assetCompressionDir, f.sha256()+".br")
if (cacheFileRaw.exists() || cacheFileGz.exists() || cacheFileBr.exists()) {
if (cacheFileRaw.exists()) {
cacheFileRaw.copyTo(f, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "r"
indexReadWriteLock.writeLock().unlock()
} else if (cacheFileGz.exists()) {
cacheFileGz.copyTo(f, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "g"
indexReadWriteLock.writeLock().unlock()
} else {
cacheFileBr.copyTo(f, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "b"
indexReadWriteLock.writeLock().unlock()
}
} else {
val future = executor.submit {
val brTmp = File(f.absolutePath + ".br.tmp")
val gzTmp = File(f.absolutePath + ".gz.tmp")
var blacklisted = false
for (f in blacklist) {
if (relName.contains(f)) {
blacklisted = true
break
}
}
if (!blacklisted) {
println("$relName: Testing with brotli")
exec {
commandLine(
brotli,
"-$compressionLevel",
f.absolutePath,
"-o", brTmp.absolutePath
)
}
println("$relName: Testing with gzip")
ant.invokeMethod(
"gzip", mapOf(
"src" to f.absolutePath,
"destfile" to gzTmp.absolutePath,
)
)
println("$brTmp: ${brTmp.length()}")
println("$gzTmp: ${gzTmp.length()}")
if (f.length() < gzTmp.length() && f.length() < brTmp.length()) {
println("$relName: Raw file wins")
f.copyTo(cacheFileRaw, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "r"
indexReadWriteLock.writeLock().unlock()
} else {
if (brTmp.length() < gzTmp.length()) {
println("$relName: Brotli wins")
f.delete()
brTmp.copyTo(f, true)
brTmp.copyTo(cacheFileBr, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "b"
indexReadWriteLock.writeLock().unlock()
} else {
println("$relName: Gzip wins")
f.delete()
gzTmp.copyTo(f, true)
gzTmp.copyTo(cacheFileGz, true)
indexReadWriteLock.writeLock().lock()
assetIndex[relName] = "g"
indexReadWriteLock.writeLock().unlock()
}
}
brTmp.delete()
gzTmp.delete()
}
}
futures.add(future)
}
}
futures.forEach { it.get() }
executor.shutdown()
}
tempDir.walkTopDown().forEach { f ->
if (f.absolutePath == tempDir.absolutePath) return@forEach
var relName = f.absolutePath.substring(topDirL).replace("\\", "/")
if (f.isDirectory && !relName.endsWith("/")) relName += "/"
if (compressionLevel == "Z") {
if (relName == "assets/flutter_assets/assets/firka.i") return@forEach
}
println(relName)
val compress = !relName.endsWith(".so") && !relName.endsWith(".arsc")
zos.setMethod(if (compress) { DEFLATED } else { STORED })
val entry = ZipEntry(relName)
if (!compress) {
entry.size = f.length()
entry.crc = FileUtils.checksumCRC32(f)
}
zos.putNextEntry(entry)
if (f.isFile) {
zos.write(f.readBytes())
}
zos.closeEntry()
}
if (compressionLevel == "Z") {
zos.setMethod(DEFLATED)
zos.putNextEntry(ZipEntry("assets/flutter_assets/assets/firka.i"))
val indexUncompressed = File(tempDir, "index.json")
indexReadWriteLock.readLock().lock()
val json = groovy.json.JsonBuilder(assetIndex)
indexReadWriteLock.readLock().unlock()
indexUncompressed.writeText(json.toString())
val indexCompressed = File(tempDir, "index.json.br")
exec {
commandLine(
brotli,
"-$compressionLevel",
indexUncompressed.absolutePath,
"-o", indexCompressed.absolutePath
)
}
zos.write(indexCompressed.readBytes())
indexUncompressed.delete()
indexCompressed.delete()
zos.closeEntry()
}
zos.close()
tempDir.deleteRecursively()
println("APK transformed successfully")
}
fun transformAppBundle() {
val buildDir = project.buildDir
val bundle = File(buildDir, "outputs/bundle/release/app-release.aab")
val bundleTmp = File(buildDir, "outputs/bundle/release/tmp.zip")
val apks = getApks(false)
val apkCount = apks.count { it.name.startsWith("app-") && it.name.endsWith("-release.apk") }
if (!bundle.exists()) {
throw Exception("Bundle not found at: $bundle")
}
if (apkCount < 3) {
throw Exception("Excepected 3 apks per abi but only found $apkCount")
}
val aabTempDir = File(project.buildDir, "tmp/aab-transform")
aabTempDir.deleteRecursively()
aabTempDir.mkdirs()
val apksUnzipped = File(project.buildDir, "tmp/apks-unzipped")
apksUnzipped.deleteRecursively()
val arm32TempDir = File(apksUnzipped, "armeabi-v7a")
arm32TempDir.mkdirs()
val arm64TempDir = File(apksUnzipped, "arm64-v8a")
arm64TempDir.mkdirs()
val x86TempDir = File(apksUnzipped, "x86_64")
x86TempDir.mkdirs()
copy {
from(zipTree(bundle))
into(aabTempDir)
}
copy {
from(zipTree(apks.first { it.name.contains("armeabi-v7a") }))
into(arm32TempDir)
}
copy {
from(zipTree(apks.first { it.name.contains("arm64-v8a") }))
into(arm64TempDir)
}
copy {
from(zipTree(apks.first { it.name.contains("x86_64") }))
into(x86TempDir)
}
val libs = File(aabTempDir, "base/lib").listFiles()!!
for (dstLibs in libs) {
println("Copying lib: ${dstLibs.name}")
val srcDir = File(apksUnzipped, dstLibs.name)
if (!srcDir.exists()) {
continue
}
val srcLibs = File(srcDir, "lib/${dstLibs.name}/")
dstLibs.listFiles()!!.forEach { it.delete() }
srcLibs.listFiles()!!.forEach { it.copyTo(File(dstLibs, it.name)) }
}
val zos = ZipOutputStream(bundleTmp.outputStream())
val bundleZip = ZipFile(bundle)
val bundleEntries = bundleZip.entries()
val brotli = findToolInPath("brotli")
?: throw Exception("Brotli not found in path")
val optipng = findToolInPath("optipng")
?: throw Exception("Optipng not found in path")
val indexReadWriteLock = ReentrantReadWriteLock()
val assetIndex = mutableMapOf<String, String>()
while (bundleEntries.hasMoreElements()) {
val entry = bundleEntries.nextElement()
/*
if (entry.name == "base/assets/flutter_assets/assets/firka.i") {
println("Patching: ${entry.name}")
zos.putNextEntry(ZipEntry("assets/flutter_assets/assets/firka.i"))
val indexUncompressed = File(aabTempDir, "index.json")
indexReadWriteLock.readLock().lock()
val json = groovy.json.JsonBuilder(assetIndex)
indexReadWriteLock.readLock().unlock()
indexUncompressed.writeText(json.toString())
val indexCompressed = File(aabTempDir, "index.json.br")
exec {
commandLine(
brotli,
"-Z",
indexUncompressed.absolutePath,
"-o", indexCompressed.absolutePath
)
}
zos.write(indexCompressed.readBytes())
indexUncompressed.delete()
indexCompressed.delete()
zos.closeEntry()
continue
}
if (entry.name.startsWith("base/lib")) {
println("Patching: ${entry.name}")
zos.putNextEntry(ZipEntry(entry.name))
zos.closeEntry()
continue
}
*/
println("Adding: ${entry.name}")
zos.putNextEntry(ZipEntry(entry.name))
if (!entry.isDirectory) {
val data = bundleZip.getInputStream(entry).readAllBytes()
zos.write(data)
}
zos.closeEntry()
}
bundleZip.close()
zos.close()
bundle.delete()
signBundle(bundleTmp, bundle)
bundleTmp.delete()
aabTempDir.deleteRecursively()
println("AAB transformed successfully")
}
fun File.sha256(): String {
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(this.readBytes())
return digest.fold("") { str, it -> str + "%02x".format(it) }
}
fun getApks(debug: Boolean): List<File> {
val buildDir = project.buildDir
val apkDir = File(buildDir, "outputs/flutter-apk")
val apks = apkDir.listFiles()!!
val flavor = if (debug) { "debug" } else { "release" }
return apks
.filter { apk -> apk.name.startsWith("app-") && apk.name.endsWith("-$flavor.apk") }
.toList()
}
fun getDebugKeystorePath(): String {
val userHome = System.getProperty("user.home")
val debugKeystore = File(userHome, ".android/debug.keystore")
if (!debugKeystore.exists()) {
throw GradleException("Debug keystore not found at: ${debugKeystore.absolutePath}")
}
return debugKeystore.absolutePath
}
fun getDefaultAndroidSdkPath(): String? {
val os = System.getProperty("os.name").lowercase()
val userHome = System.getProperty("user.home")
return when {
os.contains("win") ->
"$userHome\\AppData\\Local\\Android\\Sdk"
os.contains("mac") ->
"$userHome/Library/Android/sdk"
os.contains("linux") ->
"$userHome/Android/Sdk"
else -> null
}
}
fun findToolInPath(toolName: String): String? {
val pathEnvironment = System.getenv("PATH")
val pathDirs = pathEnvironment.split(File.pathSeparator)
val executableNames = when {
System.getProperty("os.name").lowercase().contains("win") ->
listOf("$toolName.exe", toolName)
else ->
listOf(toolName)
}
for (pathDir in pathDirs) {
for (execName in executableNames) {
val possibleTool = File(pathDir, execName)
if (possibleTool.exists() && possibleTool.canExecute()) {
return possibleTool.absolutePath
}
}
}
return null
}
fun findToolInSdkPath(toolName: String): String? {
var androidHome : String? = System.getenv("ANDROID_HOME")
?: System.getenv("ANDROID_SDK_ROOT")
if (androidHome == null) androidHome = getDefaultAndroidSdkPath()
if (androidHome != null) {
val buildTools = File(androidHome, "build-tools")
if (buildTools.exists()) {
val latestVersion = buildTools.listFiles()
?.filter { it.isDirectory }
?.filter { it.name != "debian" }
?.maxByOrNull { it.name }
if (latestVersion != null) {
val toolExec = File(latestVersion, toolName)
if (toolExec.exists()) {
return toolExec.absolutePath
}
}
}
}
if (!toolName.contains(".exe")) {
val exeTool = findToolInSdkPath("$toolName.exe")
if (exeTool != null) return exeTool
}
if (!toolName.contains(".sh")) {
val shTool = findToolInSdkPath("$toolName.sh")
if (shTool != null) return shTool
}
if (!toolName.contains(".bat")) {
val batTool = findToolInSdkPath("$toolName.bat")
if (batTool != null) return batTool
}
return null
}
fun signWithDebugKey(input: File, output: File) {
val debugKeystore = getDebugKeystorePath()
val debugKeystorePassword = "android"
val debugKeyAlias = "androiddebugkey"
val debugKeyPassword = "android"
val zipAlign: String = findToolInSdkPath("zipalign")
?: throw Exception("Could not find zipalign in ANDROID_SDK")
val apksigner: String = findToolInSdkPath("apksigner")
?: throw Exception("Could not find zipalign in ANDROID_SDK")
exec {
commandLine(
zipAlign,
"-v", "4",
input.absolutePath,
output.absolutePath
)
}
exec {
commandLine(
apksigner, "sign",
"--ks", debugKeystore,
"--ks-pass", "pass:$debugKeystorePassword",
"--ks-key-alias", debugKeyAlias,
"--key-pass", "pass:$debugKeyPassword",
output.absolutePath
)
}
println("APK signed and aligned successfully")
}
fun signWithReleaseKey(input: File, output: File) {
val secretsDir = File(projectDir.absolutePath, "../../../secrets/")
val propsFile = File(secretsDir, "keystore.properties")
if (!propsFile.exists()) {
throw Exception("Release keystore not found!")
}
val props = loadProperties(propsFile)
val releaseKeystore = File(secretsDir, props["storeFile"].toString())
val releaseKeystorePassword = props["storePassword"] as String
val releaseKeyAlias = props["keyAlias"] as String
val releaseKeyPassword = props["keyPassword"] as String
val zipAlign: String = findToolInSdkPath("zipalign")
?: throw Exception("Could not find zipalign either in ANDROID_SDK")
val apksigner: String = findToolInSdkPath("apksigner")
?: throw Exception("Could not find zipalign either in ANDROID_SDK")
exec {
commandLine(
zipAlign,
"-v", "4",
input.absolutePath,
output.absolutePath
)
}
exec {
commandLine(
apksigner, "sign",
"--ks", releaseKeystore,
"--ks-pass", "pass:$releaseKeystorePassword",
"--ks-key-alias", releaseKeyAlias,
"--key-pass", "pass:$releaseKeyPassword",
output.absolutePath
)
}
println("APK signed and aligned successfully")
}
fun signBundle(input: File, output: File) {
val secretsDir = File(projectDir.absolutePath, "../../../secrets/")
val propsFile = File(secretsDir, "keystore.properties")
if (!propsFile.exists()) {
throw Exception("Release keystore not found!")
}
val props = loadProperties(propsFile)
val releaseKeystore = File(secretsDir, props["storeFile"].toString())
val releaseKeystorePassword = props["storePassword"] as String
val releaseKeyAlias = props["keyAlias"] as String
val releaseKeyPassword = props["keyPassword"] as String
// val zipAlign: String = findToolInSdkPath("zipalign")
// ?: throw Exception("Could not find zipalign in ANDROID_SDK")
val jarsigner: String = findToolInPath("jarsigner")
?: throw Exception("Could not find jarsigner in PATH")
/*
exec {
commandLine(
zipAlign,
"-v", "4",
input.absolutePath,
output.absolutePath
)
}
*/
input.copyTo(output, true)
exec {
// -keystore $KEYSTORE -storetype $STORETYPE -storepass $STOREPASS -digestalg SHA1 -sigalg SHA256withRSA application.zip $KEYALIAS
commandLine(
jarsigner,
"-verbose",
"-sigalg", "SHA256withRSA",
"-digestalg", "SHA-256",
"-keystore", releaseKeystore,
"-storepass", releaseKeystorePassword,
output.absolutePath,
releaseKeyAlias
)
}
println("AAB signed and aligned successfully")
}

View File

@@ -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;

View File

@@ -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!),
);
}

View File

@@ -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();
},
);
}
}

View 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(),
),
);
},
),
);
}
}

View File

@@ -18,7 +18,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+1011
version: 1.1.0+2000
environment:
sdk: ^3.11.0
@@ -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

View File

@@ -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,