refactor: unused files

This commit is contained in:
cogwheel0
2025-08-20 22:41:55 +05:30
parent 4dc9ce1762
commit 6cea654b88
6 changed files with 20 additions and 2161 deletions

View File

@@ -1,370 +0,0 @@
import 'package:flutter/material.dart';
import '../../../shared/theme/theme_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:io' show Platform;
import '../../../shared/utils/platform_utils.dart';
import '../widgets/conversation_search_widget.dart';
import '../../../core/providers/app_providers.dart';
import '../providers/chat_providers.dart';
import 'chat_page.dart';
/// Dedicated page for conversation search functionality
class ConversationSearchPage extends ConsumerStatefulWidget {
const ConversationSearchPage({super.key});
@override
ConsumerState<ConversationSearchPage> createState() =>
_ConversationSearchPageState();
}
class _ConversationSearchPageState
extends ConsumerState<ConversationSearchPage> {
@override
Widget build(BuildContext context) {
final conduitTheme = context.conduitTheme;
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: _buildAppBar(context, conduitTheme),
body: ConversationSearchWidget(
onResultTap: _onSearchResultTap,
showFilters: true,
),
);
}
PreferredSizeWidget _buildAppBar(
BuildContext context,
ConduitThemeExtension theme,
) {
if (Platform.isIOS) {
return CupertinoNavigationBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
border: Border(bottom: BorderSide(color: theme.cardBorder, width: 0.5)),
leading: CupertinoNavigationBarBackButton(
color: context.conduitTheme.textPrimary,
onPressed: () => Navigator.of(context).pop(),
),
middle: Text(
'Search Conversations',
style: TextStyle(
color: context.conduitTheme.textPrimary,
fontSize: AppTypography.bodyLarge,
fontWeight: FontWeight.w600,
),
),
);
}
return AppBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: Elevation.none,
title: Text(
'Search Conversations',
style: TextStyle(
color: context.conduitTheme.textPrimary,
fontSize: AppTypography.headlineMedium,
fontWeight: FontWeight.w600,
),
),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: context.conduitTheme.textPrimary),
onPressed: () => Navigator.of(context).pop(),
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: theme.cardBorder),
),
);
}
void _onSearchResultTap(String conversationId, String? messageId) {
PlatformUtils.lightHaptic();
// Set the active conversation
final conversationsAsync = ref.read(conversationsProvider);
conversationsAsync.whenData((conversations) {
final conversation = conversations.firstWhere(
(c) => c.id == conversationId,
orElse: () => throw Exception('Conversation not found'),
);
// Set active conversation
ref.read(activeConversationProvider.notifier).state = conversation;
// Navigate back to chat
Navigator.of(context).pop();
// If we have a specific message, navigate to it and highlight it
if (messageId != null) {
// Use a custom navigation approach with message highlighting
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) =>
ChatPageWithHighlight(messageIdToHighlight: messageId),
),
);
}
});
}
}
/// Chat page wrapper that highlights a specific message
class ChatPageWithHighlight extends ConsumerStatefulWidget {
final String messageIdToHighlight;
const ChatPageWithHighlight({super.key, required this.messageIdToHighlight});
@override
ConsumerState<ChatPageWithHighlight> createState() =>
_ChatPageWithHighlightState();
}
class _ChatPageWithHighlightState extends ConsumerState<ChatPageWithHighlight> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
// Schedule highlighting after the widget is built
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToAndHighlightMessage();
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _scrollToAndHighlightMessage() async {
try {
final messages = ref.read(chatMessagesProvider);
final messageIndex = messages.indexWhere(
(msg) => msg.id == widget.messageIdToHighlight,
);
if (messageIndex >= 0 && _scrollController.hasClients) {
// Calculate the approximate position (assuming 100px per message)
final targetOffset = messageIndex * 100.0;
// Scroll to the message
await _scrollController.animateTo(
targetOffset,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
// Show a highlight indicator
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Found message'),
duration: const Duration(seconds: 2),
backgroundColor: context.conduitTheme.buttonPrimary,
),
);
}
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Message not found'),
backgroundColor: context.conduitTheme.error,
),
);
}
}
}
@override
Widget build(BuildContext context) {
return const ChatPage();
}
}
/// Search icon button for app bars
class ConversationSearchButton extends ConsumerWidget {
final VoidCallback? onPressed;
const ConversationSearchButton({super.key, this.onPressed});
@override
Widget build(BuildContext context, WidgetRef ref) {
return IconButton(
icon: Icon(
Platform.isIOS ? CupertinoIcons.search : Icons.search,
color: context.conduitTheme.iconPrimary.withValues(alpha: 0.8),
size: IconSize.lg,
),
onPressed:
onPressed ??
() {
PlatformUtils.lightHaptic();
Navigator.of(context).push(
Platform.isIOS
? CupertinoPageRoute(
builder: (context) => const ConversationSearchPage(),
)
: MaterialPageRoute(
builder: (context) => const ConversationSearchPage(),
),
);
},
tooltip: 'Search conversations',
);
}
}
/// Quick search overlay that can be shown from any page
class QuickSearchOverlay extends ConsumerStatefulWidget {
final VoidCallback? onDismiss;
const QuickSearchOverlay({super.key, this.onDismiss});
@override
ConsumerState<QuickSearchOverlay> createState() => _QuickSearchOverlayState();
}
class _QuickSearchOverlayState extends ConsumerState<QuickSearchOverlay>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
_slideAnimation =
Tween<Offset>(begin: const Offset(0, -1), end: Offset.zero).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Future<void> _dismiss() async {
await _animationController.reverse();
widget.onDismiss?.call();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Stack(
children: [
// Backdrop
GestureDetector(
onTap: _dismiss,
child: Container(
color: context.conduitTheme.surfaceBackground.withValues(
alpha: 0.7 * _fadeAnimation.value,
),
),
),
// Search panel
SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
margin: const EdgeInsets.only(top: Spacing.xxxl + Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppBorderRadius.lg),
),
),
child: Column(
children: [
// Handle bar
Container(
margin: const EdgeInsets.only(top: Spacing.sm),
width: 40,
height: 4,
decoration: BoxDecoration(
color: context.conduitTheme.textPrimary.withValues(
alpha: 0.3,
),
borderRadius: BorderRadius.circular(
AppBorderRadius.xs,
),
),
),
// Search content
Expanded(
child: ConversationSearchWidget(
onResultTap: (conversationId, messageId) {
_onSearchResultTap(conversationId, messageId);
_dismiss();
},
showFilters: false, // Simplified for overlay
),
),
],
),
),
),
),
],
);
},
);
}
void _onSearchResultTap(String conversationId, String? messageId) {
// Same logic as the search page
final conversationsAsync = ref.read(conversationsProvider);
conversationsAsync.whenData((conversations) {
final conversation = conversations.firstWhere(
(c) => c.id == conversationId,
orElse: () => throw Exception('Conversation not found'),
);
ref.read(activeConversationProvider.notifier).state = conversation;
if (messageId != null) {
debugPrint(
'Navigate to message: $messageId in conversation: $conversationId',
);
}
});
}
}
/// Show quick search overlay
void showQuickSearch(BuildContext context) {
showGeneralDialog(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
return QuickSearchOverlay(onDismiss: () => Navigator.of(context).pop());
},
);
}

View File

@@ -1,490 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:io' show Platform;
import '../../../core/models/model.dart';
import '../../../core/providers/app_providers.dart';
import '../../../shared/theme/theme_extensions.dart';
import '../../../shared/theme/app_theme.dart';
import '../../../shared/widgets/conduit_components.dart';
class ModelSelectorPage extends ConsumerStatefulWidget {
const ModelSelectorPage({super.key});
@override
ConsumerState<ModelSelectorPage> createState() => _ModelSelectorPageState();
}
class _ModelSelectorPageState extends ConsumerState<ModelSelectorPage> {
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
String _searchQuery = '';
@override
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchController.removeListener(_onSearchChanged);
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
void _onSearchChanged() {
setState(() {
_searchQuery = _searchController.text;
});
}
List<Model> _filterModels(List<Model> models) {
if (_searchQuery.isEmpty) {
return models;
}
final query = _searchQuery.toLowerCase();
return models.where((model) {
return model.name.toLowerCase().contains(query) ||
(model.description?.toLowerCase().contains(query) ?? false);
}).toList();
}
@override
Widget build(BuildContext context) {
final modelsAsync = ref.watch(modelsProvider);
final selectedModel = ref.watch(selectedModelProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: context.conduitTheme.surfaceBackground,
elevation: Elevation.none,
scrolledUnderElevation: Elevation.none,
leading: ConduitIconButton(
icon: Platform.isIOS
? CupertinoIcons.back
: Icons.arrow_back_rounded,
onPressed: () => Navigator.pop(context),
),
title: Text(
'Select Model',
style: AppTypography.headlineMediumStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
),
body: Column(
children: [
// Search bar
Container(
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground,
border: Border(
bottom: BorderSide(
color: context.conduitTheme.dividerColor.withValues(alpha: 0.1),
width: BorderWidth.regular,
),
),
),
child: _buildSearchField(),
),
// Models list
Expanded(
child: modelsAsync.when(
data: (models) {
final filteredModels = _filterModels(models);
if (models.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Platform.isIOS
? CupertinoIcons.cube_box
: Icons.view_in_ar,
size: IconSize.xxl,
color: context.conduitTheme.iconSecondary,
),
const SizedBox(height: Spacing.lg),
Text(
'No models available',
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: Spacing.sm),
Text(
'Please check your Open-WebUI configuration',
style: AppTypography.bodyMediumStyle.copyWith(
color: context.conduitTheme.textSecondary,
),
),
],
),
);
}
if (filteredModels.isEmpty && _searchQuery.isNotEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Platform.isIOS
? CupertinoIcons.search
: Icons.search_rounded,
size: IconSize.xxl,
color: context.conduitTheme.iconSecondary,
),
const SizedBox(height: Spacing.lg),
Text(
'No models found',
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: Spacing.sm),
Text(
'Try searching with different keywords',
style: AppTypography.bodyMediumStyle.copyWith(
color: context.conduitTheme.textSecondary,
),
),
],
),
);
}
// Group models by category if needed
final groupedModels = _groupModels(filteredModels);
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: groupedModels.length,
itemBuilder: (context, index) {
final group = groupedModels[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (group.title != null) ...[
Padding(
padding: const EdgeInsets.fromLTRB(
Spacing.md,
Spacing.md,
Spacing.md,
Spacing.sm,
),
child: Text(
group.title!,
style: AppTypography.labelStyle.copyWith(
color: context.conduitTheme.textSecondary,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
),
],
...group.models.map(
(model) => ModelTile(
model: model,
isSelected: selectedModel?.id == model.id,
onTap: () {
ref.read(selectedModelProvider.notifier).state =
model;
ref.read(isManualModelSelectionProvider.notifier).state = true;
Navigator.pop(context);
},
),
),
],
);
},
);
},
loading: () => Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
context.conduitTheme.buttonPrimary,
),
),
),
error: (error, _) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Platform.isIOS
? CupertinoIcons.exclamationmark_triangle
: Icons.error_rounded,
size: IconSize.xxl,
color: context.conduitTheme.error,
),
const SizedBox(height: Spacing.lg),
Text(
'Failed to load models',
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: Spacing.sm),
Text(
'Please try again later',
style: AppTypography.bodyMediumStyle.copyWith(
color: context.conduitTheme.textSecondary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: Spacing.xl),
ElevatedButton(
onPressed: () => ref.refresh(modelsProvider),
style: ElevatedButton.styleFrom(
backgroundColor: context.conduitTheme.buttonPrimary,
foregroundColor: context.conduitTheme.buttonPrimaryText,
padding: const EdgeInsets.symmetric(
horizontal: Spacing.buttonPadding,
vertical: Spacing.md,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.button),
),
elevation: Elevation.none,
),
child: Text(
'Retry',
style: AppTypography.labelStyle.copyWith(
color: context.conduitTheme.buttonPrimaryText,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
],
),
);
}
Widget _buildSearchField() {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
context.conduitTheme.inputBackground.withValues(alpha: 0.6),
context.conduitTheme.inputBackground.withValues(alpha: 0.3),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
border: Border.all(
color: context.conduitTheme.inputBorder.withValues(alpha: 0.3),
width: BorderWidth.thin,
),
),
child: TextField(
controller: _searchController,
focusNode: _searchFocusNode,
style: TextStyle(
color: context.conduitTheme.inputText,
fontSize: AppTypography.bodyMedium,
),
decoration: InputDecoration(
hintText: 'Search models...',
hintStyle: TextStyle(
color: context.conduitTheme.inputPlaceholder.withValues(alpha: 0.8),
fontSize: AppTypography.bodyMedium,
),
prefixIcon: Icon(
Platform.isIOS ? CupertinoIcons.search : Icons.search,
color: context.conduitTheme.iconSecondary,
size: IconSize.md,
),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: Icon(
Platform.isIOS
? CupertinoIcons.clear_circled_solid
: Icons.clear,
color: context.conduitTheme.iconSecondary,
size: IconSize.md,
),
onPressed: () {
_searchController.clear();
_searchFocusNode.unfocus();
},
)
: null,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
),
),
);
}
List<ModelGroup> _groupModels(List<Model> models) {
// For now, just return all models in one group
// In the future, we can group by provider, capability, etc.
return [ModelGroup(title: null, models: models)];
}
}
class ModelGroup {
final String? title;
final List<Model> models;
ModelGroup({required this.title, required this.models});
}
class ModelTile extends StatelessWidget {
final Model model;
final bool isSelected;
final VoidCallback onTap;
const ModelTile({
super.key,
required this.model,
required this.isSelected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
elevation: isSelected ? 2 : 0,
color: isSelected
? context.conduitTheme.buttonPrimary.withValues(alpha: 0.1)
: context.conduitTheme.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide(
color: isSelected
? context.conduitTheme.buttonPrimary
: context.conduitTheme.dividerColor.withValues(alpha: 0.3),
width: isSelected ? 2 : 1,
),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppBorderRadius.md),
child: Padding(
padding: const EdgeInsets.all(Spacing.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
model.name,
style: AppTypography.bodyLargeStyle.copyWith(
fontWeight: FontWeight.w600,
color: isSelected
? context.conduitTheme.buttonPrimary
: context.conduitTheme.textPrimary,
),
),
),
if (isSelected)
Icon(
Platform.isIOS
? CupertinoIcons.checkmark_circle_fill
: Icons.check_circle,
color: context.conduitTheme.buttonPrimary,
),
],
),
if (model.description != null) ...[
const SizedBox(height: Spacing.xs),
Text(
model.description!,
style: AppTypography.bodySmallStyle.copyWith(
color: context.conduitTheme.textSecondary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
const SizedBox(height: Spacing.sm),
Wrap(
spacing: 8,
children: [
if (model.isMultimodal)
_buildCapabilityChip(
context,
icon: Platform.isIOS ? CupertinoIcons.photo : Icons.image,
label: 'Multimodal',
color: AppTheme.info,
),
if (model.supportsStreaming)
_buildCapabilityChip(
context,
icon: Platform.isIOS
? CupertinoIcons.bolt
: Icons.flash_on,
label: 'Streaming',
color: AppTheme.warning,
),
if (model.supportsRAG)
_buildCapabilityChip(
context,
icon: Platform.isIOS
? CupertinoIcons.doc_text
: Icons.description,
label: 'RAG',
color: AppTheme.success,
),
],
),
],
),
),
),
);
}
Widget _buildCapabilityChip(
BuildContext context, {
required IconData icon,
required String label,
required Color color,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppBorderRadius.md),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: Spacing.xs),
Text(
label,
style: TextStyle(
fontSize: AppTypography.labelMedium,
color: color,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}