From 73129eb7c2f30e07d63c13f6d27a8b3884bb2647 Mon Sep 17 00:00:00 2001 From: Andrew Dassonville Date: Mon, 3 Jul 2023 16:03:06 -0700 Subject: [PATCH] Add initial pin implementation --- lib/main.dart | 6 + lib/pages/joystick_page.dart | 11 +- lib/pages/pin_page.dart | 160 ++++++++++++++++++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 26 ++- pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 10 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 lib/pages/pin_page.dart diff --git a/lib/main.dart b/lib/main.dart index db65087..d8582e2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'domain/sentry.dart'; import 'models/connection_model.dart'; import 'pages/config_page.dart'; import 'pages/joystick_page.dart'; +import 'pages/pin_page.dart'; // ignore: unused_import import 'theme.dart'; @@ -38,6 +39,9 @@ class UrsaApp extends StatelessWidget { // bool isDarkMode = // MediaQuery.of(context).platformBrightness == Brightness.dark; + // TODO: get pin from storage + String? pin = '1234'; + return DynamicColorBuilder( builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { // lightDynamic = lightDynamic?.copyWith( @@ -57,6 +61,8 @@ class UrsaApp extends StatelessWidget { routes: { '/': (context) => const JoystickPage(), '/config': (context) => const ConfigPage(), + '/config-auth': (context) => + pin == null ? const ConfigPage() : PinputPage(pin: pin), }, ); }, diff --git a/lib/pages/joystick_page.dart b/lib/pages/joystick_page.dart index def55dd..1c741a7 100644 --- a/lib/pages/joystick_page.dart +++ b/lib/pages/joystick_page.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:math'; import 'package:flutter/foundation.dart'; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_joystick/flutter_joystick.dart' as joystick; import 'package:provider/provider.dart'; +import 'package:universal_platform/universal_platform.dart'; import '../components/enable.dart'; import '../components/joystick/joystick.dart'; @@ -75,7 +75,7 @@ class JoystickPageState extends State { @override Widget build(BuildContext context) { // ModelViewer is not supported on desktop (yet) - bool showModelViewer = Platform.isAndroid || Platform.isIOS || kIsWeb; + bool showModelViewer = UniversalPlatform.isAndroid || UniversalPlatform.isIOS || UniversalPlatform.isWeb; // bool showModelViewer = false; return Scaffold( @@ -87,7 +87,7 @@ class JoystickPageState extends State { tooltip: 'Open settings', onPressed: () { // navigate to config_page - Navigator.pushNamed(context, '/config'); + Navigator.pushNamed(context, '/config-auth'); }, ), ], @@ -148,7 +148,10 @@ class JoystickPageState extends State { const SizedBox(height: 10), SlideToEnable( height: clampDouble( - constraints.maxHeight / 4, 50, 70,), + constraints.maxHeight / 4, + 50, + 70, + ), enabled: _isEnabled, onStateChange: (value) { setEnabledCommand(value); diff --git a/lib/pages/pin_page.dart b/lib/pages/pin_page.dart new file mode 100644 index 0000000..69a8af3 --- /dev/null +++ b/lib/pages/pin_page.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:pinput/pinput.dart'; + +class PinputPage extends StatelessWidget { + final String pin; + + const PinputPage({Key? key, required this.pin}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + PinputExample(pin: pin), + ], + ), + ); + } +} + +/// This is the basic usage of Pinput +/// For more examples check out the demo directory +class PinputExample extends StatefulWidget { + final String pin; + + const PinputExample({Key? key, required this.pin}) : super(key: key); + + @override + State createState() => PinputExampleState(); +} + +class PinputExampleState extends State { + final pinController = TextEditingController(); + final focusNode = FocusNode(); + final formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + focusNode.requestFocus(); + } + + @override + void dispose() { + pinController.dispose(); + focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + const focusedBorderColor = Color.fromRGBO(23, 171, 144, 1); + const fillColor = Color.fromRGBO(243, 246, 249, 0); + const borderColor = Color.fromRGBO(23, 171, 144, 0.4); + + final defaultPinTheme = PinTheme( + width: 56, + height: 56, + textStyle: const TextStyle( + fontSize: 22, + color: Color.fromRGBO(30, 60, 87, 1), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(19), + border: Border.all(color: borderColor), + ), + ); + + /// Optionally you can use form to validate the Pinput + return Form( + key: formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Directionality( + // Specify direction if desired + textDirection: TextDirection.ltr, + child: Pinput( + controller: pinController, + focusNode: focusNode, + defaultPinTheme: defaultPinTheme, + obscureText: true, + closeKeyboardWhenCompleted: false, + hapticFeedbackType: HapticFeedbackType.lightImpact, + onCompleted: checkAuth, + cursor: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + margin: const EdgeInsets.only(bottom: 9), + width: 22, + height: 1, + color: focusedBorderColor, + ), + ], + ), + focusedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: focusedBorderColor), + ), + ), + submittedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + color: fillColor, + borderRadius: BorderRadius.circular(19), + border: Border.all(color: focusedBorderColor), + ), + ), + errorPinTheme: defaultPinTheme.copyBorderWith( + border: Border.all(color: Colors.redAccent), + ), + ), + ), + ], + ), + ); + } + + void checkAuth(String pin) { + if (validatePin(pin)) { + debugPrint('Success'); + + // Navigate to config page + Navigator.of(context).pushReplacementNamed('/config'); + } else { + debugPrint('Invalid pin'); + + // Show invalid PIN dialog + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Invalid PIN'), + content: const Text('Please try again'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + + setState(() { + pinController.clear(); + }); + } + } + + bool validatePin(String pin) { + return pin == widget.pin; + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2528735..d99fc4e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); + g_autoptr(FlPluginRegistrar) smart_auth_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin"); + smart_auth_plugin_register_with_registrar(smart_auth_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); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b942fff..5ce1ab5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color sentry_flutter + smart_auth url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9d9ebad..598e849 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import dynamic_color import package_info_plus import sentry_flutter import shared_preferences_foundation +import smart_auth import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -18,5 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 4a4582b..66ea8a4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -318,6 +318,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: "1773743c188cdd2f8d0398ea708ec72645bb41ac9311755c4f7bb03a4184bdcf" + url: "https://pub.dev" + source: hosted + version: "2.2.31" platform: dependency: transitive description: @@ -427,6 +435,14 @@ packages: description: flutter source: sdk version: "0.0.99" + smart_auth: + dependency: transitive + description: + name: smart_auth + sha256: "8cfaec55b77d5930ed1666bb7ae70db5bade099bb1422401386853b400962113" + url: "https://pub.dev" + source: hosted + version: "1.0.8" source_span: dependency: transitive description: @@ -483,6 +499,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_platform: + dependency: "direct main" + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" url_launcher: dependency: transitive description: @@ -621,4 +645,4 @@ packages: version: "6.3.0" sdks: dart: ">=3.0.2 <4.0.0" - flutter: ">=3.4.0-17.0.pre" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 08cecf8..251b8be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,8 @@ dependencies: sentry_flutter: ^7.7.0 app_settings: ^4.2.0 dynamic_color: ^1.6.5 + pinput: ^2.2.31 + universal_platform: ^1.0.0+1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7640cae..7986710 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); + SmartAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SmartAuthPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a4dd2d9..7999008 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus dynamic_color sentry_flutter + smart_auth url_launcher_windows )