Move DashBot to bottom-right, add close/minimize/maximize buttons and toggle FAB visibility

This commit is contained in:
siddu015
2025-03-22 15:18:25 +05:30
parent 7582c78880
commit f44091a50c
3 changed files with 328 additions and 184 deletions

View File

@@ -1 +1,66 @@
export 'widgets/dashbot_widget.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/dashbot/widgets/dashbot_widget.dart';
// Provider to manage DashBot visibility state
final dashBotVisibilityProvider = StateProvider<bool>((ref) => false);
final dashBotMinimizedProvider = StateProvider<bool>((ref) => false);
// Function to show DashBot in a bottom sheet (old style)
void showDashBotBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const Padding(
padding: EdgeInsets.all(16.0),
child: DashBotWidget(),
),
);
}
// Function to toggle DashBot overlay (new style)
void toggleDashBotOverlay(WidgetRef ref) {
ref.read(dashBotVisibilityProvider.notifier).state = true;
ref.read(dashBotMinimizedProvider.notifier).state = false;
}
// DashBot Overlay Widget
class DashBotOverlay extends ConsumerWidget {
const DashBotOverlay({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isMinimized = ref.watch(dashBotMinimizedProvider);
return Material(
elevation: 8,
borderRadius: BorderRadius.circular(12),
child: SizedBox(
width: 400, // Fixed width for the DashBot
height: isMinimized ? 120 : 450,
child: const DashBotWidget(),
),
);
}
}
// FloatingActionButton for DashBot
class DashBotFAB extends ConsumerWidget {
final bool useOverlay;
const DashBotFAB({this.useOverlay = true, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return FloatingActionButton(
onPressed: () {
if (useOverlay) {
toggleDashBotOverlay(ref);
} else {
showDashBotBottomSheet(context);
}
},
child: const Icon(Icons.help_outline),
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/dashbot/providers/dashbot_providers.dart'; import 'package:apidash/dashbot/providers/dashbot_providers.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/dashbot/dashbot.dart';
import 'test_runner_widget.dart'; import 'test_runner_widget.dart';
import 'chat_bubble.dart'; import 'chat_bubble.dart';
@@ -78,7 +79,7 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
setState(() => _isLoading = false); setState(() => _isLoading = false);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo( _scrollController.animateTo(
0, _scrollController.position.minScrollExtent,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeOut, curve: Curves.easeOut,
); );
@@ -105,19 +106,18 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
final requestModel = ref.read(selectedRequestModelProvider); final requestModel = ref.read(selectedRequestModelProvider);
final statusCode = requestModel?.httpResponseModel?.statusCode; final statusCode = requestModel?.httpResponseModel?.statusCode;
final showDebugButton = statusCode != null && statusCode >= 400; final showDebugButton = statusCode != null && statusCode >= 400;
final isMinimized = ref.watch(dashBotMinimizedProvider);
return Container( return Container(
height: 450, height: double.infinity,
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4))
],
), ),
child: Column( child: isMinimized
? _buildMinimizedView(context)
: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(context), _buildHeader(context),
@@ -134,66 +134,126 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
} }
Widget _buildHeader(BuildContext context) { Widget _buildHeader(BuildContext context) {
return Row( final isMinimized = ref.watch(dashBotMinimizedProvider);
return Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text('DashBot', const Text(
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), 'DashBot',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Row(
children: [
// Minimize/Maximize button with proper alignment
IconButton( IconButton(
icon: const Icon(Icons.delete_sweep), padding: const EdgeInsets.all(8),
visualDensity: VisualDensity.compact,
icon: Icon(
isMinimized ? Icons.fullscreen : Icons.remove,
size: 20,
),
tooltip: isMinimized ? 'Maximize' : 'Minimize',
onPressed: () {
ref.read(dashBotMinimizedProvider.notifier).state = !isMinimized;
},
),
// Close button
IconButton(
padding: const EdgeInsets.all(8),
visualDensity: VisualDensity.compact,
icon: const Icon(Icons.close, size: 20),
tooltip: 'Close',
onPressed: () {
ref.read(dashBotVisibilityProvider.notifier).state = false;
},
),
// Clear chat button
IconButton(
padding: const EdgeInsets.all(8),
visualDensity: VisualDensity.compact,
icon: const Icon(Icons.delete_sweep, size: 20),
tooltip: 'Clear Chat', tooltip: 'Clear Chat',
onPressed: () { onPressed: () {
ref.read(chatMessagesProvider.notifier).clearMessages(); ref.read(chatMessagesProvider.notifier).clearMessages();
}, },
), ),
], ],
),
],
),
); );
} }
Widget _buildQuickActions(bool showDebugButton) { Widget _buildMinimizedView(BuildContext context) {
return Wrap( return Column(
spacing: 8, crossAxisAlignment: CrossAxisAlignment.start,
runSpacing: 8,
children: [ children: [
ElevatedButton.icon( _buildHeader(context),
onPressed: () => _sendMessage("Explain API"), const SizedBox(height: 8),
icon: const Icon(Icons.info_outline), Padding(
label: const Text("Explain"), padding: const EdgeInsets.symmetric(horizontal: 16),
style: ElevatedButton.styleFrom( child: _buildInputArea(context),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
if (showDebugButton)
ElevatedButton.icon(
onPressed: () => _sendMessage("Debug API"),
icon: const Icon(Icons.bug_report_outlined),
label: const Text("Debug"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
ElevatedButton.icon(
onPressed: () => _sendMessage("Document API"),
icon: const Icon(Icons.description_outlined),
label: const Text("Document"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
ElevatedButton.icon(
onPressed: () => _sendMessage("Test API"),
icon: const Icon(Icons.science_outlined),
label: const Text("Test"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
), ),
], ],
); );
} }
Widget _buildQuickActions(bool showDebugButton) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: () => _sendMessage("Explain API"),
icon: const Icon(Icons.info_outline, size: 16),
label: const Text("Explain"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
visualDensity: VisualDensity.compact,
),
),
if (showDebugButton)
ElevatedButton.icon(
onPressed: () => _sendMessage("Debug API"),
icon: const Icon(Icons.bug_report_outlined, size: 16),
label: const Text("Debug"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
visualDensity: VisualDensity.compact,
),
),
ElevatedButton.icon(
onPressed: () => _sendMessage("Document API"),
icon: const Icon(Icons.description_outlined, size: 16),
label: const Text("Document"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
visualDensity: VisualDensity.compact,
),
),
ElevatedButton.icon(
onPressed: () => _sendMessage("Test API"),
icon: const Icon(Icons.science_outlined, size: 16),
label: const Text("Test"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
visualDensity: VisualDensity.compact,
),
),
],
),
);
}
Widget _buildChatArea(List<Map<String, dynamic>> messages) { Widget _buildChatArea(List<Map<String, dynamic>> messages) {
return ListView.builder( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView.builder(
controller: _scrollController, controller: _scrollController,
reverse: true, reverse: true,
itemCount: messages.length, itemCount: messages.length,
@@ -213,8 +273,11 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4),
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => _showTestRunner(testCases), onPressed: () => _showTestRunner(testCases),
icon: const Icon(Icons.play_arrow), icon: const Icon(Icons.play_arrow, size: 16),
label: const Text("Run Test Cases"), label: const Text("Run Test Cases"),
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.compact,
),
), ),
), ),
], ],
@@ -226,23 +289,26 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
isUser: message['role'] == 'user', isUser: message['role'] == 'user',
); );
}, },
),
); );
} }
Widget _buildLoadingIndicator() { Widget _buildLoadingIndicator() {
return const Padding( return const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: LinearProgressIndicator(), child: LinearProgressIndicator(),
); );
} }
Widget _buildInputArea(BuildContext context) { Widget _buildInputArea(BuildContext context) {
final isMinimized = ref.watch(dashBotMinimizedProvider);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
), ),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -251,19 +317,28 @@ class _DashBotWidgetState extends ConsumerState<DashBotWidget> {
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: 'Ask DashBot...', hintText: 'Ask DashBot...',
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 8),
), ),
onSubmitted: (value) { onSubmitted: (value) {
_sendMessage(value); _sendMessage(value);
_controller.clear(); _controller.clear();
if (isMinimized) {
ref.read(dashBotMinimizedProvider.notifier).state = false;
}
}, },
maxLines: 1, maxLines: 1,
), ),
), ),
IconButton( IconButton(
icon: const Icon(Icons.send), padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
icon: const Icon(Icons.send, size: 20),
onPressed: () { onPressed: () {
_sendMessage(_controller.text); _sendMessage(_controller.text);
_controller.clear(); _controller.clear();
if (isMinimized) {
ref.read(dashBotMinimizedProvider.notifier).state = false;
}
}, },
), ),
], ],

View File

@@ -17,9 +17,13 @@ class Dashboard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final railIdx = ref.watch(navRailIndexStateProvider); final railIdx = ref.watch(navRailIndexStateProvider);
final isDashBotVisible = ref.watch(dashBotVisibilityProvider);
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
child: Row( child: Stack(
children: [
Row(
children: <Widget>[ children: <Widget>[
Column( Column(
children: [ children: [
@@ -124,19 +128,19 @@ class Dashboard extends ConsumerWidget {
) )
], ],
), ),
// DashBot Overlay
if (isDashBotVisible)
Positioned(
bottom: 20,
right: 20,
child: const DashBotOverlay(),
), ),
// TODO: Release DashBot ],
floatingActionButton: FloatingActionButton(
onPressed: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const Padding(
padding: EdgeInsets.all(16.0),
child: DashBotWidget(),
), ),
), ),
child: const Icon(Icons.help_outline), // Conditionally show FAB only when DashBot is not visible
), floatingActionButton: !isDashBotVisible ? const DashBotFAB() : null,
); );
} }
} }