mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-02-16 10:39:20 -06:00
[client] WIP: primary instance takeover
This commit is contained in:
@@ -97,7 +97,7 @@ class PhylumAccount extends Account<PhylumAccount> {
|
||||
if (errorResponse is PhylumApiErrorResponse) {
|
||||
if (errorResponse.code == "credentials_invalid") {
|
||||
logger.i('Invalid Credentials - Logging out');
|
||||
logOut();
|
||||
close(clearData: true);
|
||||
l.call();
|
||||
}
|
||||
}
|
||||
@@ -108,9 +108,11 @@ class PhylumAccount extends Account<PhylumAccount> {
|
||||
Future<void> transaction(Future<void> Function() fn) => db.transaction(fn);
|
||||
|
||||
@override
|
||||
Future<void> cleanup() async {
|
||||
await super.cleanup();
|
||||
await db.dropDatabase();
|
||||
Future<void> cleanup(bool clearData) async {
|
||||
await super.cleanup(clearData);
|
||||
if (clearData) {
|
||||
await db.dropDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<PhylumAccount> createFromLoginResponse(
|
||||
|
||||
@@ -3,31 +3,22 @@ import 'dart:io';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:phylum/ui/app/app.dart';
|
||||
import 'package:phylum/integrations/directories.dart';
|
||||
import 'package:phylum/libphylum/actions/deserializers.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/login/login_app.dart';
|
||||
import 'package:phylum/util/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'presence_stub.dart' if (dart.library.js_interop) 'presence_web.dart';
|
||||
import 'ui/account/presence_stub.dart' if (dart.library.js_interop) 'ui/account/presence_web.dart';
|
||||
|
||||
const storageDir = String.fromEnvironment("STORAGE_DIR");
|
||||
|
||||
void main() async {
|
||||
if (isAnother()) {
|
||||
runApp(const AnotherRunningMessage());
|
||||
return;
|
||||
}
|
||||
usePathUrlStrategy();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -62,57 +53,5 @@ void main() async {
|
||||
}
|
||||
Hive.registerAdapter(OfflineActionAdapter(actionDeserializers));
|
||||
|
||||
final accountManager = await AccountManager.restore((id) async {
|
||||
final account = PhylumAccount.restore(id: id);
|
||||
final err = await account.initialized;
|
||||
if (err != null) {
|
||||
logger.e("Unable to restore account: $err");
|
||||
return null;
|
||||
}
|
||||
return account;
|
||||
});
|
||||
|
||||
runApp(AccountSelector(accountManager: accountManager));
|
||||
}
|
||||
|
||||
class AnotherRunningMessage extends StatelessWidget {
|
||||
const AnotherRunningMessage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text('Phylum is running in another tab or window.'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountSelector extends StatelessWidget {
|
||||
final AccountManager<PhylumAccount> accountManager;
|
||||
|
||||
const AccountSelector({
|
||||
super.key,
|
||||
required this.accountManager,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
StateNotifierProvider<AccountManager<PhylumAccount>, AccountManagerState<PhylumAccount>>.value(
|
||||
value: accountManager,
|
||||
builder: (context, child) {
|
||||
final account =
|
||||
context.select<AccountManagerState<PhylumAccount>, PhylumAccount?>((state) => state.selectedAccount);
|
||||
|
||||
if (account == null) {
|
||||
return const LoginApp(key: ValueKey('login'));
|
||||
}
|
||||
return PhylumApp.create(account);
|
||||
});
|
||||
runApp(buildApp());
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
bool isAnother() => false;
|
||||
@@ -1,4 +0,0 @@
|
||||
import 'dart:js_interop';
|
||||
|
||||
@JS()
|
||||
external bool isAnother();
|
||||
62
client/lib/ui/account/account_selector.dart
Normal file
62
client/lib/ui/account/account_selector.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/ui/account/login_page.dart';
|
||||
import 'package:phylum/ui/app/app.dart';
|
||||
import 'package:phylum/ui/account/account_selector_app.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AccountSelector extends StatefulWidget {
|
||||
const AccountSelector({super.key});
|
||||
|
||||
@override
|
||||
State<AccountSelector> createState() => AccountSelectorState();
|
||||
}
|
||||
|
||||
class AccountSelectorState extends State<AccountSelector> {
|
||||
AccountManager<PhylumAccount>? _accountManager;
|
||||
bool _errorsDismissed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
AccountManager.restore((id) async {
|
||||
final account = PhylumAccount.restore(id: id);
|
||||
final err = await account.initialized;
|
||||
if (err != null) {
|
||||
throw err;
|
||||
}
|
||||
return account;
|
||||
}).then((accountManager) {
|
||||
setState(() {
|
||||
_accountManager = accountManager;
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accountManager = _accountManager;
|
||||
if (accountManager == null) {
|
||||
return Placeholder();
|
||||
}
|
||||
return StateNotifierProvider<AccountManager<PhylumAccount>, AccountManagerState<PhylumAccount>>.value(
|
||||
value: accountManager,
|
||||
builder: (context, child) {
|
||||
if (!_errorsDismissed) {}
|
||||
final account =
|
||||
context.select<AccountManagerState<PhylumAccount>, PhylumAccount?>((state) => state.selectedAccount);
|
||||
|
||||
if (account == null) {
|
||||
return const AccountSelectorApp(key: ValueKey('login'), child: LoginPage());
|
||||
}
|
||||
|
||||
return PhylumApp.create(account);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> closeAccountManager() => _accountManager?.close() ?? SynchronousFuture(null);
|
||||
}
|
||||
@@ -2,17 +2,16 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
|
||||
import 'login_page.dart';
|
||||
|
||||
class LoginApp extends StatefulWidget {
|
||||
const LoginApp({super.key});
|
||||
class AccountSelectorApp extends StatefulWidget {
|
||||
final Widget child;
|
||||
const AccountSelectorApp({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<LoginApp> createState() => _LoginAppState();
|
||||
State<AccountSelectorApp> createState() => _AccountSelectorAppState();
|
||||
}
|
||||
|
||||
class _LoginAppState extends State<LoginApp> {
|
||||
final delegate = LoginRouterDelegate();
|
||||
class _AccountSelectorAppState extends State<AccountSelectorApp> {
|
||||
late final delegate = AccountSelectorRouterDelegate(child: widget.child);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -27,7 +26,7 @@ class _LoginAppState extends State<LoginApp> {
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
return MaterialApp.router(
|
||||
title: 'Login | Phylum',
|
||||
title: 'Phylum',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
@@ -37,28 +36,28 @@ class _LoginAppState extends State<LoginApp> {
|
||||
}
|
||||
}
|
||||
|
||||
class LoginRouteConfiguration {
|
||||
class AccountSelectorRouteConfiguration {
|
||||
final String? next;
|
||||
|
||||
LoginRouteConfiguration(this.next);
|
||||
AccountSelectorRouteConfiguration(this.next);
|
||||
}
|
||||
|
||||
class LoginRouteInformationParser extends RouteInformationParser<LoginRouteConfiguration> {
|
||||
class LoginRouteInformationParser extends RouteInformationParser<AccountSelectorRouteConfiguration> {
|
||||
const LoginRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<LoginRouteConfiguration> parseRouteInformation(RouteInformation routeInformation) {
|
||||
Future<AccountSelectorRouteConfiguration> parseRouteInformation(RouteInformation routeInformation) {
|
||||
final uri = routeInformation.uri;
|
||||
final segments = uri.pathSegments;
|
||||
|
||||
final next = (segments.isEmpty || (segments.length == 1 && (segments[0] == 'home' || segments[0] == 'login')))
|
||||
? null
|
||||
: uri.path;
|
||||
return SynchronousFuture(LoginRouteConfiguration(next));
|
||||
return SynchronousFuture(AccountSelectorRouteConfiguration(next));
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation? restoreRouteInformation(LoginRouteConfiguration configuration) {
|
||||
RouteInformation? restoreRouteInformation(AccountSelectorRouteConfiguration configuration) {
|
||||
final b = UriBuilder()..path = '/login';
|
||||
if (configuration.next != null) {
|
||||
b.queryParameters['next'] = configuration.next!;
|
||||
@@ -68,27 +67,28 @@ class LoginRouteInformationParser extends RouteInformationParser<LoginRouteConfi
|
||||
}
|
||||
}
|
||||
|
||||
class LoginRouterDelegate extends RouterDelegate<LoginRouteConfiguration>
|
||||
with ChangeNotifier, PopNavigatorRouterDelegateMixin<LoginRouteConfiguration> {
|
||||
class AccountSelectorRouterDelegate extends RouterDelegate<AccountSelectorRouteConfiguration>
|
||||
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AccountSelectorRouteConfiguration> {
|
||||
final Widget child;
|
||||
@override
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
LoginRouterDelegate();
|
||||
AccountSelectorRouterDelegate({required this.child});
|
||||
|
||||
LoginRouteConfiguration? _currentConfiguration;
|
||||
AccountSelectorRouteConfiguration? _currentConfiguration;
|
||||
@override
|
||||
LoginRouteConfiguration? get currentConfiguration => _currentConfiguration;
|
||||
AccountSelectorRouteConfiguration? get currentConfiguration => _currentConfiguration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => child),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(LoginRouteConfiguration configuration) {
|
||||
Future<void> setNewRoutePath(AccountSelectorRouteConfiguration configuration) {
|
||||
_currentConfiguration = configuration;
|
||||
notifyListeners();
|
||||
return SynchronousFuture(null);
|
||||
@@ -17,7 +17,7 @@ class LogoutPage extends StatelessWidget {
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: state == AccountState.loggedOut ? buildLoggedOutView(context) : buildLoggingOutView(),
|
||||
child: state == AccountState.closed ? buildLoggedOutView(context) : buildLoggingOutView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
4
client/lib/ui/account/presence_stub.dart
Normal file
4
client/lib/ui/account/presence_stub.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/ui/account/account_selector.dart';
|
||||
|
||||
Widget buildApp() => AccountSelector();
|
||||
93
client/lib/ui/account/presence_web.dart
Normal file
93
client/lib/ui/account/presence_web.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/ui/account/account_selector.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
@JS()
|
||||
external bool isPrimary();
|
||||
|
||||
@JS()
|
||||
external void setPrimary(JSBoolean value);
|
||||
|
||||
Widget buildApp() => const PrimaryNegotiator();
|
||||
|
||||
class PrimaryNegotiator extends StatefulWidget {
|
||||
const PrimaryNegotiator({super.key});
|
||||
|
||||
@override
|
||||
State<PrimaryNegotiator> createState() => _PrimaryNegotiatorState();
|
||||
}
|
||||
|
||||
class _PrimaryNegotiatorState extends State<PrimaryNegotiator> {
|
||||
final key = GlobalKey<AccountSelectorState>();
|
||||
final channel = web.BroadcastChannel('takover');
|
||||
bool primary = isPrimary();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
channel.onmessage = processMessage.toJS;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (primary) {
|
||||
return AccountSelector(key: key);
|
||||
}
|
||||
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Phylum is running in another tab or window.'),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: Text('Use here'),
|
||||
onPressed: () {
|
||||
channel.postMessage('close'.toJS);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void processMessage(web.MessageEvent event) {
|
||||
final msg = event.data.toString();
|
||||
if (msg == 'close') {
|
||||
final state = key.currentState;
|
||||
if (state != null) {
|
||||
channel.postMessage('closing'.toJS);
|
||||
state.closeAccountManager().then((v) {
|
||||
channel.postMessage('closed'.toJS);
|
||||
setPrimary(false.toJS);
|
||||
setState(() {
|
||||
primary = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
if (msg == 'closed') {
|
||||
setPrimary(true.toJS);
|
||||
setState(() {
|
||||
primary = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/integrations/download_manager.dart';
|
||||
import 'package:phylum/ui/account/account_selector_app.dart';
|
||||
import 'package:phylum/ui/account/logout_page.dart';
|
||||
import 'package:phylum/ui/app/app_shortcuts.dart';
|
||||
import 'package:phylum/ui/app/router.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/libphylum/phylum_api_types.dart';
|
||||
import 'package:phylum/ui/app/action_queue_status_notifier.dart';
|
||||
import 'package:phylum/ui/logout/logout_app.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PhylumApp extends StatefulWidget {
|
||||
@@ -55,7 +56,6 @@ class _PhylumAppState extends State<PhylumApp> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ready = context.select<AccountState, bool>((state) => state == AccountState.ready);
|
||||
final theme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Color(0xff769e57),
|
||||
@@ -66,18 +66,21 @@ class _PhylumAppState extends State<PhylumApp> {
|
||||
colorScheme: theme.colorScheme.copyWith(brightness: Brightness.dark),
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
if (ready) {
|
||||
return MaterialApp.router(
|
||||
key: ValueKey('ready'),
|
||||
title: 'Phylum',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
routeInformationParser: _routeInformationParser,
|
||||
routerDelegate: _routerDelegate,
|
||||
shortcuts: appShortcuts,
|
||||
);
|
||||
|
||||
final ready = context.select<AccountState, bool>((state) => state == AccountState.ready);
|
||||
|
||||
if (!ready) {
|
||||
return const AccountSelectorApp(key: ValueKey('logout'), child: LogoutPage());
|
||||
}
|
||||
return const LogoutApp(key: ValueKey('logout'));
|
||||
return MaterialApp.router(
|
||||
key: ValueKey('ready'),
|
||||
title: 'Phylum',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
routeInformationParser: _routeInformationParser,
|
||||
routerDelegate: _routerDelegate,
|
||||
shortcuts: appShortcuts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class NavList extends StatelessWidget {
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Log Out'),
|
||||
onTap: () {
|
||||
context.read<PhylumAccount>().logOut();
|
||||
context.read<PhylumAccount>().close(clearData: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/ui/logout/logout_page.dart';
|
||||
|
||||
class LogoutApp extends StatefulWidget {
|
||||
const LogoutApp({super.key});
|
||||
|
||||
@override
|
||||
State<LogoutApp> createState() => _LoginAppState();
|
||||
}
|
||||
|
||||
class _LoginAppState extends State<LogoutApp> {
|
||||
final delegate = LogoutRouterDelegate();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Color(0xff769e57),
|
||||
dynamicSchemeVariant: DynamicSchemeVariant.neutral,
|
||||
),
|
||||
);
|
||||
final darkTheme = ThemeData(
|
||||
colorScheme: theme.colorScheme.copyWith(brightness: Brightness.dark),
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
return MaterialApp.router(
|
||||
title: 'Login | Phylum',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme,
|
||||
darkTheme: darkTheme,
|
||||
routeInformationParser: const LogoutRouteInformationParser(),
|
||||
routerDelegate: delegate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LogoutRouteConfiguration {
|
||||
const LogoutRouteConfiguration();
|
||||
}
|
||||
|
||||
class LogoutRouteInformationParser extends RouteInformationParser<LogoutRouteConfiguration> {
|
||||
const LogoutRouteInformationParser();
|
||||
|
||||
@override
|
||||
Future<LogoutRouteConfiguration> parseRouteInformation(RouteInformation routeInformation) {
|
||||
return SynchronousFuture(const LogoutRouteConfiguration());
|
||||
}
|
||||
|
||||
@override
|
||||
RouteInformation? restoreRouteInformation(LogoutRouteConfiguration configuration) {
|
||||
return RouteInformation(uri: Uri(path: '/logout'));
|
||||
}
|
||||
}
|
||||
|
||||
class LogoutRouterDelegate extends RouterDelegate<LogoutRouteConfiguration>
|
||||
with ChangeNotifier, PopNavigatorRouterDelegateMixin<LogoutRouteConfiguration> {
|
||||
@override
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
LogoutRouterDelegate();
|
||||
|
||||
LogoutRouteConfiguration? _currentConfiguration;
|
||||
@override
|
||||
LogoutRouteConfiguration? get currentConfiguration => _currentConfiguration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => const LogoutPage()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(LogoutRouteConfiguration configuration) {
|
||||
_currentConfiguration = configuration;
|
||||
notifyListeners();
|
||||
return SynchronousFuture(null);
|
||||
}
|
||||
}
|
||||
@@ -478,10 +478,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hive_ce
|
||||
sha256: fdc19336f03ecd01dbc1d1afe69d87ed9336bdf996c5374a25f9c21ef5f2989e
|
||||
sha256: "192b7334299e3672efa1f85d544fef0091c5c592be5caada61417e37723addc6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.1"
|
||||
version: "2.11.2"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -662,8 +662,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "2a01d7ce8e8c0964a133c74b2b019d66fd611a69"
|
||||
resolved-ref: "2a01d7ce8e8c0964a133c74b2b019d66fd611a69"
|
||||
ref: "2365704ff865d530169124600b4753f51656e815"
|
||||
resolved-ref: "2365704ff865d530169124600b4753f51656e815"
|
||||
url: "https://codeberg.org/shroff/offtheline.git"
|
||||
source: git
|
||||
version: "0.16.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ publish_to: 'none'
|
||||
version: 0.3.0+30
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
sdk: ^3.6.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -27,7 +27,7 @@ dependencies:
|
||||
offtheline:
|
||||
git:
|
||||
url: https://codeberg.org/shroff/offtheline.git
|
||||
ref: 2a01d7ce8e8c0964a133c74b2b019d66fd611a69
|
||||
ref: 2365704ff865d530169124600b4753f51656e815
|
||||
open_file:
|
||||
package_info_plus:
|
||||
path:
|
||||
@@ -68,4 +68,4 @@ dependency_overrides:
|
||||
git:
|
||||
url: https://github.com/moshe5745/flutter_keyboard_visibility.git
|
||||
ref: b8393c50c483fa6fd16148d43dff454e6e845201
|
||||
path: flutter_keyboard_visibility_web
|
||||
path: flutter_keyboard_visibility_web
|
||||
@@ -36,18 +36,24 @@
|
||||
<body oncontextmenu="return false;">
|
||||
<script>
|
||||
const channel = new BroadcastChannel("the_one");
|
||||
var another = false;
|
||||
var primary = true;
|
||||
|
||||
channel.onmessage = (event) => {
|
||||
if (event.data == 'ping') {
|
||||
if (event.data == 'ping' && primary) {
|
||||
channel.postMessage('pong');
|
||||
}
|
||||
if (event.data == 'pong') {
|
||||
another = true;
|
||||
primary = false;
|
||||
}
|
||||
};
|
||||
channel.postMessage("ping");
|
||||
function isAnother() {
|
||||
return another;
|
||||
|
||||
function setPrimary(value) {
|
||||
primary = value;
|
||||
}
|
||||
|
||||
function isPrimary() {
|
||||
return primary;
|
||||
}
|
||||
</script>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
|
||||
Reference in New Issue
Block a user