@tduncan / Cortana / commits / 4155b8b

refactor: improve code quality with linting fixes, update package dependencies, and clean up UI components

abdulmominsakib committed Apr 1, 2026 at 13:12 UTC 4155b8ba78a7d7b8d2b87905d36227f14001752b
20 files changed +74 -118
android/app/build.gradle.kts
+2 -2
@@ -6,7 +6,7 @@ plugins {
6 }
7
8 android {
9 - namespace = "com.example.localmind"
9 + namespace = "pro.momin.localmind"
10 compileSdk = flutter.compileSdkVersion
11 ndkVersion = flutter.ndkVersion
12
@@ -21,7 +21,7 @@ android {
21
22 defaultConfig {
23 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24 - applicationId = "com.example.localmind"
24 + applicationId = "pro.momin.localmind"
25 // You can update the following values to match your application needs.
26 // For more information, see: https://flutter.dev/to/review-gradle-config.
27 minSdk = flutter.minSdkVersion
android/app/src/main/AndroidManifest.xml
+1 -1
@@ -1,7 +1,7 @@
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2 <uses-permission android:name="android.permission.INTERNET" />
3 <application
4 - android:label="localmind"
4 + android:label="LocalMind"
5 android:name="${applicationName}"
6 android:icon="@mipmap/ic_launcher"
7 android:usesCleartextTraffic="true">
android/app/src/main/kotlin/pro/momin/localmind/MainActivity.kt renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package com.example.localmind
1 +package pro.momin.localmind
2
3 import io.flutter.embedding.android.FlutterActivity
4
ios/Runner.xcodeproj/project.pbxproj
+6 -6
@@ -482,7 +482,7 @@
482 "$(inherited)",
483 "@executable_path/Frameworks",
484 );
485 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind;
485 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind;
486 PRODUCT_NAME = "$(TARGET_NAME)";
487 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
488 SWIFT_VERSION = 5.0;
@@ -499,7 +499,7 @@
499 CURRENT_PROJECT_VERSION = 1;
500 GENERATE_INFOPLIST_FILE = YES;
501 MARKETING_VERSION = 1.0;
502 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind.RunnerTests;
502 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind.RunnerTests;
503 PRODUCT_NAME = "$(TARGET_NAME)";
504 SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
505 SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -517,7 +517,7 @@
517 CURRENT_PROJECT_VERSION = 1;
518 GENERATE_INFOPLIST_FILE = YES;
519 MARKETING_VERSION = 1.0;
520 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind.RunnerTests;
520 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind.RunnerTests;
521 PRODUCT_NAME = "$(TARGET_NAME)";
522 SWIFT_VERSION = 5.0;
523 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -533,7 +533,7 @@
533 CURRENT_PROJECT_VERSION = 1;
534 GENERATE_INFOPLIST_FILE = YES;
535 MARKETING_VERSION = 1.0;
536 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind.RunnerTests;
536 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind.RunnerTests;
537 PRODUCT_NAME = "$(TARGET_NAME)";
538 SWIFT_VERSION = 5.0;
539 TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@@ -665,7 +665,7 @@
665 "$(inherited)",
666 "@executable_path/Frameworks",
667 );
668 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind;
668 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind;
669 PRODUCT_NAME = "$(TARGET_NAME)";
670 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
671 SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -688,7 +688,7 @@
688 "$(inherited)",
689 "@executable_path/Frameworks",
690 );
691 - PRODUCT_BUNDLE_IDENTIFIER = com.example.localmind;
691 + PRODUCT_BUNDLE_IDENTIFIER = pro.momin.localmind;
692 PRODUCT_NAME = "$(TARGET_NAME)";
693 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
694 SWIFT_VERSION = 5.0;
lib/core/repository/server/server_repository.dart
-1
@@ -1,4 +1,3 @@
1 -import 'package:localmind/core/models/enums.dart';
1 import 'package:localmind/features/models/data/models/model_info.dart';
2 import 'package:localmind/features/servers/data/models/server.dart';
3
lib/core/utils/date_utils.dart
+2 -1
@@ -27,8 +27,9 @@ class AppDateUtils {
27
28 static String formatDuration(Duration duration) {
29 if (duration.inSeconds < 60) return '${duration.inSeconds}s';
30 - if (duration.inMinutes < 60)
30 + if (duration.inMinutes < 60) {
31 return '${duration.inMinutes}m ${duration.inSeconds % 60}s';
32 + }
33 return '${duration.inHours}h ${duration.inMinutes % 60}m';
34 }
35
lib/features/chat/data/chat_service.dart
+15 -6
@@ -142,15 +142,18 @@ class LMStudioChatService implements ChatService {
142 }
143 if (params.topK != null) body['top_k'] = params.topK;
144 if (params.minP != null) body['min_p'] = params.minP;
145 - if (params.repeatPenalty != null)
145 + if (params.repeatPenalty != null) {
146 body['repeat_penalty'] = params.repeatPenalty;
147 - if (params.reasoningLevel != null)
147 + }
148 + if (params.reasoningLevel != null) {
149 body['reasoning'] = params.reasoningLevel;
150 + }
151 if (integrations != null && integrations.isNotEmpty) {
152 body['integrations'] = integrations.map((i) => i.toJson()).toList();
153 }
152 - if (previousResponseId != null)
154 + if (previousResponseId != null) {
155 body['previous_response_id'] = previousResponseId;
156 + }
157
158 final response = await _dio.post<ResponseBody>(
159 server.chatEndpoint,
@@ -436,7 +439,9 @@ class OpenAICompatibleChatService implements ChatService {
439 content: content,
440 );
441 }
439 - } catch (e) {}
442 + } catch (e) {
443 + // ignore: empty_catches
444 + }
445 }
446 }
447 }
@@ -508,7 +513,9 @@ class OllamaChatService implements ChatService {
513 yield const ChatResponse(type: ChatResponseType.done);
514 return;
515 }
511 - } catch (e) {}
516 + } catch (e) {
517 + // ignore: empty_catches
518 + }
519 }
520 }
521 }
@@ -587,7 +594,9 @@ class OpenRouterChatService implements ChatService {
594 content: content,
595 );
596 }
590 - } catch (e) {}
597 + } catch (e) {
598 + // ignore: empty_catches
599 + }
600 }
601 }
602 }
lib/features/chat/providers/chat_providers.dart
+6 -4
@@ -141,11 +141,13 @@ final chatParamsProvider = Provider<ChatParameters>((ref) {
141 );
142 if (persona != null && persona.preferredParams != null) {
143 final params = persona.preferredParams as Map<String, dynamic>;
144 - if (params['temperature'] != null)
144 + if (params['temperature'] != null) {
145 temperature = (params['temperature'] as num).toDouble();
146 + }
147 if (params['topP'] != null) topP = (params['topP'] as num).toDouble();
147 - if (params['maxTokens'] != null)
148 + if (params['maxTokens'] != null) {
149 maxTokens = (params['maxTokens'] as num).toInt();
150 + }
151 }
152 } catch (_) {}
153 }
@@ -504,7 +506,7 @@ class ChatNotifier extends Notifier<ChatState> {
506 if (personaPrompt != null) {
507 messages.add(
508 Message(
507 - id: 'system-${_currentConversationId}',
509 + id: 'system-$_currentConversationId',
510 conversationId: _currentConversationId ?? '',
511 role: MessageRole.system,
512 content: personaPrompt,
@@ -515,7 +517,7 @@ class ChatNotifier extends Notifier<ChatState> {
517 } else if (settings.showSystemMessages) {
518 messages.add(
519 Message(
518 - id: 'system-default-${_currentConversationId}',
520 + id: 'system-default-$_currentConversationId',
521 conversationId: _currentConversationId ?? '',
522 role: MessageRole.system,
523 content:
lib/features/chat/screens/chat_screen.dart
+1 -1
@@ -815,7 +815,7 @@ class _SmartReplyChips extends ConsumerWidget {
815 child: ListView.separated(
816 scrollDirection: Axis.horizontal,
817 itemCount: suggestions.length,
818 - separatorBuilder: (_, __) => const SizedBox(width: 6),
818 + separatorBuilder: (_, _) => const SizedBox(width: 6),
819 itemBuilder: (context, index) {
820 final label = suggestions[index];
821 return GestureDetector(
lib/features/chat/views/components/chat_input_bar.dart
+1 -1
@@ -107,7 +107,7 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar> {
107 child: ListView.separated(
108 scrollDirection: Axis.horizontal,
109 itemCount: _attachedFiles.length,
110 - separatorBuilder: (_, __) => const SizedBox(width: 6),
110 + separatorBuilder: (_, _) => const SizedBox(width: 6),
111 itemBuilder: (context, index) {
112 final file = _attachedFiles[index];
113 return Stack(
lib/features/conversations/views/components/conversation_drawer_header.dart
+5 -10
@@ -19,15 +19,12 @@ class ConversationDrawerHeader extends StatelessWidget {
19 Container(
20 padding: const EdgeInsets.all(8),
21 decoration: BoxDecoration(
22 - color: isDark
23 - ? const Color(0xFF3B82F6)
24 - : const Color(0xFF2563EB),
22 borderRadius: BorderRadius.circular(8),
23 ),
27 - child: const Icon(
28 - Icons.psychology,
29 - color: Colors.white,
30 - size: 20,
24 + child: Image.asset(
25 + 'assets/images/logo.webp',
26 + width: 24,
27 + height: 24,
28 ),
29 ),
30 const SizedBox(width: 12),
@@ -44,9 +41,7 @@ class ConversationDrawerHeader extends StatelessWidget {
41 IconButton(
42 icon: Icon(
43 Icons.settings,
47 - color: isDark
48 - ? const Color(0xFF888888)
49 - : const Color(0xFF666666),
44 + color: isDark ? const Color(0xFF888888) : const Color(0xFF666666),
45 ),
46 onPressed: () {
47 Navigator.pop(context); // Close drawer
lib/features/conversations/views/components/conversation_list.dart
-51
@@ -82,57 +82,6 @@ class ConversationList extends ConsumerWidget {
82 );
83 }
84
85 - void _showConversationOptions(
86 - BuildContext context,
87 - WidgetRef ref,
88 - Conversation conversation,
89 - ) {
90 - showModalBottomSheet(
91 - context: context,
92 - builder: (context) {
93 - return SafeArea(
94 - child: Column(
95 - mainAxisSize: MainAxisSize.min,
96 - children: [
97 - ListTile(
98 - leading: Icon(
99 - conversation.isPinned
100 - ? Icons.push_pin
101 - : Icons.push_pin_outlined,
102 - ),
103 - title: Text(conversation.isPinned ? 'Unpin' : 'Pin'),
104 - onTap: () {
105 - Navigator.pop(context);
106 - ref
107 - .read(conversationsProvider.notifier)
108 - .togglePin(conversation.id);
109 - },
110 - ),
111 - ListTile(
112 - leading: const Icon(Icons.edit),
113 - title: const Text('Rename'),
114 - onTap: () {
115 - Navigator.pop(context);
116 - _showRenameDialog(context, ref, conversation);
117 - },
118 - ),
119 - ListTile(
120 - leading: const Icon(Icons.delete, color: Colors.red),
121 - title: const Text(
122 - 'Delete',
123 - style: TextStyle(color: Colors.red),
124 - ),
125 - onTap: () {
126 - Navigator.pop(context);
127 - _showDeleteConfirmation(context, ref, conversation);
128 - },
129 - ),
130 - ],
131 - ),
132 - );
133 - },
134 - );
135 - }
85
86 void _showRenameDialog(
87 BuildContext context,
lib/features/models/data/model_cache.dart
-1
@@ -1,4 +1,3 @@
1 -import 'dart:collection';
1 import 'package:localmind/features/models/data/models/model_info.dart';
2
3 class ModelCache {
lib/features/models/screens/model_picker_sheet.dart
+10 -12
@@ -396,19 +396,17 @@ class _ModelTile extends StatelessWidget {
396 children: [
397 Row(
398 children: [
399 - Expanded(
400 - child: Text(
401 - model.displayName,
402 - style: TextStyle(
403 - fontSize: 15,
404 - fontWeight: isSelected
405 - ? FontWeight.w600
406 - : FontWeight.w500,
407 - color: isDark ? Colors.white : Colors.black,
408 - ),
409 - maxLines: 1,
410 - overflow: TextOverflow.ellipsis,
399 + Text(
400 + model.displayName,
401 + style: TextStyle(
402 + fontSize: 15,
403 + fontWeight: isSelected
404 + ? FontWeight.w600
405 + : FontWeight.w500,
406 + color: isDark ? Colors.white : Colors.black,
407 ),
408 + maxLines: 1,
409 + overflow: TextOverflow.ellipsis,
410 ),
411 if (isLoaded)
412 Container(
lib/features/onboarding/screens/onboarding_server_type_screen.dart
+5 -2
@@ -27,10 +27,13 @@ class _OnboardingServerTypeScreenState
27 appBar: AppBar(
28 title: Row(
29 children: [
30 - /// TODO: Replace with actual app logo
30 Padding(
31 padding: const EdgeInsets.all(8),
33 - child: Placeholder(fallbackHeight: 24, fallbackWidth: 24),
32 + child: Image.asset(
33 + 'assets/images/logo.webp',
34 + width: 24,
35 + height: 24,
36 + ),
37 ),
38 const Text(
39 'LOCALMIND',
lib/features/personas/views/create_persona_screen.dart
+4 -3
@@ -201,7 +201,7 @@ class _CreatePersonaScreenState extends ConsumerState<CreatePersonaScreen> {
201 child: ListView.separated(
202 scrollDirection: Axis.horizontal,
203 itemCount: _emojis.length,
204 - separatorBuilder: (_, __) => const SizedBox(width: 4),
204 + separatorBuilder: (_, _) => const SizedBox(width: 4),
205 itemBuilder: (context, index) {
206 final emoji = _emojis[index];
207 final isSelected = emoji == _selectedEmoji;
@@ -263,7 +263,7 @@ class _CreatePersonaScreenState extends ConsumerState<CreatePersonaScreen> {
263 const SizedBox(height: 12),
264
265 DropdownButtonFormField<String>(
266 - value: _selectedCategory,
266 + initialValue: _selectedCategory,
267 decoration: const InputDecoration(labelText: 'Category'),
268 items: _categories
269 .map((c) => DropdownMenuItem(value: c, child: Text(c)))
@@ -362,8 +362,9 @@ class _CreatePersonaScreenState extends ConsumerState<CreatePersonaScreen> {
362 minLines: 4,
363 maxLength: 4000,
364 validator: (v) {
365 - if (v == null || v.trim().isEmpty)
365 + if (v == null || v.trim().isEmpty) {
366 return 'System prompt is required';
367 + }
368 if (v.trim().length > 4000) return 'Max 4000 characters';
369 return null;
370 },
lib/features/personas/views/persona_list_screen.dart
+4 -4
@@ -196,7 +196,6 @@ class PersonaListScreen extends ConsumerWidget {
196 showModalBottomSheet(
197 context: context,
198 builder: (ctx) {
199 - final isDark = Theme.of(ctx).brightness == Brightness.dark;
199 return SafeArea(
200 child: Column(
201 mainAxisSize: MainAxisSize.min,
@@ -254,9 +253,10 @@ class PersonaListScreen extends ConsumerWidget {
253 final clone = ref
254 .read(personasNotifierProvider.notifier)
255 .clonePersona(persona.id);
257 - clone.then(
258 - (c) => context.push(AppRoutes.createPersona, extra: c),
259 - );
256 + clone.then((c) {
257 + if (!context.mounted) return;
258 + context.push(AppRoutes.createPersona, extra: c);
259 + });
260 },
261 ),
262 const SizedBox(height: 8),
lib/features/servers/data/server_api_service.dart
-9
@@ -267,15 +267,6 @@ class ServerApiService {
267 .join(' ');
268 }
269
270 - int? _extractParameterCount(String id) {
271 - final regex = RegExp(r'(\d+(?:\.\d+)?)\s*[Bb]');
272 - final match = regex.firstMatch(id);
273 - if (match != null) {
274 - final value = double.tryParse(match.group(1) ?? '');
275 - return value?.toInt();
276 - }
277 - return null;
278 - }
270
271 int? _parseParameterSize(String size) {
272 final cleaned = size.replaceAll('B', '').replaceAll('b', '').trim();
pubspec.lock
+8
@@ -97,6 +97,14 @@ packages:
97 url: "https://pub.dev"
98 source: hosted
99 version: "8.12.5"
100 + change_app_package_name:
101 + dependency: "direct dev"
102 + description:
103 + name: change_app_package_name
104 + sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5"
105 + url: "https://pub.dev"
106 + source: hosted
107 + version: "1.5.0"
108 characters:
109 dependency: transitive
110 description:
pubspec.yaml
+3 -2
@@ -2,7 +2,7 @@ name: localmind
2 description: "A new Flutter project."
3 # The following line prevents the package from being accidentally published to
4 # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 -publish_to: 'none' # Remove this line if you wish to publish to pub.dev
5 +publish_to: "none" # Remove this line if you wish to publish to pub.dev
6
7 # The following defines the version and build number for your application.
8 # A version number is three numbers separated by dots, like 1.2.43
@@ -61,13 +61,13 @@ dev_dependencies:
61 flutter_lints: ^6.0.0
62 build_runner: ^2.13.1
63 hive_ce_generator: ^1.11.1
64 + change_app_package_name: ^1.4.0
65
66 # For information on the generic Dart part of this file, see the
67 # following page: https://dart.dev/tools/pub/pubspec
68
69 # The following section is specific to Flutter packages.
70 flutter:
70 -
71 # The following line ensures that the Material Icons font is
72 # included with your application, so that you can use the icons in
73 # the material Icons class.
@@ -76,6 +76,7 @@ flutter:
76 # To add assets to your application, add an assets section, like this:
77 assets:
78 - assets/google_fonts/
79 + - assets/images/
80
81 # An image asset can refer to one or more resolution-specific "variants", see
82 # https://flutter.dev/to/resolution-aware-images