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 00000000..0d50bc49 Binary files /dev/null and b/packages/dashbot/assets/dashbot_icon_1.png differ diff --git a/packages/dashbot/assets/dashbot_icon_2.png b/packages/dashbot/assets/dashbot_icon_2.png new file mode 100644 index 00000000..b294d14f Binary files /dev/null and b/packages/dashbot/assets/dashbot_icon_2.png differ 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