mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Merge branch 'login' of github.com:OpenBikeControl/bikecontrol into login
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/utils/requirements/windows.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
@@ -43,6 +44,13 @@ class _LoginPageState extends State<LoginPage> {
|
||||
core.supabase.auth.signOut();
|
||||
},
|
||||
),
|
||||
if (kDebugMode && Platform.isWindows)
|
||||
Button.secondary(
|
||||
child: Text("Register"),
|
||||
onPressed: () {
|
||||
WindowsProtocolHandler().register("bikecontrol");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,9 @@ import 'package:bike_control/utils/windows_store_environment.dart';
|
||||
import 'package:bike_control/widgets/ui/toast.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:gotrue/src/types/auth_state.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:windows_iap/windows_iap.dart';
|
||||
|
||||
/// Windows-specific IAP service
|
||||
@@ -44,6 +47,32 @@ class WindowsIAPService {
|
||||
_lastCommandDate = await _prefs.read(key: _lastCommandDateKey);
|
||||
_dailyCommandCount = int.tryParse(await _prefs.read(key: _dailyCommandCountKey) ?? '0');
|
||||
_isInitialized = true;
|
||||
|
||||
|
||||
_authSubscription = core.supabase.auth.onAuthStateChange.listen((data) {
|
||||
final AuthChangeEvent event = data.event;
|
||||
final Session? session = data.session;
|
||||
|
||||
Logger.info('event: $event, session: ${session?.user.id} via ${session?.user.email}');
|
||||
|
||||
switch (event) {
|
||||
case AuthChangeEvent.initialSession:
|
||||
case AuthChangeEvent.signedIn:
|
||||
// handle signed in
|
||||
case AuthChangeEvent.signedOut:
|
||||
// handle signed out
|
||||
case AuthChangeEvent.passwordRecovery:
|
||||
// handle password recovery
|
||||
case AuthChangeEvent.tokenRefreshed:
|
||||
// handle token refreshed
|
||||
case AuthChangeEvent.userUpdated:
|
||||
// handle user updated
|
||||
case AuthChangeEvent.userDeleted:
|
||||
// handle user deleted
|
||||
case AuthChangeEvent.mfaChallengeVerified:
|
||||
// handle mfa challenge verified
|
||||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
recordError(e, s, context: 'Initializing');
|
||||
debugPrint('Failed to initialize Windows IAP: $e');
|
||||
@@ -108,6 +137,8 @@ class WindowsIAPService {
|
||||
/// Get the number of days remaining in the trial
|
||||
int trialDaysRemaining = 0;
|
||||
|
||||
late final StreamSubscription<AuthState> _authSubscription;
|
||||
|
||||
/// Check if the trial has expired
|
||||
bool get isTrialExpired {
|
||||
return !IAPManager.instance.isPurchased.value && hasTrialStarted && trialDaysRemaining <= 0;
|
||||
|
||||
70
lib/utils/requirements/windows.dart
Normal file
70
lib/utils/requirements/windows.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:win32/win32.dart';
|
||||
|
||||
const _hive = HKEY_CURRENT_USER;
|
||||
|
||||
class WindowsProtocolHandler {
|
||||
List<String> getArguments(List<String>? arguments) {
|
||||
if (arguments == null) return ['%s'];
|
||||
|
||||
if (arguments.isEmpty && !arguments.any((e) => e.contains('%s'))) {
|
||||
throw ArgumentError('arguments must contain at least 1 instance of "%s"');
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
void register(String scheme, {String? executable, List<String>? arguments}) {
|
||||
if (defaultTargetPlatform != TargetPlatform.windows) return;
|
||||
|
||||
final prefix = _regPrefix(scheme);
|
||||
final capitalized = scheme[0].toUpperCase() + scheme.substring(1);
|
||||
final args = getArguments(arguments).map((a) => _sanitize(a));
|
||||
final cmd = '${executable ?? Platform.resolvedExecutable} ${args.join(' ')}';
|
||||
|
||||
_regCreateStringKey(_hive, prefix, '', 'URL:$capitalized');
|
||||
_regCreateStringKey(_hive, prefix, 'URL Protocol', '');
|
||||
_regCreateStringKey(_hive, '$prefix\\shell\\open\\command', '', cmd);
|
||||
}
|
||||
|
||||
void unregister(String scheme) {
|
||||
if (defaultTargetPlatform != TargetPlatform.windows) return;
|
||||
|
||||
final txtKey = TEXT(_regPrefix(scheme));
|
||||
try {
|
||||
RegDeleteTree(HKEY_CURRENT_USER, txtKey);
|
||||
} finally {
|
||||
free(txtKey);
|
||||
}
|
||||
}
|
||||
|
||||
String _regPrefix(String scheme) => 'SOFTWARE\\Classes\\$scheme';
|
||||
|
||||
int _regCreateStringKey(int hKey, String key, String valueName, String data) {
|
||||
final txtKey = TEXT(key);
|
||||
final txtValue = TEXT(valueName);
|
||||
final txtData = TEXT(data);
|
||||
try {
|
||||
return RegSetKeyValue(
|
||||
hKey,
|
||||
txtKey,
|
||||
txtValue,
|
||||
REG_SZ,
|
||||
txtData,
|
||||
txtData.length * 2 + 2,
|
||||
);
|
||||
} finally {
|
||||
free(txtKey);
|
||||
free(txtValue);
|
||||
free(txtData);
|
||||
}
|
||||
}
|
||||
|
||||
String _sanitize(String value) {
|
||||
value = value.replaceAll(r'%s', '%1').replaceAll(r'"', '\\"');
|
||||
return '"$value"';
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/standard_method_codec.h>
|
||||
#include "app_links/app_links_plugin_c_api.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -44,9 +45,54 @@ namespace
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SendAppLinkToInstance(const std::wstring &title)
|
||||
{
|
||||
// Find our exact window
|
||||
HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", title.c_str());
|
||||
|
||||
if (hwnd)
|
||||
{
|
||||
// Dispatch new link to current window
|
||||
SendAppLink(hwnd);
|
||||
|
||||
// (Optional) Restore our window to front in same state
|
||||
WINDOWPLACEMENT place = {sizeof(WINDOWPLACEMENT)};
|
||||
GetWindowPlacement(hwnd, &place);
|
||||
|
||||
switch (place.showCmd)
|
||||
{
|
||||
case SW_SHOWMAXIMIZED:
|
||||
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
|
||||
break;
|
||||
case SW_SHOWMINIMIZED:
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
break;
|
||||
default:
|
||||
ShowWindow(hwnd, SW_NORMAL);
|
||||
break;
|
||||
}
|
||||
|
||||
SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
|
||||
SetForegroundWindow(hwnd);
|
||||
// END (Optional) Restore
|
||||
|
||||
// Window has been found, don't create another one.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command)
|
||||
{
|
||||
// Replace "example" with the generated title found as parameter of `window.Create` in this file.
|
||||
// You may ignore the result if you need to create another window.
|
||||
if (SendAppLinkToInstance(L"BikeControl"))
|
||||
{
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
|
||||
|
||||
Reference in New Issue
Block a user