From b9cc42b019749e24922c6b462c4b23f93ca1de07 Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Sun, 31 Aug 2025 12:15:43 +0530 Subject: [PATCH] feat: add initial dashbot package implementation - Created a new Dashbot package with core functionalities. - Implemented Dashbot window model and notifier for state management. - Added UI components including Dashbot default page and home page. - Integrated routing for Dashbot with initial routes defined. - Included assets for Dashbot icons and set up pubspec.yaml. - Added tests for Dashbot window notifier to ensure functionality. - Established a basic README and CHANGELOG for the package. --- lib/screens/dashboard.dart | 16 +- packages/dashbot/.gitignore | 31 ++++ packages/dashbot/CHANGELOG.md | 3 + packages/dashbot/LICENSE | 1 + packages/dashbot/README.md | 39 +++++ packages/dashbot/analysis_options.yaml | 4 + packages/dashbot/assets/dashbot_icon_1.png | Bin 0 -> 2464 bytes packages/dashbot/assets/dashbot_icon_2.png | Bin 0 -> 2261 bytes .../common/pages/dashbot_default_page.dart | 40 +++++ .../lib/core/model/dashbot_window_model.dart | 31 ++++ .../providers/dashbot_window_notifier.dart | 36 +++++ .../providers/dashbot_window_notifier.g.dart | 27 ++++ .../lib/core/routes/dashbot_router.dart | 31 ++++ .../lib/core/routes/dashbot_routes.dart | 6 + .../dashbot/lib/core/utils/dashbot_icons.dart | 17 ++ .../dashbot/lib/core/utils/show_dashbot.dart | 25 +++ packages/dashbot/lib/core/utils/utils.dart | 2 + packages/dashbot/lib/dashbot.dart | 3 + packages/dashbot/lib/dashbot_dashboard.dart | 147 ++++++++++++++++++ .../features/home/view/pages/home_page.dart | 135 ++++++++++++++++ packages/dashbot/pubspec.yaml | 63 ++++++++ packages/dashbot/test/dashbot_test.dart | 5 + .../dashbot_window_notifier_test.dart | 131 ++++++++++++++++ pubspec.lock | 15 ++ pubspec.yaml | 2 + 25 files changed, 802 insertions(+), 8 deletions(-) create mode 100644 packages/dashbot/.gitignore create mode 100644 packages/dashbot/CHANGELOG.md create mode 100644 packages/dashbot/LICENSE create mode 100644 packages/dashbot/README.md create mode 100644 packages/dashbot/analysis_options.yaml create mode 100644 packages/dashbot/assets/dashbot_icon_1.png create mode 100644 packages/dashbot/assets/dashbot_icon_2.png create mode 100644 packages/dashbot/lib/core/common/pages/dashbot_default_page.dart create mode 100644 packages/dashbot/lib/core/model/dashbot_window_model.dart create mode 100644 packages/dashbot/lib/core/providers/dashbot_window_notifier.dart create mode 100644 packages/dashbot/lib/core/providers/dashbot_window_notifier.g.dart create mode 100644 packages/dashbot/lib/core/routes/dashbot_router.dart create mode 100644 packages/dashbot/lib/core/routes/dashbot_routes.dart create mode 100644 packages/dashbot/lib/core/utils/dashbot_icons.dart create mode 100644 packages/dashbot/lib/core/utils/show_dashbot.dart create mode 100644 packages/dashbot/lib/core/utils/utils.dart create mode 100644 packages/dashbot/lib/dashbot.dart create mode 100644 packages/dashbot/lib/dashbot_dashboard.dart create mode 100644 packages/dashbot/lib/features/home/view/pages/home_page.dart create mode 100644 packages/dashbot/pubspec.yaml create mode 100644 packages/dashbot/test/dashbot_test.dart create mode 100644 packages/dashbot/test/providers/dashbot_window_notifier_test.dart diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 6e711c15..b0dc140f 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -1,10 +1,10 @@ import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:dashbot/dashbot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; -import 'package:apidash/dashbot/dashbot.dart'; import 'common_widgets/common_widgets.dart'; import 'envvar/environment_page.dart'; import 'home_page/home_page.dart'; @@ -129,15 +129,15 @@ class Dashboard extends ConsumerWidget { ), floatingActionButton: isDashBotEnabled ? FloatingActionButton( - onPressed: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => const Padding( - padding: EdgeInsets.all(16.0), - child: DashBotWidget(), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + onPressed: () => showDashbotWindow(context, ref), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 6.0, + horizontal: 10, ), + child: DashbotIcons.getDashbotIcon1(), ), - child: const Icon(Icons.help_outline), ) : null, ); diff --git a/packages/dashbot/.gitignore b/packages/dashbot/.gitignore new file mode 100644 index 00000000..eb6c05cd --- /dev/null +++ b/packages/dashbot/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/packages/dashbot/CHANGELOG.md b/packages/dashbot/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/dashbot/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/dashbot/LICENSE b/packages/dashbot/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/dashbot/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/dashbot/README.md b/packages/dashbot/README.md new file mode 100644 index 00000000..4a260d8d --- /dev/null +++ b/packages/dashbot/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/dashbot/analysis_options.yaml b/packages/dashbot/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/dashbot/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/dashbot/assets/dashbot_icon_1.png b/packages/dashbot/assets/dashbot_icon_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0d50bc4969fff51509e3a000e26b9c036f37934a GIT binary patch literal 2464 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 literal 0 HcmV?d00001 diff --git a/packages/dashbot/lib/core/common/pages/dashbot_default_page.dart b/packages/dashbot/lib/core/common/pages/dashbot_default_page.dart new file mode 100644 index 00000000..a50cda8f --- /dev/null +++ b/packages/dashbot/lib/core/common/pages/dashbot_default_page.dart @@ -0,0 +1,40 @@ +import 'package:apidash_design_system/apidash_design_system.dart' + show kVSpacer20, kVSpacer16, kVSpacer10; +import 'package:dashbot/core/utils/dashbot_icons.dart'; +import 'package:flutter/material.dart'; + +class DashbotDefaultPage extends StatelessWidget { + const DashbotDefaultPage({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + kVSpacer16, + DashbotIcons.getDashbotIcon1(width: 60), + + kVSpacer20, + Text( + 'Hello there!', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.w800), + ), + kVSpacer10, + Text( + 'Request not made yet', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + Text( + "Why not go ahead and make one?", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w200), + ), + ], + ); + } +} diff --git a/packages/dashbot/lib/core/model/dashbot_window_model.dart b/packages/dashbot/lib/core/model/dashbot_window_model.dart new file mode 100644 index 00000000..e9ef2123 --- /dev/null +++ b/packages/dashbot/lib/core/model/dashbot_window_model.dart @@ -0,0 +1,31 @@ +class DashbotWindowModel { + final double width; + final double height; + final double right; + final double bottom; + final bool isActive; + + const DashbotWindowModel({ + this.width = 350, + this.height = 450, + this.right = 50, + this.bottom = 100, + this.isActive = false, + }); + + DashbotWindowModel copyWith({ + double? width, + double? height, + double? right, + double? bottom, + bool? isActive, + }) { + return DashbotWindowModel( + width: width ?? this.width, + height: height ?? this.height, + right: right ?? this.right, + bottom: bottom ?? this.bottom, + isActive: isActive ?? this.isActive, + ); + } +} diff --git a/packages/dashbot/lib/core/providers/dashbot_window_notifier.dart b/packages/dashbot/lib/core/providers/dashbot_window_notifier.dart new file mode 100644 index 00000000..c1626505 --- /dev/null +++ b/packages/dashbot/lib/core/providers/dashbot_window_notifier.dart @@ -0,0 +1,36 @@ +import '../model/dashbot_window_model.dart'; +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'dashbot_window_notifier.g.dart'; + +@Riverpod(keepAlive: true) +class DashbotWindowNotifier extends _$DashbotWindowNotifier { + @override + DashbotWindowModel build() { + return const DashbotWindowModel(); + } + + void updatePosition(double dx, double dy, Size screenSize) { + state = state.copyWith( + right: (state.right - dx).clamp(0, screenSize.width - state.width), + bottom: (state.bottom - dy).clamp(0, screenSize.height - state.height), + ); + } + + void updateSize(double dx, double dy, Size screenSize) { + final newWidth = + (state.width - dx).clamp(300, screenSize.width - state.right); + final newHeight = + (state.height - dy).clamp(350, screenSize.height - state.bottom); + + state = state.copyWith( + width: newWidth.toDouble(), + height: newHeight.toDouble(), + ); + } + + void toggleActive() { + state = state.copyWith(isActive: !state.isActive); + } +} diff --git a/packages/dashbot/lib/core/providers/dashbot_window_notifier.g.dart b/packages/dashbot/lib/core/providers/dashbot_window_notifier.g.dart new file mode 100644 index 00000000..e4a8aed2 --- /dev/null +++ b/packages/dashbot/lib/core/providers/dashbot_window_notifier.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dashbot_window_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$dashbotWindowNotifierHash() => + r'239915bec100bc33e5533291ff10233ea197d556'; + +/// See also [DashbotWindowNotifier]. +@ProviderFor(DashbotWindowNotifier) +final dashbotWindowNotifierProvider = + NotifierProvider.internal( + DashbotWindowNotifier.new, + name: r'dashbotWindowNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$dashbotWindowNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$DashbotWindowNotifier = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/packages/dashbot/lib/core/routes/dashbot_router.dart b/packages/dashbot/lib/core/routes/dashbot_router.dart new file mode 100644 index 00000000..ead2599c --- /dev/null +++ b/packages/dashbot/lib/core/routes/dashbot_router.dart @@ -0,0 +1,31 @@ +import 'dashbot_routes.dart'; +import '../common/pages/dashbot_default_page.dart'; +import '../../features/home/view/pages/home_page.dart'; +import 'package:flutter/material.dart'; + +Route? generateRoute( + RouteSettings settings, +) { + switch (settings.name) { + case (DashbotRoutes.dashbotHome): + return MaterialPageRoute( + builder: (context) => DashbotHomePage(), + ); + case (DashbotRoutes.dashbotDefault): + return MaterialPageRoute( + builder: (context) => DashbotDefaultPage(), + ); + // case (DashbotRoutes.dashbotChat): + // final args = settings.arguments as Map?; + // final initialPrompt = args?['initialPrompt'] as String; + // return MaterialPageRoute( + // builder: (context) => ChatScreen( + // initialPrompt: initialPrompt, + // ), + // ); + default: + return MaterialPageRoute( + builder: (context) => DashbotDefaultPage(), + ); + } +} diff --git a/packages/dashbot/lib/core/routes/dashbot_routes.dart b/packages/dashbot/lib/core/routes/dashbot_routes.dart new file mode 100644 index 00000000..39b904d9 --- /dev/null +++ b/packages/dashbot/lib/core/routes/dashbot_routes.dart @@ -0,0 +1,6 @@ +class DashbotRoutes { + static const String dashbotHome = '/dashbothome'; + static const String dashbotDefault = '/dashbotdefault'; + static const String dashbotChat = '/dashbotchat'; + static const String dashbotUnknown = '/dashbotunknown'; +} diff --git a/packages/dashbot/lib/core/utils/dashbot_icons.dart b/packages/dashbot/lib/core/utils/dashbot_icons.dart new file mode 100644 index 00000000..1fb34432 --- /dev/null +++ b/packages/dashbot/lib/core/utils/dashbot_icons.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; + +class DashbotIcons { + DashbotIcons._(); + static String get dashbotIcon1 => + 'packages/dashbot/assets/dashbot_icon_1.png'; + static String get dashbotIcon2 => + 'packages/dashbot/assets/dashbot_icon_2.png'; + + static Image getDashbotIcon1({double? width, double? height, BoxFit? fit}) { + return Image.asset(dashbotIcon1, width: width, height: height, fit: fit); + } + + static Image getDashbotIcon2({double? width, double? height, BoxFit? fit}) { + return Image.asset(dashbotIcon2, width: width, height: height, fit: fit); + } +} diff --git a/packages/dashbot/lib/core/utils/show_dashbot.dart b/packages/dashbot/lib/core/utils/show_dashbot.dart new file mode 100644 index 00000000..68d291af --- /dev/null +++ b/packages/dashbot/lib/core/utils/show_dashbot.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../dashbot_dashboard.dart'; +import '../providers/dashbot_window_notifier.dart'; + +void showDashbotWindow(BuildContext context, WidgetRef ref) { + final isDashbotActive = ref.read(dashbotWindowNotifierProvider).isActive; + final windowNotifier = ref.read(dashbotWindowNotifierProvider.notifier); + if (isDashbotActive) return; + final overlay = Overlay.of(context); + OverlayEntry? entry; + + entry = OverlayEntry( + builder: + (context) => DashbotWindow( + onClose: () { + entry?.remove(); + windowNotifier.toggleActive(); + }, + ), + ); + windowNotifier.toggleActive(); + overlay.insert(entry); +} diff --git a/packages/dashbot/lib/core/utils/utils.dart b/packages/dashbot/lib/core/utils/utils.dart new file mode 100644 index 00000000..f1139d25 --- /dev/null +++ b/packages/dashbot/lib/core/utils/utils.dart @@ -0,0 +1,2 @@ +export 'show_dashbot.dart'; +export 'dashbot_icons.dart'; diff --git a/packages/dashbot/lib/dashbot.dart b/packages/dashbot/lib/dashbot.dart new file mode 100644 index 00000000..76a71656 --- /dev/null +++ b/packages/dashbot/lib/dashbot.dart @@ -0,0 +1,3 @@ +export 'dashbot_dashboard.dart'; +export 'core/providers/dashbot_window_notifier.dart'; +export 'core/utils/utils.dart'; diff --git a/packages/dashbot/lib/dashbot_dashboard.dart b/packages/dashbot/lib/dashbot_dashboard.dart new file mode 100644 index 00000000..6ca95c20 --- /dev/null +++ b/packages/dashbot/lib/dashbot_dashboard.dart @@ -0,0 +1,147 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:dashbot/core/utils/dashbot_icons.dart'; + +import 'core/providers/dashbot_window_notifier.dart'; +import 'core/routes/dashbot_router.dart'; +import 'core/routes/dashbot_routes.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class DashbotWindow extends ConsumerWidget { + final VoidCallback onClose; + + const DashbotWindow({super.key, required this.onClose}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final windowState = ref.watch(dashbotWindowNotifierProvider); + final windowNotifier = ref.read(dashbotWindowNotifierProvider.notifier); + // final RequestModel? currentRequest = ref.watch( + // selectedRequestModelProvider, + // ); + + return Stack( + children: [ + Positioned( + right: windowState.right, + bottom: windowState.bottom, + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(8), + child: Container( + width: windowState.width, + height: windowState.height, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + Column( + children: [ + // This is to update position + GestureDetector( + onPanUpdate: (details) { + windowNotifier.updatePosition( + details.delta.dx, + details.delta.dy, + MediaQuery.of(context).size, + ); + }, + child: Container( + height: 50, + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.primaryContainer, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(10), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + kHSpacer20, + DashbotIcons.getDashbotIcon1(width: 38), + + kHSpacer12, + Text( + 'DashBot', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: + Theme.of(context).colorScheme.surface, + ), + ), + ], + ), + IconButton( + icon: Icon( + Icons.close, + color: + Theme.of(context).colorScheme.onPrimary, + ), + onPressed: onClose, + ), + ], + ), + ), + ), + Expanded( + child: Navigator( + initialRoute: DashbotRoutes.dashbotHome, + // currentRequest?.responseStatus == null + // ? DashbotRoutes.dashbotDefault + // : DashbotRoutes.dashbotHome, + onGenerateRoute: generateRoute, + ), + ), + ], + ), + // This is to update size + Positioned( + left: 0, + top: 0, + child: GestureDetector( + onPanUpdate: (details) { + windowNotifier.updateSize( + details.delta.dx, + details.delta.dy, + MediaQuery.of(context).size, + ); + }, + child: MouseRegion( + cursor: SystemMouseCursors.resizeUpLeft, + child: Container( + padding: EdgeInsets.only(top: 6, left: 1), + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + ), + color: Theme.of(context) + .colorScheme + .primaryContainer + .withValues(alpha: 0.7), + ), + child: Icon( + Icons.drag_indicator_rounded, + size: 16, + color: Theme.of(context).colorScheme.surfaceBright, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/packages/dashbot/lib/features/home/view/pages/home_page.dart b/packages/dashbot/lib/features/home/view/pages/home_page.dart new file mode 100644 index 00000000..fb5b15a1 --- /dev/null +++ b/packages/dashbot/lib/features/home/view/pages/home_page.dart @@ -0,0 +1,135 @@ +import 'package:dashbot/core/utils/dashbot_icons.dart'; + +import '../../../../core/routes/dashbot_routes.dart'; +import 'package:apidash_design_system/tokens/measurements.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class DashbotHomePage extends ConsumerStatefulWidget { + const DashbotHomePage({super.key}); + + @override + ConsumerState createState() => _DashbotHomePageState(); +} + +class _DashbotHomePageState extends ConsumerState { + void navigateToChat(String prompt) { + Navigator.of(context).pushNamed( + DashbotRoutes.dashbotChat, + arguments: {'initialPrompt': prompt}, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + kVSpacer16, + DashbotIcons.getDashbotIcon1(width: 60), + + kVSpacer16, + Text( + 'Hello there,', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.w800), + ), + Text('How can I help you today?'), + kVSpacer16, + Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + // TextButton( + // onPressed: () { + // Navigator.of(context).pushNamed( + // DashbotRoutes.dashbotChat, + // ); + // }, + // style: TextButton.styleFrom( + // side: BorderSide( + // color: Theme.of(context).colorScheme.primary, + // ), + // padding: const EdgeInsets.symmetric( + // vertical: 0, + // horizontal: 16, + // ), + // ), + // child: const Text("🤖 Chat with Dashbot"), + // ), + TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + padding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 16, + ), + ), + child: const Text("🔎 Explain me this response"), + ), + // TextButton( + // onPressed: () {}, + // style: TextButton.styleFrom( + // side: BorderSide( + // color: Theme.of(context).colorScheme.primary, + // ), + // padding: const EdgeInsets.symmetric( + // vertical: 0, + // horizontal: 16, + // ), + // ), + // child: const Text("🐞 Help me debug this error"), + // ), + TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + padding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 16, + ), + ), + child: const Text( + "📄 Generate documentation", + textAlign: TextAlign.center, + ), + ), + TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + padding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 16, + ), + ), + child: const Text("📝 Generate Tests"), + ), + // TextButton( + // onPressed: () {}, + // style: TextButton.styleFrom( + // side: BorderSide( + // color: Theme.of(context).colorScheme.primary, + // ), + // padding: const EdgeInsets.symmetric( + // vertical: 0, + // horizontal: 16, + // ), + // ), + // child: const Text("📊 Generate Visualizations"), + // ), + ], + ), + ], + ), + ); + } +} diff --git a/packages/dashbot/pubspec.yaml b/packages/dashbot/pubspec.yaml new file mode 100644 index 00000000..2548779f --- /dev/null +++ b/packages/dashbot/pubspec.yaml @@ -0,0 +1,63 @@ +name: dashbot +description: "A new Flutter package project." +version: 0.0.1 +publish_to: none + +environment: + sdk: ^3.7.2 + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + http: ^1.3.0 + apidash_design_system: + path: ../apidash_design_system + apidash_core: + path: ../apidash_core + flutter_riverpod: ^2.5.1 + riverpod_annotation: ^2.5.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + riverpod_lint: ^2.5.1 + riverpod_generator: ^2.5.1 + custom_lint: ^0.7.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # To add assets to your package, add an assets section, like this: + assets: + - assets/dashbot_icon_1.png + - assets/dashbot_icon_2.png + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/packages/dashbot/test/dashbot_test.dart b/packages/dashbot/test/dashbot_test.dart new file mode 100644 index 00000000..0da434d9 --- /dev/null +++ b/packages/dashbot/test/dashbot_test.dart @@ -0,0 +1,5 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('adds one to input values', () {}); +} diff --git a/packages/dashbot/test/providers/dashbot_window_notifier_test.dart b/packages/dashbot/test/providers/dashbot_window_notifier_test.dart new file mode 100644 index 00000000..e0ff8671 --- /dev/null +++ b/packages/dashbot/test/providers/dashbot_window_notifier_test.dart @@ -0,0 +1,131 @@ +import 'dart:ui'; +import 'package:dashbot/core/model/dashbot_window_model.dart'; +import 'package:dashbot/core/providers/dashbot_window_notifier.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../test/providers/helpers.dart'; + +void main() { + const testScreenSize = Size(1200, 800); + + group("DashbotWindowNotifier - ", () { + test( + 'given dashbot window model when instatiated then initial values must match the default values', + () { + final container = createContainer(); + + final initialState = container.read(dashbotWindowNotifierProvider); + + expect(initialState, const DashbotWindowModel()); + expect(initialState.width, 350); + expect(initialState.height, 450); + expect(initialState.right, 50); + expect(initialState.bottom, 100); + expect(initialState.isActive, false); + }); + + test('Toggle active state', () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Initial state is false + expect(container.read(dashbotWindowNotifierProvider).isActive, false); + + // First toggle + notifier.toggleActive(); + expect(container.read(dashbotWindowNotifierProvider).isActive, true); + + // Second toggle + notifier.toggleActive(); + expect(container.read(dashbotWindowNotifierProvider).isActive, false); + }); + + group('Position updates', () { + test( + 'given dashbot window notifier when position is updated 100px to the left and 50px down then then values must be 150 and 50', + () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Drag 100px left and 50px down + notifier.updatePosition(-100, 50, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + expect(state.right, 50 - (-100)); // 50 - (-100) = 150 + expect(state.bottom, 100 - 50); // 100 - 50 = 50 + }); + + test( + 'given dashbot window notifier when position is updated beyond the left boundary then the value must be clamped to the upper boundary', + () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Try to drag 1200px left (dx positive when moving right in coordinates) + notifier.updatePosition(-1200, 0, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + expect(state.right, + 850); // 50 - (-1200) = 1250 → not within bounds, max is screen width(1200) - width(350) = 850 + }); + + test( + 'given dashbot window notifier when position is updated beyond bottom boundary then the value must be clamped to the upper boundary', + () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Move to bottom edge + notifier.updatePosition(0, -700, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + // 100 - (-700) = 800 → but max is screenHeight(800) - height(450) = 350 + expect(state.bottom, 350); + }); + }); + + group('Size updates', () { + test('Normal resize within bounds', () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Increase width by 100px, height by 50px + notifier.updateSize(-100, -50, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + expect(state.width, 350 - (-100)); // = 450 + expect(state.height, 450 - (-50)); // = 500 + }); + + test( + 'given dashbot window notifier when tried to resize below the minimum limit then the value must be clamped to the lower boundary', + () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Try to shrink too much + notifier.updateSize(100, 100, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + expect(state.width, 300); // Clamped to minimum + expect(state.height, 350); // Clamped to minimum + }); + + test( + 'given dashbot window notifier when tried to resize above the maximum limit then the value must be clamped to the upper boundary', + () { + final container = createContainer(); + final notifier = container.read(dashbotWindowNotifierProvider.notifier); + + // Try to expand beyond screen + notifier.updateSize(-1200, -900, testScreenSize); + + final state = container.read(dashbotWindowNotifierProvider); + // Max width = screenWidth(1200) - right(50) = 1150 + expect(state.width, 1150); + // Max height = screenHeight(800) - bottom(100) = 700 + expect(state.height, 700); + }); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index 6d3a82d2..d10ddfa9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -381,6 +381,13 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + dashbot: + dependency: "direct main" + description: + path: "packages/dashbot" + relative: true + source: path + version: "0.0.1" data_table_2: dependency: "direct main" description: @@ -1388,6 +1395,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.1" + riverpod_annotation: + dependency: transitive + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a6d02747..868b4bc2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: path: packages/apidash_core apidash_design_system: path: packages/apidash_design_system + dashbot: + path: packages/dashbot carousel_slider: ^5.0.0 code_builder: ^4.10.0 csv: ^6.0.0