From be7cb6941bdad946227294f727d0c5ad419c420e Mon Sep 17 00:00:00 2001 From: omega ui Date: Thu, 11 Jan 2024 21:45:11 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20NEW=20Text=20Filtering=20Panel,?= =?UTF-8?q?=20just=20click=20the=20`expand=20icon`=20on=20the=20`All=20Tex?= =?UTF-8?q?ts`=20Panel.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/clipboard_state_machine.dart | 5 + .../presentation/commands_state_machine.dart | 2 + .../presentation/emojis_state_machine.dart | 2 + .../presentation/notes_state_machine.dart | 2 + lib/app/powermode/domain/text_type.dart | 49 ++++ .../dialogs/daemon_manager_dialog.dart | 2 +- .../dialogs/entity_info_dialog.dart | 4 +- .../dialogs/power_mode_settings.dart | 2 +- .../presentation/panels/collection_panel.dart | 10 +- .../panels/collections/text_panel.dart | 24 +- .../presentation/power_mode_app.dart | 229 ------------------ .../presentation/power_mode_controller.dart | 16 ++ .../power_mode_state_machine.dart | 29 +++ .../presentation/power_mode_view.dart | 65 +++++ .../states/power_mode_loaded_state_view.dart | 224 +++++++++++++++++ .../power_mode_text_view_state_view.dart | 159 ++++++++++++ .../widgets/styled_text_card.dart | 126 ++++++++++ .../widgets/text_filter_group.dart | 100 ++++++++ lib/core/powermode/power_data_handler.dart | 2 +- lib/core/storage/storage.dart | 2 + lib/main.dart | 4 +- 21 files changed, 817 insertions(+), 241 deletions(-) create mode 100644 lib/app/powermode/domain/text_type.dart delete mode 100644 lib/app/powermode/presentation/power_mode_app.dart create mode 100644 lib/app/powermode/presentation/power_mode_controller.dart create mode 100644 lib/app/powermode/presentation/power_mode_state_machine.dart create mode 100644 lib/app/powermode/presentation/power_mode_view.dart create mode 100644 lib/app/powermode/presentation/states/power_mode_loaded_state_view.dart create mode 100644 lib/app/powermode/presentation/states/power_mode_text_view_state_view.dart create mode 100644 lib/app/powermode/presentation/widgets/styled_text_card.dart create mode 100644 lib/app/powermode/presentation/widgets/text_filter_group.dart diff --git a/lib/app/clipboard/presentation/clipboard_state_machine.dart b/lib/app/clipboard/presentation/clipboard_state_machine.dart index 6e75103..69a6171 100644 --- a/lib/app/clipboard/presentation/clipboard_state_machine.dart +++ b/lib/app/clipboard/presentation/clipboard_state_machine.dart @@ -61,14 +61,19 @@ class ClipboardStateMachine case ClipboardLoadingEvent: currentState = ClipboardLoadingState((e as ClipboardLoadingEvent).fastLoad); + break; case ClipboardDaemonMissingEvent: currentState = ClipboardDaemonMissingState(); + break; case ClipboardDaemonIntegrationEvent: currentState = ClipboardDaemonIntegrationState(); + break; case ClipboardEmptyEvent: currentState = ClipboardEmptyState((e as ClipboardEmptyEvent).cause); + break; case ClipboardInitializedEvent: currentState = ClipboardInitializedState(); + break; case ClipboardUpdateEvent: currentState = ClipboardUpdateState(); } diff --git a/lib/app/commands/presentation/commands_state_machine.dart b/lib/app/commands/presentation/commands_state_machine.dart index eeaa2c3..7ea42e9 100644 --- a/lib/app/commands/presentation/commands_state_machine.dart +++ b/lib/app/commands/presentation/commands_state_machine.dart @@ -43,8 +43,10 @@ class CommandsStateMachine extends StateMachine { switch (e.runtimeType) { case CommandsLoadingEvent: currentState = CommandsLoadingState(); + break; case CommandsEmptyEvent: currentState = CommandsEmptyState((e as CommandsEmptyEvent).cause); + break; case CommandsInitializedEvent: currentState = CommandsInitializedState(); } diff --git a/lib/app/emojis/presentation/emojis_state_machine.dart b/lib/app/emojis/presentation/emojis_state_machine.dart index 3033b97..6e52409 100644 --- a/lib/app/emojis/presentation/emojis_state_machine.dart +++ b/lib/app/emojis/presentation/emojis_state_machine.dart @@ -24,8 +24,10 @@ class EmojisStateMachine extends StateMachine { switch (e.runtimeType) { case EmojisLoadingEvent: currentState = EmojisLoadingState(); + break; case EmojisEmptyEvent: currentState = EmojisEmptyState(); + break; case EmojisInitializedEvent: currentState = EmojisInitializedState(); } diff --git a/lib/app/notes/presentation/notes_state_machine.dart b/lib/app/notes/presentation/notes_state_machine.dart index ea8aa00..f6022b4 100644 --- a/lib/app/notes/presentation/notes_state_machine.dart +++ b/lib/app/notes/presentation/notes_state_machine.dart @@ -43,8 +43,10 @@ class NotesStateMachine extends StateMachine { switch (e.runtimeType) { case NotesLoadingEvent: currentState = NotesLoadingState(); + break; case NotesEmptyEvent: currentState = NotesEmptyState((e as NotesEmptyEvent).cause); + break; case NotesInitializedEvent: currentState = NotesInitializedState(); } diff --git a/lib/app/powermode/domain/text_type.dart b/lib/app/powermode/domain/text_type.dart new file mode 100644 index 0000000..5cfc544 --- /dev/null +++ b/lib/app/powermode/domain/text_type.dart @@ -0,0 +1,49 @@ +enum TextType { code, url, word, sentence, paragraph } + +TextType determineTextType(String text) { + // Check if the text is code + if (isCode(text)) { + return TextType.code; + } + + // Check if the text is a URL + if (Uri.tryParse(text)?.isAbsolute ?? false) { + return TextType.url; + } + + // Check if the text is a single word + if (!text.trim().contains(' ')) { + return TextType.word; + } + + // Check if the text is a single sentence + if (text.endsWith('.') || text.endsWith('!') || text.endsWith('?')) { + return TextType.sentence; + } + + // If none of the above conditions are met, consider it a paragraph + return TextType.paragraph; +} + +bool isCode(String text) { + // Check for keywords and punctuation common in code. + final keywords = RegExp( + r"\b(if|else|for|while|try|except|def|class|return|import|from|print)\b"); + final punctuation = RegExp(r"[{}()\[\];,.<>?:]+"); + final operators = RegExp(r"[+\-*/%|=!<>&]"); + + // Check for presence of keywords, punctuation, and operators. + if (keywords.hasMatch(text) || + punctuation.hasMatch(text) || + operators.hasMatch(text)) { + return true; + } + + // Check for specific patterns like comments or imports. + if (RegExp(r"#.*").hasMatch(text) || RegExp(r"import\s+.*").hasMatch(text)) { + return true; + } + + // If none of the above patterns are found, the text is likely not code. + return false; +} diff --git a/lib/app/powermode/presentation/dialogs/daemon_manager_dialog.dart b/lib/app/powermode/presentation/dialogs/daemon_manager_dialog.dart index cad4704..8b3b403 100644 --- a/lib/app/powermode/presentation/dialogs/daemon_manager_dialog.dart +++ b/lib/app/powermode/presentation/dialogs/daemon_manager_dialog.dart @@ -1,7 +1,7 @@ // ignore_for_file: use_build_context_synchronously import 'package:cliptopia/app/clipboard/data/clipboard_repository.dart'; -import 'package:cliptopia/app/powermode/presentation/power_mode_app.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_loaded_state_view.dart'; import 'package:cliptopia/config/assets/app_animations.dart'; import 'package:cliptopia/config/assets/app_icons.dart'; import 'package:cliptopia/config/themes/app_theme.dart'; diff --git a/lib/app/powermode/presentation/dialogs/entity_info_dialog.dart b/lib/app/powermode/presentation/dialogs/entity_info_dialog.dart index 7cd8224..32ec0dd 100644 --- a/lib/app/powermode/presentation/dialogs/entity_info_dialog.dart +++ b/lib/app/powermode/presentation/dialogs/entity_info_dialog.dart @@ -1,10 +1,10 @@ import 'package:cliptopia/app/clipboard/domain/entity/clipboard_entity.dart'; import 'package:cliptopia/app/powermode/domain/entity_info.dart'; -import 'package:cliptopia/app/powermode/presentation/power_mode_app.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_loaded_state_view.dart'; import 'package:cliptopia/app/settings/presentation/widgets/option.dart'; -import 'package:cliptopia/core/powermode/power_utils.dart'; import 'package:cliptopia/config/assets/app_icons.dart'; import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:cliptopia/core/powermode/power_utils.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:intl/intl.dart'; diff --git a/lib/app/powermode/presentation/dialogs/power_mode_settings.dart b/lib/app/powermode/presentation/dialogs/power_mode_settings.dart index cec47b0..9eb0257 100644 --- a/lib/app/powermode/presentation/dialogs/power_mode_settings.dart +++ b/lib/app/powermode/presentation/dialogs/power_mode_settings.dart @@ -1,4 +1,4 @@ -import 'package:cliptopia/app/powermode/presentation/power_mode_app.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_loaded_state_view.dart'; import 'package:cliptopia/app/settings/presentation/widgets/option.dart'; import 'package:cliptopia/config/assets/app_artworks.dart'; import 'package:cliptopia/config/assets/app_icons.dart'; diff --git a/lib/app/powermode/presentation/panels/collection_panel.dart b/lib/app/powermode/presentation/panels/collection_panel.dart index 7ddd47a..c126f4d 100644 --- a/lib/app/powermode/presentation/panels/collection_panel.dart +++ b/lib/app/powermode/presentation/panels/collection_panel.dart @@ -3,13 +3,19 @@ import 'package:cliptopia/app/powermode/presentation/panels/collections/command_panel.dart'; import 'package:cliptopia/app/powermode/presentation/panels/collections/file_panel.dart'; import 'package:cliptopia/app/powermode/presentation/panels/collections/text_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_controller.dart'; import 'package:cliptopia/config/themes/app_theme.dart'; import 'package:cliptopia/core/powermode/power_data_handler.dart'; import 'package:cliptopia/main.dart'; import 'package:flutter/material.dart'; class CollectionPanel extends StatefulWidget { - const CollectionPanel({super.key}); + const CollectionPanel({ + super.key, + required this.controller, + }); + + final PowerModeController controller; @override State createState() => _CollectionPanelState(); @@ -46,7 +52,7 @@ class _CollectionPanelState extends State { ), child: Row( children: [ - TextPanel(), + TextPanel(controller: widget.controller), CommandPanel(), FilePanel(), ], diff --git a/lib/app/powermode/presentation/panels/collections/text_panel.dart b/lib/app/powermode/presentation/panels/collections/text_panel.dart index dd32aa7..4d9c232 100644 --- a/lib/app/powermode/presentation/panels/collections/text_panel.dart +++ b/lib/app/powermode/presentation/panels/collections/text_panel.dart @@ -1,5 +1,6 @@ import 'package:cliptopia/app/powermode/domain/entity/typedefs.dart'; import 'package:cliptopia/app/powermode/presentation/panels/widgets/text_card.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_controller.dart'; import 'package:cliptopia/config/assets/app_animations.dart'; import 'package:cliptopia/config/assets/app_icons.dart'; import 'package:cliptopia/config/themes/app_theme.dart'; @@ -11,7 +12,12 @@ import 'package:gap/gap.dart'; import 'package:lottie/lottie.dart'; class TextPanel extends StatefulWidget { - const TextPanel({super.key}); + const TextPanel({ + super.key, + required this.controller, + }); + + final PowerModeController controller; @override State createState() => _TextPanelState(); @@ -90,7 +96,18 @@ class _TextPanelState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - if (!isEmpty) + if (!isEmpty) ...[ + IconButton( + onPressed: () { + widget.controller.gotoTextView(); + }, + tooltip: "Expand this view", + icon: Icon( + Icons.fullscreen_exit, + color: Colors.grey.shade800, + ), + ), + const Gap(5), IconButton( onPressed: () { for (var e in PowerDataHandler.texts) { @@ -104,7 +121,8 @@ class _TextPanelState extends State { color: Colors.red, ), ), - const Gap(5), + const Gap(5), + ], ], ), ), diff --git a/lib/app/powermode/presentation/power_mode_app.dart b/lib/app/powermode/presentation/power_mode_app.dart deleted file mode 100644 index 0344d5a..0000000 --- a/lib/app/powermode/presentation/power_mode_app.dart +++ /dev/null @@ -1,229 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:cliptopia/app/powermode/domain/entity/typedefs.dart'; -import 'package:cliptopia/app/powermode/presentation/panels/collection_panel.dart'; -import 'package:cliptopia/app/powermode/presentation/panels/colors_panel.dart'; -import 'package:cliptopia/app/powermode/presentation/panels/emoji_panel.dart'; -import 'package:cliptopia/app/powermode/presentation/panels/images_panel.dart'; -import 'package:cliptopia/app/powermode/presentation/panels/search_panel.dart'; -import 'package:cliptopia/config/assets/app_icons.dart'; -import 'package:cliptopia/config/themes/app_theme.dart'; -import 'package:cliptopia/core/app_session.dart'; -import 'package:cliptopia/core/logger.dart'; -import 'package:cliptopia/core/powermode/power_data_handler.dart'; -import 'package:cliptopia/core/services/injector.dart'; -import 'package:cliptopia/core/services/route_service.dart'; -import 'package:cliptopia/core/storage/storage.dart'; -import 'package:cliptopia/main.dart'; -import 'package:cliptopia/widgets/message_bird.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:gap/gap.dart'; - -import '../../../core/powermode/power_data_store.dart'; - -final appFocusNode = FocusNode(); - -dynamic _appState; - -void rebuildView({required String message}) { - if (_appState != null) { - prettyLog(value: "Rebuilding View: Reason -> $message"); - _appState(); - } -} - -class PowerModeApp extends StatefulWidget { - const PowerModeApp({super.key}); - - @override - State createState() => _PowerModeAppState(); -} - -class _PowerModeAppState extends State { - bool initialized = false; - - @override - void initState() { - super.initState(); - start(); - } - - void start() { - Future( - () { - FlutterView view = - WidgetsBinding.instance.platformDispatcher.views.first; - Size size = view.physicalSize; - windowSize = size; - PowerDataStore.init(); - PowerDataHandler.searchTypeChangeListeners.add(() { - rebuild(); - }); - Injector.init( - onRebuild: () {}, - onFinished: () { - initialized = true; - rebuild(); - }, - ); - }, - ); - } - - void rebuild() { - if (mounted) { - setState(() {}); - } - } - - Widget _buildInitializingView() { - return Container( - decoration: BoxDecoration( - color: PowerModeTheme.background, - ), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - AppIcons.appIcon, - ), - Gap(30), - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Text( - "Reading your clipboard storage ...", - style: AppTheme.fontSize(16), - ), - ), - ), - Gap(20), - CircularProgressIndicator(), - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - if (!initialized) { - return _buildInitializingView(); - } - _appState = rebuild; - final defaultViewMode = Storage.get(StorageKeys.viewMode, - fallback: StorageValues.defaultViewMode) == - StorageValues.defaultViewMode; - return MaterialApp( - debugShowCheckedModeBanner: false, - title: "Cliptopia (Power Mode)", - themeMode: AppTheme.mode, - navigatorKey: RouteService.navigatorKey, - theme: ThemeData( - useMaterial3: true, - visualDensity: VisualDensity.adaptivePlatformDensity, - tooltipTheme: TooltipThemeData( - waitDuration: const Duration(seconds: 1), - textStyle: AppTheme.fontSize(14) - .makeMedium() - .withColor(PowerModeTheme.background), - ), - ), - home: CallbackShortcuts( - bindings: { - const SingleActivator(LogicalKeyboardKey.escape): () => - Injector.find().endSession(), - const SingleActivator(LogicalKeyboardKey.keyI, control: true): () { - if (Storage.get(StorageKeys.hideImagePanelKey, fallback: false)) { - TempStorage.toggle(StorageKeys.hideImagePanelKey, - fallback: false); - rebuild(); - } - }, - const SingleActivator(LogicalKeyboardKey.keyI, - control: true, shift: true): () { - if (Storage.isSensitivityOn()) { - TempStorage.set(StorageKeys.sensitivity, - !TempStorage.canShowSensitiveContent()); - rebuild(); - } - }, - const SingleActivator(LogicalKeyboardKey.keyT, alt: true): () { - PowerDataHandler.searchTypeUpdate(PowerSearchType.Text); - rebuild(); - }, - const SingleActivator(LogicalKeyboardKey.keyR, alt: true): () { - PowerDataHandler.searchTypeUpdate(PowerSearchType.Regex); - rebuild(); - }, - const SingleActivator(LogicalKeyboardKey.keyI, alt: true): () { - PowerDataHandler.searchTypeUpdate(PowerSearchType.Image); - rebuild(); - }, - const SingleActivator(LogicalKeyboardKey.keyC, alt: true): () { - PowerDataHandler.searchTypeUpdate(PowerSearchType.Comment); - rebuild(); - }, - }, - child: Focus( - focusNode: appFocusNode, - child: Scaffold( - backgroundColor: Colors.transparent, - body: GestureDetector( - onTap: () { - appFocusNode.requestFocus(); - }, - child: Container( - color: defaultViewMode - ? PowerModeTheme.barrier - : Colors.transparent, - child: Stack( - children: [ - Align( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SearchPanel(), - if (!Storage.get(StorageKeys.hideImagePanelKey, - fallback: false) || - !TempStorage.get(StorageKeys.hideImagePanelKey, - fallback: true)) ...[ - const Gap(25), - ImagesPanel(), - ], - if (PowerDataHandler.searchType != - PowerSearchType.Image) ...[ - if (!Storage.get(StorageKeys.hideColorPanelKey, - fallback: false)) ...[ - const Gap(25), - ColorsPanel(), - ], - if (!Storage.get(StorageKeys.hideEmojiPanelKey, - fallback: true)) ...[ - const Gap(25), - EmojiPanel(), - ], - const Gap(25), - CollectionPanel(), - const Gap(35), - ], - ], - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: MessageBird.create(), - ), - ], - ), - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/app/powermode/presentation/power_mode_controller.dart b/lib/app/powermode/presentation/power_mode_controller.dart new file mode 100644 index 0000000..8a01bb7 --- /dev/null +++ b/lib/app/powermode/presentation/power_mode_controller.dart @@ -0,0 +1,16 @@ +import 'package:cliptopia/app/powermode/presentation/power_mode_state_machine.dart'; +import 'package:cliptopia/constants/typedefs.dart'; +import 'package:cliptopia/core/services/machine/controller.dart'; + +class PowerModeController extends Controller { + PowerModeController({required RebuildCallback onRebuild}) + : super(onRebuild: onRebuild, stateMachine: PowerModeStateMachine()); + + void gotoHomeView() { + onEvent(PowerModeLoadedEvent()); + } + + void gotoTextView() { + onEvent(PowerModeTextViewEvent()); + } +} diff --git a/lib/app/powermode/presentation/power_mode_state_machine.dart b/lib/app/powermode/presentation/power_mode_state_machine.dart new file mode 100644 index 0000000..2f0d8c6 --- /dev/null +++ b/lib/app/powermode/presentation/power_mode_state_machine.dart @@ -0,0 +1,29 @@ +import 'package:cliptopia/core/services/machine/state_machine.dart'; + +class PowerModeEvent {} + +class PowerModeLoadedEvent extends PowerModeEvent {} + +class PowerModeTextViewEvent extends PowerModeEvent {} + +class PowerModeState {} + +class PowerModeLoadedState extends PowerModeState {} + +class PowerModeTextViewState extends PowerModeState {} + +class PowerModeStateMachine + extends StateMachine { + PowerModeStateMachine() : super(initialState: PowerModeLoadedState()); + + @override + void changeStateOnEvent(PowerModeEvent e) { + switch (e.runtimeType) { + case PowerModeLoadedEvent: + currentState = PowerModeLoadedState(); + break; + case PowerModeTextViewEvent: + currentState = PowerModeTextViewState(); + } + } +} diff --git a/lib/app/powermode/presentation/power_mode_view.dart b/lib/app/powermode/presentation/power_mode_view.dart new file mode 100644 index 0000000..fff832d --- /dev/null +++ b/lib/app/powermode/presentation/power_mode_view.dart @@ -0,0 +1,65 @@ +import 'package:cliptopia/app/powermode/presentation/power_mode_controller.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_state_machine.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_loaded_state_view.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_text_view_state_view.dart'; +import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:cliptopia/core/services/route_service.dart'; +import 'package:flutter/material.dart'; + +class PowerModeView extends StatefulWidget { + const PowerModeView({ + super.key, + this.arguments, + }); + + final dynamic arguments; + + @override + State createState() => _PowerModeViewState(); +} + +class _PowerModeViewState extends State { + late PowerModeController controller; + + @override + void initState() { + super.initState(); + controller = PowerModeController(onRebuild: () { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: "Cliptopia (Power Mode)", + themeMode: AppTheme.mode, + navigatorKey: RouteService.navigatorKey, + theme: ThemeData( + useMaterial3: true, + visualDensity: VisualDensity.adaptivePlatformDensity, + tooltipTheme: TooltipThemeData( + waitDuration: const Duration(seconds: 1), + textStyle: AppTheme.fontSize(14) + .makeMedium() + .withColor(PowerModeTheme.background), + ), + ), + home: _buildState(), + ); + } + + Widget _buildState() { + final currentState = controller.getCurrentState(); + switch (currentState.runtimeType) { + case PowerModeLoadedState: + return PowerModeLoadedStateView(controller: controller); + case PowerModeTextViewState: + return PowerModeTextViewStateView(controller: controller); + default: + throw Exception( + "Unrecognized State Exception: ${currentState.runtimeType}"); + } + } +} diff --git a/lib/app/powermode/presentation/states/power_mode_loaded_state_view.dart b/lib/app/powermode/presentation/states/power_mode_loaded_state_view.dart new file mode 100644 index 0000000..17af185 --- /dev/null +++ b/lib/app/powermode/presentation/states/power_mode_loaded_state_view.dart @@ -0,0 +1,224 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:cliptopia/app/powermode/domain/entity/typedefs.dart'; +import 'package:cliptopia/app/powermode/presentation/panels/collection_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/panels/colors_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/panels/emoji_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/panels/images_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/panels/search_panel.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_controller.dart'; +import 'package:cliptopia/config/assets/app_icons.dart'; +import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:cliptopia/core/app_session.dart'; +import 'package:cliptopia/core/logger.dart'; +import 'package:cliptopia/core/powermode/power_data_handler.dart'; +import 'package:cliptopia/core/services/injector.dart'; +import 'package:cliptopia/core/storage/storage.dart'; +import 'package:cliptopia/main.dart'; +import 'package:cliptopia/widgets/message_bird.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:gap/gap.dart'; + +import '../../../../core/powermode/power_data_store.dart'; + +final appFocusNode = FocusNode(); + +dynamic _appState; + +void rebuildView({required String message}) { + if (_appState != null) { + prettyLog(value: "Rebuilding View: Reason -> $message"); + _appState(); + } +} + +class PowerModeLoadedStateView extends StatefulWidget { + const PowerModeLoadedStateView({ + super.key, + required this.controller, + }); + + final PowerModeController controller; + + @override + State createState() => + _PowerModeLoadedStateViewState(); +} + +class _PowerModeLoadedStateViewState extends State { + bool initialized = false; + + @override + void initState() { + super.initState(); + start(); + } + + void start() { + if (!TempStorage.get(StorageKeys.clipboardLoadedKey, + fallback: StorageValues.defaultClipboardLoadedValue)) { + TempStorage.set(StorageKeys.clipboardLoadedKey, true); + Future( + () { + FlutterView view = + WidgetsBinding.instance.platformDispatcher.views.first; + Size size = view.physicalSize; + windowSize = size; + PowerDataStore.init(); + PowerDataHandler.searchTypeChangeListeners.add(() { + rebuild(); + }); + Injector.init( + onRebuild: () {}, + onFinished: () { + initialized = true; + rebuild(); + }, + ); + }, + ); + } else { + initialized = true; + rebuild(); + } + } + + void rebuild() { + if (mounted) { + setState(() {}); + } + } + + Widget _buildInitializingView() { + return Container( + decoration: BoxDecoration( + color: PowerModeTheme.background, + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + AppIcons.appIcon, + ), + Gap(30), + Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Text( + "Reading your clipboard storage ...", + style: AppTheme.fontSize(16), + ), + ), + ), + Gap(20), + CircularProgressIndicator(), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + if (!initialized) { + return _buildInitializingView(); + } + _appState = rebuild; + final defaultViewMode = Storage.get(StorageKeys.viewMode, + fallback: StorageValues.defaultViewMode) == + StorageValues.defaultViewMode; + return CallbackShortcuts( + bindings: { + const SingleActivator(LogicalKeyboardKey.escape): () => + Injector.find().endSession(), + const SingleActivator(LogicalKeyboardKey.keyI, control: true): () { + if (Storage.get(StorageKeys.hideImagePanelKey, fallback: false)) { + TempStorage.toggle(StorageKeys.hideImagePanelKey, fallback: false); + rebuild(); + } + }, + const SingleActivator(LogicalKeyboardKey.keyI, + control: true, shift: true): () { + if (Storage.isSensitivityOn()) { + TempStorage.set(StorageKeys.sensitivity, + !TempStorage.canShowSensitiveContent()); + rebuild(); + } + }, + const SingleActivator(LogicalKeyboardKey.keyT, alt: true): () { + PowerDataHandler.searchTypeUpdate(PowerSearchType.Text); + rebuild(); + }, + const SingleActivator(LogicalKeyboardKey.keyR, alt: true): () { + PowerDataHandler.searchTypeUpdate(PowerSearchType.Regex); + rebuild(); + }, + const SingleActivator(LogicalKeyboardKey.keyI, alt: true): () { + PowerDataHandler.searchTypeUpdate(PowerSearchType.Image); + rebuild(); + }, + const SingleActivator(LogicalKeyboardKey.keyC, alt: true): () { + PowerDataHandler.searchTypeUpdate(PowerSearchType.Comment); + rebuild(); + }, + }, + child: Focus( + focusNode: appFocusNode, + child: Scaffold( + backgroundColor: Colors.transparent, + body: GestureDetector( + onTap: () { + appFocusNode.requestFocus(); + }, + child: Container( + color: + defaultViewMode ? PowerModeTheme.barrier : Colors.transparent, + child: Stack( + children: [ + Align( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SearchPanel(), + if (!Storage.get(StorageKeys.hideImagePanelKey, + fallback: false) || + !TempStorage.get(StorageKeys.hideImagePanelKey, + fallback: true)) ...[ + const Gap(25), + ImagesPanel(), + ], + if (PowerDataHandler.searchType != + PowerSearchType.Image) ...[ + if (!Storage.get(StorageKeys.hideColorPanelKey, + fallback: false)) ...[ + const Gap(25), + ColorsPanel(), + ], + if (!Storage.get(StorageKeys.hideEmojiPanelKey, + fallback: true)) ...[ + const Gap(25), + EmojiPanel(), + ], + const Gap(25), + CollectionPanel(controller: widget.controller), + const Gap(35), + ], + ], + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: MessageBird.create(), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/powermode/presentation/states/power_mode_text_view_state_view.dart b/lib/app/powermode/presentation/states/power_mode_text_view_state_view.dart new file mode 100644 index 0000000..4f39d6d --- /dev/null +++ b/lib/app/powermode/presentation/states/power_mode_text_view_state_view.dart @@ -0,0 +1,159 @@ +import 'package:cliptopia/app/powermode/domain/entity/text_entity.dart'; +import 'package:cliptopia/app/powermode/domain/text_type.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_controller.dart'; +import 'package:cliptopia/app/powermode/presentation/widgets/styled_text_card.dart'; +import 'package:cliptopia/app/powermode/presentation/widgets/text_filter_group.dart'; +import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:cliptopia/core/powermode/power_data_handler.dart'; +import 'package:cliptopia/core/storage/storage.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +class PowerModeTextViewStateView extends StatefulWidget { + const PowerModeTextViewStateView({ + super.key, + required this.controller, + }); + + final PowerModeController controller; + + @override + State createState() => + _PowerModeTextViewStateViewState(); +} + +class _PowerModeTextViewStateViewState + extends State { + String currentFilter = 'all'; + final Map _textTypeMap = {}; + final Map _filterStats = { + 'All': 0, + 'Code': 0, + 'Url': 0, + 'Word': 0, + 'Sentence': 0, + 'Paragraph': 0, + }; + + @override + void initState() { + super.initState(); + final texts = PowerDataHandler.texts + .where((e) => + !e.entity.isMarkedDeleted && + (e.search(PowerDataHandler.searchText))) + .where((e) => + !e.entity.info.sensitive || + !Storage.isSensitivityOn() || + TempStorage.canShowSensitiveContent()); + for (final text in texts) { + final type = determineTextType(text.text); + _textTypeMap[text] = type; + _filterStats['All'] = _filterStats['All']! + 1; + switch (type) { + case TextType.code: + _filterStats['Code'] = _filterStats['Code']! + 1; + break; + case TextType.url: + _filterStats['Url'] = _filterStats['Url']! + 1; + break; + case TextType.word: + _filterStats['Word'] = _filterStats['Word']! + 1; + break; + case TextType.sentence: + _filterStats['Sentence'] = _filterStats['Sentence']! + 1; + break; + case TextType.paragraph: + _filterStats['Paragraph'] = _filterStats['Paragraph']! + 1; + } + } + } + + bool filter(TextType value) { + if (currentFilter == 'all') { + return true; + } + return currentFilter == value.name; + } + + @override + Widget build(BuildContext context) { + final defaultViewMode = Storage.get(StorageKeys.viewMode, + fallback: StorageValues.defaultViewMode) == + StorageValues.defaultViewMode; + return Scaffold( + backgroundColor: + defaultViewMode ? PowerModeTheme.barrier : Colors.transparent, + body: Center( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 150, vertical: 60), + padding: const EdgeInsets.all(25), + decoration: BoxDecoration( + color: PowerModeTheme.background, + borderRadius: BorderRadius.circular(40), + border: Border.all(color: PowerModeTheme.borderColor), + boxShadow: [ + BoxShadow( + color: + Colors.black.withOpacity(PowerModeTheme.dropShadowOpacity), + blurRadius: 48, + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + IconButton( + onPressed: () { + widget.controller.gotoHomeView(); + }, + icon: Icon( + Icons.arrow_back, + color: Colors.grey.shade800, + ), + iconSize: 32, + ), + const Gap(10), + Text( + "Clipboard Text", + style: AppTheme.fontSize(32), + ), + ], + ), + const Gap(10), + TextFilterGroup( + stats: _filterStats, + onChanged: (filter) { + setState(() { + currentFilter = filter.toLowerCase(); + }); + }, + ), + const Gap(10), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Wrap( + spacing: 10, + runSpacing: 10, + children: _textTypeMap.entries + .where((e) => filter(e.value)) + .map( + (e) => StyledTextCard( + entity: e.key, + type: e.value, + ), + ) + .toList(), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/powermode/presentation/widgets/styled_text_card.dart b/lib/app/powermode/presentation/widgets/styled_text_card.dart new file mode 100644 index 0000000..ae7dcb0 --- /dev/null +++ b/lib/app/powermode/presentation/widgets/styled_text_card.dart @@ -0,0 +1,126 @@ +import 'package:cliptopia/app/powermode/domain/entity/text_entity.dart'; +import 'package:cliptopia/app/powermode/domain/text_type.dart'; +import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:cliptopia/core/utils.dart'; +import 'package:cliptopia/widgets/tiles/text_tile.dart'; +import 'package:flutter/material.dart'; + +class StyledTextCard extends StatefulWidget { + const StyledTextCard({ + super.key, + required this.entity, + required this.type, + }); + + final TextEntity entity; + final TextType type; + + @override + State createState() => _StyledTextCardState(); +} + +class _StyledTextCardState extends State { + bool hover = false; + + Color getCardBackgroundColor() { + switch (widget.type) { + case TextType.code: + return Colors.blue; + case TextType.url: + return Colors.green; + case TextType.word: + return Colors.brown; + case TextType.sentence: + return Colors.black; + case TextType.paragraph: + return Colors.blueGrey; + } + } + + Color getCardForegroundColor() { + switch (widget.type) { + case TextType.code: + case TextType.url: + case TextType.word: + case TextType.sentence: + case TextType.paragraph: + return Colors.white; + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + EntityUtils.inject(widget.entity.entity); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (e) => setState(() => hover = true), + onExit: (e) => setState(() => hover = false), + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.only(top: 15), + child: AnimatedSize( + duration: const Duration(milliseconds: 250), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500, + minWidth: 500, + minHeight: 120, + ), + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: hover ? null : 500.0, + height: hover ? null : 120.0, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: PowerModeTheme.collectionCardColor, + border: Border.all(color: PowerModeTheme.cardBorderColor), + borderRadius: BorderRadius.circular(20), + boxShadow: hover + ? [ + BoxShadow( + color: PowerModeTheme + .collectionCardDropShadowColor, + blurRadius: 16, + ), + ] + : [], + ), + child: Text( + hover + ? widget.entity.text + : TextTile.getRepresentableText(widget.entity.text), + style: AppTheme.fontSize(14), + ), + ), + ), + ), + ), + FittedBox( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + margin: const EdgeInsets.only(left: 10), + decoration: BoxDecoration( + color: getCardBackgroundColor(), + borderRadius: BorderRadius.circular(20), + ), + child: Center( + child: Text( + widget.type.name, + style: AppTheme.fontSize(14) + .makeMedium() + .withColor(getCardForegroundColor()), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/powermode/presentation/widgets/text_filter_group.dart b/lib/app/powermode/presentation/widgets/text_filter_group.dart new file mode 100644 index 0000000..1843231 --- /dev/null +++ b/lib/app/powermode/presentation/widgets/text_filter_group.dart @@ -0,0 +1,100 @@ +import 'package:cliptopia/config/themes/app_theme.dart'; +import 'package:flutter/material.dart'; + +class TextFilterGroup extends StatefulWidget { + const TextFilterGroup({ + super.key, + required this.stats, + required this.onChanged, + }); + + final Map stats; + final void Function(String filter) onChanged; + + @override + State createState() => _TextFilterGroupState(); +} + +class _TextFilterGroupState extends State { + String currentFilter = 'All'; + final filters = []; + + @override + void initState() { + super.initState(); + filters.addAll(['All', 'Code', 'Url', 'Word', 'Sentence', 'Paragraph']); + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 20, + children: filters.map((e) { + return TextFilterButton( + filter: e, + itemCount: widget.stats[e]!, + onSelected: () { + setState(() { + currentFilter = e; + widget.onChanged(e); + }); + }, + active: currentFilter == e, + ); + }).toList(), + ); + } +} + +class TextFilterButton extends StatefulWidget { + const TextFilterButton({ + super.key, + required this.filter, + required this.itemCount, + required this.onSelected, + required this.active, + }); + + final String filter; + final int itemCount; + final VoidCallback onSelected; + final bool active; + + @override + State createState() => _TextFilterButtonState(); +} + +class _TextFilterButtonState extends State { + bool hover = false; + + @override + Widget build(BuildContext context) { + return AnimatedScale( + duration: const Duration(milliseconds: 50), + scale: hover ? 1.1 : 1.0, + child: GestureDetector( + onTap: widget.onSelected, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (e) => setState(() => hover = true), + onExit: (e) => setState(() => hover = false), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: widget.active + ? PowerModeTheme.activeImageFilterBackgroundColor + : PowerModeTheme.collectionCardColor, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + "${widget.filter} (${widget.itemCount})", + style: AppTheme.fontSize(16).copyWith( + fontWeight: widget.active ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/core/powermode/power_data_handler.dart b/lib/core/powermode/power_data_handler.dart index 2d9cb39..c8e490f 100644 --- a/lib/core/powermode/power_data_handler.dart +++ b/lib/core/powermode/power_data_handler.dart @@ -8,7 +8,7 @@ import 'package:cliptopia/app/powermode/domain/entity/image_entity.dart'; import 'package:cliptopia/app/powermode/domain/entity/recent_emoji_entity.dart'; import 'package:cliptopia/app/powermode/domain/entity/text_entity.dart'; import 'package:cliptopia/app/powermode/domain/entity/typedefs.dart'; -import 'package:cliptopia/app/powermode/presentation/power_mode_app.dart'; +import 'package:cliptopia/app/powermode/presentation/states/power_mode_loaded_state_view.dart'; import 'package:cliptopia/constants/typedefs.dart'; import 'package:cliptopia/core/logger.dart'; import 'package:cliptopia/core/powermode/power_data_store.dart'; diff --git a/lib/core/storage/storage.dart b/lib/core/storage/storage.dart index fde47e9..6c11447 100644 --- a/lib/core/storage/storage.dart +++ b/lib/core/storage/storage.dart @@ -131,6 +131,7 @@ class StorageKeys { static const hideEmojiPanelKey = 'hide-emoji-panel'; static const hideColorPanelKey = 'hide-color-panel'; static const saveDateFilerKey = 'save-date-filter'; + static const clipboardLoadedKey = 'clipboard-loaded'; static const dontShowDaemonSleepingDialogKey = 'dont-show-daemon-sleeping-dialog'; } @@ -143,4 +144,5 @@ class StorageValues { static const defaultCliptopiaPath = '/usr/bin/cliptopia'; // manual installer default static const defaultAnimationEnabledKey = true; + static const defaultClipboardLoadedValue = false; } diff --git a/lib/main.dart b/lib/main.dart index 44e72b4..aa28357 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:bitsdojo_window/bitsdojo_window.dart'; -import 'package:cliptopia/app/powermode/presentation/power_mode_app.dart'; +import 'package:cliptopia/app/powermode/presentation/power_mode_view.dart'; import 'package:cliptopia/app/welcome/presentation/welcome_dialog.dart'; import 'package:cliptopia/config/themes/app_theme.dart'; import 'package:cliptopia/constants/meta_info.dart'; @@ -59,7 +59,7 @@ void main(List arguments) { if (ArgumentHandler.isPowerMode()) { PowerModeTheme.init(); - return runApp(const PowerModeApp()); + return runApp(const PowerModeView()); } return runApp(const App());