Replace the stock Flutter launcher icon, add a Linux desktop icon
Adopt the shared icon family (python_pkg/app_icons in testsAndMisc): a charcoal #1B1D21 field carrying a deep-purple checklist glyph, deep purple being this app's own theme seedColor. Two ticked-off rows rather than a lone checkmark, so it reads as a notes app and not as a generic "done" badge. Android gains the adaptive-icon layers the project never had, including an Android 13 monochrome layer. Linux needs separate handling: Flutter's GTK runner never sets a window icon, so my_application.cc now calls gtk_window_set_icon_name and install_desktop_entry.sh populates the hicolor theme from the pre-rendered PNGs in linux/icons (committed, so the installer needs no image tooling). StartupWMClass is the APPLICATION_ID rather than the binary name - verified with xprop against a running window, where WM_CLASS is the GApplication id. No Dart code changes, so test coverage is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
14
android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
<monochrome>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_monochrome"
|
||||
android:inset="16%" />
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 789 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
4
android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#1B1D21</color>
|
||||
</resources>
|
||||
BIN
assets/icon/icon.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
9
assets/icon/icon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<rect width="1024" height="1024" fill="#1B1D21"/>
|
||||
<g fill="none" stroke="#7E57C2" stroke-width="72" stroke-linecap="round" stroke-linejoin="round" transform="translate(0,8)">
|
||||
<path d="M 272 372 L 346 446 L 478 314"/>
|
||||
<path d="M 594 400 L 752 400"/>
|
||||
<path d="M 272 620 L 346 694 L 478 562"/>
|
||||
<path d="M 594 648 L 752 648"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 449 B |
BIN
assets/icon/icon_foreground.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
8
assets/icon/icon_foreground.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<g fill="none" stroke="#7E57C2" stroke-width="72" stroke-linecap="round" stroke-linejoin="round" transform="translate(0,8)">
|
||||
<path d="M 272 372 L 346 446 L 478 314"/>
|
||||
<path d="M 594 400 L 752 400"/>
|
||||
<path d="M 272 620 L 346 694 L 478 562"/>
|
||||
<path d="M 594 648 L 752 648"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
BIN
assets/icon/icon_monochrome.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
8
assets/icon/icon_monochrome.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<g fill="none" stroke="#FFFFFF" stroke-width="72" stroke-linecap="round" stroke-linejoin="round" transform="translate(0,8)">
|
||||
<path d="M 272 372 L 346 446 L 478 314"/>
|
||||
<path d="M 594 400 L 752 400"/>
|
||||
<path d="M 272 620 L 346 694 L 478 562"/>
|
||||
<path d="M 594 648 L 752 648"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
BIN
linux/icons/128/todo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
linux/icons/16/todo.png
Normal file
|
After Width: | Height: | Size: 308 B |
BIN
linux/icons/24/todo.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
linux/icons/256/todo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
linux/icons/32/todo.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
linux/icons/48/todo.png
Normal file
|
After Width: | Height: | Size: 788 B |
BIN
linux/icons/512/todo.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
linux/icons/64/todo.png
Normal file
|
After Width: | Height: | Size: 1023 B |
118
linux/install_desktop_entry.sh
Executable file
@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Install the todo launcher icon and .desktop entry for the Linux desktop.
|
||||
#
|
||||
# Flutter's GTK runner has no icon of its own: my_application.cc calls
|
||||
# gtk_window_set_icon_name("todo"), which resolves through the hicolor icon
|
||||
# theme. This script is what puts the icon into that theme, plus a .desktop
|
||||
# entry so the app appears in application menus.
|
||||
#
|
||||
# Icon PNGs are pre-rendered and committed under linux/icons/, so this script
|
||||
# needs no image tooling at run time. Regenerate them with:
|
||||
# PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons \
|
||||
# generate --app todo --linux-out linux/icons
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
LINUX_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_DIR="$(dirname "$LINUX_DIR")"
|
||||
readonly SCRIPT_NAME LINUX_DIR REPO_DIR
|
||||
readonly ICON_NAME="todo"
|
||||
readonly BINARY_NAME="todo"
|
||||
# GTK derives WM_CLASS from the GApplication id in linux/CMakeLists.txt,
|
||||
# not from the binary name; verified with xprop on the running window.
|
||||
readonly WM_CLASS="dev.kuhy.todo"
|
||||
readonly ICON_SRC_DIR="$LINUX_DIR/icons"
|
||||
readonly ICON_THEME_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor"
|
||||
readonly DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
||||
readonly BUNDLE_BIN="$REPO_DIR/build/linux/x64/release/bundle/$BINARY_NAME"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $SCRIPT_NAME"
|
||||
echo "Installs the todo icon into the hicolor theme and writes a .desktop entry."
|
||||
exit 0
|
||||
}
|
||||
|
||||
validate_requirements() {
|
||||
if [[ ! -d "$ICON_SRC_DIR" ]]; then
|
||||
echo "Error: $ICON_SRC_DIR is missing; regenerate the icons first" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_theme_index() {
|
||||
# A user-local hicolor tree created from scratch has no index.theme, and
|
||||
# without it gtk-update-icon-cache refuses to build a cache ("No theme
|
||||
# index file"). Seed it from the system theme when one is available.
|
||||
local system_index="/usr/share/icons/hicolor/index.theme"
|
||||
if [[ ! -f "$ICON_THEME_DIR/index.theme" && -f "$system_index" ]]; then
|
||||
install -Dm644 "$system_index" "$ICON_THEME_DIR/index.theme"
|
||||
fi
|
||||
}
|
||||
|
||||
install_icons() {
|
||||
local size_dir size icon
|
||||
for size_dir in "$ICON_SRC_DIR"/*/; do
|
||||
size="$(basename "$size_dir")"
|
||||
icon="$size_dir$ICON_NAME.png"
|
||||
[[ -f "$icon" ]] || continue
|
||||
install -Dm644 "$icon" \
|
||||
"$ICON_THEME_DIR/${size}x${size}/apps/$ICON_NAME.png"
|
||||
done
|
||||
# Refresh the theme cache so GTK picks the icon up without a re-login.
|
||||
# Harmless if the tool is absent: the icon still resolves, just slower.
|
||||
if command -v gtk-update-icon-cache >/dev/null; then
|
||||
gtk-update-icon-cache --quiet --force "$ICON_THEME_DIR" || true
|
||||
fi
|
||||
}
|
||||
|
||||
install_desktop_entry() {
|
||||
mkdir -p "$DESKTOP_DIR"
|
||||
# StartupWMClass must match the window's actual WM class, or the taskbar
|
||||
# shows a generic icon for the running window even though the launcher
|
||||
# entry itself looks right.
|
||||
cat > "$DESKTOP_DIR/$ICON_NAME.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=todo
|
||||
Comment=Offline-first, CRDT-synced notes
|
||||
Exec=$BUNDLE_BIN
|
||||
Icon=$ICON_NAME
|
||||
Terminal=false
|
||||
Categories=Utility;TextEditor;
|
||||
StartupWMClass=$WM_CLASS
|
||||
EOF
|
||||
if command -v update-desktop-database >/dev/null; then
|
||||
update-desktop-database "$DESKTOP_DIR" || true
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
validate_requirements
|
||||
ensure_theme_index
|
||||
install_icons
|
||||
install_desktop_entry
|
||||
echo "Installed $ICON_NAME icon into $ICON_THEME_DIR"
|
||||
echo "Installed $DESKTOP_DIR/$ICON_NAME.desktop"
|
||||
if [[ ! -x "$BUNDLE_BIN" ]]; then
|
||||
echo "Note: $BUNDLE_BIN does not exist yet."
|
||||
echo " Run 'flutter build linux --release' to make the entry launchable."
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main "$@"
|
||||
@ -54,6 +54,11 @@ static void my_application_activate(GApplication* application) {
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
// Resolved from the hicolor icon theme, which linux/install_desktop_entry.sh
|
||||
// populates from linux/icons/. Without this the GTK runner shows a generic
|
||||
// placeholder in the taskbar, since Flutter never sets a window icon itself.
|
||||
gtk_window_set_icon_name(window, "todo");
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(
|
||||
project, self->dart_entrypoint_arguments);
|
||||
|
||||
72
pubspec.lock
@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -41,6 +49,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -223,6 +247,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -321,6 +353,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.1"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -337,6 +377,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -489,6 +537,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -513,6 +569,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
process_run:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -846,6 +910,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
15
pubspec.yaml
@ -57,6 +57,9 @@ dependencies:
|
||||
# libsecret) instead of plaintext SharedPreferences.
|
||||
flutter_secure_storage: ^10.3.1
|
||||
dev_dependencies:
|
||||
# Generates the Android launcher icons from assets/icon/ (see
|
||||
# python_pkg/app_icons in testsAndMisc for the shared style).
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@ -117,3 +120,15 @@ flutter:
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
|
||||
# Launcher icon generation. Source art is produced by the shared generator:
|
||||
# PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --app <key>
|
||||
# then run: dart run flutter_launcher_icons
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: false
|
||||
image_path: "assets/icon/icon.png"
|
||||
adaptive_icon_background: "#1B1D21"
|
||||
adaptive_icon_foreground: "assets/icon/icon_foreground.png"
|
||||
adaptive_icon_monochrome: "assets/icon/icon_monochrome.png"
|
||||
min_sdk_android: 21
|
||||
|
||||