# The Linux app as a Snap, built in LXD on a developer's machine, in the repository # root: # # snapcraft pack # # It builds what is committed (the source is this repository's HEAD), never the # working tree with its build caches, and always for learn.halkyon.eu. Flutter is # pinned like the local and CI one (FLUTTER_VERSION in .forgejo/workflows/ci.yaml): # snapcraft's own Flutter plugin can only take a channel and can't pass # --dart-define, so the build is spelled out below. name: halkyon-learn title: Halkyon Learn base: core24 adopt-info: halkyon-learn # the version, from the app's pubspec.yaml summary: Learn with flashcards, studied offline and synced between your devices description: | Halkyon Learn is a spaced-repetition app for your own material: studysets of flashcards, notes, learning paths and PDFs, reviewed on an FSRS schedule. Every studyset is made by its learners: build yours for any subject, or copy one that someone published. It works offline and syncs between your devices. Learn from what you watch: play films and shows from your Jellyfin server, and every subtitle line is split into dictionary words, with readings above and the translation below. Tap a word, and it is a flashcard. Dictionaries for Japanese, Chinese, Korean, German, Greek and English; kanji, hanzi and Hangul written by hand, each stroke checked; exam plans for the JLPT, HSK and Korean levels, with a daily pace to your exam's date; and studysets published to Explore and copied, later releases coming in as merges that keep your own edits. It starts with an anonymous account; signing in with GitHub or an email address later keeps everything. It connects to learn.halkyon.eu, the project's instance, or to any other: everything an instance needs is in the repository, to run your own. The app is AGPL-3.0 and the server SSPL-1.0, so all of its code is public. Questions and reports: support@halkyon.app license: AGPL-3.0-or-later source-code: https://code.halkyon.app/project-halkyon/halkyon # The project's, not an instance's: the app works with any instance. website: https://halkyon.app/learn # No contact or issues: the Store refuses an upload whose links aren't http(s) URLs # ("Links must be http(s):// URLs", checked with snapcraft v9.1.4 on 2026-10-14), # though snapcraft's reference allows mail addresses there (canonical/snapcraft#6062 # asks for them). Reports come by mail until people can open an account on the issue # tracker, so the address is in the description. # The app's icon with rounded corners, made by tool/app_icons.py; also the desktop file's. icon: snap/gui/halkyon-learn.png grade: stable confinement: strict platforms: amd64: lint: ignore: # PDFium is loaded by name while the app runs, which the linter can't see. - library: - bin/lib/libpdfium.so apps: halkyon-learn: command: bin/halkyon_learn_flutter # How the session is kept: see snap/local/secret-backend. command-chain: [bin/secret-backend] # GTK, WebKitGTK, GStreamer and libsecret come from the GNOME runtime; files # and the browser go through the desktop's portals. extensions: [gnome] plugs: - network # GitHub sign-in answers on a loopback address the app listens on. - network-bind - audio-playback # Never connected by itself; whoever connects it by hand gets the keyring. - password-manager-service # Lets Serverpod hear from NetworkManager that the network went, and end # the sync's connection at once rather than after 40 s of silence. Not # connected by itself until the Store grants it on a forum request; # without it the app logs the refusal once and works on. - network-manager-observe parts: flutter: plugin: nil source: https://github.com/flutter/flutter.git source-tag: '3.47.5' source-depth: 1 build-packages: - clang - cmake - curl - git - libgtk-3-dev - lld - llvm-18 - ninja-build - pkg-config - unzip - xz-utils - zip override-build: | mkdir -p "$CRAFT_PART_INSTALL/usr/libexec" "$CRAFT_PART_INSTALL/usr/bin" cp -r "$CRAFT_PART_SRC" "$CRAFT_PART_INSTALL/usr/libexec/flutter" ln -s ../libexec/flutter/bin/flutter "$CRAFT_PART_INSTALL/usr/bin/flutter" ln -s ../libexec/flutter/bin/dart "$CRAFT_PART_INSTALL/usr/bin/dart" "$CRAFT_PART_INSTALL/usr/bin/flutter" --disable-analytics "$CRAFT_PART_INSTALL/usr/bin/flutter" precache --linux # Needed to build, not in the Snap. override-prime: '' halkyon-learn: after: [flutter] plugin: nil source: . source-type: git # The sound player links GStreamer, which the GNOME SDK lacks; the GNOME # runtime brings the same GStreamer (1.24) to run. The video player # (media_kit, for Jellyfin) links libmpv, which neither has: the Snap # brings Ubuntu's libmpv2 with what it needs (ffmpeg's libraries), or the # app wouldn't start. build-packages: - libgstreamer1.0-dev - libgstreamer-plugins-base1.0-dev - libmpv-dev stage-packages: - libmpv2 override-build: | set -eu export PATH="$CRAFT_STAGE/usr/bin:$PATH" cd apps/learn/halkyon_learn_flutter craftctl set version="$(sed -n 's/^version: *\([^+]*\).*/\1/p' pubspec.yaml)" flutter pub get --enforce-lockfile flutter build linux --release \ --dart-define=SERVER_URL=https://learn.halkyon.eu/ mkdir -p "$CRAFT_PART_INSTALL/bin" cp -r build/linux/x64/release/bundle/. "$CRAFT_PART_INSTALL/bin/" # ffmpeg's speech recognition (libsphinxbase) links libblas.so.3 and # liblapack.so.3, which Ubuntu keeps in blas/ and lapack/ and links into # the library folder from its packages' install scripts # (update-alternatives), which staging never runs: without these links # the app doesn't start. The linter doesn't notice, as the build # machine has them. lib="$CRAFT_PART_INSTALL/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR" mkdir -p "$lib" ln -sf blas/libblas.so.3 "$lib/libblas.so.3" ln -sf lapack/liblapack.so.3 "$lib/liblapack.so.3" secret-backend: plugin: dump source: snap/local organize: secret-backend: bin/secret-backend # Staged packages bring their whole dependency tree, also what the GNOME # runtime and the base hold already: libmpv's ffmpeg brought Ubuntu's older # librsvg, cairo, pango, harfbuzz and gdk-pixbuf, which came first on the # library path and hid the runtime's newer ones from its GTK (its SVG # loader failed for a function only the runtime's librsvg has). So every # file the runtime or the base has at the same path goes, as the GNOME # extension's reference shows, but only under usr/ and never a directory: # its example removes every path the base has, and core24's bin and lib are # links, so it would take the app's bin/ with them. blas/ and lapack/ stay: # the runtime has them too, but the links into them are the app's own. cleanup: after: [halkyon-learn, secret-backend] plugin: nil build-snaps: [core24, gnome-46-2404] override-prime: | set -eu for snap in core24 gnome-46-2404; do cd "/snap/$snap/current/usr" find . \( -type f -o -type l \) -print0 | while IFS= read -r -d '' path; do case "$path" in */blas/* | */lapack/*) continue ;; esac target="$CRAFT_PRIME/usr/$path" if [ -L "$target" ] || [ -f "$target" ]; then rm -f "$target" fi done done