From 3e4bacb7b939b1de98a2fb07115f638750637357 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 8 May 2026 00:05:39 +0300 Subject: Rewrite as Flutter app and rename to Quicklog Drop the Go/Fyne implementation and replace it with a native Flutter app targeting Android (primary) and Linux desktop (development). Rename quicklogger -> quicklog throughout (project, applicationId, MethodChannel, cache filename, AppBar title, Linux binary). Storage on Android moves to /Android/data/org.buetow.quicklog/files/ so MANAGE_EXTERNAL_STORAGE is no longer required. Share-intent receive is preserved via a Kotlin ShareActivity that writes shared text to the app cache, which Dart reads through a MethodChannel on app resume; MainActivity also accepts SEND directly so launchers that prefer the main target work. New since the Fyne version: an entry browser screen with read-only viewer and long-press delete. Cross-compiling to Android ARM from amd64 Linux no longer needs Docker / fyne-cross / NDK -- flutter build apk emits ARM binaries directly. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 37 +- .metadata | 33 + FyneApp.toml | 8 - Magefile.go | 201 ------- README.md | 119 ++-- analysis_options.yaml | 28 + android/.gitignore | 14 + android/AndroidManifest.xml | 36 -- android/app/build.gradle.kts | 44 ++ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 56 ++ .../kotlin/org/buetow/quicklog/MainActivity.kt | 56 ++ .../kotlin/org/buetow/quicklog/ShareActivity.kt | 54 ++ .../res/drawable-hdpi/ic_launcher_foreground.png | Bin 0 -> 26129 bytes .../res/drawable-mdpi/ic_launcher_foreground.png | Bin 0 -> 15260 bytes .../main/res/drawable-v21/launch_background.xml | 12 + .../res/drawable-xhdpi/ic_launcher_foreground.png | Bin 0 -> 38865 bytes .../res/drawable-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 67448 bytes .../drawable-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 98799 bytes .../src/main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 9 + .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 9013 bytes .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 5127 bytes .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 13086 bytes .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 22244 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 32096 bytes android/app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/colors.xml | 4 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle.kts | 24 + android/gradle.properties | 2 + android/gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle.kts | 26 + android_shared_android.go | 26 - android_shared_stub.go | 7 - android_shared_stub_test.go | 51 -- build.sh | 14 - go.mod | 38 -- go.sum | 668 --------------------- lib/main.dart | 30 + lib/screens/entry_browser_screen.dart | 170 ++++++ lib/screens/home_screen.dart | 230 +++++++ lib/screens/preferences_screen.dart | 96 +++ lib/services/log_service.dart | 57 ++ lib/services/preferences.dart | 30 + lib/services/share_service.dart | 25 + lib/services/shared_text_handler.dart | 57 ++ lib/services/storage.dart | 14 + linux/.gitignore | 1 + linux/CMakeLists.txt | 128 ++++ linux/flutter/CMakeLists.txt | 88 +++ linux/flutter/generated_plugin_registrant.cc | 11 + linux/flutter/generated_plugin_registrant.h | 15 + linux/flutter/generated_plugins.cmake | 24 + linux/runner/CMakeLists.txt | 26 + linux/runner/main.cc | 6 + linux/runner/my_application.cc | 148 +++++ linux/runner/my_application.h | 21 + main.go | 246 -------- main_test.go | 261 -------- pubspec.lock | 562 +++++++++++++++++ pubspec.yaml | 35 ++ test/home_screen_widget_test.dart | 44 ++ test/log_service_test.dart | 67 +++ test/shared_text_handler_test.dart | 147 +++++ 66 files changed, 2561 insertions(+), 1612 deletions(-) create mode 100644 .metadata delete mode 100644 FyneApp.toml delete mode 100644 Magefile.go create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore delete mode 100644 android/AndroidManifest.xml create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/org/buetow/quicklog/MainActivity.kt create mode 100644 android/app/src/main/kotlin/org/buetow/quicklog/ShareActivity.kt create mode 100644 android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle.kts delete mode 100644 android_shared_android.go delete mode 100644 android_shared_stub.go delete mode 100644 android_shared_stub_test.go delete mode 100755 build.sh delete mode 100644 go.mod delete mode 100644 go.sum create mode 100644 lib/main.dart create mode 100644 lib/screens/entry_browser_screen.dart create mode 100644 lib/screens/home_screen.dart create mode 100644 lib/screens/preferences_screen.dart create mode 100644 lib/services/log_service.dart create mode 100644 lib/services/preferences.dart create mode 100644 lib/services/share_service.dart create mode 100644 lib/services/shared_text_handler.dart create mode 100644 lib/services/storage.dart create mode 100644 linux/.gitignore create mode 100644 linux/CMakeLists.txt create mode 100644 linux/flutter/CMakeLists.txt create mode 100644 linux/flutter/generated_plugin_registrant.cc create mode 100644 linux/flutter/generated_plugin_registrant.h create mode 100644 linux/flutter/generated_plugins.cmake create mode 100644 linux/runner/CMakeLists.txt create mode 100644 linux/runner/main.cc create mode 100644 linux/runner/my_application.cc create mode 100644 linux/runner/my_application.h delete mode 100644 main.go delete mode 100644 main_test.go create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/home_screen_widget_test.dart create mode 100644 test/log_service_test.dart create mode 100644 test/shared_text_handler_test.dart diff --git a/.gitignore b/.gitignore index 2c12244..a9b77fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,34 @@ -fyne-cross -quicklogger -tmp-pkg +# Dart / Flutter +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +.dart_tool/ +pubspec.lock.bak + +# IDE +.idea/ +*.iml +*.ipr +*.iws +.vscode/ + +# Android +android/.gradle/ +android/local.properties +android/captures/ +android/.cxx/ +android/key.properties +*.jks +*.keystore + +# Linux +linux/flutter/ephemeral/ + +# Misc +.DS_Store +.claude/ +.serena/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..d137bb0 --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + - platform: android + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + - platform: linux + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/FyneApp.toml b/FyneApp.toml deleted file mode 100644 index 2d15de9..0000000 --- a/FyneApp.toml +++ /dev/null @@ -1,8 +0,0 @@ -Website = "https://codeberg.org/snonux/quicklogger" - -[Details] - Icon = "icon.png" - Name = "quicklogger" - ID = "org.buetow.quicklogger" - Version = "0.1.1" - Build = 64 diff --git a/Magefile.go b/Magefile.go deleted file mode 100644 index ef25814..0000000 --- a/Magefile.go +++ /dev/null @@ -1,201 +0,0 @@ -//go:build mage -// +build mage - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "strconv" - "time" - - "github.com/magefile/mage/sh" -) - -// Build compiles the quicklogger app into ./bin. -func Build() error { - outDir := "bin" - if err := os.MkdirAll(outDir, 0o755); err != nil { - return err - } - - binName := "quicklogger" - if runtime.GOOS == "windows" { - binName += ".exe" - } - - out := filepath.Join(outDir, binName) - fmt.Printf("Building %s\n", out) - return sh.RunV("go", "build", "-o", out, ".") -} - -// Run launches the app with `go run .`. -func Run() error { - fmt.Println("Running quicklogger (verbose build)") - // Show Go version and key env for diagnostics - _ = sh.RunV("go", "version") - _ = sh.RunV("go", "env", "GOVERSION", "GOOS", "GOARCH", "GOMOD", "GOMODCACHE", "GOCACHE") - // Compile+run with verbose and trace flags to show build steps - return sh.RunV("go", "run", "-v", "-x", ".") -} - -// Clean removes build artifacts (bin/ and local APK). -func Clean() error { - fmt.Println("Cleaning build artifacts") - // Remove bin directory - if err := os.RemoveAll("bin"); err != nil { - return err - } - // Remove generated APK if present - _ = os.Remove("quicklogger.apk") - return nil -} - -// Android packages an Android APK via Fyne and copies it to ~/Documents/APKs if present. -func Android() error { - env := map[string]string{} - - // Respect existing ANDROID_NDK_HOME. If not set, try legacy var or a common default path. - ndk := os.Getenv("ANDROID_NDK_HOME") - if ndk == "" { - // Some setups export a misspelled var; honor it if present. - ndk = os.Getenv("ANDRIOD_NDK_HOME") - } - if ndk == "" { - if home, err := os.UserHomeDir(); err == nil { - guess := filepath.Join(home, "android-ndk", "android-ndk-r26b") - env["ANDROID_NDK_HOME"] = guess - fmt.Printf("ANDROID_NDK_HOME not set, guessing %s\n", guess) - } - } - - fmt.Println("Packaging Android APK via Fyne") - if err := sh.RunWithV(env, "fyne", "package", "-os", "android"); err != nil { - return err - } - - // If ~/Documents/APKs exists, copy the APK there. - home, err := os.UserHomeDir() - if err != nil { - return nil // packaging succeeded; copying is best-effort - } - destDir := filepath.Join(home, "Documents", "APKs") - if st, err := os.Stat(destDir); err == nil && st.IsDir() { - src := "quicklogger.apk" - if _, err := os.Stat(src); err == nil { - dst := filepath.Join(destDir, filepath.Base(src)) - if err := copyFile(src, dst); err != nil { - return err - } - fmt.Printf("Copied %s to %s\n", src, dst) - } - } - return nil -} - -// AndroidCross builds an Android APK using fyne-cross with Docker/Podman. -// Mirrors steps from README using a Podman socket if available. -func AndroidCross() error { - env := map[string]string{} - - // If DOCKER_HOST is not set, try a sensible Podman socket default. - if os.Getenv("DOCKER_HOST") == "" { - uid := os.Geteuid() - sock := filepath.Join("/run/user", strconv.Itoa(uid), "podman", "podman.sock") - if _, err := os.Stat(sock); err == nil { - env["DOCKER_HOST"] = "unix://" + sock - fmt.Printf("Using Podman socket: %s\n", env["DOCKER_HOST"]) - } else { - fmt.Println("DOCKER_HOST is not set and no Podman socket found; relying on default Docker daemon.") - } - } - - // Ensure fyne-cross is available; attempt install if not. - if _, err := sh.Output("which", "fyne-cross"); err != nil { - fmt.Println("Installing fyne-cross (requires network access)...") - if err := sh.RunV("go", "install", "github.com/fyne-io/fyne-cross@latest"); err != nil { - return fmt.Errorf("fyne-cross not found and installation failed: %w", err) - } - } - - // Pull/update builder image then build. - if err := sh.RunWithV(env, "fyne-cross", "android", "--pull"); err != nil { - return err - } - if err := sh.RunWithV(env, "fyne-cross", "android"); err != nil { - return err - } - - // Copy newest APK to ~/Documents/APKs if available. - apk, err := newestAPK("fyne-cross/dist/android") - if err != nil { - // Not fatal; print a note and finish - fmt.Printf("Note: could not locate built APK: %v\n", err) - return nil - } - home, herr := os.UserHomeDir() - if herr != nil { - return nil - } - destDir := filepath.Join(home, "Documents", "APKs") - if st, err := os.Stat(destDir); err == nil && st.IsDir() { - dst := filepath.Join(destDir, filepath.Base(apk)) - if err := copyFile(apk, dst); err != nil { - return err - } - fmt.Printf("Copied %s to %s\n", apk, dst) - } - return nil -} - -func newestAPK(dir string) (string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return "", err - } - var newest string - var newestMod time.Time - for _, e := range entries { - if e.IsDir() { - continue - } - name := e.Name() - if filepath.Ext(name) != ".apk" { - continue - } - info, err := e.Info() - if err != nil { - continue - } - if info.ModTime().After(newestMod) { - newestMod = info.ModTime() - newest = filepath.Join(dir, name) - } - } - if newest == "" { - return "", fmt.Errorf("no .apk found in %s", dir) - } - return newest, nil -} - -func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer func() { _ = out.Close() }() - - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() -} diff --git a/README.md b/README.md index 01458e3..57fc561 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,96 @@ -# Quick logger +# Quicklog -![Quicklogger](./logo-small.png) +![Quicklog](./logo-small.png) -This is a tiny GUI app written in Go using the Fyne framework to quickly log a message to a file. Read on my blog more about this: https://foo.zone/gemfeed/2024-03-03-a-fine-fyne-android-app-for-quickly-logging-ideas-programmed-in-golang.html - -The purpose of this is to have a small Android app to quickly log Ideas into a folder as plain text files. From there, Syncthing will sync it to my computer at home. +Tiny GUI app to quickly jot a thought into a timestamped Markdown file. +Originally a Go/Fyne app called *Quicklogger* — this is the Flutter rewrite, +renamed to **Quicklog**, targeting Android (primary) and Linux desktop +(development). -This are screenshots of the App running on Android and Fedora Linux. +The intent is the same as before: type a quick note on Android, hit **Log +text**, and let Syncthing copy the resulting `ql-YYMMDD-HHMMSS.md` file to your +home computer. ![Screenshot](./screenshot-android.png) ![Screenshot](./screenshot-fedora.png) -## Build and Run (Mage) +## Features -This repo includes Mage tasks to build, run and cross‑compile. +- Single-screen text editor with character counter and a one-shot warning at + 5,000 characters. +- Each press of **Log text** writes the current text to a new file named + `ql-YYMMDD-HHMMSS.md` in the configured directory. +- **Preferences**: configurable log directory and an "Auto-log shared text" + toggle. +- **Entry browser**: list of previous entries (newest first) with read-only + viewer and long-press to delete. +- **Share to Quicklog** on Android: share text from any app and Quicklog + either prefills the editor or logs it immediately, depending on the + preference. -Install Mage: +## Requirements -```sh -go install github.com/magefile/mage@latest -``` +- [Flutter](https://flutter.dev) stable channel (3.41+). +- For Android builds: Android SDK + JDK 17 (Flutter's Gradle does not yet + support newer JDKs). On Fedora, GraalVM 17 works — point Flutter at it with + `flutter config --jdk-dir=/usr/lib/jvm/graalvm-community-openjdk-17.0.9+9.1`. +- For Linux desktop builds: `gtk3-devel`, `mesa-demos`, `clang`, `cmake`, + `ninja-build`, `pkg-config`, `xz-devel`. -Clone and enter the repo: +## Build and Run + +### Linux desktop ```sh -git clone https://codeberg.org/snonux/quicklogger -cd quicklogger +flutter run -d linux # dev with hot reload +flutter build linux --release # release bundle: build/linux/x64/release/bundle/ ``` -Common tasks: +### Android ```sh -# Build desktop binary into ./bin -mage build - -# Run the app (shows verbose Go build output) -mage run - -# Clean build artifacts -mage clean +flutter run -d # dev on a connected device +flutter build apk --release # fat APK with all ABIs ``` -## Android Builds - -Two options exist: local Fyne packaging or containerized cross‑compile. - -- Local packaging (requires Fyne CLI and Android NDK): +### Cross-compile from amd64 Linux to ARM Android APK - ```sh - # Install Fyne CLI if needed - go install fyne.io/fyne/v2/cmd/fyne@latest +Unlike the previous Fyne build, no Docker / Podman / `fyne-cross` / NDK is +needed. Flutter's Dart AOT compiler emits ARM machine code directly from x86_64. - # Ensure ANDROID_NDK_HOME points to your NDK (e.g. ~/android-ndk/android-ndk-r26b) - export ANDROID_NDK_HOME=~/android-ndk/android-ndk-r26b - - # Build APK in the project root as quicklogger.apk - mage android - ``` - -- Containerized cross‑compile (recommended, uses fyne-cross with Docker/Podman): +```sh +# Per-ABI split APKs (smaller, recommended for distribution) +flutter build apk --release --split-per-abi +# → build/app/outputs/flutter-apk/app-arm64-v8a-release.apk (modern phones) +# → build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk (older 32-bit) +# → build/app/outputs/flutter-apk/app-x86_64-release.apk (emulators) + +# ARM64 only +flutter build apk --release --target-platform android-arm64 +``` - ```sh - # Start Podman if you prefer Podman over Docker - sudo systemctl start podman +Install on the device: - # The task auto-detects a user Podman socket; otherwise it uses Docker defaults - mage androidcross - ``` +```sh +adb install -r build/app/outputs/flutter-apk/app-arm64-v8a-release.apk +``` -After cross‑compiling, the APK is located at `fyne-cross/dist/android/quicklogger.apk`. -Copy it to your device and install it (you may need to allow installing from unknown sources): +### Tests ```sh -adb install -r fyne-cross/dist/android/quicklogger.apk -# or copy manually and install on device +flutter test +flutter analyze ``` -## Share with QuickLogger on Android +## Share with Quicklog on Android + +From any app that can share text, choose **Share** → **Quicklog**. With +auto-log off (default), the text opens in the editor for review. Toggle +"Auto-log shared text" in Preferences to have shared text written +straight to disk. -From any Android app that can share text, choose **Share** and send it to QuickLogger. The text opens in the editor so you can review it, edit it, or tap **Log text**. +## Storage on Android -If you want shared text to be saved immediately, open **Preferences** and enable **Auto-log shared text**. With that on, shared text goes straight to your log directory instead of being prefilled. +Log files are written to the app-specific external directory at +`/Android/data/org.buetow.quicklog/files/`. No storage permissions are +required; point Syncthing at that folder to sync to your home computer. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml deleted file mode 100644 index 0e6115e..0000000 --- a/android/AndroidManifest.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..3333b8a --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "org.buetow.quicklog" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "org.buetow.quicklog" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6eddff0 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/org/buetow/quicklog/MainActivity.kt b/android/app/src/main/kotlin/org/buetow/quicklog/MainActivity.kt new file mode 100644 index 0000000..426589f --- /dev/null +++ b/android/app/src/main/kotlin/org/buetow/quicklog/MainActivity.kt @@ -0,0 +1,56 @@ +package org.buetow.quicklog + +import android.content.Intent +import android.os.Bundle +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import java.io.File + +class MainActivity : FlutterActivity() { + private val channelName = "org.buetow.quicklog/share" + private val cacheFilename = "quicklog-shared.txt" + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName) + .setMethodCallHandler { call, result -> + when (call.method) { + "readSharedTextFromCache" -> result.success(readCache()) + "clearSharedTextCache" -> { + clearCache() + result.success(null) + } + else -> result.notImplemented() + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + captureSendIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + captureSendIntent(intent) + } + + private fun captureSendIntent(intent: Intent?) { + if (intent?.action == Intent.ACTION_SEND && intent.type?.startsWith("text/") == true) { + intent.getStringExtra(Intent.EXTRA_TEXT)?.let { text -> + File(cacheDir, cacheFilename).writeText(text) + } + } + } + + private fun readCache(): String? { + val f = File(cacheDir, cacheFilename) + return if (f.exists()) f.readText() else null + } + + private fun clearCache() { + val f = File(cacheDir, cacheFilename) + if (f.exists()) f.delete() + } +} diff --git a/android/app/src/main/kotlin/org/buetow/quicklog/ShareActivity.kt b/android/app/src/main/kotlin/org/buetow/quicklog/ShareActivity.kt new file mode 100644 index 0000000..15261e6 --- /dev/null +++ b/android/app/src/main/kotlin/org/buetow/quicklog/ShareActivity.kt @@ -0,0 +1,54 @@ +package org.buetow.quicklog + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import java.io.File + +class ShareActivity : Activity() { + companion object { + private const val TAG = "QuicklogShare" + private const val CACHE_FILENAME = "quicklog-shared.txt" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handleShare(intent) + launchMain() + finish() + } + + private fun handleShare(intent: Intent?) { + if (intent == null) return + val type = intent.type + if (intent.action == Intent.ACTION_SEND && type != null && type.startsWith("text/")) { + val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) + if (sharedText != null) { + try { + val f = File(cacheDir, CACHE_FILENAME) + f.writeText(sharedText) + Log.i(TAG, "Wrote shared text to ${f.absolutePath}") + } catch (e: Exception) { + Log.e(TAG, "Error writing shared text", e) + } + } + } + } + + private fun launchMain() { + try { + val launch = Intent(this, MainActivity::class.java).apply { + action = Intent.ACTION_MAIN + addCategory(Intent.CATEGORY_LAUNCHER) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + } + startActivity(launch) + } catch (t: Throwable) { + Log.e(TAG, "Failed to launch main activity", t) + } + } +} diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a454844 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7a9bc40 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7badce7 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5ed2063 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..089a22a Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..c79c58a --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..f75d22f Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..d24ba69 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..b49236f Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..a20c849 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..9a1fafa Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/android_shared_android.go b/android_shared_android.go deleted file mode 100644 index b29f830..0000000 --- a/android_shared_android.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build android - -package main - -import ( - "os" - "path/filepath" -) - -// sharedTextCachePath returns the cache file used for Android share intents. -func sharedTextCachePath() string { - dir, derr := os.UserCacheDir() - if derr != nil || dir == "" { - dir = os.TempDir() - } - return filepath.Join(dir, "quicklogger-shared.txt") -} - -// readSharedFromCache tries to read the shared text written by the Android activity. -func readSharedFromCache() (string, error) { - b, err := os.ReadFile(sharedTextCachePath()) - if err != nil { - return "", err - } - return string(b), nil -} diff --git a/android_shared_stub.go b/android_shared_stub.go deleted file mode 100644 index 82429f7..0000000 --- a/android_shared_stub.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !android - -package main - -func readSharedFromCache() (string, error) { return "", nil } - -func sharedTextCachePath() string { return "" } diff --git a/android_shared_stub_test.go b/android_shared_stub_test.go deleted file mode 100644 index 7b42b3a..0000000 --- a/android_shared_stub_test.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build !android - -package main - -import "testing" - -func TestReadSharedFromCacheStub(t *testing.T) { - txt, err := readSharedFromCache() - if err != nil { - t.Fatalf("stub returned error: %v", err) - } - if txt != "" { - t.Errorf("stub returned non-empty text: %q", txt) - } -} - -func TestHandleSharedTextLoadAutoLogSkipsEmptyStubText(t *testing.T) { - var clearCalls int - - handleSharedTextLoad( - "", - true, - t.TempDir(), - func(string) { - t.Fatal("prefill should not be called for empty shared text") - }, - func() { - t.Fatal("focus should not be called for empty shared text") - }, - func() { - t.Fatal("resetInput should not be called for empty shared text") - }, - func() { - clearCalls++ - }, - func(string, string) error { - t.Fatal("logEntry should not be called for empty shared text") - return nil - }, - func(string, string) { - t.Fatal("info dialog should not be shown for empty shared text") - }, - func(error) { - t.Fatal("error dialog should not be shown for empty shared text") - }, - ) - - if clearCalls != 1 { - t.Fatalf("expected cache cleanup once, got %d", clearCalls) - } -} diff --git a/build.sh b/build.sh deleted file mode 100755 index 1baaf80..0000000 --- a/build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -if [ -z "$ANDORID_NDK_HOME" ]; then - # This is where I personally have installed the Android NDK - export ANDROID_NDK_HOME=~/android-ndk/android-ndk-r26b -fi - -fyne package -os android - -if [ -d ~/Documents/APKs ]; then - # Will be synced to my phone by Syncthing, so I can install it there. - cp -v quicklogger.apk ~/Documents/APKs -fi - diff --git a/go.mod b/go.mod deleted file mode 100644 index d5214e4..0000000 --- a/go.mod +++ /dev/null @@ -1,38 +0,0 @@ -module codeberg.org/snonux/quicklogger - -go 1.21.6 - -require ( - fyne.io/fyne/v2 v2.4.3 - github.com/magefile/mage v1.15.0 -) - -require ( - fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fredbi/uri v1.0.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect - github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect - github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect - github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect - github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect - github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect - github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect - github.com/stretchr/testify v1.8.4 // indirect - github.com/tevino/abool v1.2.0 // indirect - github.com/yuin/goldmark v1.5.5 // indirect - golang.org/x/image v0.11.0 // indirect - golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index f7804c4..0000000 --- a/go.sum +++ /dev/null @@ -1,668 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.4.3 h1:v2wncjEAcwXZ8UNmTCWTGL9+sGyPc5RuzBvM96GcC78= -fyne.io/fyne/v2 v2.4.3/go.mod h1:1h3BKxmQYRJlr2g+RGVxedzr6vLVQ/AJmFWcF9CJnoQ= -fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8= -fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= -github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= -github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= -github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= -github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= -github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= -github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= -github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc= -github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a h1:VjN8ttdfklC0dnAdKbZqGNESdERUxtE3l8a/4Grgarc= -github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= -github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= -github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/