15 files changed
+372
-157
.gitignore
+4
-2
@@ -47,5 +47,7 @@ app.*.map.json
47
# FVM Version Cache
48
.fvm/
49
playstore_keys
50
-.agent
51
-.claude
\ No newline at end of file
50
+.agent/
51
+.claude/
52
+.dart_skills/
53
+private/
\ No newline at end of file
lib/app.dart
+4
-4
@@ -5,9 +5,9 @@ import 'package:shadcn_ui/shadcn_ui.dart';
5
import 'package:localmind/core/theme/app_theme.dart';
6
import 'package:localmind/core/providers/app_providers.dart';
7
import 'package:localmind/features/servers/data/models/server.dart';
8
-import 'package:localmind/features/chat/screens/chat_screen.dart';
9
-import 'package:localmind/features/conversations/views/conversation_sidebar.dart';
10
-import 'package:localmind/features/conversations/views/conversation_drawer.dart';
8
+import 'package:localmind/features/chat/views/chat_screen.dart';
9
+import 'package:localmind/features/sidebar/sidebar_widget.dart';
10
+import 'package:localmind/features/sidebar/sidebar_drawer.dart';
11
import 'package:localmind/features/servers/views/server_list_screen.dart';
12
import 'package:localmind/features/servers/views/add_server_screen.dart';
13
import 'package:localmind/features/personas/views/persona_list_screen.dart';
@@ -200,7 +200,7 @@ class AppShell extends StatelessWidget {
200
return Scaffold(
201
body: Row(
202
children: [
203
- const ConversationSidebar(),
203
+ const SidebarWidget(),
204
VerticalDivider(
205
width: 1,
206
thickness: 1,
lib/features/chat/data/chat_service.dart
+8
-8
@@ -168,12 +168,12 @@ class LMStudioChatService implements ChatService {
168
cancelToken: _cancelToken,
169
);
170
171
- final stream = response.data!.stream;
171
+ final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
172
String buffer = '';
173
String currentEventType = '';
174
175
await for (final chunk in stream) {
176
- buffer += String.fromCharCodes(chunk);
176
+ buffer += chunk;
177
final lines = buffer.split('\n');
178
buffer = lines.removeLast();
179
@@ -415,11 +415,11 @@ class OpenAICompatibleChatService implements ChatService {
415
cancelToken: _cancelToken,
416
);
417
418
- final stream = response.data!.stream;
418
+ final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
419
String buffer = '';
420
421
await for (final chunk in stream) {
422
- buffer += String.fromCharCodes(chunk);
422
+ buffer += chunk;
423
final lines = buffer.split('\n');
424
buffer = lines.removeLast();
425
@@ -490,11 +490,11 @@ class OllamaChatService implements ChatService {
490
cancelToken: _cancelToken,
491
);
492
493
- final stream = response.data!.stream;
493
+ final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
494
String buffer = '';
495
496
await for (final chunk in stream) {
497
- buffer += String.fromCharCodes(chunk);
497
+ buffer += chunk;
498
final lines = buffer.split('\n');
499
buffer = lines.removeLast();
500
@@ -570,11 +570,11 @@ class OpenRouterChatService implements ChatService {
570
cancelToken: _cancelToken,
571
);
572
573
- final stream = response.data!.stream;
573
+ final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
574
String buffer = '';
575
576
await for (final chunk in stream) {
577
- buffer += String.fromCharCodes(chunk);
577
+ buffer += chunk;
578
final lines = buffer.split('\n');
579
buffer = lines.removeLast();
580
lib/features/chat/data/models/message.g.dart
+2
-1
@@ -8,7 +8,7 @@ part of 'message.dart';
8
9
class MessageAdapter extends TypeAdapter<Message> {
10
@override
11
- final int typeId = 1;
11
+ final typeId = 1;
12
13
@override
14
Message read(BinaryReader reader) {
@@ -31,6 +31,7 @@ class MessageAdapter extends TypeAdapter<Message> {
31
attachmentPaths: (fields[9] as List?)?.cast<String>(),
32
generationTimeMs: (fields[10] as num?)?.toInt(),
33
reasoningContent: fields[11] as String?,
34
+ toolCalls: (fields[12] as List?)?.cast<ToolCallData>(),
35
toolCallId: fields[13] as String?,
36
);
37
}
lib/features/chat/views/chat_screen.dart
renamed
+22
-9
@@ -272,6 +272,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
272
.read(chatProvider.notifier)
273
.deleteMessage(messageId);
274
},
275
+ hasSmartReplies:
276
+ !chatState.isStreaming &&
277
+ ref.read(smartRepliesProvider).isNotEmpty,
278
),
279
if (_showScrollToBottom && chatState.messages.isNotEmpty)
280
Positioned(
@@ -339,15 +342,20 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
342
),
343
),
344
),
345
+ if (!chatState.isStreaming)
346
+ Positioned(
347
+ bottom: 8,
348
+ left: 0,
349
+ right: 0,
350
+ child: _SmartReplyChips(
351
+ onSend: (message) {
352
+ ref.read(chatProvider.notifier).sendMessage(message);
353
+ },
354
+ ),
355
+ ),
356
],
357
),
358
),
345
- if (!chatState.isStreaming)
346
- _SmartReplyChips(
347
- onSend: (message) {
348
- ref.read(chatProvider.notifier).sendMessage(message);
349
- },
350
- ),
359
ChatInputBar(
360
isStreaming: chatState.isStreaming,
361
onSend: (message) {
@@ -895,6 +903,7 @@ class _MessageList extends StatelessWidget {
903
required this.isStreaming,
904
required this.onRetry,
905
required this.onDelete,
906
+ this.hasSmartReplies = false,
907
});
908
909
final ScrollController scrollController;
@@ -903,6 +912,7 @@ class _MessageList extends StatelessWidget {
912
final bool isStreaming;
913
final void Function(String) onRetry;
914
final void Function(String) onDelete;
915
+ final bool hasSmartReplies;
916
917
@override
918
Widget build(BuildContext context) {
@@ -919,7 +929,10 @@ class _MessageList extends StatelessWidget {
929
930
return ListView.builder(
931
controller: scrollController,
922
- padding: EdgeInsets.only(top: 16, bottom: 16),
932
+ padding: EdgeInsets.only(
933
+ top: 16,
934
+ bottom: 16 + (hasSmartReplies ? 56 : 0),
935
+ ),
936
itemCount:
937
allMessages.length +
938
(streamingMessage != null && isStreaming ? 1 : 0),
@@ -1044,10 +1057,10 @@ class _SmartReplyChipsState extends ConsumerState<_SmartReplyChips>
1057
return AnimatedSize(
1058
duration: const Duration(milliseconds: 200),
1059
curve: Curves.easeOut,
1047
- child: Container(
1060
+ child: SizedBox(
1061
height: 40,
1049
- padding: const EdgeInsets.symmetric(horizontal: 8),
1062
child: ListView.separated(
1063
+ padding: EdgeInsets.only(left: 8),
1064
scrollDirection: Axis.horizontal,
1065
itemCount: suggestions.length,
1066
separatorBuilder: (_, _) => const SizedBox(width: 6),
lib/features/conversations/views/components/conversation_list.dart
-1
@@ -82,7 +82,6 @@ class ConversationList extends ConsumerWidget {
82
);
83
}
84
85
-
85
void _showRenameDialog(
86
BuildContext context,
87
WidgetRef ref,
lib/features/onboarding/screens/onboarding_server_setup_screen.dart
+1
-1
@@ -64,7 +64,7 @@ class _OnboardingServerSetupScreenState
64
_hostController = TextEditingController(
65
text: widget.selectedType == ServerType.openRouter
66
? ''
67
- : (Platform.isAndroid ? '10.0.2.2' : '127.0.0.1'),
67
+ : (Platform.isAndroid ? '192.168.0.0' : '127.0.0.1'),
68
);
69
_portController = TextEditingController(text: defaultPort);
70
_apiKeyController = TextEditingController();
lib/features/servers/views/components/server_type_selector.dart
+91
-30
@@ -1,4 +1,5 @@
1
import 'package:flutter/material.dart';
2
+import 'package:hugeicons/hugeicons.dart';
3
import '../../../../core/models/enums.dart';
4
5
class ServerTypeSelector extends StatelessWidget {
@@ -13,36 +14,96 @@ class ServerTypeSelector extends StatelessWidget {
14
15
@override
16
Widget build(BuildContext context) {
16
- return SegmentedButton<ServerType>(
17
- segments: const [
18
- ButtonSegment<ServerType>(
19
- value: ServerType.lmStudio,
20
- label: Text('LM Studio'),
21
- icon: Icon(Icons.terminal, size: 18),
22
- ),
23
- ButtonSegment<ServerType>(
24
- value: ServerType.openAICompatible,
25
- label: Text('OpenAI'),
26
- icon: Icon(Icons.api, size: 18),
27
- ),
28
- ButtonSegment<ServerType>(
29
- value: ServerType.ollama,
30
- label: Text('Ollama'),
31
- icon: Icon(Icons.pets, size: 18),
32
- ),
33
- ButtonSegment<ServerType>(
34
- value: ServerType.openRouter,
35
- label: Text('OpenRouter'),
36
- icon: Icon(Icons.cloud, size: 18),
37
- ),
38
- ],
39
- selected: {selectedType},
40
- onSelectionChanged: (Set<ServerType> selected) {
41
- if (selected.isNotEmpty) {
42
- onChanged(selected.first);
43
- }
44
- },
45
- showSelectedIcon: false,
17
+ final theme = Theme.of(context);
18
+ final colorScheme = theme.colorScheme;
19
+
20
+ final types = [
21
+ (
22
+ ServerType.lmStudio,
23
+ 'LM Studio',
24
+ HugeIcons.strokeRoundedAiComputer,
25
+ Colors.blue,
26
+ ),
27
+ (
28
+ ServerType.openAICompatible,
29
+ 'OpenAI',
30
+ HugeIcons.strokeRoundedApi,
31
+ Colors.green,
32
+ ),
33
+ (
34
+ ServerType.ollama,
35
+ 'Ollama',
36
+ HugeIcons.strokeRoundedPencilEdit02,
37
+ Colors.orange,
38
+ ),
39
+ (
40
+ ServerType.openRouter,
41
+ 'OpenRouter',
42
+ HugeIcons.strokeRoundedCloud,
43
+ Colors.purple,
44
+ ),
45
+ ];
46
+
47
+ return GridView.count(
48
+ shrinkWrap: true,
49
+ physics: const NeverScrollableScrollPhysics(),
50
+ crossAxisCount: 2,
51
+ mainAxisSpacing: 8,
52
+ crossAxisSpacing: 8,
53
+ childAspectRatio: 2.0,
54
+ children: types.map((item) {
55
+ final type = item.$1;
56
+ final label = item.$2;
57
+ final icon = item.$3;
58
+ final accentColor = item.$4;
59
+ final isSelected = selectedType == type;
60
+
61
+ return GestureDetector(
62
+ onTap: () => onChanged(type),
63
+ child: Container(
64
+ decoration: BoxDecoration(
65
+ color: isSelected
66
+ ? accentColor.withValues(alpha: 0.15)
67
+ : colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
68
+ borderRadius: BorderRadius.circular(12),
69
+ border: Border.all(
70
+ color: isSelected
71
+ ? accentColor.withValues(alpha: 0.5)
72
+ : colorScheme.outline.withValues(alpha: 0.3),
73
+ width: isSelected ? 2 : 1,
74
+ ),
75
+ ),
76
+ child: Column(
77
+ mainAxisAlignment: MainAxisAlignment.center,
78
+ children: [
79
+ Container(
80
+ width: 32,
81
+ height: 32,
82
+ decoration: BoxDecoration(
83
+ color: isSelected
84
+ ? accentColor.withValues(alpha: 0.2)
85
+ : colorScheme.surface,
86
+ borderRadius: BorderRadius.circular(8),
87
+ ),
88
+ child: HugeIcon(
89
+ icon: icon,
90
+ size: 18,
91
+ color: isSelected ? accentColor : colorScheme.onSurface,
92
+ ),
93
+ ),
94
+ const SizedBox(height: 6),
95
+ Text(
96
+ label,
97
+ style: theme.textTheme.bodySmall?.copyWith(
98
+ fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
99
+ color: isSelected ? accentColor : colorScheme.onSurface,
100
+ ),
101
+ ),
102
+ ],
103
+ ),
104
+ ),
105
+ );
106
+ }).toList(),
107
);
108
}
109
}
lib/features/settings/data/models/app_settings.g.dart
+73
-84
@@ -6,90 +6,6 @@ part of 'app_settings.dart';
6
// TypeAdapterGenerator
7
// **************************************************************************
8
9
-class SyntaxThemeNameAdapter extends TypeAdapter<SyntaxThemeName> {
10
- @override
11
- final typeId = 5;
12
-
13
- @override
14
- SyntaxThemeName read(BinaryReader reader) {
15
- switch (reader.readByte()) {
16
- case 0:
17
- return SyntaxThemeName.vscodeDark;
18
- case 1:
19
- return SyntaxThemeName.vscodeLight;
20
- case 2:
21
- return SyntaxThemeName.dracula;
22
- case 3:
23
- return SyntaxThemeName.monokaiSublime;
24
- case 4:
25
- return SyntaxThemeName.ayuLight;
26
- case 5:
27
- return SyntaxThemeName.ayuDark;
28
- case 6:
29
- return SyntaxThemeName.gravityLight;
30
- case 7:
31
- return SyntaxThemeName.gravityDark;
32
- case 8:
33
- return SyntaxThemeName.obsidian;
34
- case 9:
35
- return SyntaxThemeName.oceanSunset;
36
- case 10:
37
- return SyntaxThemeName.standard;
38
- default:
39
- return SyntaxThemeName.vscodeDark;
40
- }
41
- }
42
-
43
- @override
44
- void write(BinaryWriter writer, SyntaxThemeName obj) {
45
- switch (obj) {
46
- case SyntaxThemeName.vscodeDark:
47
- writer.writeByte(0);
48
- break;
49
- case SyntaxThemeName.vscodeLight:
50
- writer.writeByte(1);
51
- break;
52
- case SyntaxThemeName.dracula:
53
- writer.writeByte(2);
54
- break;
55
- case SyntaxThemeName.monokaiSublime:
56
- writer.writeByte(3);
57
- break;
58
- case SyntaxThemeName.ayuLight:
59
- writer.writeByte(4);
60
- break;
61
- case SyntaxThemeName.ayuDark:
62
- writer.writeByte(5);
63
- break;
64
- case SyntaxThemeName.gravityLight:
65
- writer.writeByte(6);
66
- break;
67
- case SyntaxThemeName.gravityDark:
68
- writer.writeByte(7);
69
- break;
70
- case SyntaxThemeName.obsidian:
71
- writer.writeByte(8);
72
- break;
73
- case SyntaxThemeName.oceanSunset:
74
- writer.writeByte(9);
75
- break;
76
- case SyntaxThemeName.standard:
77
- writer.writeByte(10);
78
- break;
79
- }
80
- }
81
-
82
- @override
83
- int get hashCode => typeId.hashCode;
84
-
85
- @override
86
- bool operator ==(Object other) =>
87
- identical(this, other) ||
88
- other is SyntaxThemeNameAdapter &&
89
- runtimeType == other.runtimeType &&
90
- typeId == other.typeId;
91
-}
92
-
9
class AppSettingsAdapter extends TypeAdapter<AppSettings> {
10
@override
11
final typeId = 4;
@@ -173,3 +89,76 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
89
runtimeType == other.runtimeType &&
90
typeId == other.typeId;
91
}
92
+
93
+class SyntaxThemeNameAdapter extends TypeAdapter<SyntaxThemeName> {
94
+ @override
95
+ final typeId = 5;
96
+
97
+ @override
98
+ SyntaxThemeName read(BinaryReader reader) {
99
+ switch (reader.readByte()) {
100
+ case 0:
101
+ return SyntaxThemeName.vscodeDark;
102
+ case 1:
103
+ return SyntaxThemeName.vscodeLight;
104
+ case 2:
105
+ return SyntaxThemeName.dracula;
106
+ case 3:
107
+ return SyntaxThemeName.monokaiSublime;
108
+ case 4:
109
+ return SyntaxThemeName.ayuLight;
110
+ case 5:
111
+ return SyntaxThemeName.ayuDark;
112
+ case 6:
113
+ return SyntaxThemeName.gravityLight;
114
+ case 7:
115
+ return SyntaxThemeName.gravityDark;
116
+ case 8:
117
+ return SyntaxThemeName.obsidian;
118
+ case 9:
119
+ return SyntaxThemeName.oceanSunset;
120
+ case 10:
121
+ return SyntaxThemeName.standard;
122
+ default:
123
+ return SyntaxThemeName.vscodeDark;
124
+ }
125
+ }
126
+
127
+ @override
128
+ void write(BinaryWriter writer, SyntaxThemeName obj) {
129
+ switch (obj) {
130
+ case SyntaxThemeName.vscodeDark:
131
+ writer.writeByte(0);
132
+ case SyntaxThemeName.vscodeLight:
133
+ writer.writeByte(1);
134
+ case SyntaxThemeName.dracula:
135
+ writer.writeByte(2);
136
+ case SyntaxThemeName.monokaiSublime:
137
+ writer.writeByte(3);
138
+ case SyntaxThemeName.ayuLight:
139
+ writer.writeByte(4);
140
+ case SyntaxThemeName.ayuDark:
141
+ writer.writeByte(5);
142
+ case SyntaxThemeName.gravityLight:
143
+ writer.writeByte(6);
144
+ case SyntaxThemeName.gravityDark:
145
+ writer.writeByte(7);
146
+ case SyntaxThemeName.obsidian:
147
+ writer.writeByte(8);
148
+ case SyntaxThemeName.oceanSunset:
149
+ writer.writeByte(9);
150
+ case SyntaxThemeName.standard:
151
+ writer.writeByte(10);
152
+ }
153
+ }
154
+
155
+ @override
156
+ int get hashCode => typeId.hashCode;
157
+
158
+ @override
159
+ bool operator ==(Object other) =>
160
+ identical(this, other) ||
161
+ other is SyntaxThemeNameAdapter &&
162
+ runtimeType == other.runtimeType &&
163
+ typeId == other.typeId;
164
+}
lib/features/sidebar/components/active_server_indicator.dart
new
+151
@@ -0,0 +1,151 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+import 'package:hugeicons/hugeicons.dart';
4
+import 'package:go_router/go_router.dart';
5
+
6
+import '../../../core/models/enums.dart';
7
+import '../../../core/routes/app_routes.dart';
8
+import '../../servers/providers/server_providers.dart';
9
+import '../../servers/views/components/server_icon_picker.dart';
10
+
11
+class ActiveServerIndicator extends ConsumerWidget {
12
+ const ActiveServerIndicator({super.key});
13
+
14
+ Color _getStatusColor(ConnectionStatus status) {
15
+ switch (status) {
16
+ case ConnectionStatus.connected:
17
+ return Colors.green;
18
+ case ConnectionStatus.error:
19
+ return Colors.red;
20
+ case ConnectionStatus.checking:
21
+ return Colors.orange;
22
+ case ConnectionStatus.disconnected:
23
+ return Colors.grey;
24
+ }
25
+ }
26
+
27
+ String _getStatusText(ConnectionStatus status) {
28
+ switch (status) {
29
+ case ConnectionStatus.connected:
30
+ return 'Online';
31
+ case ConnectionStatus.error:
32
+ return 'Error';
33
+ case ConnectionStatus.checking:
34
+ return 'Checking...';
35
+ case ConnectionStatus.disconnected:
36
+ return 'Offline';
37
+ }
38
+ }
39
+
40
+ @override
41
+ Widget build(BuildContext context, WidgetRef ref) {
42
+ final theme = Theme.of(context);
43
+ final colorScheme = theme.colorScheme;
44
+ final activeServer = ref.watch(activeServerProvider);
45
+ final connectionStatus = ref.watch(connectionStatusProvider);
46
+
47
+ if (activeServer == null) {
48
+ return Padding(
49
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
50
+ child: Container(
51
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
52
+ decoration: BoxDecoration(
53
+ color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
54
+ borderRadius: BorderRadius.circular(8),
55
+ ),
56
+ child: Row(
57
+ children: [
58
+ HugeIcon(
59
+ icon: HugeIcons.strokeRoundedServerStack01,
60
+ size: 18,
61
+ color: colorScheme.onSurfaceVariant,
62
+ ),
63
+ const SizedBox(width: 10),
64
+ Expanded(
65
+ child: Text(
66
+ 'No server selected',
67
+ style: theme.textTheme.bodySmall?.copyWith(
68
+ color: colorScheme.onSurfaceVariant,
69
+ ),
70
+ overflow: TextOverflow.ellipsis,
71
+ ),
72
+ ),
73
+ ],
74
+ ),
75
+ ),
76
+ );
77
+ }
78
+
79
+ final statusColor = _getStatusColor(connectionStatus);
80
+ final iconName = activeServer.iconName;
81
+ final serverIcon = iconName != null
82
+ ? (getHugeIconByName(iconName)?.icon ??
83
+ getDefaultServerIcon(activeServer.type.name)?.icon)
84
+ : getDefaultServerIcon(activeServer.type.name)?.icon;
85
+
86
+ return Padding(
87
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
88
+ child: GestureDetector(
89
+ onTap: () {
90
+ context.go(AppRoutes.servers);
91
+ if (Scaffold.maybeOf(context)?.isDrawerOpen ?? false) {
92
+ Navigator.pop(context);
93
+ }
94
+ },
95
+ child: Container(
96
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
97
+ decoration: BoxDecoration(
98
+ color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
99
+ borderRadius: BorderRadius.circular(8),
100
+ border: Border.all(
101
+ color: colorScheme.outline.withValues(alpha: 0.2),
102
+ ),
103
+ ),
104
+ child: Row(
105
+ children: [
106
+ HugeIcon(
107
+ icon: serverIcon ?? HugeIcons.strokeRoundedServerStack01,
108
+ size: 18,
109
+ color: colorScheme.onSurface,
110
+ ),
111
+ const SizedBox(width: 10),
112
+ Expanded(
113
+ child: Column(
114
+ crossAxisAlignment: CrossAxisAlignment.start,
115
+ mainAxisSize: MainAxisSize.min,
116
+ children: [
117
+ Text(
118
+ activeServer.name,
119
+ style: theme.textTheme.bodySmall?.copyWith(
120
+ fontWeight: FontWeight.w500,
121
+ color: colorScheme.onSurface,
122
+ ),
123
+ overflow: TextOverflow.ellipsis,
124
+ maxLines: 1,
125
+ ),
126
+ Text(
127
+ _getStatusText(connectionStatus),
128
+ style: theme.textTheme.labelSmall?.copyWith(
129
+ color: statusColor,
130
+ fontSize: 10,
131
+ fontWeight: FontWeight.w500,
132
+ ),
133
+ ),
134
+ ],
135
+ ),
136
+ ),
137
+ Container(
138
+ width: 8,
139
+ height: 8,
140
+ decoration: BoxDecoration(
141
+ color: statusColor,
142
+ shape: BoxShape.circle,
143
+ ),
144
+ ),
145
+ ],
146
+ ),
147
+ ),
148
+ ),
149
+ );
150
+ }
151
+}
lib/features/sidebar/components/conversation_drawer_header.dart
renamed
lib/features/sidebar/components/drawer_nav_item.dart
renamed
lib/features/sidebar/sidebar_drawer.dart
renamed
+2
-5
@@ -1,14 +1,11 @@
1
import 'package:flutter/material.dart';
2
-import 'conversation_sidebar.dart';
2
+import 'sidebar_widget.dart';
3
4
class ConversationDrawer extends StatelessWidget {
5
const ConversationDrawer({super.key});
6
7
@override
8
Widget build(BuildContext context) {
9
- return const Drawer(
10
- child: ConversationSidebar(),
11
- );
9
+ return const Drawer(child: SidebarWidget());
10
}
11
}
14
-
lib/features/sidebar/sidebar_widget.dart
renamed
+10
-8
@@ -3,17 +3,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
import 'package:go_router/go_router.dart';
4
import 'package:hugeicons/hugeicons.dart';
5
6
-import '../../../core/routes/app_routes.dart';
7
-import '../providers/conversation_providers.dart';
6
+import '../../core/routes/app_routes.dart';
7
+import '../conversations/providers/conversation_providers.dart';
8
+import 'components/active_server_indicator.dart';
9
import 'components/conversation_drawer_header.dart';
9
-import 'components/conversation_empty_state.dart';
10
-import 'components/conversation_list.dart';
11
-import 'components/conversation_search_bar.dart';
10
+import '../conversations/views/components/conversation_empty_state.dart';
11
+import '../conversations/views/components/conversation_list.dart';
12
+import '../conversations/views/components/conversation_search_bar.dart';
13
import 'components/drawer_nav_item.dart';
13
-import 'components/new_chat_button.dart';
14
+import '../conversations/views/components/new_chat_button.dart';
15
15
-class ConversationSidebar extends ConsumerWidget {
16
- const ConversationSidebar({super.key});
16
+class SidebarWidget extends ConsumerWidget {
17
+ const SidebarWidget({super.key});
18
19
@override
20
Widget build(BuildContext context, WidgetRef ref) {
@@ -55,6 +56,7 @@ class ConversationSidebar extends ConsumerWidget {
56
),
57
),
58
const Divider(height: 1),
59
+ const ActiveServerIndicator(),
60
const SizedBox(height: 8),
61
DrawerNavItem(
62
iconData: HugeIcons.strokeRoundedMessageMultiple01,
lib/hive_registrar.g.dart
+4
-4
@@ -3,30 +3,30 @@
3
// Check in to version control
4
5
import 'package:hive_ce/hive_ce.dart';
6
-import 'package:localmind/features/settings/data/models/app_settings.dart';
7
-import 'package:localmind/features/conversations/data/models/conversation.dart';
6
import 'package:localmind/features/chat/data/models/message.dart';
7
+import 'package:localmind/features/conversations/data/models/conversation.dart';
8
import 'package:localmind/features/personas/data/models/persona.dart';
9
import 'package:localmind/features/servers/data/models/server.dart';
10
+import 'package:localmind/features/settings/data/models/app_settings.dart';
11
12
extension HiveRegistrar on HiveInterface {
13
void registerAdapters() {
14
- registerAdapter(SyntaxThemeNameAdapter());
14
registerAdapter(AppSettingsAdapter());
15
registerAdapter(ConversationAdapter());
16
registerAdapter(MessageAdapter());
17
registerAdapter(PersonaAdapter());
18
registerAdapter(ServerAdapter());
19
+ registerAdapter(SyntaxThemeNameAdapter());
20
}
21
}
22
23
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
24
void registerAdapters() {
25
- registerAdapter(SyntaxThemeNameAdapter());
25
registerAdapter(AppSettingsAdapter());
26
registerAdapter(ConversationAdapter());
27
registerAdapter(MessageAdapter());
28
registerAdapter(PersonaAdapter());
29
registerAdapter(ServerAdapter());
30
+ registerAdapter(SyntaxThemeNameAdapter());
31
}
32
}