14 Commits
Author SHA1 Message Date
julian 41505677d5 ci: fix key properties filename
Build and deploy the latest docker container / deploy (push) Successful in 1m13s
Build and release the latest android apk / rolling-release-apk (push) Successful in 5m25s
2026-07-09 18:45:35 +02:00
julian 276dd6c97f ci: add app signing
Build and deploy the latest docker container / deploy (push) Successful in 33m33s
Build and release the latest android apk / rolling-release-apk (push) Failing after 2m30s
2026-07-09 09:11:56 +02:00
julian 479b23690c android: setup app signing 2026-07-09 09:11:45 +02:00
julian 52eb2fbab1 pubspec: add build number to distinguish apps in fdroid
Build and deploy the latest docker container / deploy (push) Successful in 1m11s
Build and release the latest android apk / rolling-release-apk (push) Failing after 6m3s
Build and release a versioned android apk / release-apk (push) Successful in 5m10s
2026-07-04 11:45:10 +02:00
julian 416979ab3d sheets view: add refresh button for annotations
Build and deploy the latest docker container / deploy (push) Successful in 1m22s
Build and release the latest android apk / rolling-release-apk (push) Successful in 5m25s
2026-07-04 11:26:04 +02:00
julian 984e32cd03 linux: regenerate to fix bugs after update 2026-07-04 11:14:50 +02:00
julian 125346668a upgrade everything, devenv, flutter, gradle
Build and deploy the latest docker container / deploy (push) Successful in 29m5s
Build and release the latest android apk / rolling-release-apk (push) Successful in 6m1s
2026-07-03 22:35:01 +02:00
julian 38eb1cccc5 pubspec: update
Build and deploy the latest docker container / deploy (push) Successful in 59s
Build and release the latest android apk / rolling-release-apk (push) Successful in 4m50s
2026-07-03 18:41:03 +02:00
julian 46c88df707 use wasm for web compilation, fixing pdfrx 2026-07-03 18:40:47 +02:00
julian c8f941a7e0 Only check for cached pdf files if not web
Build and deploy the latest docker container / deploy (push) Successful in 57s
Build and release the latest android apk / rolling-release-apk (push) Successful in 5m2s
2026-07-03 18:20:33 +02:00
julian 4d1dda0263 Save refresh token only if not null
Build and deploy the latest docker container / deploy (push) Successful in 57s
Build and release the latest android apk / rolling-release-apk (push) Successful in 4m55s
Build and release a versioned android apk / release-apk (push) Successful in 5m2s
2026-07-03 18:15:22 +02:00
julian 1c5df097c4 Make this version 0.2.1
Build and deploy the latest docker container / deploy (push) Successful in 1m6s
Build and release the latest android apk / rolling-release-apk (push) Successful in 5m31s
Build and release a versioned android apk / release-apk (push) Successful in 5m9s
2026-07-03 17:51:03 +02:00
julian 73336ce3fa Show offline symbol at first start if offline 2026-07-03 17:49:06 +02:00
julian baf20e1ca8 Add symbol for offline available sheets 2026-07-03 17:46:50 +02:00
29 changed files with 259 additions and 265 deletions
@@ -11,6 +11,20 @@ jobs:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Decode keystore
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/keystore.jks
env:
KEYSTORE_BASE64: ${{ secrets.FRAJUL_KEYSTORE_BASE64 }}
- name: Create keystore.properties
run: |
cat <<EOF > android/key.properties
storeFile=keystore.jks
storePassword=${{ secrets.FRAJUL_KEYSTORE_PASSWORD }}
keyAlias=${{ secrets.FRAJUL_KEYSTORE_ALIAS }}
keyPassword=${{ secrets.FRAJUL_KEYSTORE_KEY_PASSWORD }}
EOF
- name: Build apk - name: Build apk
run: devenv shell build-android run: devenv shell build-android
@@ -11,6 +11,22 @@ jobs:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Decode keystore
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/keystore.jks
env:
KEYSTORE_BASE64: ${{ secrets.FRAJUL_KEYSTORE_BASE64 }}
- name: Create keystore.properties
run: |
cat <<EOF > android/key.properties
storeFile=keystore.jks
storePassword=${{ secrets.FRAJUL_KEYSTORE_PASSWORD }}
keyAlias=${{ secrets.FRAJUL_KEYSTORE_ALIAS }}
keyPassword=${{ secrets.FRAJUL_KEYSTORE_KEY_PASSWORD }}
EOF
- name: Build apk
run: devenv shell build-android
- name: Build apk - name: Build apk
run: devenv shell build-android run: devenv shell build-android
-15
View File
@@ -15,24 +15,9 @@ migration:
- platform: root - platform: root
create_revision: nixpkgs000000000000000000000000000000000 create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000 base_revision: nixpkgs000000000000000000000000000000000
- platform: android
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: ios
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: linux - platform: linux
create_revision: nixpkgs000000000000000000000000000000000 create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000 base_revision: nixpkgs000000000000000000000000000000000
- platform: macos
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: web
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
- platform: windows
create_revision: nixpkgs000000000000000000000000000000000
base_revision: nixpkgs000000000000000000000000000000000
# User provided section # User provided section
+19 -3
View File
@@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") id("kotlin-android")
@@ -5,6 +8,12 @@ plugins {
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android { android {
namespace = "com.example.sheetless" namespace = "com.example.sheetless"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
@@ -30,11 +39,18 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig = signingConfigs.getByName("release")
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
} }
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@ subprojects {
val android = project.extensions.findByName("android") val android = project.extensions.findByName("android")
if (android is com.android.build.gradle.BaseExtension) { if (android is com.android.build.gradle.BaseExtension) {
// Force the same sdk version everywhere // Force the same sdk version everywhere
android.ndkVersion = "27.2.12479018" android.ndkVersion = "29.0.14206865"
} }
} }
} }
File diff suppressed because one or more lines are too long
+4
View File
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-all.zip
+2 -2
View File
@@ -18,8 +18,8 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false id("com.android.application") version "8.12.0" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false
} }
include(":app") include(":app")
+6 -4
View File
@@ -3,10 +3,11 @@
"devenv": { "devenv": {
"locked": { "locked": {
"dir": "src/modules", "dir": "src/modules",
"lastModified": 1770167382, "lastModified": 1783049965,
"narHash": "sha256-z2QvrvE+gzLnpqfLpQA4aq2gAB4xn6C5q2o1l7GBMXU=",
"owner": "cachix", "owner": "cachix",
"repo": "devenv", "repo": "devenv",
"rev": "195375e3f0a3c3ac4f12b21d3f59a7ddb209bd1f", "rev": "afed7bf3240c70d2827569568d2c46c25a64db51",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -18,10 +19,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1770115704, "lastModified": 1782723713,
"narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e6eae2ee2110f3d31110d5c222cd395303343b08", "rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8",
"type": "github" "type": "github"
}, },
"original": { "original": {
+12 -10
View File
@@ -6,23 +6,25 @@
flutter.package = pkgs.flutter; flutter.package = pkgs.flutter;
buildTools.version = [ buildTools.version = [
# Put version to compile with first! # Put the latest version to compile with first!
"36.0.0" # Android 16/17 target
"35.0.0" "35.0.0"
"33.0.1"
"34.0.0" "34.0.0"
]; ];
platforms.version = [ platforms.version = [
"36" "37" # Android 17
"35" "36" # Android 16
"34" "35" # Android 15
]; ];
ndk.enable = true; ndk.enable = true;
ndk.version = [ ndk.version = [
"27.2.12479018" "29.0.14206865" # NDK r29 (Latest Stable)
"27.0.12077973" "27.3.13750724" # NDK r27d (Latest LTS)
"26.3.11579264" "27.2.12479018" # Kept for backward compatibility
"25.1.8937393"
]; ];
extraLicenses = [ extraLicenses = [
"android-googlexr-license" "android-googlexr-license"
"android-sdk-preview-license" "android-sdk-preview-license"
@@ -51,7 +53,7 @@
build-docker.exec = '' build-docker.exec = ''
dart run pdfrx:remove_wasm_modules --revert // Make sure WASM is put in output dart run pdfrx:remove_wasm_modules --revert // Make sure WASM is put in output
flutter build web --release flutter build web --release --wasm
docker build -t gitlab.julian-mutter.de/julian/sheetless-frontend:latest . docker build -t gitlab.julian-mutter.de/julian/sheetless-frontend:latest .
''; '';
+2
View File
@@ -7,12 +7,14 @@ class Sheet {
String name; String name;
String composer; String composer;
DateTime updatedAt; DateTime updatedAt;
bool availableOffline;
Sheet({ Sheet({
required this.uuid, required this.uuid,
required this.name, required this.name,
required this.composer, required this.composer,
required this.updatedAt, required this.updatedAt,
this.availableOffline = false,
}); });
/// Creates a [Sheet] from a JSON map returned by the API. /// Creates a [Sheet] from a JSON map returned by the API.
@@ -23,16 +23,18 @@ class AnnotationSyncService {
/// For each page, compares server's lastModified with local lastModified. /// For each page, compares server's lastModified with local lastModified.
/// If server is newer, overwrites local. Local annotations that are newer /// If server is newer, overwrites local. Local annotations that are newer
/// are preserved. /// are preserved.
Future<void> syncFromServer(String sheetUuid) async { Future<void> syncAnnotationsFromServer(String sheetUuid) async {
try { try {
_log.info('Syncing annotations from server for sheet $sheetUuid'); _log.info('Syncing annotations from server for sheet $sheetUuid');
// Fetch all annotations from server // Fetch all annotations from server
final serverAnnotations = await _apiClient.fetchAnnotations(sheetUuid); final serverAnnotations = await _apiClient.fetchAnnotations(sheetUuid);
_log.fine('Fetched annotations from server for sheet $sheetUuid');
// Get all local annotations with metadata // Get all local annotations with metadata
final localAnnotations = final localAnnotations = await _storageService
await _storageService.readAllAnnotationsWithMetadata(sheetUuid); .readAllAnnotationsWithMetadata(sheetUuid);
int updatedCount = 0; int updatedCount = 0;
@@ -118,7 +120,8 @@ class AnnotationSyncService {
return false; return false;
} catch (e) { } catch (e) {
_log.warning( _log.warning(
'Unexpected error uploading annotation, queuing for later: $e'); 'Unexpected error uploading annotation, queuing for later: $e',
);
await _queueForLaterUpload( await _queueForLaterUpload(
sheetUuid: sheetUuid, sheetUuid: sheetUuid,
page: page, page: page,
+7
View File
@@ -254,6 +254,13 @@ class ApiClient {
return cachedFile; return cachedFile;
} }
Future<bool> isPdfFileCached(String sheetUuid) async {
final cacheDir = await getTemporaryDirectory();
final cachedFile = File('${cacheDir.path}/$sheetUuid.pdf');
return await cachedFile.exists();
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Annotation Operations // Annotation Operations
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
+7
View File
@@ -101,8 +101,15 @@ class _LoginPageState extends State<LoginPage> {
await apiClient.login(_usernameController.text, _passwordController.text); await apiClient.login(_usernameController.text, _passwordController.text);
// Save credentials for next time // Save credentials for next time
if (apiClient.refreshToken == null) {
_log.warning('Login failed, refreshToken is null');
setState(() {
_errorMessage = 'Login failed.\nRefresh token is null';
});
} else {
await _saveCredentials(apiClient.refreshToken!); await _saveCredentials(apiClient.refreshToken!);
await _navigateToHome(); await _navigateToHome();
}
} catch (e) { } catch (e) {
_log.warning('Login failed', e); _log.warning('Login failed', e);
setState(() { setState(() {
+9
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_fullscreen/flutter_fullscreen.dart'; import 'package:flutter_fullscreen/flutter_fullscreen.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -123,7 +124,15 @@ class _HomePageState extends State<HomePage> with RouteAware {
// Sort and store sheets // Sort and store sheets
_sheets = await _sortSheetsByRecency(result.sheets); _sheets = await _sortSheetsByRecency(result.sheets);
setState(() {
_isOnline = result.isOnline; _isOnline = result.isOnline;
});
if (!kIsWeb) {
for (var sheet in _sheets) {
sheet.availableOffline = await _apiClient!.isPdfFileCached(sheet.uuid);
}
}
return result; return result;
} }
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import '../../../core/models/sheet.dart'; import '../../../core/models/sheet.dart';
/// A list tile displaying a single sheet's information. /// A list tile displaying a single sheet's information.
@@ -24,6 +25,10 @@ class SheetListItem extends StatelessWidget {
subtitle: Text(sheet.composer), subtitle: Text(sheet.composer),
onTap: onTap, onTap: onTap,
onLongPress: onLongPress, onLongPress: onLongPress,
trailing: Visibility(
visible: !kIsWeb && sheet.availableOffline,
child: Icon(Icons.download_done, color: Colors.green),
),
); );
} }
} }
@@ -40,6 +40,7 @@ class _SheetViewerPageState extends State<SheetViewerPage>
PdfDocument? _document; PdfDocument? _document;
late Future<bool> _documentLoaded; late Future<bool> _documentLoaded;
late Future<void> _annotationsLoaded;
int _currentPage = 1; int _currentPage = 1;
int _totalPages = 1; int _totalPages = 1;
bool _isPaintMode = false; bool _isPaintMode = false;
@@ -69,6 +70,15 @@ class _SheetViewerPageState extends State<SheetViewerPage>
FullScreen.setFullScreen(widget.config.fullscreen); FullScreen.setFullScreen(widget.config.fullscreen);
} }
_documentLoaded = _loadPdf(); _documentLoaded = _loadPdf();
_syncAnnotations();
}
void _syncAnnotations() {
setState(() {
_annotationsLoaded = _syncService.syncAnnotationsFromServer(
widget.sheet.uuid,
);
});
} }
@override @override
@@ -97,19 +107,22 @@ class _SheetViewerPageState extends State<SheetViewerPage>
} else { } else {
// Native: use file cache // Native: use file cache
final file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid); final file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid);
_log.fine("Found file, opening...");
_document = await PdfDocument.openFile(file.path); _document = await PdfDocument.openFile(file.path);
widget.sheet.availableOffline = true;
} }
_log.fine("Pdf loaded");
setState(() { setState(() {
_totalPages = _document!.pages.length; _totalPages = _document!.pages.length;
}); });
// Sync annotations from server (downloads newer versions)
await _syncService.syncFromServer(widget.sheet.uuid);
// Load annotations for current page(s) // Load annotations for current page(s)
await _loadAnnotationsForCurrentPages(); await _loadAnnotationsForCurrentPages();
_log.fine("Annotations loaded");
return true; return true;
} }
@@ -303,6 +316,26 @@ class _SheetViewerPageState extends State<SheetViewerPage>
: 'Enter Fullscreen', : 'Enter Fullscreen',
onPressed: _toggleFullscreen, onPressed: _toggleFullscreen,
), ),
FutureBuilder<void>(
future: _annotationsLoaded,
builder: (context, snapshot) {
if (snapshot.hasError) {
_log.warning('Error loading annotations', snapshot.error);
return IconButton(
onPressed: _syncAnnotations,
icon: Icon(Icons.error, color: Colors.red),
);
}
if (snapshot.connectionState == ConnectionState.done) {
_loadAnnotationsForCurrentPages();
return IconButton(
onPressed: _syncAnnotations,
icon: Icon(Icons.refresh),
);
}
return const CircularProgressIndicator();
},
),
IconButton( IconButton(
icon: Icon(_isPaintMode ? Icons.brush : Icons.brush_outlined), icon: Icon(_isPaintMode ? Icons.brush : Icons.brush_outlined),
tooltip: _isPaintMode ? 'Exit Paint Mode' : 'Enter Paint Mode', tooltip: _isPaintMode ? 'Exit Paint Mode' : 'Enter Paint Mode',
+10 -20
View File
@@ -1,5 +1,5 @@
# Project-level configuration. # Project-level configuration.
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX) project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
@@ -54,25 +54,8 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed. # Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble) add_dependencies(${BINARY_NAME} flutter_assemble)
@@ -86,6 +69,7 @@ set_target_properties(${BINARY_NAME}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
) )
# Generated plugin build rules, which manage building the plugins and adding # Generated plugin build rules, which manage building the plugins and adding
# them to the application. # them to the application.
include(flutter/generated_plugins.cmake) include(flutter/generated_plugins.cmake)
@@ -122,6 +106,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime) COMPONENT Runtime)
endforeach(bundled_library) endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
-1
View File
@@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni jni
pdfium_flutter
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)
-6
View File
@@ -1,6 +0,0 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
-104
View File
@@ -1,104 +0,0 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "sheetless");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "sheetless");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
+25 -7
View File
@@ -14,6 +14,11 @@ struct _MyApplication {
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// Implements GApplication::activate. // Implements GApplication::activate.
static void my_application_activate(GApplication* application) { static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application); MyApplication* self = MY_APPLICATION(application);
@@ -48,22 +53,35 @@ static void my_application_activate(GApplication* application) {
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project); FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000
// for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view)); gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view)); gtk_widget_grab_focus(GTK_WIDGET(view));
} }
// Implements GApplication::local_command_line. // Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments,
int* exit_status) {
MyApplication* self = MY_APPLICATION(application); MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name. // Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
@@ -108,7 +126,8 @@ static void my_application_dispose(GObject* object) {
static void my_application_class_init(MyApplicationClass* klass) { static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->local_command_line =
my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose; G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
@@ -124,7 +143,6 @@ MyApplication* my_application_new() {
g_set_prgname(APPLICATION_ID); g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(), return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "application-id", APPLICATION_ID, "flags",
"flags", G_APPLICATION_NON_UNIQUE, G_APPLICATION_NON_UNIQUE, nullptr));
nullptr));
} }
+4 -1
View File
@@ -3,7 +3,10 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, G_DECLARE_FINAL_TYPE(MyApplication,
my_application,
MY,
APPLICATION,
GtkApplication) GtkApplication)
/** /**
+2 -2
View File
@@ -1,3 +1,3 @@
# This file was automatically generated by nix-shell. # This file was automatically generated by nix-shell.
sdk.dir=/nix/store/ri84qrj5wk61x6cja29cfcv5x0iarhs5-androidsdk/libexec/android-sdk sdk.dir=/nix/store/xxvb6m6dqjgdxk9xvwk8vlwapm8pgsdk-androidsdk/libexec/android-sdk
ndk.dir=/nix/store/ri84qrj5wk61x6cja29cfcv5x0iarhs5-androidsdk/libexec/android-sdk/ndk-bundle ndk.dir=/nix/store/xxvb6m6dqjgdxk9xvwk8vlwapm8pgsdk-androidsdk/libexec/android-sdk/ndk-bundle
+54 -46
View File
@@ -37,10 +37,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
@@ -113,6 +113,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
ffi_leak_tracker:
dependency: transitive
description:
name: ffi_leak_tracker
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -210,10 +218,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_windows name: flutter_secure_storage_windows
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.2.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -260,10 +268,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.9.1"
jni: jni:
dependency: transitive dependency: transitive
description: description:
@@ -340,26 +348,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
objective_c: objective_c:
dependency: transitive dependency: transitive
description: description:
@@ -380,18 +388,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" sha256: f5c435dc0e0d461e5b32471a870f769b6a1cc46930637efe24fbc535314e78ad
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.1" version: "10.2.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "4.1.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -452,34 +460,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pdfium_dart name: pdfium_dart
sha256: f1683b9070ddc5c9189c6ee008c285791da66328ce1b882c7162d3393f3a4a74 sha256: "86e95c66b09f3245b95c4924f2edce8d4f7c9786876e5c5ee8e36b104a94bbb0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3" version: "0.2.5"
pdfium_flutter: pdfium_flutter:
dependency: transitive dependency: transitive
description: description:
name: pdfium_flutter name: pdfium_flutter
sha256: "0c8b7d5d11d20a1486eade599648e907067568955bd14a1b06de076a968b60a1" sha256: "420ba8e7673b54da387ceeeb18a72c8bc6e4452128dbb391dca900c748f9e9ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.9" version: "0.2.2"
pdfrx: pdfrx:
dependency: "direct main" dependency: "direct main"
description: description:
name: pdfrx name: pdfrx
sha256: e32e0c786528eec2b3c56b43f59ef1debce3a27c7accd862b95413f949afcfa9 sha256: e0ca318004c3f32144db8e74fa612abb80ebe79004677293af74ed9af119f47f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.24" version: "2.4.4"
pdfrx_engine: pdfrx_engine:
dependency: transitive dependency: transitive
description: description:
name: pdfrx_engine name: pdfrx_engine
sha256: a8914433d1f6188b903c53d36b9d7dc908bfa89131591a9db22f1a22470d3a48 sha256: "89865e158ced818690ab207a13a34a65803cd67ee1bec9f5f87838eb5a79600b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.9" version: "0.4.3"
permission_handler: permission_handler:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -641,42 +649,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2+1" version: "2.4.3"
sqflite_android: sqflite_android:
dependency: transitive dependency: transitive
description: description:
name: sqflite_android name: sqflite_android
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2+3" version: "2.4.3"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.8" version: "2.5.11"
sqflite_darwin: sqflite_darwin:
dependency: transitive dependency: transitive
description: description:
name: sqflite_darwin name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.3+1"
sqflite_platform_interface: sqflite_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: sqflite_platform_interface name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -705,10 +713,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: synchronized name: synchronized
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.0" version: "3.4.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -721,10 +729,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.7" version: "0.7.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -745,10 +753,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.30" version: "6.3.32"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@@ -833,10 +841,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.15.0" version: "6.3.0"
window_manager: window_manager:
dependency: transitive dependency: transitive
description: description:
@@ -857,10 +865,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.1" version: "7.0.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@@ -870,5 +878,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.10.3 <4.0.0" dart: ">=3.12.0 <4.0.0"
flutter: ">=3.38.4" flutter: ">=3.44.0"
+3 -3
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.2.0 version: 0.2.1+2
environment: environment:
sdk: ^3.0.0 sdk: ^3.0.0
@@ -43,12 +43,12 @@ dependencies:
flutter_cache_manager: ^3.4.1 flutter_cache_manager: ^3.4.1
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
jwt_decoder: ^2.0.1 jwt_decoder: ^2.0.1
pdfrx: ^2.0.4 pdfrx: ^2.4.4
logging: ^1.3.0 logging: ^1.3.0
flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: ^0.14.4
hive: ^2.2.3 hive: ^2.2.3
flutter_fullscreen: ^1.2.0 flutter_fullscreen: ^1.2.0
package_info_plus: ^9.0.0 package_info_plus: ^10.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
-1
View File
@@ -12,7 +12,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni jni
pdfium_flutter
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)