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:
co-authored by
Claude Opus 4.8
parent
c800cc0b32
commit
0f4844bf80
@@ -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)');
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user