Remove launcher lettering and fit IKAR mark to circular icons
@@ -28,13 +28,17 @@
|
||||
|
||||
Проверки: полный клиентский прогон — 1189 прошли, 3 пропущены; сервер — 150 прошли, без пропусков. Анализ `lib` и `test` не выявил ошибок и предупреждений, остались 42 информационных замечания стиля. Проверены выбор качества видео, сохранение темы, доступ к расшифрованному тексту, жесты, переключение невидимости, переподключение и сохранение публичной активности при обновлении схемы. Локальные отчёты: `artifacts/komet-full-flutter-tests.log`, `artifacts/komet-server-tests.log`, `artifacts/komet-implementation-analyze.log`.
|
||||
|
||||
Эти изменения относятся к исходникам клиента и сервера. Для использования на телефоне нужны обновление сервера и сборка нового APK; текущий опубликованный выпуск их не содержит. Новый APK, развёртывание и проверка этих изменений на телефоне в этой задаче не выполнялись.
|
||||
Рабочий API обновлён до `releases/20261007-komet`. Проверены совпадение SHA-256 серверной сборки, целостность рабочей базы и резервной копии, новая колонка публичной активности, `/health`, отсутствие перезапусков API и сохранность соседних контейнеров. Резервная копия перед обновлением: `/mnt/data/ikar_server/backups/ikar-20261007T191617Z.tar.gz`; предыдущий выпуск сохранён для отката. Через публичный HTTPS прошли 15 общих проверок API, 8 проверок невидимости и 5 проверок видео. Они охватывают регистрацию, доставку и историю, права доступа, файлы, речь на Pi, группы, каналы, реакции, опросы, отложенную отправку и сигнализацию звонков. Невидимая сессия получает сообщения и приглашения звонка, не меняя публичное время активности. Отчёты: `artifacts/deployment-komet.json`, `artifacts/ghost-live-verification.json`, `artifacts/live-smoke.json`, `artifacts/video-live-verification.json`.
|
||||
|
||||
Главная иконка содержит только рисунок Икара без надписей. Размер рисунка — около 90% диаметра круглой области; адаптивный слой учитывает расширение Android с 72 до 108 dp. Исходный рисунок — `assets/ikar_mark.png`, основные ресурсы — `assets/ikar_icon.png` и `assets/ikar.png`. Пересоздание: `dart run tool/make_ikar_icons.dart`, затем `dart run flutter_launcher_icons`. Ресурсы основной иконки обновлены для Android, iOS, Windows и macOS; альтернативная иконка сохранена.
|
||||
|
||||
Новая иконка и клиентские изменения требуют нового APK; текущий опубликованный выпуск их не содержит. Выпуск APK и проверка этих клиентских изменений на телефоне в этой задаче не выполнялись.
|
||||
|
||||
## Размещение на Pi
|
||||
|
||||
Внешний диск: `/mnt/data` (`/dev/sdb1`, ext4). Корень новой установки: `/mnt/data/ikar_server`.
|
||||
|
||||
- `current` — ссылка на действующий выпуск в `releases/20261007-video`;
|
||||
- `current` — ссылка на действующий выпуск в `releases/20261007-komet`;
|
||||
- `shared/data` — SQLite, вложения, данные устройств и публикация APK;
|
||||
- `shared/server.env`, `shared/turn.env` — закрытые настройки, доступ 0600;
|
||||
- `backups` — ежедневные согласованные копии SQLite и вложений, хранение 14 дней;
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 37 KiB |
@@ -4,6 +4,6 @@
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
android:inset="0%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
<color name="ic_launcher_minimal_background">#000000</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 650 KiB |
@@ -0,0 +1,8 @@
|
||||
Mode: built-in image_gen imagegen tool.
|
||||
Edit targets: original assets/ikar_icon.png and assets/ikar.png.
|
||||
Generated source: assets/ikar_mark.png.
|
||||
|
||||
Use case: precise-object-edit / background-extraction. Edit target: image 1 is the existing IKAR app emblem, image 2 shows the same existing emblem with text that must be removed. Produce one clean square PNG launcher foreground with real transparent background. Keep ONLY the existing illustrated winged Icarus figure and its two chat bubbles and golden sun, exactly matching the existing shapes, pose, colors and visual identity. Remove ALL text including ИКАР, МЕССЕНДЖЕР, underlines, sparkle, and remove the near-white background and the ground/drop shadow. Do not redesign, do not add objects, do not turn chat bubbles into text. Center the complete emblem as a single composition, scale it to occupy approximately 90 percent of a circle centered in the square: entire emblem must fit fully inside a circle of radius 45 percent of canvas side, with nothing clipped. Preserve the cyan/blue wings, golden wing and sun, white bubble interiors and dotted bubble details. Output is just the cleaned icon art on genuine alpha transparency; no circle outline, no mockup, no labels, no watermark.
|
||||
|
||||
Launcher packaging: tool/make_ikar_icons.dart scales and centers the generated artwork for a 90 percent circular footprint. Android adaptive foreground uses a 72/108 viewport ratio and 0 percent additional XML inset. Background is white.
|
||||
Android layer reference: https://developer.android.com/develop/ui/compose/system/icon_design_adaptive
|
||||
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 499 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -140,9 +140,10 @@ flutter_launcher_icons:
|
||||
android: "ic_launcher"
|
||||
ios: true
|
||||
remove_alpha_ios: true
|
||||
background_color_ios: "#000000"
|
||||
adaptive_icon_background: "#000000"
|
||||
background_color_ios: "#FFFFFF"
|
||||
adaptive_icon_background: "#FFFFFF"
|
||||
adaptive_icon_foreground: "assets/ikar.png"
|
||||
adaptive_icon_foreground_inset: 0
|
||||
windows:
|
||||
generate: true
|
||||
image_path: "assets/ikar_icon.png"
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
const _size = 1024;
|
||||
const _coverage = 0.90;
|
||||
|
||||
void main() {
|
||||
final source = img.decodePng(File('assets/ikar_mark.png').readAsBytesSync())!;
|
||||
var left = source.width;
|
||||
var top = source.height;
|
||||
var right = 0;
|
||||
var bottom = 0;
|
||||
for (final pixel in source) {
|
||||
if (pixel.a < 128) continue;
|
||||
left = math.min(left, pixel.x);
|
||||
top = math.min(top, pixel.y);
|
||||
right = math.max(right, pixel.x);
|
||||
bottom = math.max(bottom, pixel.y);
|
||||
}
|
||||
final artwork = img.copyCrop(
|
||||
source,
|
||||
x: left,
|
||||
y: top,
|
||||
width: right - left + 1,
|
||||
height: bottom - top + 1,
|
||||
);
|
||||
var radius = 0.0;
|
||||
for (final pixel in artwork) {
|
||||
if (pixel.a < 128) continue;
|
||||
final dx = pixel.x + 0.5 - artwork.width / 2;
|
||||
final dy = pixel.y + 0.5 - artwork.height / 2;
|
||||
radius = math.max(radius, math.sqrt(dx * dx + dy * dy));
|
||||
}
|
||||
|
||||
img.Image render(double viewportFraction, {required bool opaque}) {
|
||||
final targetRadius = _size * viewportFraction * _coverage / 2 - 1;
|
||||
final scale = targetRadius / radius;
|
||||
final scaled = img.copyResize(
|
||||
artwork,
|
||||
width: (artwork.width * scale).round(),
|
||||
height: (artwork.height * scale).round(),
|
||||
interpolation: img.Interpolation.cubic,
|
||||
);
|
||||
final canvas = img.Image(width: _size, height: _size, numChannels: 4);
|
||||
if (opaque) img.fill(canvas, color: img.ColorRgba8(255, 255, 255, 255));
|
||||
img.compositeImage(
|
||||
canvas,
|
||||
scaled,
|
||||
dstX: (_size - scaled.width) ~/ 2,
|
||||
dstY: (_size - scaled.height) ~/ 2,
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
final icon = render(1, opaque: true);
|
||||
final foreground = render(72 / 108, opaque: false);
|
||||
File('assets/ikar_icon.png').writeAsBytesSync(img.encodePng(icon));
|
||||
File('assets/ikar.png').writeAsBytesSync(img.encodePng(foreground));
|
||||
Directory('artifacts').createSync(recursive: true);
|
||||
final preview = img.copyResize(icon, width: 512, height: 512);
|
||||
for (final pixel in preview) {
|
||||
final dx = pixel.x + 0.5 - 256;
|
||||
final dy = pixel.y + 0.5 - 256;
|
||||
if (dx * dx + dy * dy > 256 * 256) pixel.a = 0;
|
||||
}
|
||||
File(
|
||||
'artifacts/ikar-icon-circle.png',
|
||||
).writeAsBytesSync(img.encodePng(preview));
|
||||
var actualRadius = 0.0;
|
||||
for (final pixel in foreground) {
|
||||
if (pixel.a < 128) continue;
|
||||
final dx = pixel.x + 0.5 - _size / 2;
|
||||
final dy = pixel.y + 0.5 - _size / 2;
|
||||
actualRadius = math.max(actualRadius, math.sqrt(dx * dx + dy * dy));
|
||||
}
|
||||
final actualCoverage = actualRadius / (_size * (72 / 108) / 2);
|
||||
if ((actualCoverage - _coverage).abs() > 0.01) {
|
||||
throw StateError('Unexpected circular coverage: $actualCoverage');
|
||||
}
|
||||
stdout.writeln(
|
||||
'Circular artwork coverage: ${(actualCoverage * 100).toStringAsFixed(1)}%',
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 27 KiB |