mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:07:16 +01:00
Run the workout app on Linux, and let it hold the screen lock
The phone is lost, so no workout can be recorded and enforcement has been disarmed since 2026-08-21. This gives the PC a first-class way to do the workout, reusing the existing app rather than reimplementing the progression rules a second time -- two implementations would guarantee the divergence the "same weights everywhere" requirement forbids. Storage: sqflite has no Linux plugin, so main() installs sqflite_common_ffi's factory, which runs the same schema and migrations against the same SQL. It was already a dev dependency driving the test suite; this only promotes it. Auth: the account password in ~/.config/crdt-sync no longer authenticates (verified: HTTP 400 INVALID_LOGIN_CREDENTIALS). The PC fleet has been running on the cached refresh token all along, so the desktop credential store reads that same 0600 file -- the Dart and Python credential JSON keys are already identical -- and passes null instead of the stale password. This also avoids google_sign_in, which ships no Linux implementation. Lock mode: --lock-mode makes the window the lock surface. There is no Dart API for this, so the runner sets override-redirect and takes an exclusive gdk_seat_grab. Two details cost a debugging cycle each and are commented in place: override-redirect must be set BEFORE the first map, or the window stays mapped and never repaints; and gtk_window_fullscreen() only ASKS the window manager, which cannot see an override-redirect window -- so the geometry is set from the monitor directly, otherwise a strip of live desktop is left uncovered at the edge of a larger screen. The grab is acquired by retry rather than by asking the supervisor to release first: the Python locker keeps its grab while this window maps on top, so the screen is covered continuously instead of exposing the desktop for the whole of Flutter's cold start. Verified against the live data: pulls all 7 exercises from Firebase (Bench 17.5x12, RDL 27.5x8, Row 25x6, Situp 10x31 -- the real weights, not factory defaults), leaves the remote progression byte-identical, and mints its own device id rather than reusing the phone's. A Tk client is refused the grab while the lock holds it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfJC1Wu1SEgJ6X49k5Jqtg
This commit is contained in:
parent
2c206e5070
commit
186bfcabc4
@ -4,7 +4,7 @@
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42"
|
||||
revision: "4cf24164269a5ebf0c16a028a00727d0e77bbb05"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
@ -13,11 +13,14 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
- platform: android
|
||||
create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
|
||||
create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
- platform: linux
|
||||
create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:workout_app/screens/home_screen.dart';
|
||||
import 'package:workout_app/services/backup_service.dart';
|
||||
import 'package:workout_app/services/http_server_service.dart';
|
||||
import 'package:workout_app/services/lock_mode.dart';
|
||||
import 'package:workout_app/services/progression_sync_service.dart';
|
||||
import 'package:workout_app/services/storage_service.dart';
|
||||
import 'package:workout_app/services/sync_device_id.dart';
|
||||
@ -13,8 +16,18 @@ import 'package:workout_app/ui/theme.dart';
|
||||
// App bootstrap: permission request, DB init, and a real listening socket —
|
||||
// platform-channel work that can't run on the CI test host. The pieces it wires
|
||||
// up are unit-tested individually; the entry point itself is not.
|
||||
void main() async {
|
||||
void main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Must precede runApp: the UI reads this to decide whether to offer
|
||||
// any way out of the workout.
|
||||
lockModeEnabled = parseLockMode(args);
|
||||
// Linux desktop has no sqflite plugin. The FFI factory runs the same
|
||||
// schema and migrations against the same SQL, so this is a transport
|
||||
// swap, not a second storage implementation. Must precede any DB open.
|
||||
if (Platform.isLinux) {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
// Before anything that stamps an Hlc or syncs: until this resolves, the
|
||||
// device id falls back to the pre-migration constant.
|
||||
await initSyncDeviceId();
|
||||
@ -62,7 +75,12 @@ void main() async {
|
||||
),
|
||||
);
|
||||
debugPrint('WorkoutApp: ${restored.reason}');
|
||||
await HttpServerService.instance.start();
|
||||
// Android-only transport: it exists so the PC can pull today's workout off
|
||||
// the phone over LAN. On the PC itself it would bind the very port the PC
|
||||
// scans, so it is skipped rather than serving the machine to itself.
|
||||
if (!Platform.isLinux) {
|
||||
await HttpServerService.instance.start();
|
||||
}
|
||||
runApp(const WorkoutApp());
|
||||
}
|
||||
// coverage:ignore-end
|
||||
@ -95,6 +113,9 @@ class _WorkoutAppState extends State<WorkoutApp> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
// The socket is never started on Linux (see main()), so there is nothing
|
||||
// to stop or restart here either.
|
||||
if (Platform.isLinux) return;
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:workout_app/models/exercise.dart';
|
||||
import 'package:workout_app/models/exercise_result.dart';
|
||||
import 'package:workout_app/models/set_result.dart';
|
||||
import 'package:workout_app/models/workout_session.dart';
|
||||
import 'package:workout_app/services/lock_mode.dart';
|
||||
import 'package:workout_app/services/progression_sync_service.dart';
|
||||
import 'package:workout_app/services/storage_service.dart';
|
||||
import 'package:workout_app/services/sync_service.dart';
|
||||
@ -209,10 +210,11 @@ class _WorkoutScreenState extends State<WorkoutScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
// Explicit `canPop: true` makes it clear this scope never blocks the back
|
||||
// button — a future reader must not assume the default silently.
|
||||
// ignore: avoid_redundant_argument_values
|
||||
canPop: true,
|
||||
// In lock mode the workout IS the lock: popping back to the home screen
|
||||
// would leave a fullscreen window holding the X grab with no way to
|
||||
// finish, so the route is pinned until the workout is finished or reset.
|
||||
// Outside lock mode the back button behaves exactly as it always has.
|
||||
canPop: !lockModeEnabled,
|
||||
child: Scaffold(
|
||||
appBar: _WorkoutAppBar(
|
||||
title:
|
||||
|
||||
@ -12,13 +12,19 @@
|
||||
/// half. On Android there is no such file.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crdt_sync/crdt_sync.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:workout_app/services/google_sign_in_backend.dart';
|
||||
|
||||
part 'firebase_backend_desktop.dart';
|
||||
part 'firebase_backend_google.dart';
|
||||
|
||||
/// The shared `kuhy-syncs` project.
|
||||
///
|
||||
/// `databaseUrl` is the **regional** host. The plain `*.firebaseio.com` form
|
||||
@ -42,14 +48,45 @@ const _secure = FlutterSecureStorage();
|
||||
// coverage:ignore-start
|
||||
|
||||
/// The keystore-backed home for the Firebase refresh token.
|
||||
SecureCredentialStore credentialStore() => SecureCredentialStore(
|
||||
read: (key) => _secure.read(key: key),
|
||||
write: (key, value) => _secure.write(key: key, value: value),
|
||||
delete: (key) => _secure.delete(key: key),
|
||||
);
|
||||
///
|
||||
/// On Linux the "keystore" is the shared 0600 file above rather than
|
||||
/// libsecret, so the desktop build signs in with the same durable refresh
|
||||
/// token the Python side already holds — the account password on this fleet
|
||||
/// is stale, and only the refresh token still authenticates.
|
||||
SecureCredentialStore credentialStore() {
|
||||
if (Platform.isLinux) {
|
||||
return SecureCredentialStore(
|
||||
read: (key) async {
|
||||
final file = _desktopCredentialFile();
|
||||
if (!file.existsSync()) return null;
|
||||
return file.readAsString();
|
||||
},
|
||||
write: (key, value) async {
|
||||
final file = _desktopCredentialFile();
|
||||
await file.parent.create(recursive: true);
|
||||
await file.writeAsString(value);
|
||||
// The refresh token is the secret worth protecting; keep it 0600 the
|
||||
// way the Python half writes it.
|
||||
await Process.run('chmod', ['600', file.path]);
|
||||
},
|
||||
delete: (key) async {
|
||||
final file = _desktopCredentialFile();
|
||||
if (file.existsSync()) await file.delete();
|
||||
},
|
||||
);
|
||||
}
|
||||
return SecureCredentialStore(
|
||||
read: (key) => _secure.read(key: key),
|
||||
write: (key, value) => _secure.write(key: key, value: value),
|
||||
delete: (key) => _secure.delete(key: key),
|
||||
);
|
||||
}
|
||||
|
||||
/// Reads the per-device account, or null when sync has not been set up.
|
||||
Future<FirebaseAccount?> loadAccount() async {
|
||||
// Linux desktop reads the shared PC credential instead of the keystore,
|
||||
// so a fresh desktop install is connected the moment it starts.
|
||||
if (Platform.isLinux) return _accountFromDesktopConfig();
|
||||
try {
|
||||
return FirebaseAccount.tryParse(
|
||||
await _secure.read(key: kFirebaseAccountKey),
|
||||
@ -121,7 +158,15 @@ Future<FirebaseRestClient?> openFirebase() async {
|
||||
// Passing '' would make firebaseClientFor treat it as a usable
|
||||
// credential and sign in with it, which fails; null correctly
|
||||
// means "no password on this device".
|
||||
password: account.password.isEmpty ? null : account.password,
|
||||
//
|
||||
// On Linux the shared refresh token in ~/.config/screen_locker is the
|
||||
// real credential and short-circuits sign-in before any password is
|
||||
// consulted. The password in ~/.config/crdt-sync no longer authenticates
|
||||
// on this fleet, so offering it would only turn a working token into an
|
||||
// INVALID_LOGIN_CREDENTIALS failure.
|
||||
password: (Platform.isLinux || account.password.isEmpty)
|
||||
? null
|
||||
: account.password,
|
||||
// Deliberately NOT offering Google here. This path runs from background
|
||||
// timers and, in some apps, before runApp -- offering it would let a
|
||||
// non-interactive tick raise the OS account picker with no user action
|
||||
@ -130,54 +175,6 @@ Future<FirebaseRestClient?> openFirebase() async {
|
||||
);
|
||||
}
|
||||
|
||||
/// Signs in with Google alone, for a device that has no account stored yet.
|
||||
///
|
||||
/// This is the one-tap path: [openFirebase] needs an account in the keystore
|
||||
/// to know which email to use, but a fresh install has none. The Google token
|
||||
/// carries the identity, so nothing needs to be typed.
|
||||
///
|
||||
/// The account is stored under the email **Firebase reports**, never one read
|
||||
/// from the UI: a fresh install has no email anywhere on the device, so taking
|
||||
/// it from a text field would persist an empty account and send the next
|
||||
/// launch down the password path with ''.
|
||||
///
|
||||
/// Returns null when the user dismisses the picker; throws [FirebaseAuthError]
|
||||
/// when Google succeeds but resolves to a uid the security rules do not pin,
|
||||
/// which would otherwise authenticate fine and then be denied every read and
|
||||
/// write with no other symptom.
|
||||
Future<FirebaseRestClient?> openFirebaseWithGoogle({
|
||||
Future<String?> Function()? tokenFetcher,
|
||||
Future<void> Function(FirebaseAccount)? accountSaver,
|
||||
http.Client? httpClient,
|
||||
}) async {
|
||||
final token = await (tokenFetcher ?? googleIdToken)();
|
||||
if (token == null) return null;
|
||||
final auth = FirebaseTokenProvider(
|
||||
apiKey: kProject.apiKey,
|
||||
store: credentialStore(),
|
||||
httpClient: httpClient,
|
||||
);
|
||||
final email = await auth.signInWithGoogle(
|
||||
idToken: token,
|
||||
expectedUid: kSyncUid,
|
||||
);
|
||||
// Saved unconditionally, and deliberately not gated on `email`:
|
||||
// `signInWithIdp` omits that field whenever the Google account hides it, and
|
||||
// gating the write on it returned a working client while persisting nothing,
|
||||
// so the next launch looked unconfigured and fell back to GitHub-only.
|
||||
// The session itself is already durable here -- signInWithGoogle stored the
|
||||
// refresh token -- and that token, not the address, is the credential.
|
||||
await (accountSaver ?? saveAccount)(
|
||||
FirebaseAccount(email: email ?? '', password: ''),
|
||||
);
|
||||
return FirebaseRestClient(databaseUrl: kProject.databaseUrl, auth: auth);
|
||||
}
|
||||
|
||||
/// Whether this device can actually authenticate against Firebase.
|
||||
///
|
||||
/// True when either half of the state is present: the account marker
|
||||
/// [loadAccount] reads, or a stored refresh token. The token is the half that
|
||||
/// matters -- it is what signs requests -- so reporting the marker alone calls
|
||||
/// a working device "not connected", which is exactly how a phone that was in
|
||||
/// fact syncing looked broken.
|
||||
Future<bool> isFirebaseConfigured() async {
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
part of 'firebase_backend.dart';
|
||||
|
||||
// The Linux-desktop credential edge. Kept beside the Android keystore path
|
||||
// rather than in it, because the two answer the same questions from entirely
|
||||
// different places: Android from the OS keystore, desktop from the 0600 files
|
||||
// the Python half of this fleet already maintains.
|
||||
//
|
||||
// Every function here touches the filesystem or the environment, which the
|
||||
// `flutter test` host has no meaningful version of -- the same reason the rest
|
||||
// of firebase_backend.dart is excluded.
|
||||
// coverage:ignore-start
|
||||
|
||||
/// Path of the shared desktop credential cache, matching the Python
|
||||
/// `crdt_sync.credential_store_for("screen_locker")`.
|
||||
///
|
||||
/// Deliberately the SAME file the PC's own sync already uses: the fleet holds
|
||||
/// one long-lived refresh token per app, and a second copy would drift out of
|
||||
/// date the first time either half refreshed it.
|
||||
File _desktopCredentialFile() => File(
|
||||
p.join(
|
||||
Platform.environment['HOME'] ?? '',
|
||||
'.config',
|
||||
'screen_locker',
|
||||
'firebase_auth.json',
|
||||
),
|
||||
);
|
||||
|
||||
/// Reads the shared desktop credential from `~/.config/crdt-sync/`.
|
||||
///
|
||||
/// Linux desktop only. The PC half of this fleet (screen_locker, and the
|
||||
/// `crdt_sync` Python library) already keeps the sync account there as
|
||||
/// `firebase.json` + `password`, both mode 600, so the desktop build reads
|
||||
/// that rather than standing up a second credential the user would have to
|
||||
/// enter and keep in step. It also sidesteps `google_sign_in`, which has no
|
||||
/// Linux implementation at all.
|
||||
///
|
||||
/// Returns null when the files are absent or malformed; the caller then
|
||||
/// reports the device as not connected, exactly as an empty keystore would.
|
||||
Future<FirebaseAccount?> _accountFromDesktopConfig() async {
|
||||
try {
|
||||
final home = Platform.environment['HOME'];
|
||||
if (home == null || home.isEmpty) {
|
||||
log(
|
||||
'WorkoutApp: HOME is unset — cannot locate ~/.config/crdt-sync, so '
|
||||
'this desktop is NOT connected to sync.',
|
||||
level: 1000,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final dir = Directory(p.join(home, '.config', 'crdt-sync'));
|
||||
final configFile = File(p.join(dir.path, 'firebase.json'));
|
||||
final passwordFile = File(p.join(dir.path, 'password'));
|
||||
if (!configFile.existsSync() || !passwordFile.existsSync()) {
|
||||
log(
|
||||
'WorkoutApp: no desktop sync credential at ${dir.path} '
|
||||
'(need firebase.json + password) — this desktop is NOT connected to '
|
||||
'sync, so progression and workouts will not reach any other device.',
|
||||
level: 1000,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final config =
|
||||
jsonDecode(await configFile.readAsString()) as Map<String, dynamic>;
|
||||
final email = config['email'] as String?;
|
||||
if (email == null || email.isEmpty) {
|
||||
log(
|
||||
'WorkoutApp: ${configFile.path} has no "email" key — cannot sign in.',
|
||||
level: 1000,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final password = (await passwordFile.readAsString()).trim();
|
||||
return FirebaseAccount(email: email, password: password);
|
||||
} on Object catch (error) {
|
||||
log(
|
||||
'WorkoutApp: failed to read the desktop sync credential from '
|
||||
'~/.config/crdt-sync ($error) — treating this desktop as NOT connected.',
|
||||
level: 1000,
|
||||
error: error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// coverage:ignore-end
|
||||
@ -0,0 +1,57 @@
|
||||
part of 'firebase_backend.dart';
|
||||
|
||||
// The interactive Google sign-in path. Separate from the rest of the backend
|
||||
// because it is the only piece that can raise UI, and because it is dead code
|
||||
// on Linux desktop -- google_sign_in ships no Linux implementation, so the
|
||||
// desktop build authenticates from the shared refresh token instead.
|
||||
// coverage:ignore-start
|
||||
|
||||
/// Signs in with Google alone, for a device that has no account stored yet.
|
||||
///
|
||||
/// This is the one-tap path: [openFirebase] needs an account in the keystore
|
||||
/// to know which email to use, but a fresh install has none. The Google token
|
||||
/// carries the identity, so nothing needs to be typed.
|
||||
///
|
||||
/// The account is stored under the email **Firebase reports**, never one read
|
||||
/// from the UI: a fresh install has no email anywhere on the device, so taking
|
||||
/// it from a text field would persist an empty account and send the next
|
||||
/// launch down the password path with ''.
|
||||
///
|
||||
/// Returns null when the user dismisses the picker; throws [FirebaseAuthError]
|
||||
/// when Google succeeds but resolves to a uid the security rules do not pin,
|
||||
/// which would otherwise authenticate fine and then be denied every read and
|
||||
/// write with no other symptom.
|
||||
Future<FirebaseRestClient?> openFirebaseWithGoogle({
|
||||
Future<String?> Function()? tokenFetcher,
|
||||
Future<void> Function(FirebaseAccount)? accountSaver,
|
||||
http.Client? httpClient,
|
||||
}) async {
|
||||
final token = await (tokenFetcher ?? googleIdToken)();
|
||||
if (token == null) return null;
|
||||
final auth = FirebaseTokenProvider(
|
||||
apiKey: kProject.apiKey,
|
||||
store: credentialStore(),
|
||||
httpClient: httpClient,
|
||||
);
|
||||
final email = await auth.signInWithGoogle(
|
||||
idToken: token,
|
||||
expectedUid: kSyncUid,
|
||||
);
|
||||
// Saved unconditionally, and deliberately not gated on `email`:
|
||||
// `signInWithIdp` omits that field whenever the Google account hides it, and
|
||||
// gating the write on it returned a working client while persisting nothing,
|
||||
// so the next launch looked unconfigured and fell back to GitHub-only.
|
||||
// The session itself is already durable here -- signInWithGoogle stored the
|
||||
// refresh token -- and that token, not the address, is the credential.
|
||||
await (accountSaver ?? saveAccount)(
|
||||
FirebaseAccount(email: email ?? '', password: ''),
|
||||
);
|
||||
return FirebaseRestClient(databaseUrl: kProject.databaseUrl, auth: auth);
|
||||
}
|
||||
|
||||
/// Whether this device can actually authenticate against Firebase.
|
||||
///
|
||||
/// True when either half of the state is present: the account marker
|
||||
/// [loadAccount] reads, or a stored refresh token. The token is the half that
|
||||
/// matters -- it is what signs requests -- so reporting the marker alone calls
|
||||
// coverage:ignore-end
|
||||
@ -0,0 +1,23 @@
|
||||
/// Whether this process is acting as the screen lock.
|
||||
///
|
||||
/// The Linux runner takes the X grab and drops the window decorations when
|
||||
/// `--lock-mode` is passed (see `linux/runner/my_application.cc`); this is the
|
||||
/// Dart half, which removes the ways *out* of the app. Both halves read the
|
||||
/// same argument so they can never disagree about which mode is live.
|
||||
///
|
||||
/// The distinction matters because a lock the user can dismiss is not a lock:
|
||||
/// with enforcement armed, leaving the workout early has to be impossible
|
||||
/// rather than merely discouraged.
|
||||
library;
|
||||
|
||||
/// True when the process was started with `--lock-mode`.
|
||||
///
|
||||
/// Set once from `main()`; defaults to false so every test and the ordinary
|
||||
/// windowed launch behave exactly as before.
|
||||
bool lockModeEnabled = false;
|
||||
|
||||
/// The flag the runner and the Dart side agree on.
|
||||
const String kLockModeFlag = '--lock-mode';
|
||||
|
||||
/// Returns whether [args] requests lock mode.
|
||||
bool parseLockMode(List<String> args) => args.contains(kLockModeFlag);
|
||||
@ -3,8 +3,10 @@ library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:workout_app/models/exercise.dart';
|
||||
import 'package:workout_app/models/workout_plan.dart';
|
||||
@ -103,7 +105,7 @@ class StorageService {
|
||||
final dbPath =
|
||||
_testDbPath ??
|
||||
// coverage:ignore-start
|
||||
p.join(await getDatabasesPath(), 'workout_app.db');
|
||||
p.join(await _databaseDirectory(), 'workout_app.db');
|
||||
// coverage:ignore-end
|
||||
_db = await openDatabase(
|
||||
dbPath,
|
||||
|
||||
@ -93,6 +93,19 @@ extension StorageServiceSchema on StorageService {
|
||||
}
|
||||
}
|
||||
|
||||
// coverage:ignore-start
|
||||
// Platform-channel / filesystem edge: Android answers from the sqflite
|
||||
// plugin, Linux desktop has no such plugin and uses an XDG directory under
|
||||
// the FFI factory wired up in main().
|
||||
Future<String> _databaseDirectory() async {
|
||||
if (Platform.isLinux) {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
return dir.path;
|
||||
}
|
||||
return getDatabasesPath();
|
||||
}
|
||||
// coverage:ignore-end
|
||||
|
||||
// ── Settings ───────────────────────────────────────────────────────────────
|
||||
|
||||
}
|
||||
|
||||
1
stronglift_replacement/workout_app/linux/.gitignore
vendored
Normal file
1
stronglift_replacement/workout_app/linux/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
flutter/ephemeral
|
||||
128
stronglift_replacement/workout_app/linux/CMakeLists.txt
Normal file
128
stronglift_replacement/workout_app/linux/CMakeLists.txt
Normal file
@ -0,0 +1,128 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "workout_app")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.kuhy.workout_app")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
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
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
@ -0,0 +1,88 @@
|
||||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
|
||||
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@ -0,0 +1,27 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
audioplayers_linux
|
||||
flutter_secure_storage_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, 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 preprocessor definitions for the application ID.
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
6
stronglift_replacement/workout_app/linux/runner/main.cc
Normal file
6
stronglift_replacement/workout_app/linux/runner/main.cc
Normal file
@ -0,0 +1,6 @@
|
||||
#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);
|
||||
}
|
||||
@ -0,0 +1,250 @@
|
||||
#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)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lock mode (--lock-mode): this window IS the screen lock.
|
||||
//
|
||||
// The Python locker (screen_locker) still owns the gatelock arbiter claim and
|
||||
// keeps holding the X grab while we start up, so our first grab attempts fail
|
||||
// by design. We map fullscreen + override-redirect FIRST -- covering the
|
||||
// screen before anything is released -- then print the ready line, and retry
|
||||
// the grab every 200ms until the supervisor drops its own. Ordering matters:
|
||||
// releasing before we are up would expose the live desktop for the whole of
|
||||
// Flutter's cold start.
|
||||
//
|
||||
// There is no Dart API for override-redirect or an X11 grab, which is why
|
||||
// this lives in the runner rather than in lib/.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Retry cadence for the grab, matching gatelock's _DEFAULT_GRAB_RETRY_MS.
|
||||
static const guint kGrabRetryMs = 200;
|
||||
|
||||
static gboolean lock_mode_enabled = FALSE;
|
||||
static gboolean lock_grab_held = FALSE;
|
||||
|
||||
// Takes the exclusive seat grab. Returns TRUE while it should keep retrying.
|
||||
static gboolean try_seat_grab(gpointer data) {
|
||||
GtkWidget* window = GTK_WIDGET(data);
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(window);
|
||||
if (gdk_window == nullptr) return G_SOURCE_CONTINUE; // not realized yet
|
||||
|
||||
GdkDisplay* display = gdk_window_get_display(gdk_window);
|
||||
GdkSeat* seat = gdk_display_get_default_seat(display);
|
||||
GdkGrabStatus status =
|
||||
gdk_seat_grab(seat, gdk_window, GDK_SEAT_CAPABILITY_ALL,
|
||||
TRUE, nullptr, nullptr, nullptr, nullptr);
|
||||
if (status != GDK_GRAB_SUCCESS) {
|
||||
// Expected until the supervisor releases. Never fatal: a lock that gave up
|
||||
// here would leave the machine unlocked, which is the whole failure mode
|
||||
// this design exists to prevent.
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
lock_grab_held = TRUE;
|
||||
g_print("WORKOUT_LOCK: grab acquired\n");
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// Applies override-redirect + fullscreen, then starts the grab retry loop.
|
||||
static void enter_lock_mode(GtkWindow* window) {
|
||||
GtkWidget* widget = GTK_WIDGET(window);
|
||||
gtk_window_set_decorated(window, FALSE);
|
||||
gtk_window_set_deletable(window, FALSE);
|
||||
gtk_window_set_keep_above(window, TRUE);
|
||||
|
||||
// An override-redirect window is invisible to the window manager, so
|
||||
// gtk_window_fullscreen() -- which merely ASKS the WM for fullscreen -- does
|
||||
// nothing here. Size and place it against the monitor geometry directly,
|
||||
// otherwise the window keeps its default 1280x720 and leaves an uncovered
|
||||
// strip of live desktop at the edge of a larger screen.
|
||||
GdkDisplay* display = gtk_widget_get_display(widget);
|
||||
GdkMonitor* monitor = gdk_display_get_primary_monitor(display);
|
||||
if (monitor == nullptr) monitor = gdk_display_get_monitor(display, 0);
|
||||
if (monitor != nullptr) {
|
||||
GdkRectangle geometry;
|
||||
gdk_monitor_get_geometry(monitor, &geometry);
|
||||
gtk_window_move(window, geometry.x, geometry.y);
|
||||
gtk_window_resize(window, geometry.width, geometry.height);
|
||||
}
|
||||
gtk_window_fullscreen(window);
|
||||
|
||||
// Realize (create the X window) without mapping it, so override-redirect is
|
||||
// set on an unmapped window.
|
||||
gtk_widget_realize(widget);
|
||||
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(widget);
|
||||
if (gdk_window != nullptr) {
|
||||
// Bypass the WM entirely: i3 would otherwise re-tile or let the user
|
||||
// move/close this window.
|
||||
gdk_window_set_override_redirect(gdk_window, TRUE);
|
||||
}
|
||||
g_timeout_add(kGrabRetryMs, try_seat_grab, widget);
|
||||
}
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
|
||||
if (lock_mode_enabled) {
|
||||
// Configure BEFORE the first map: toggling override-redirect on a window
|
||||
// that is already mapped leaves it mapped but never repainting (a blank
|
||||
// grey rectangle that still holds the grab -- the worst possible lock).
|
||||
enter_lock_mode(GTK_WINDOW(toplevel));
|
||||
gtk_widget_show(toplevel);
|
||||
// The supervisor releases its own grab only after seeing this line, so the
|
||||
// screen is already covered by us when it does. Flushed because stdout to
|
||||
// a pipe is block-buffered and the handshake would otherwise deadlock.
|
||||
g_print("WORKOUT_LOCK: ready\n");
|
||||
fflush(stdout);
|
||||
} else {
|
||||
gtk_widget_show(toplevel);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = !lock_mode_enabled;
|
||||
#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, "workout_app");
|
||||
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, "workout_app");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
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);
|
||||
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_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));
|
||||
|
||||
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);
|
||||
|
||||
// --lock-mode is handled here AND forwarded to Dart: the runner needs it
|
||||
// for the grab, the Dart side for the no-exit UI.
|
||||
for (gchar** arg = self->dart_entrypoint_arguments; *arg != nullptr; arg++) {
|
||||
if (g_strcmp0(*arg, "--lock-mode") == 0) {
|
||||
lock_mode_enabled = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
// MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// 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_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
// Set the program name to the application ID, which helps various systems
|
||||
// like GTK and desktop environments map this running application to its
|
||||
// corresponding .desktop file. This ensures better integration by allowing
|
||||
// the application to be recognized beyond its binary name.
|
||||
g_set_prgname(APPLICATION_ID);
|
||||
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID, "flags",
|
||||
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#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_
|
||||
@ -5,10 +5,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
sha256: be169cf6ac481e052c4538715d88841d567150dfe1df38aaec76461a4e7b39f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
version: "4.1.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -29,58 +29,58 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audioplayers
|
||||
sha256: "1d0c1b1f2095e59080e2d5046639096417a86687d89778da41b0c9a06d683dfd"
|
||||
sha256: "2ba4bb2944baacbdd5372ff8254a8e7feb8c10d7739545e392f5605a8f618745"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
version: "6.8.1"
|
||||
audioplayers_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_android
|
||||
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
|
||||
sha256: f5ff5b15620fbab8cb0849e9636c48e2b96c3f0f71723bbbe2ad3c761b205f05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
version: "5.3.0"
|
||||
audioplayers_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_darwin
|
||||
sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91
|
||||
sha256: "1ca553add991384ecf421b9569da850f3ab2472ffb83f6970b0416365abc51be"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
version: "6.5.0"
|
||||
audioplayers_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_linux
|
||||
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
|
||||
sha256: "15178b726b7cdee5364d0463c8d445630c4e0fb7d26612b73c767e7d25de9417"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
version: "4.3.0"
|
||||
audioplayers_platform_interface:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: audioplayers_platform_interface
|
||||
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
|
||||
sha256: "765f6f0e6dca55cb471c9483fc77700564b3484d19198aca4ebb5147c6c85acb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.1"
|
||||
version: "7.2.0"
|
||||
audioplayers_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_web
|
||||
sha256: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545"
|
||||
sha256: ae1e0103c865a03e273f6d13d97b93f5595eac09915729cd5e37ef96e2857319
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
version: "5.3.0"
|
||||
audioplayers_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audioplayers_windows
|
||||
sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a"
|
||||
sha256: a70ae82bba2dfcb6eb03dd4815d737a2d46d33ea5a96a03f535cfcaac490e413
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.1"
|
||||
version: "4.4.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -125,10 +125,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "67cf6d84013f9c601e42a6f8a6b74c4c0d9dc1a1619d775f2b28b732d3551b85"
|
||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -158,10 +158,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "6a642e1daa10190af89ba6cb6386c0df7d071a3592080bfe1e44faa63ae1df65"
|
||||
sha256: "0891702f96b2e465fe567b7ec448380e6b1c14f60af552a8536d9f583b6b8442"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.1.0"
|
||||
version: "13.2.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -251,18 +251,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5
|
||||
sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.3"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -349,10 +349,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.2"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -373,26 +373,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
||||
sha256: "1976370a4df3091bb0f72409c187ad1f9132a818bc6b95ca59c0bae1c75c688e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.1"
|
||||
version: "4.9.2"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||
sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.3"
|
||||
jni_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni_flutter
|
||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||
sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
jni_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni_util
|
||||
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -461,34 +469,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.0"
|
||||
version: "1.18.3"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96
|
||||
sha256: f9c168717100ae6d9fee9ffb0be379bf1f8b26b0f6bcbd4fdddcd931993a6a72
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.1"
|
||||
version: "0.19.2"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.1"
|
||||
version: "9.5.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "3.0.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -501,10 +509,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.1.6"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -525,18 +533,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.2"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -549,10 +557,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: ca045d03615023c08ccdb297aad46a9198193666039ddd36d4d85fd0b1864b98
|
||||
sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.2"
|
||||
version: "12.0.3"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -565,42 +573,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "447c18bc3c5fdea5c3039f042b2b365fd51e3634f5f6e269ed22c1f00071addc"
|
||||
sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.8"
|
||||
version: "9.6.1"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||
sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+5"
|
||||
version: "0.1.4+1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||
sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
version: "4.4.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
version: "0.2.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -621,10 +621,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
version: "6.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -653,10 +653,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.23"
|
||||
version: "2.4.27"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -714,58 +714,58 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
||||
sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+1"
|
||||
version: "2.4.3"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
||||
sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+3"
|
||||
version: "2.4.3"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: cce558075afe2a83f3fd7fc123acd6b090683e4f23910d44fbb31ecd7800b014
|
||||
sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.9"
|
||||
version: "2.5.11"
|
||||
sqflite_common_ffi:
|
||||
dependency: "direct dev"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: "3ddad0ec96ad411d5fea45b4912c3cd5743436c9e1890c26a6e688a32d901cae"
|
||||
sha256: d5564f1308cafbf064be498f2beb1e993e742985a7cca9e2e228d6cbccd84a05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2+1"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3+1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189"
|
||||
sha256: "4c7fe79840389aaeaf05fd093f795b631b5a98e2bd28d54e555c100f4a9c7a1c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
version: "3.5.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -803,10 +803,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153"
|
||||
sha256: "3a7b5d17422dd0f8d5c6c14feaa5a1c65638b9455f871a96f08437562c046931"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
version: "3.4.1+2"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -907,26 +907,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47
|
||||
sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.0"
|
||||
very_good_analysis:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: very_good_analysis
|
||||
sha256: d1cb1d66a5aae2c702d68caca6c8347306d35e728fd94555fa21fa0448a972e0
|
||||
sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
version: "10.3.0"
|
||||
vibration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: vibration
|
||||
sha256: "9bb06614c69260f8bd11c80fe01ed7988905cf00e3417d656c2647e41f261d87"
|
||||
sha256: c6c25eb7acadfd231925a60004736443cbf63f1eb5740d6dd83a1bc09cf00f6d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.8"
|
||||
version: "3.2.0"
|
||||
vibration_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -939,10 +939,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
version: "15.3.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -955,10 +955,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
|
||||
sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
version: "6.4.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -975,14 +975,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -27,6 +27,9 @@ dependencies:
|
||||
permission_handler: ^12.0.0
|
||||
shared_preferences: ^2.5.3
|
||||
sqflite: ^2.4.2
|
||||
# Linux desktop has no sqflite plugin; the FFI factory drives the same
|
||||
# schema (and already backed the test suite before the desktop target).
|
||||
sqflite_common_ffi: ^2.4.1
|
||||
sync_settings_ui:
|
||||
git:
|
||||
url: https://github.com/kuhyx/utils
|
||||
@ -44,7 +47,6 @@ dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
sqflite_common_ffi: ^2.4.1
|
||||
very_good_analysis: ^10.2.0
|
||||
|
||||
flutter:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user