From d5f34efd45a0cae3b99d590ab9691c58dc098596 Mon Sep 17 00:00:00 2001 From: Paul_GD Date: Sun, 18 Feb 2024 19:26:23 +0100 Subject: [PATCH] Merge development rework into dev (#4) * wip * fix to work on macos for dev * (wip) virtual keyboard * (fix) revert theme changes * (fix) change label naming for error screens * (feat) fix regex for wifi on macos, add refresh * (etc) various changes * (fix) Files screen jumps back to top when changing directory. * (etc) Change placeholder values * (dev) Move development tests to seperate Debug entry in Settings * (feat) Add Print Error screen. * (feat) Add Brightness Control to colors. * (feat) Initial implementation of custom native onscreen keyboard --------- Co-authored-by: Ibrahim Mourade --- README.md | 19 +- android/build.gradle | 2 +- ios/Podfile | 2 +- ios/Podfile.lock | 25 +- ios/Runner.xcodeproj/project.pbxproj | 7 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/files/details_screen.dart | 2 +- lib/files/files_screen.dart | 47 +- lib/home/home_screen.dart | 38 +- lib/main.dart | 14 +- lib/settings/about_screen.dart | 55 ++- lib/settings/calibrate_screen.dart | 2 +- lib/settings/debug_screen.dart | 64 +++ lib/settings/settings_screen.dart | 23 +- lib/settings/wifi_screen.dart | 339 ++++++++----- lib/status/error_print_failure.dart | 208 ++++++++ lib/status/fatal_error_screen.dart | 25 +- lib/status/normal_error_screen.dart | 25 +- lib/status/status_screen.dart | 37 +- lib/themes/themes.dart | 28 ++ lib/util/orion_kb_container.dart | 37 ++ lib/util/orion_keyboard.dart | 222 +++++++++ lib/util/orion_keyboard_modal.dart | 67 +++ lib/util/orion_textfield.dart | 67 +++ macos/Flutter/GeneratedPluginRegistrant.swift | 4 +- macos/Podfile.lock | 23 +- macos/Runner.xcodeproj/project.pbxproj | 9 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- pubspec.lock | 446 ++++++++++++------ pubspec.yaml | 16 +- 30 files changed, 1417 insertions(+), 440 deletions(-) create mode 100644 lib/settings/debug_screen.dart create mode 100644 lib/status/error_print_failure.dart create mode 100644 lib/themes/themes.dart create mode 100644 lib/util/orion_kb_container.dart create mode 100644 lib/util/orion_keyboard.dart create mode 100644 lib/util/orion_keyboard_modal.dart create mode 100644 lib/util/orion_textfield.dart diff --git a/README.md b/README.md index 6211dc8..de0b56e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,3 @@ -# orion - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +# Orion +---------------------------------- +Orion is the front-end user interface for mSLA printers that run our Odyssey print engine. diff --git a/android/build.gradle b/android/build.gradle index 83ae220..3cdaac9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/ios/Podfile b/ios/Podfile index 88359b2..fe628cb 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +platform :ios, '17.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 645b902..f032ba3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,41 +1,48 @@ PODS: - Flutter (1.0.0) + - package_info (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - permission_handler_apple (9.1.1): - Flutter - - shared_preferences_ios (0.0.1): + - shared_preferences_foundation (0.0.1): - Flutter + - FlutterMacOS - wifi_info_flutter (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - package_info (from `.symlinks/plugins/package_info/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - wifi_info_flutter (from `.symlinks/plugins/wifi_info_flutter/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter + package_info: + :path: ".symlinks/plugins/package_info/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" wifi_info_flutter: :path: ".symlinks/plugins/wifi_info_flutter/ios" SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 wifi_info_flutter: 55d4c2469034f5b5c38063ccefa62f708a503a2b -PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 +PODFILE CHECKSUM: 5a367937f10bf0c459576e5e472a1159ee029c13 -COCOAPODS: 1.14.2 +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 0fda363..8b036a4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -222,10 +222,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -236,6 +238,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..a6b826d 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ _FilesScreenState(); @@ -55,7 +70,7 @@ class _FilesScreenState extends State { @override void initState() { - _directory = Directory('/Users/${Platform.environment['USER']}/Downloads'); + _directory = getInitialDir(context); _files = []; super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -79,14 +94,13 @@ class _FilesScreenState extends State { return a.path.toLowerCase().compareTo(b.path.toLowerCase()); }); }); - _sortByAlpha = false; - _toggleSortOrder(); + //_sortByAlpha = false; + //_toggleSortOrder(); }); }); } void refresh() { - _getFiles(); setState( () { _files = _directory @@ -132,9 +146,7 @@ class _FilesScreenState extends State { } Future _getFiles() async { - //final Directory directory = Directory.systemTemp; - final Directory directory = - Directory('/Users/${Platform.environment['USER']}/Downloads'); + final Directory directory = getInitialDir(Theme.of(context).platform); List files = getAccessibleDirectories(directory); files = files .where((file) => @@ -181,7 +193,14 @@ class _FilesScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_directory.path.replaceFirst('/Users/paul/', '')), + title: Text( + path.basename(_directory.path) == 'printer_files' + ? 'Print Files' + : path.basename(_directory.path) == 'Download' || + path.basename(_directory.path) == "Downloads" + ? path.basename(_directory.path) + : _directory.path, + ), actions: [ Padding( padding: const EdgeInsets.only(right: 5.0), @@ -221,13 +240,14 @@ class _FilesScreenState extends State { body: _directory == null ? const Center(child: CircularProgressIndicator()) : ListView.builder( + controller: _scrollController, itemCount: _files.length + 1, itemBuilder: (BuildContext context, int index) { if (index == 0) { return ListTile( leading: const Icon(Icons.subdirectory_arrow_left_rounded), - title: Row( - children: const [ + title: const Row( + children: [ Text('Leave Directory', style: TextStyle(fontSize: 24)), ], ), @@ -305,6 +325,7 @@ class _FilesScreenState extends State { onTap: () { try { if (file is Directory) { + _scrollController.jumpTo(0.0); setState(() { _directory = file; _files = file diff --git a/lib/home/home_screen.dart b/lib/home/home_screen.dart index b40df5c..83e166c 100644 --- a/lib/home/home_screen.dart +++ b/lib/home/home_screen.dart @@ -5,10 +5,18 @@ import 'package:go_router/go_router.dart'; class HomeScreen extends StatelessWidget { /// Constructs a [HomeScreen] - const HomeScreen({Key? key}) : super(key: key); - + const HomeScreen({super.key}); @override Widget build(BuildContext context) { + const Size homeBtnSize = Size(200, 110); + final theme = Theme.of(context).copyWith( + elevatedButtonTheme: ElevatedButtonThemeData(style: ButtonStyle( + minimumSize: MaterialStateProperty.resolveWith( + (Set states) { + return homeBtnSize; + }, + ), + ))); return Scaffold( appBar: AppBar( title: Stack( @@ -37,14 +45,12 @@ class HomeScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( + style: theme.elevatedButtonTheme.style, onPressed: () => context.go('/status'), - style: ElevatedButton.styleFrom( - minimumSize: const Size(200, 110), - ), - child: Column( + child: const Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon(Icons.info_outline, size: 48), Text('Status', style: TextStyle(fontSize: 24)), ], @@ -52,14 +58,12 @@ class HomeScreen extends StatelessWidget { ), const SizedBox(width: 20), ElevatedButton( + style: theme.elevatedButtonTheme.style, onPressed: () => context.go('/files'), - style: ElevatedButton.styleFrom( - minimumSize: const Size(200, 110), - ), - child: Column( + child: const Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon(Icons.folder_open_outlined, size: 48), Text('Print Files', style: TextStyle(fontSize: 24)), ], @@ -67,14 +71,12 @@ class HomeScreen extends StatelessWidget { ), const SizedBox(width: 20), ElevatedButton( + style: theme.elevatedButtonTheme.style, onPressed: () => context.go('/settings'), - style: ElevatedButton.styleFrom( - minimumSize: const Size(200, 110), - ), - child: Column( + child: const Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, - children: const [ + children: [ Icon(Icons.settings_outlined, size: 48), Text('Settings', style: TextStyle(fontSize: 24)), ], @@ -90,7 +92,7 @@ class HomeScreen extends StatelessWidget { /// A live clock widget class LiveClock extends StatefulWidget { /// Constructs a [LiveClock] - const LiveClock({Key? key}) : super(key: key); + const LiveClock({super.key}); @override // ignore: library_private_types_in_public_api diff --git a/lib/main.dart b/lib/main.dart index ecf1acf..a9e62a1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:orion/themes/themes.dart'; import 'home/home_screen.dart'; import 'status/status_screen.dart'; @@ -80,7 +81,7 @@ final GoRouter _router = GoRouter( /// The main app. class Orion extends StatelessWidget { /// Constructs a [Orion] - const Orion({Key? key}) : super(key: key); + const Orion({super.key}); @override Widget build(BuildContext context) { @@ -91,15 +92,8 @@ class Orion extends StatelessWidget { child: MaterialApp.router( debugShowCheckedModeBanner: false, routerConfig: _router, - theme: ThemeData( - brightness: Brightness.light, - colorSchemeSeed: const Color(0xff6750a4), - useMaterial3: true), - darkTheme: ThemeData( - brightness: Brightness.dark, - colorSchemeSeed: const Color(0xff6750a4), - useMaterial3: true, - ), + theme: themeLight, + darkTheme: themeDark, themeMode: ThemeMode.system, ), ); diff --git a/lib/settings/about_screen.dart b/lib/settings/about_screen.dart index 464fc29..79226e0 100644 --- a/lib/settings/about_screen.dart +++ b/lib/settings/about_screen.dart @@ -1,9 +1,18 @@ +// ignore_for_file: avoid_print + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:package_info/package_info.dart'; + +Future getVersionNumber() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return '${packageInfo.version} | Build ${packageInfo.buildNumber}'; +} class AboutScreen extends StatefulWidget { - const AboutScreen({Key? key}) : super(key: key); + const AboutScreen({super.key}); @override // ignore: library_private_types_in_public_api @@ -48,12 +57,14 @@ class _AboutScreenState extends State { }); }); - const String title = 'Prometheus mSLA'; - const String serialNumber = 'Serial: CS-PROM2023DEV'; - const String orionVersion = 'Orion: 0.1.0 Alpha'; - const String apiVersion = 'Odyssey: 0.1.0 Alpha'; - const String boardType = 'Board: Apollo 3.5.2'; - const String warranty = 'Warranty Date: 0000-00-00'; + const String title = kDebugMode ? 'Debug Machine' : 'Prometheus mSLA'; + const String serialNumber = + kDebugMode ? 'S/N: DBG-0001-001' : 'No S/N Available'; + const String apiVersion = + kDebugMode ? 'Odyssey: Simulated' : 'Odyssey: 0.1.0 Alpha'; + const String boardType = + kDebugMode ? 'Hardware: Debugger' : 'Hardware: Apollo 3.5.2'; + const String warranty = 'No Warranty Available'; return Scaffold( body: Stack( @@ -97,10 +108,19 @@ class _AboutScreenState extends State { child: Padding( padding: EdgeInsets.only(left: leftPadding), child: FittedBox( - child: Text( - orionVersion, - key: textKey3, - style: TextStyle(fontSize: 20, color: _standardColor), + child: FutureBuilder( + future: getVersionNumber(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return Text('Orion: ${snapshot.data}', + key: textKey3, + style: TextStyle( + fontSize: 20, color: _standardColor)); + } else { + return const CircularProgressIndicator(); + } + }, ), ), ), @@ -147,7 +167,7 @@ class _AboutScreenState extends State { ), ), ), - //const SizedBox(height: kToolbarHeight / 2), //TODO: Figure out why centered text looks off + const SizedBox(height: kToolbarHeight / 2), ], ), Align( @@ -157,13 +177,16 @@ class _AboutScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImage( - data: 'https://github.com/TheContrappostoShop', + QrImageView( + data: 'https://github.com/TheContrappostoShop/Orion', version: QrVersions.auto, size: 220.0, - foregroundColor: _qrColor, + eyeStyle: QrEyeStyle(color: _qrColor), + dataModuleStyle: QrDataModuleStyle( + color: _qrColor, + dataModuleShape: QrDataModuleShape.circle), ), - //const SizedBox(height: kToolbarHeight / 2), + const SizedBox(height: kToolbarHeight / 2), ], ), ), diff --git a/lib/settings/calibrate_screen.dart b/lib/settings/calibrate_screen.dart index 1481829..13f8a0c 100644 --- a/lib/settings/calibrate_screen.dart +++ b/lib/settings/calibrate_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; /// The calibrate screen class CalibrateScreen extends StatelessWidget { /// Constructs a [CalibrateScreen] - const CalibrateScreen({Key? key}) : super(key: key); + const CalibrateScreen({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/settings/debug_screen.dart b/lib/settings/debug_screen.dart new file mode 100644 index 0000000..32a0b72 --- /dev/null +++ b/lib/settings/debug_screen.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:orion/status/error_print_failure.dart'; +import 'package:orion/status/fatal_error_screen.dart'; +import 'package:orion/status/normal_error_screen.dart'; +import 'package:orion/util/orion_kb_container.dart'; +import 'package:orion/util/orion_keyboard_modal.dart'; + +/// The calibrate screen +class DebugScreen extends StatelessWidget { + /// Constructs a [DebugScreen] + const DebugScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Text( + 'Orion Debug - OrionKbModal, OrionKbContainer, OrionTextField', + style: TextStyle(fontSize: 14, color: Colors.red), + ), + ), + /*ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ErrorScreen()), + ); + }, + child: const Text('Go to Error Screen'), + ), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const FatalScreen()), + ); + }, + child: const Text('Go to Fatal Error Screen'), + ), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PrintErrorScreen()), + ); + }, + child: const Text('Go to Print Error Screen'), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).push(OrionKbModal()); + }, + child: const Text('Open Keyboard'), + ),*/ + const SpawnOrionKB(), + ], + ), + ); + } +} diff --git a/lib/settings/settings_screen.dart b/lib/settings/settings_screen.dart index 96c315c..90f902f 100644 --- a/lib/settings/settings_screen.dart +++ b/lib/settings/settings_screen.dart @@ -1,4 +1,6 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:orion/settings/debug_screen.dart'; import 'calibrate_screen.dart'; import 'wifi_screen.dart'; @@ -7,7 +9,7 @@ import 'about_screen.dart'; /// The settings screen class SettingsScreen extends StatefulWidget { /// Constructs a [SettingsScreen] - const SettingsScreen({Key? key}) : super(key: key); + const SettingsScreen({super.key}); @override // ignore: library_private_types_in_public_api @@ -23,16 +25,26 @@ class _SettingsScreenState extends State { }); } + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Settings')), + appBar: AppBar( + title: const Text('Settings'), + ), body: _selectedIndex == 0 ? const CalibrateScreen() : _selectedIndex == 1 ? const WifiScreen() - : const AboutScreen(), + : _selectedIndex == 2 + ? const AboutScreen() + : const DebugScreen(), bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, items: const [ BottomNavigationBarItem( icon: Icon(Icons.settings), @@ -46,6 +58,11 @@ class _SettingsScreenState extends State { icon: Icon(Icons.info), label: 'About', ), + if (kDebugMode) + BottomNavigationBarItem( + icon: Icon(Icons.bug_report), + label: 'Debug', + ), ], currentIndex: _selectedIndex, selectedItemColor: Theme.of(context).colorScheme.primary, diff --git a/lib/settings/wifi_screen.dart b/lib/settings/wifi_screen.dart index ecf51e4..298fdff 100644 --- a/lib/settings/wifi_screen.dart +++ b/lib/settings/wifi_screen.dart @@ -1,151 +1,240 @@ -import 'package:flutter/foundation.dart'; +// ignore_for_file: avoid_print, use_build_context_synchronously, library_private_types_in_public_api +import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:wifi_flutter/wifi_flutter.dart'; -import 'package:wifi_info_flutter/wifi_info_flutter.dart'; -abstract class CommonInterface { - String get ssid; - int get signalStrength; +class WifiScreen extends StatefulWidget { + const WifiScreen({super.key}); + + @override + _WifiScreenState createState() => _WifiScreenState(); } -class MockWifiNetwork implements CommonInterface { +class _WifiScreenState extends State { + List wifiNetworks = []; + String? currentWifiSSID; + Future>>? _networksFuture; + @override - final String ssid; + void initState() { + super.initState(); + } + @override - final int signalStrength; + void didChangeDependencies() { + super.didChangeDependencies(); + _networksFuture = _getWifiNetworks(); + } - MockWifiNetwork({required this.ssid, required this.signalStrength}); -} + Icon getSignalStrengthIcon(dynamic signalStrengthReceived, String platform) { + int signalStrength = 0; + try { + signalStrength = int.parse(signalStrengthReceived); + } catch (e) { + print(e); + return const Icon(Icons.warning_rounded); + } + + var icons = { + 80: Icon(Icons.network_wifi_rounded, color: Colors.green[300]), + 60: Icon(Icons.network_wifi_3_bar_rounded, color: Colors.green[300]), + 40: Icon(Icons.network_wifi_2_bar_rounded, color: Colors.orange[300]), + 20: Icon(Icons.network_wifi_1_bar_rounded, color: Colors.orange[300]), + 0: Icon(Icons.warning_rounded, color: Colors.red[300]), + }; -class WifiScreen extends StatelessWidget { - const WifiScreen({Key? key}) : super(key: key); - - IconData getSignalStrengthIcon(int signalStrength) { - if (signalStrength >= 80) { - return Icons.network_wifi_rounded; - } else if (signalStrength >= 60) { - return Icons.network_wifi_3_bar_rounded; - } else if (signalStrength >= 40) { - return Icons.network_wifi_2_bar_rounded; - } else if (signalStrength >= 20) { - return Icons.network_wifi_1_bar_rounded; + if (platform == 'linux') { + for (var threshold in icons.keys.toList().reversed) { + if (signalStrength >= threshold) { + return icons[threshold]!; + } + } } else { - return Icons.warning_rounded; + icons = { + -0: Icon(Icons.network_wifi_rounded, color: Colors.green[300]), + -50: Icon(Icons.network_wifi_3_bar_rounded, color: Colors.green[300]), + -70: Icon(Icons.network_wifi_2_bar_rounded, color: Colors.orange[300]), + -90: Icon(Icons.warning_rounded, color: Colors.red[300]), + }; + + signalStrength = signalStrength.abs(); + for (var threshold in icons.keys.toList().reversed) { + if (signalStrength >= threshold.abs()) { + return icons[threshold]!; + } + } } + return const Icon(Icons.warning_rounded); } - Future> getWifiNetworks(BuildContext context) async { - // Check if the app is running on macOS - if (Theme.of(context).platform == TargetPlatform.macOS) { - // Return a list of 10 random placeholder networks - List mockNetworks = - List.generate(10, (int index) { - return MockWifiNetwork( - ssid: 'Mock Network $index', signalStrength: index * 10); - }); - mockNetworks.sort((a, b) => b.signalStrength.compareTo(a.signalStrength)); - return mockNetworks; - } else { - // Fetch the list of available WiFi networks - final Iterable wifiNetworks = await WifiFlutter.wifiNetworks; - final List sortedWifiNetworks = - wifiNetworks.map((WifiNetwork wifiNetwork) { - return wifiNetwork as CommonInterface; - }).toList(); - sortedWifiNetworks - .sort((a, b) => b.signalStrength.compareTo(a.signalStrength)); - return sortedWifiNetworks; + late String platform; + Future>> _getWifiNetworks() async { + wifiNetworks.clear(); + try { + ProcessResult? result; + ProcessResult? currentSSID; + switch (Theme.of(context).platform) { + case TargetPlatform.macOS: + result = await Process.run( + '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport', + ['-s']); + currentSSID = await Process.run( + '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport', + ['-I'], + ); + platform = 'macos'; + final match = + RegExp(r'SSID: (.*)').firstMatch(currentSSID.stdout.toString()); + currentWifiSSID = match?.group(1)?.trim() ?? ''; + break; + case TargetPlatform.linux: + result = await Process.run('nmcli', ['device', 'wifi', 'list']); + platform = 'linux'; + break; + default: + } + if (result?.exitCode == 0) { + final List> networks = []; + final List lines = result!.stdout.toString().split('\n'); + + RegExp pattern = RegExp(''); + + switch (Theme.of(context).platform) { + case TargetPlatform.macOS: + pattern = RegExp( + r'^\s*(.+?)\s{2,}(.+?)\s{2,}([^]+?)\s{2,}([^]+?)\s{2,}([^]+)$'); + break; + case TargetPlatform.linux: + pattern = RegExp(''); + break; + default: + } + + // Skip the first two lines (header and separator) + for (int i = 2; i < lines.length; i++) { + final RegExpMatch? match = pattern.firstMatch(lines[i]); + + if (match != null) { + /*if (kDebugMode) { + print('---------------------------'); + for (int i = 1; i < match.groupCount; i++) { + print('Group $i: ${match.group(i)}'); + } + }*/ + + networks.add({ + 'SSID': match.group(1) ?? '', + 'SIGNAL': match.group(2) ?? '', + }); + } + } + print('Current Network: $currentWifiSSID'); + // Sort by strongest signal + networks.sort((a, b) { + if (a['SSID'] == currentWifiSSID) { + return -1; + } else if (b['SSID'] == currentWifiSSID) { + return 1; + } else { + int signalA = int.parse(a['SIGNAL'] ?? '0'); + int signalB = int.parse(b['SIGNAL'] ?? '0'); + return signalB.compareTo(signalA); + } + }); + return mergeNetworks(networks); + } else { + print('Failed to get Wi-Fi networks'); + return []; + } + } catch (e) { + print('Error: $e'); + return []; } } - Future getWifiIP(BuildContext context) async { - // Check if the app is running on macOS - if (Theme.of(context).platform == TargetPlatform.macOS) { - // Return a mock IP address - return Future.value('192.168.1.1'); - } else { - // Fetch the IP address of the connected WiFi network - String? wifiIP = await WifiInfo().getWifiIP(); - return wifiIP ?? 'Unknown'; + List> mergeNetworks(List> networks) { + var mergedNetworks = >{}; + + for (var network in networks) { + var ssid = network['SSID']; + if (mergedNetworks.containsKey(ssid)) { + var existingNetwork = mergedNetworks[ssid]!; + var existingSignalStrength = int.parse(existingNetwork['SIGNAL']!); + var newSignalStrength = int.parse(network['SIGNAL']!); + if (newSignalStrength > existingSignalStrength) { + mergedNetworks[ssid ?? ''] = network; + } + } else { + mergedNetworks[ssid ?? ''] = network; + } } + + return mergedNetworks.values.toList(); } @override Widget build(BuildContext context) { - return Column( - children: [ - FutureBuilder( - future: getWifiIP(context), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); + return Scaffold( + body: FutureBuilder>>( + future: _networksFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else { + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); } else { - final String wifiIP = snapshot.data ?? 'Unknown'; - return ListTile( - title: Text( - 'IP Address: $wifiIP', - style: const TextStyle(fontSize: 24), - ), + final List> networks = snapshot.data ?? []; + final String currentSSID = currentWifiSSID ?? ''; + return ListView.builder( + itemCount: networks.length, + itemBuilder: (context, index) { + final network = networks[index]; + return ListTile( + key: ValueKey(network['SSID']), + title: Text( + '${network['SSID']}', + style: TextStyle( + color: network['SSID'] == currentSSID + ? Theme.of(context) + .colorScheme + .primary + .withAlpha(130) + : null, + ), + ), + subtitle: Text( + 'Signal Strength: ${network['SIGNAL']} dBm', + style: TextStyle( + color: network['SSID'] == currentSSID + ? Theme.of(context) + .colorScheme + .primary + .withAlpha(130) + : null, + ), + ), + trailing: + getSignalStrengthIcon(network['SIGNAL']!, platform), + // Add onTap logic to connect to the selected network if needed + onTap: () { + // Your logic to connect to this network + }, + ); + }, ); } - }, - ), - Expanded( - child: FutureBuilder>( - future: getWifiNetworks(context), - builder: (BuildContext context, - AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - final List wifiNetworks = snapshot.data ?? []; - return ListView.builder( - itemCount: wifiNetworks.length, - itemBuilder: (BuildContext context, int index) { - final CommonInterface wifiNetwork = wifiNetworks[index]; - final bool isWeakSignal = wifiNetwork.signalStrength < 20; - Widget listItem = ListTile( - leading: Icon( - getSignalStrengthIcon(wifiNetwork.signalStrength)), - title: Text('SSID: ${wifiNetwork.ssid}', - style: TextStyle( - color: isWeakSignal - ? const Color.fromARGB(255, 100, 100, 100) - : null)), - subtitle: Text( - 'Signal Strength: ${wifiNetwork.signalStrength}', - style: TextStyle( - color: isWeakSignal - ? const Color.fromARGB(255, 100, 100, 100) - : null), - ), - ); - - if (isWeakSignal) { - return IgnorePointer( - child: listItem, - ); - } else { - return InkWell( - onTap: () { - if (kDebugMode) { - print('Tapped ${wifiNetwork.ssid}'); - } - }, - child: listItem, - ); - } - }, - ); - } - }, - ), - ), - ], + } + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await Future.delayed(const Duration(milliseconds: 100)); + setState(() { + _networksFuture = _getWifiNetworks(); + }); + }, + child: const Icon(Icons.refresh_rounded), + ), ); } } diff --git a/lib/status/error_print_failure.dart b/lib/status/error_print_failure.dart new file mode 100644 index 0000000..c2d5898 --- /dev/null +++ b/lib/status/error_print_failure.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +class PrintErrorScreen extends StatefulWidget { + const PrintErrorScreen({super.key}); + + @override + // ignore: library_private_types_in_public_api + _PrintErrorScreenState createState() => _PrintErrorScreenState(); +} + +/// The about screen +class _PrintErrorScreenState extends State { + double leftPadding = 0; + double rightPadding = 0; + + Color? _standardColor = Colors.white; + + final GlobalKey textKey1 = GlobalKey(); + final GlobalKey textKey2 = GlobalKey(); + final GlobalKey textKey3 = GlobalKey(); + final GlobalKey textKey4 = GlobalKey(); + final GlobalKey textKey5 = GlobalKey(); + final GlobalKey textKey6 = GlobalKey(); + + @override + Widget build(BuildContext context) { + SchedulerBinding.instance.addPostFrameCallback((_) { + final keys = [textKey1, textKey2, textKey3, textKey4, textKey5, textKey6]; + double maxWidth = 0; + + for (var key in keys) { + final width = key.currentContext?.size?.width ?? 0; + if (width > maxWidth) { + maxWidth = width; + } + } + + final screenWidth = MediaQuery.of(context).size.width; + setState(() { + leftPadding = (screenWidth - maxWidth - 250) / 3; + rightPadding = leftPadding; + _standardColor = Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black; + }); + }); + + const String title = 'Print Failure Detected!'; + const String hint = 'Scan QR Code for Guidance'; + const String boardVersion = 'Board: Apollo 3.5.2'; + const String referenceCode = 'Reference Code: S1-AP-PR-FA'; + const String restartNote = 'Please Check For Problems.'; + + return Scaffold( + backgroundColor: const Color.fromARGB(215, 207, 124, 0), + /*appBar: AppBar( + title: const Text('DEBUG ORION ERROR WATCHDOG'), + ),*/ + body: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: leftPadding), + child: FittedBox( + child: Text( + title, + key: textKey1, + style: const TextStyle( + fontSize: 26, fontWeight: FontWeight.bold), + ), + ), + ), + ), + const SizedBox(height: 20), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: leftPadding), + child: FittedBox( + child: Text( + hint, + key: textKey2, + style: const TextStyle(fontSize: 22), + ), + ), + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: leftPadding), + child: FittedBox( + child: Text( + boardVersion, + key: textKey3, + style: const TextStyle(fontSize: 22), + ), + ), + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: leftPadding), + child: FittedBox( + child: Text( + referenceCode, + key: textKey4, + style: const TextStyle(fontSize: 22), + ), + ), + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.only(left: leftPadding), + child: FittedBox( + child: Text( + restartNote, + key: textKey5, + style: const TextStyle(fontSize: 22), + ), + ), + ), + ), + ], + ), + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only(right: rightPadding), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: 'https://github.com/TheContrappostoShop', + version: QrVersions.auto, + size: 250.0, + eyeStyle: QrEyeStyle(color: _standardColor), + dataModuleStyle: QrDataModuleStyle( + color: _standardColor, + dataModuleShape: QrDataModuleShape.circle), + ), + ], + ), + ), + ), + ], + ), + bottomNavigationBar: Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + const Color.fromARGB(255, 207, 124, 0)), + overlayColor: MaterialStateProperty.all( + Theme.of(context).primaryColor.withOpacity(0.2)), + minimumSize: MaterialStateProperty.all( + const Size(double.infinity, kToolbarHeight * 1.2)), + shape: MaterialStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.zero)), + ), + child: Text('Resume Print', + style: TextStyle(fontSize: 22, color: _standardColor)), + ), + ), + const SizedBox( + height: kToolbarHeight, + child: VerticalDivider( + width: 2, color: Color.fromARGB(215, 207, 124, 0))), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + const Color.fromARGB(255, 207, 124, 0)), + overlayColor: MaterialStateProperty.all( + Theme.of(context).primaryColor.withOpacity(0.2)), + minimumSize: MaterialStateProperty.all( + const Size(double.infinity, kToolbarHeight * 1.2)), + shape: MaterialStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.zero)), + ), + child: Text('Cancel Print', + style: TextStyle(fontSize: 22, color: _standardColor)), + ), + ), + ], + ), + ); + } +} diff --git a/lib/status/fatal_error_screen.dart b/lib/status/fatal_error_screen.dart index 4b7a4a1..848aa90 100644 --- a/lib/status/fatal_error_screen.dart +++ b/lib/status/fatal_error_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/scheduler.dart'; import 'package:qr_flutter/qr_flutter.dart'; class FatalScreen extends StatefulWidget { - const FatalScreen({Key? key}) : super(key: key); + const FatalScreen({super.key}); @override // ignore: library_private_types_in_public_api @@ -48,10 +48,10 @@ class _FatalScreenState extends State { }); const String title = 'Cooling Fan Failure!'; - const String serialNumber = 'Scan QR Code for Guidance'; - const String orionVersion = 'Board: Apollo 3.5.2'; - const String apiVersion = 'Reference Code: S1-AP-CF-F'; - const String boardType = 'Power Cycle to Continue.'; + const String hint = 'Scan QR Code for Guidance'; + const String boardVersion = 'Board: Apollo 3.5.2'; + const String referenceCode = 'Reference Code: S1-AP-CF-F'; + const String restartNote = 'Power Cycle to Continue.'; return Scaffold( backgroundColor: const Color.fromARGB(255, 207, 0, 0), @@ -84,7 +84,7 @@ class _FatalScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - serialNumber, + hint, key: textKey2, style: const TextStyle(fontSize: 22), ), @@ -98,7 +98,7 @@ class _FatalScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - orionVersion, + boardVersion, key: textKey3, style: const TextStyle(fontSize: 22), ), @@ -112,7 +112,7 @@ class _FatalScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - apiVersion, + referenceCode, key: textKey4, style: const TextStyle(fontSize: 22), ), @@ -126,7 +126,7 @@ class _FatalScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - boardType, + restartNote, key: textKey5, style: const TextStyle(fontSize: 22), ), @@ -143,11 +143,14 @@ class _FatalScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImage( + QrImageView( data: 'https://prometheus-msla.org', version: QrVersions.auto, size: 250.0, - foregroundColor: _standardColor, + eyeStyle: QrEyeStyle(color: _standardColor), + dataModuleStyle: QrDataModuleStyle( + color: _standardColor, + dataModuleShape: QrDataModuleShape.circle), ), //const SizedBox(height: kToolbarHeight / 2), ], diff --git a/lib/status/normal_error_screen.dart b/lib/status/normal_error_screen.dart index e1ff4d9..a3fd711 100644 --- a/lib/status/normal_error_screen.dart +++ b/lib/status/normal_error_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/scheduler.dart'; import 'package:qr_flutter/qr_flutter.dart'; class ErrorScreen extends StatefulWidget { - const ErrorScreen({Key? key}) : super(key: key); + const ErrorScreen({super.key}); @override // ignore: library_private_types_in_public_api @@ -48,10 +48,10 @@ class _ErrorScreenState extends State { }); const String title = 'UV Panel Overheated!'; - const String serialNumber = 'Scan QR Code for Guidance'; - const String orionVersion = 'Board: Apollo 3.5.2'; - const String apiVersion = 'Reference Code: S1-AP-UV-OH'; - const String boardType = 'Please Restart to Continue.'; + const String hint = 'Scan QR Code for Guidance'; + const String boardVersion = 'Board: Apollo 3.5.2'; + const String referenceCode = 'Reference Code: S1-AP-UV-OH'; + const String restartNote = 'Please Restart to Continue.'; return Scaffold( backgroundColor: const Color.fromARGB(215, 207, 124, 0), @@ -84,7 +84,7 @@ class _ErrorScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - serialNumber, + hint, key: textKey2, style: const TextStyle(fontSize: 22), ), @@ -98,7 +98,7 @@ class _ErrorScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - orionVersion, + boardVersion, key: textKey3, style: const TextStyle(fontSize: 22), ), @@ -112,7 +112,7 @@ class _ErrorScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - apiVersion, + referenceCode, key: textKey4, style: const TextStyle(fontSize: 22), ), @@ -126,7 +126,7 @@ class _ErrorScreenState extends State { padding: EdgeInsets.only(left: leftPadding), child: FittedBox( child: Text( - boardType, + restartNote, key: textKey5, style: const TextStyle(fontSize: 22), ), @@ -142,11 +142,14 @@ class _ErrorScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - QrImage( + QrImageView( data: 'https://github.com/TheContrappostoShop', version: QrVersions.auto, size: 250.0, - foregroundColor: _standardColor, + eyeStyle: QrEyeStyle(color: _standardColor), + dataModuleStyle: QrDataModuleStyle( + color: _standardColor, + dataModuleShape: QrDataModuleShape.circle), ), ], ), diff --git a/lib/status/status_screen.dart b/lib/status/status_screen.dart index 4610af7..b2574a6 100644 --- a/lib/status/status_screen.dart +++ b/lib/status/status_screen.dart @@ -1,44 +1,29 @@ +// ignore_for_file: library_private_types_in_public_api + import 'package:flutter/material.dart'; -import 'normal_error_screen.dart'; -import 'fatal_error_screen.dart'; +class StatusScreen extends StatefulWidget { + const StatusScreen({super.key}); -/// The status screen -class StatusScreen extends StatelessWidget { - /// Constructs a [StatusScreen] - const StatusScreen({Key? key}) : super(key: key); + @override + _StatusScreenState createState() => _StatusScreenState(); +} +/// The status screen +class _StatusScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Status')), - body: Column( + body: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Center( + Center( child: Text( 'Printer is ready.', style: TextStyle(fontSize: 24), ), ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const ErrorScreen()), - ); - }, - child: const Text('Go to Error Screen'), - ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const FatalScreen()), - ); - }, - child: const Text('Go to Fatal Error Screen'), - ), ], ), ); diff --git a/lib/themes/themes.dart b/lib/themes/themes.dart new file mode 100644 index 0000000..7465efe --- /dev/null +++ b/lib/themes/themes.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +final ThemeData themeLight = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xff6750a4), + brightness: Brightness.light, + ), + useMaterial3: true, +); + +final ThemeData themeDark = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xff6750a4), + brightness: Brightness.dark, + ), + useMaterial3: true, +); + +extension ColorBrightness on Color { + Color withBrightness(double factor) { + assert(factor >= 0); + + final hsl = HSLColor.fromColor(this); + final increasedLightness = (hsl.lightness * factor).clamp(0.0, 1.0); + + return hsl.withLightness(increasedLightness).toColor(); + } +} diff --git a/lib/util/orion_kb_container.dart b/lib/util/orion_kb_container.dart new file mode 100644 index 0000000..278d1df --- /dev/null +++ b/lib/util/orion_kb_container.dart @@ -0,0 +1,37 @@ +// OrionKbContainer.dart +import 'package:flutter/material.dart'; +import 'package:orion/util/orion_textfield.dart'; + +class SpawnOrionKB extends StatefulWidget { + const SpawnOrionKB({super.key}); + + @override + SpawnOrionKBState createState() => SpawnOrionKBState(); +} + +class SpawnOrionKBState extends State { + final ValueNotifier isKeyboardOpen = ValueNotifier(false); + + @override + Widget build(BuildContext context) { + double halfScreenHeight = MediaQuery.of(context).size.height / 2; + + return ValueListenableBuilder( + valueListenable: isKeyboardOpen, + builder: (context, keyboardOpen, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: + EdgeInsets.only(bottom: keyboardOpen ? halfScreenHeight : 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Your other widgets... + OrionTextField(isKeyboardOpen: isKeyboardOpen), + ], + ), + ); + }, + ); + } +} diff --git a/lib/util/orion_keyboard.dart b/lib/util/orion_keyboard.dart new file mode 100644 index 0000000..f5aeadf --- /dev/null +++ b/lib/util/orion_keyboard.dart @@ -0,0 +1,222 @@ +/* + * Custom Keyboard for Orion + * Copyright (c) 2024 TheContrappostoShop (Paul S.) + * GPLv3 Licensing (see LICENSE) + */ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:orion/themes/themes.dart'; + +class OrionKeyboard extends StatefulWidget { + const OrionKeyboard({super.key}); + + @override + OrionKeyboardState createState() => OrionKeyboardState(); +} + +class OrionKeyboardState extends State { + final ValueNotifier _isShiftEnabled = ValueNotifier(false); + final ValueNotifier _isCapsEnabled = ValueNotifier(false); + + // Lookup table for en_US keyboard layout + final Map _keyboardLayout = { + 'row1': 'qwertyuiop', + 'row2': 'asdfghjkl', + 'row3': 'zxcvbnm', + 'bottomRow1': '123', + 'bottomRow2': '', + 'bottomRow3': 'return', + }; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _isShiftEnabled, + builder: (context, isShiftEnabled, child) { + return SizedBox( + height: MediaQuery.of(context).size.height / 2.0, + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + buildRow(_keyboardLayout['row1']!), + buildRow(_keyboardLayout['row2']!), + buildRow(_keyboardLayout['row3']!, hasShiftAndBackspace: true), + buildBottomRow(), + ], + ), + ); + }, + ); + } + + Widget buildRow(String rowCharacters, {bool hasShiftAndBackspace = false}) { + return Expanded( + child: Row( + children: [ + SizedBox(width: hasShiftAndBackspace ? 10 : 0), + if (hasShiftAndBackspace) + Expanded( + child: ValueListenableBuilder( + valueListenable: _isCapsEnabled, + builder: (context, isCapsEnabled, child) { + return KeyboardButton( + text: _isCapsEnabled.value ? "⇪" : "⇧", + onPressed: () { + if (_isShiftEnabled.value == true) { + if (_isCapsEnabled.value == false) { + _isCapsEnabled.value = true; + } else { + _isCapsEnabled.value = false; + _isShiftEnabled.value = !_isShiftEnabled.value; + } + } else { + _isShiftEnabled.value = !_isShiftEnabled.value; + } + if (kDebugMode) { + print("ShiftState ${_isShiftEnabled.value}"); + print("CapsState ${_isCapsEnabled.value}"); + } + }, + isShiftEnabled: _isShiftEnabled, + isCapsEnabled: _isCapsEnabled, + ); + }, + ), + ), + const SizedBox(width: 10), + ...rowCharacters + .split('') + .expand((char) => [ + Expanded( + child: ValueListenableBuilder( + valueListenable: _isShiftEnabled, + builder: (context, isShiftEnabled, child) { + return KeyboardButton( + text: isShiftEnabled ? char.toUpperCase() : char, + isShiftEnabled: _isShiftEnabled, + isCapsEnabled: _isCapsEnabled, + ); + }, + ), + ), + const SizedBox(width: 10), + ]) + .toList() + ..removeLast(), + SizedBox(width: hasShiftAndBackspace ? 10 : 0), + if (hasShiftAndBackspace) + Expanded( + child: KeyboardButton( + text: "⌫", + isShiftEnabled: _isShiftEnabled, + isCapsEnabled: _isCapsEnabled, + ), + ), + const SizedBox(width: 10), + ], + ), + ); + } + + Widget buildBottomRow() { + return Expanded( + child: Row( + children: [ + const SizedBox(width: 10), + Expanded( + flex: 1, + child: KeyboardButton( + text: _keyboardLayout['bottomRow1']!, + isShiftEnabled: _isShiftEnabled, + isCapsEnabled: _isCapsEnabled, + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 5, + child: KeyboardButton( + text: _keyboardLayout['bottomRow2']!, + isShiftEnabled: _isShiftEnabled, + isCapsEnabled: _isCapsEnabled, + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 1, + child: KeyboardButton( + text: _keyboardLayout['bottomRow3']!, + isShiftEnabled: ValueNotifier(false), + isCapsEnabled: _isCapsEnabled, + ), + ), + const SizedBox(width: 10), + ], + ), + ); + } +} + +class KeyboardButton extends StatelessWidget { + final String text; + final VoidCallback? onPressed; + final ValueChanged? onKeyPress; + final ValueNotifier isShiftEnabled; + final ValueNotifier isCapsEnabled; + + const KeyboardButton({ + super.key, + required this.text, + this.onPressed, + this.onKeyPress, + required this.isShiftEnabled, + required this.isCapsEnabled, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 40, + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: _getButtonBackgroundColor(context), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + ), + ), + onPressed: onPressed ?? + () => isCapsEnabled.value ? null : isShiftEnabled.value = false, + child: Container( + alignment: Alignment.center, + child: ValueListenableBuilder( + valueListenable: isShiftEnabled, + builder: (context, isShiftEnabled, child) { + return Text( + isShiftEnabled ? text.toUpperCase() : text.toLowerCase(), + style: const TextStyle(fontSize: 20), + ); + }, + ), + ), + ), + ); + } + + // This method returns the background color for the keyboard button based on the text value. + // The brightness of the color is determined by a lookup table. + Color? _getButtonBackgroundColor(BuildContext context) { + final lookupTable = { + // 3.0 is the brightness factor for the shift button + // 1.3 is the brightness factor for modifier keys + '⇧': isShiftEnabled.value ? 3.0 : 1.3, + '⇪': isCapsEnabled.value ? 3.0 : 1.3, + '⌫': 1.3, + '123': 1.3, + 'return': 1.3, + }; + + // 1.8 is the brightness factor for all alphanumeric keys + final brightness = lookupTable[text] ?? 1.8; + return Theme.of(context).colorScheme.surface.withBrightness(brightness); + } +} diff --git a/lib/util/orion_keyboard_modal.dart b/lib/util/orion_keyboard_modal.dart new file mode 100644 index 0000000..53b0d43 --- /dev/null +++ b/lib/util/orion_keyboard_modal.dart @@ -0,0 +1,67 @@ +/* + * Custom Modal to display the Orion Keyboard + * Copyright (c) 2024 TheContrappostoShop (Paul S.) + * GPLv3 Licensing (see LICENSE) + */ + +import 'package:flutter/material.dart'; +import 'package:orion/util/orion_keyboard.dart'; + +class OrionKbModal extends ModalRoute { + @override + Duration get transitionDuration => const Duration(milliseconds: 300); + + @override + bool get opaque => false; + + @override + bool get barrierDismissible => true; + + @override + Color get barrierColor => Colors.black.withOpacity(0.2); + + @override + String? get barrierLabel => null; + + @override + bool get maintainState => true; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return Material( + type: MaterialType.transparency, + child: SafeArea( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30.0), + topRight: Radius.circular(30.0), + ), + ), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 5.0), + child: OrionKeyboard(), + ), + ), + ), + ); + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 0.5), + end: Offset.zero, + ).animate(animation), + child: FractionallySizedBox( + heightFactor: 0.5, + alignment: Alignment.bottomCenter, + child: child, + ), + ); + } +} diff --git a/lib/util/orion_textfield.dart b/lib/util/orion_textfield.dart new file mode 100644 index 0000000..ece51fb --- /dev/null +++ b/lib/util/orion_textfield.dart @@ -0,0 +1,67 @@ +/* + * Custom Textfield to display the Orion Keyboard + * Copyright (c) 2024 TheContrappostoShop (Paul S.) + * GPLv3 Licensing (see LICENSE) + */ + +import 'package:flutter/material.dart'; +import 'package:orion/util/orion_keyboard_modal.dart'; + +class OrionTextField extends StatefulWidget { + final ValueNotifier isKeyboardOpen; + + const OrionTextField({super.key, required this.isKeyboardOpen}); + + @override + OrionTextFieldState createState() => OrionTextFieldState(); +} + +class OrionTextFieldState extends State { + late FocusNode _focusNode; + late TextEditingController _controller; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _controller = TextEditingController(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + widget.isKeyboardOpen.value = true; + Navigator.of(context).push(OrionKbModal()).then( + (result) { + widget.isKeyboardOpen.value = false; + if (result != null) { + _controller.text = result; + } + }, + ); + }, + child: MouseRegion( + cursor: SystemMouseCursors.text, + child: AbsorbPointer( + child: TextField( + focusNode: _focusNode, + controller: _controller, + readOnly: true, + showCursor: true, // to prevent the system keyboard from showing + decoration: const InputDecoration( + hintText: 'Tap to enter text', + ), + ), + ), + ), + ); + } + + @override + void dispose() { + _focusNode.dispose(); + _controller.dispose(); + super.dispose(); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7ef7099..5169571 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import package_info import path_provider_foundation -import shared_preferences_macos +import shared_preferences_foundation import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 6d85b71..3b8d402 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,33 +1,40 @@ PODS: - FlutterMacOS (1.0.0) + - package_info (0.0.1): + - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - shared_preferences_macos (0.0.1): + - shared_preferences_foundation (0.0.1): + - Flutter - FlutterMacOS - window_size (0.0.2): - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) - - shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) + - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral + package_info: + :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos - shared_preferences_macos: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin window_size: :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos SPEC CHECKSUMS: - FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - shared_preferences_macos: 8b221d457159a85f478c0b9d2f19aeae9feff475 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 PODFILE CHECKSUM: ae543142af37865437ba92bdbda0c110b25e440b diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 8d899d1..d213106 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -204,7 +204,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -274,6 +274,7 @@ }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -405,7 +406,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -487,7 +488,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -534,7 +535,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c12a9d9..d84ca29 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =2.18.5 <3.0.0" - flutter: ">=3.3.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2796636..34569de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +version: 0.1.4+1 environment: sdk: '>=2.18.5 <3.0.0' @@ -39,21 +39,23 @@ dependencies: english_words: ^4.0.0 side_navigation: ^1.0.4 device_preview: ^1.1.0 - go_router: ^6.0.9 + go_router: ^12.1.3 path_provider: ^2.1.0 qr_flutter: ^4.0.0 - wifi_info_flutter: ^2.0.2 window_size: git: url: https://github.com/google/flutter-desktop-embedding.git path: plugins/window_size - flutter_svg: ^1.1.6 + flutter_svg: ^2.0.9 archive: ^3.4.2 permission_handler: ^11.0.1 ini: ^2.1.0 crypto: ^3.0.2 - intl: ^0.17.0 - wifi_flutter: ^0.2.0 + intl: ^0.18.1 + wifi_info_flutter: ^2.0.2 + package_info: ^2.0.2 + onscreen_keyboard: ^1.0.3 + vk: ^0.2.1 dev_dependencies: flutter_test: @@ -64,7 +66,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec