feat(macos): сборка DMG, фикс keychain и opus-библиотек

- secure storage: usesDataProtectionKeychain=false — вход в аккаунт
  больше не падает с -34018 без аккаунта разработчика
- entitlements: убран app-sandbox/keychain-access-groups (adhoc без Team ID)
- иконка: squircle с прозрачными полями (нативный macOS-вид)
- opus/ogg: битые bitcode-библиотеки ogg_opus_player заменены на
  собранные из исходников universal (.a в macos/prebuilt/opus +
  scripts/fix_opus_macos.sh применяет их после pub get)
- scripts/build_dmg.sh: adhoc-сборка universal DMG с дизайном (create-dmg)
- CI: build-macos.yml собирает DMG-артефакт; flutter-main.yml чинит libs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
klockky
2026-06-25 20:08:10 +03:00
co-authored by Claude Opus 4.8
parent c800cc0b32
commit 0f4844bf80
24 changed files with 445 additions and 22 deletions
+6 -12
View File
@@ -35,22 +35,16 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: 'stable'
- name: Get dependencies
run: flutter pub get
- name: Install create-dmg
run: brew install create-dmg
- name: Build macOS
run: flutter build macos --release
- name: Build signed DMG
run: ./scripts/build_dmg.sh
- name: Package macOS
run: |
mkdir -p dist
APP=$(find build/macos/Build/Products/Release -maxdepth 1 -name "*.app" | head -1)
ditto -c -k --keepParent "$APP" dist/Komet-macos.zip
- name: Upload macOS artifact
- name: Upload macOS DMG artifact
uses: actions/upload-artifact@v4
with:
name: komet-macos
path: dist/Komet-macos.zip
path: dist/*.dmg
if-no-files-found: error
retention-days: 30
+3
View File
@@ -121,5 +121,8 @@ jobs:
- name: Build iOS
run: flutter build ios --release --no-codesign
- name: Fix ogg_opus_player macOS libs
run: ./scripts/fix_opus_macos.sh
- name: Build macOS
run: flutter build macos --release
+1
View File
@@ -50,6 +50,7 @@ analysis_benchmark.json
.pub-preload-cache/
.pub/
build/
dist/
flutter_*.png
linked_*.ds
unlinked.ds
+1
View File
@@ -7,6 +7,7 @@ class TokenStorage {
static const _secure = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
mOptions: MacOsOptions(usesDataProtectionKeychain: false),
);
static Future<void> writeSecure(String key, String value) async {
+210 -6
View File
@@ -1,54 +1,258 @@
PODS:
- app_links (6.4.1):
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- dynamic_color (0.0.2):
- FlutterMacOS
- file_picker (0.0.1):
- FlutterMacOS
- Firebase/CoreOnly (12.15.0):
- FirebaseCore (~> 12.15.0)
- Firebase/Messaging (12.15.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 12.15.0)
- firebase_core (4.11.0):
- Firebase/CoreOnly (~> 12.15.0)
- FlutterMacOS
- firebase_messaging (16.4.1):
- Firebase/CoreOnly (~> 12.15.0)
- Firebase/Messaging (~> 12.15.0)
- firebase_core
- FlutterMacOS
- FirebaseCore (12.15.0):
- FirebaseCoreInternal (~> 12.15.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreInternal (12.15.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseInstallations (12.15.0):
- FirebaseCore (~> 12.15.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (12.15.0):
- FirebaseCore (~> 12.15.0)
- FirebaseInstallations (~> 12.15.0)
- GoogleDataTransport (~> 10.1)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Reachability (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- nanopb (~> 3.30910.0)
- flutter_inappwebview_macos (0.0.1):
- FlutterMacOS
- OrderedSet (~> 6.0.3)
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- flutter_secure_storage_darwin (10.0.0):
- Flutter
- FlutterMacOS
- flutter_timezone (0.1.0):
- FlutterMacOS
- flutter_webrtc (1.4.0):
- FlutterMacOS
- WebRTC-SDK (= 144.7559.09)
- FlutterMacOS (1.0.0)
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.1.1):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.1):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.1):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.1):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.1)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.1)
- GoogleUtilities/Reachability (8.1.1):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.1):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- media_kit_libs_macos_video (1.0.4):
- FlutterMacOS
- media_kit_video (0.0.1):
- FlutterMacOS
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- ogg_opus_player (0.0.1):
- Flutter
- FlutterMacOS
- OrderedSet (6.0.3)
- package_info_plus (0.0.1):
- FlutterMacOS
- photo_manager (3.9.0):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.1)
- record_macos (1.2.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- FlutterMacOS
- WebRTC-SDK (144.7559.09)
DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- dynamic_color (from `Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`)
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
- ogg_opus_player (from `Flutter/ephemeral/.symlinks/plugins/ogg_opus_player/darwin`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- photo_manager (from `Flutter/ephemeral/.symlinks/plugins/photo_manager/darwin`)
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- GoogleDataTransport
- GoogleUtilities
- nanopb
- OrderedSet
- PromisesObjC
- WebRTC-SDK
EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
dynamic_color:
:path: Flutter/ephemeral/.symlinks/plugins/dynamic_color/macos
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
firebase_core:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
firebase_messaging:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
flutter_inappwebview_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
flutter_local_notifications:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
flutter_secure_storage_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin
flutter_timezone:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos
flutter_webrtc:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos
FlutterMacOS:
:path: Flutter/ephemeral
geolocator_apple:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
mobile_scanner:
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin
ogg_opus_player:
:path: Flutter/ephemeral/.symlinks/plugins/ogg_opus_player/darwin
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
photo_manager:
:path: Flutter/ephemeral/.symlinks/plugins/photo_manager/darwin
record_macos:
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
video_player_avfoundation:
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
wakelock_plus:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
SPEC CHECKSUMS:
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
dynamic_color: 5fdff3953fb3457311091863f72914fc76ea3209
flutter_secure_storage_darwin: 557817588b80e60213cbecb573c45c76b788018d
flutter_timezone: b3bc0c587d8780d395651284a1ff46eb1e5753ac
app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
dynamic_color: cb7c2a300ee67ed3bd96c3e852df3af0300bf610
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
Firebase: a8539b633d474fbeb654c7043f9c1649e274045b
firebase_core: 4922bf7ac16c97fb67008a1a00842059b345b0d9
firebase_messaging: 6c506190a7499e67e644957961f86efe1ac1c3e3
FirebaseCore: 2e86a4ea1684d4381707069e4a6d89ac808e901e
FirebaseCoreInternal: 6ab6a02c94446c026d2cf35cf5383842ebaa4992
FirebaseInstallations: eb29ccbf64eaedf86fd5b2ccc7fabde567660b52
FirebaseMessaging: 40017d7bc8457ee295b0f41d480a80fdabc9994e
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_local_notifications: 1fc7ffb10a83d6a2eeeeddb152d43f1944b0aad0
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
flutter_timezone: d272288c69082ad571630e0d17140b3d6b93dc0c
flutter_webrtc: fec3dc9284a1f6d21ec2caa91c90bb4c1824eb04
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
ogg_opus_player: 954784304f4e2722780018c4abf284e4f93cddf5
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273
record_macos: 5d55909f9650314be6424ffd6b123ac75a08c3c1
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
WebRTC-SDK: e6006119cd730d6315d875e4a421b6cc8bb88833
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
+18
View File
@@ -241,6 +241,7 @@
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
AAC6FCD26EB13D7261C78A3C /* [CP] Embed Pods Frameworks */,
BF4E11513C18E180EA08A66C /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -400,6 +401,23 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
BF4E11513C18E180EA08A66C /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
C78DF3AD05984D1177E03977 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 B

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

-2
View File
@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
-2
View File
@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.audio-input</key>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="Komet"
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APP_PATH="$PROJECT_ROOT/build/macos/Build/Products/Release/$APP_NAME.app"
VERSION="$(grep '^version:' "$PROJECT_ROOT/pubspec.yaml" | sed 's/version: *//' | cut -d'+' -f1)"
DIST_DIR="$PROJECT_ROOT/dist"
DMG_PATH="$DIST_DIR/$APP_NAME-$VERSION.dmg"
echo "==> Building release macOS app"
cd "$PROJECT_ROOT"
flutter pub get
"$PROJECT_ROOT/scripts/fix_opus_macos.sh"
flutter build macos --release
if [[ ! -d "$APP_PATH" ]]; then
echo "error: $APP_PATH not found after build" >&2
exit 1
fi
echo "==> Verifying ad-hoc signature (Xcode signs with CODE_SIGN_IDENTITY=- and Release.entitlements)"
codesign --verify --deep --strict "$APP_PATH"
if codesign -d --entitlements :- "$APP_PATH" 2>/dev/null | grep -q "com.apple.security.app-sandbox"; then
echo "error: app-sandbox is enabled; flutter_secure_storage keychain will fail (-34018) without a Developer Team" >&2
exit 1
fi
mkdir -p "$DIST_DIR"
rm -f "$DMG_PATH"
BACKGROUND="$PROJECT_ROOT/scripts/dmg/background.png"
if command -v create-dmg >/dev/null 2>&1; then
echo "==> Packaging with create-dmg"
create-dmg \
--volname "$APP_NAME $VERSION" \
--background "$BACKGROUND" \
--window-pos 200 120 \
--window-size 640 400 \
--icon-size 128 \
--text-size 13 \
--icon "$APP_NAME.app" 170 190 \
--app-drop-link 470 190 \
--hide-extension "$APP_NAME.app" \
--no-internet-enable \
"$DMG_PATH" \
"$APP_PATH"
else
echo "==> create-dmg not found, packaging with hdiutil"
STAGING="$(mktemp -d)"
cp -R "$APP_PATH" "$STAGING/"
ln -s /Applications "$STAGING/Applications"
hdiutil create \
-volname "$APP_NAME $VERSION" \
-srcfolder "$STAGING" \
-ov -format UDZO \
"$DMG_PATH"
rm -rf "$STAGING"
fi
echo "==> Done: $DMG_PATH"
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
# Replaces the broken bitcode-only opus/ogg static libraries shipped by
# ogg_opus_player with real machine-code universal libraries vendored in
# macos/prebuilt/opus. Run after `flutter pub get`, before building macOS.
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="$PROJECT_ROOT/macos/prebuilt/opus"
PKG_CFG="$PROJECT_ROOT/.dart_tool/package_config.json"
LIBS=""
if [ -f "$PKG_CFG" ] && command -v python3 >/dev/null 2>&1; then
ROOT="$(python3 - "$PKG_CFG" <<'PY'
import json, sys, urllib.parse
data = json.load(open(sys.argv[1]))
for pkg in data.get("packages", []):
if pkg["name"] == "ogg_opus_player":
print(urllib.parse.unquote(urllib.parse.urlparse(pkg["rootUri"]).path))
break
PY
)"
if [ -n "$ROOT" ]; then
case "$ROOT" in
/*) PKGDIR="$ROOT" ;;
*) PKGDIR="$(cd "$PROJECT_ROOT/.dart_tool/$ROOT" && pwd)" ;;
esac
LIBS="$PKGDIR/darwin/Libs"
fi
fi
if [ -z "$LIBS" ] || [ ! -d "$LIBS" ]; then
for cand in "${PUB_CACHE:-$HOME/.pub-cache}/hosted"/*/ogg_opus_player-*/darwin/Libs; do
[ -d "$cand" ] && LIBS="$cand" && break
done
fi
if [ -z "$LIBS" ] || [ ! -d "$LIBS" ]; then
echo "warning: ogg_opus_player macOS Libs dir not found; skipping opus fix" >&2
exit 0
fi
echo "==> Installing prebuilt opus libs into: $LIBS"
for f in libogg.a libopus.a libopusfile.a libopusenc.a; do
cp -f "$SRC/$f" "$LIBS/$f"
echo " installed $f ($(lipo -archs "$LIBS/$f" 2>/dev/null))"
done
echo "==> opus libs fixed"
+32
View File
@@ -0,0 +1,32 @@
import 'dart:io';
import 'package:image/image.dart' as img;
void main() {
const w = 1280;
const h = 800;
final bg = img.Image(width: w, height: h, numChannels: 4);
final topR = 251, topG = 251, topB = 253;
final botR = 233, botG = 233, botB = 237;
for (var y = 0; y < h; y++) {
final t = y / (h - 1);
final r = (topR + (botR - topR) * t).round();
final g = (topG + (botG - topG) * t).round();
final b = (topB + (botB - topB) * t).round();
img.fillRect(bg, x1: 0, y1: y, x2: w - 1, y2: y, color: img.ColorRgb8(r, g, b));
}
final green = img.ColorRgb8(124, 191, 64);
const ay = 380;
img.fillRect(bg, x1: 548, y1: ay - 9, x2: 704, y2: ay + 9, color: green);
img.fillPolygon(bg, vertices: [
img.Point(700, ay - 30),
img.Point(700, ay + 30),
img.Point(752, ay),
], color: green);
final outDir = Directory('scripts/dmg');
outDir.createSync(recursive: true);
File('scripts/dmg/background.png').writeAsBytesSync(img.encodePng(bg));
stdout.writeln('wrote scripts/dmg/background.png (${w}x$h)');
}
+62
View File
@@ -0,0 +1,62 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:image/image.dart' as img;
const _dir = 'macos/Runner/Assets.xcassets/AppIcon.appiconset';
const _sizes = {
'app_icon_16.png': 16,
'app_icon_32.png': 32,
'app_icon_64.png': 64,
'app_icon_128.png': 128,
'app_icon_256.png': 256,
'app_icon_512.png': 512,
'app_icon_1024.png': 1024,
};
void main() {
final source = img.decodePng(File('assets/meteor_icon.png').readAsBytesSync()) ??
img.decodePng(File('$_dir/app_icon_1024.png').readAsBytesSync())!;
const canvas = 1024;
const content = 824;
const margin = (canvas - content) / 2;
const radius = 184.0;
final body = img.copyResize(source,
width: content, height: content, interpolation: img.Interpolation.cubic);
final master = img.Image(width: canvas, height: canvas, numChannels: 4);
img.compositeImage(master, body, dstX: margin.toInt(), dstY: margin.toInt());
_applyRoundedMask(master, margin, margin, margin + content, margin + content, radius);
for (final entry in _sizes.entries) {
final out = img.copyResize(master,
width: entry.value,
height: entry.value,
interpolation: img.Interpolation.cubic);
File('$_dir/${entry.key}').writeAsBytesSync(img.encodePng(out));
stdout.writeln('wrote ${entry.key} (${entry.value}x${entry.value})');
}
}
void _applyRoundedMask(
img.Image im, double x0, double y0, double x1, double y1, double r) {
final cx = (x0 + x1) / 2;
final cy = (y0 + y1) / 2;
final hw = (x1 - x0) / 2;
final hh = (y1 - y0) / 2;
for (final p in im) {
final qx = (p.x + 0.5 - cx).abs() - (hw - r);
final qy = (p.y + 0.5 - cy).abs() - (hh - r);
final dx = math.max(qx, 0.0);
final dy = math.max(qy, 0.0);
final dist =
math.sqrt(dx * dx + dy * dy) + math.min(math.max(qx, qy), 0.0) - r;
final coverage = (0.5 - dist).clamp(0.0, 1.0);
if (coverage < 1.0) {
p.a = (p.a * coverage).round().clamp(0, 255);
}
}
}