updated ui added new features

This commit is contained in:
zach
2025-12-27 15:32:32 -07:00
parent 02ca7801ea
commit a2cfae3a22
589 changed files with 181780 additions and 569 deletions
+83
View File
@@ -0,0 +1,83 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class QuickSwitchBar extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
const QuickSwitchBar({
super.key,
required this.selectedIndex,
required this.onDestinationSelected,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final labelStyle = theme.textTheme.labelMedium ?? const TextStyle();
return SizedBox(
width: double.infinity,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 14, sigmaY: 14),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.4),
),
),
child: NavigationBarTheme(
data: NavigationBarThemeData(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent,
indicatorColor: colorScheme.primaryContainer,
labelTextStyle: MaterialStateProperty.resolveWith((states) {
final isSelected = states.contains(MaterialState.selected);
return labelStyle.copyWith(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
color: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
);
}),
iconTheme: MaterialStateProperty.resolveWith((states) {
final isSelected = states.contains(MaterialState.selected);
return IconThemeData(
color: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
);
}),
),
child: NavigationBar(
height: 60,
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: const [
NavigationDestination(
icon: Icon(Icons.people_outline),
label: 'Contacts',
),
NavigationDestination(
icon: Icon(Icons.tag),
label: 'Channels',
),
NavigationDestination(
icon: Icon(Icons.map_outlined),
label: 'Map',
),
],
),
),
),
),
),
);
}
}
+134
View File
@@ -0,0 +1,134 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:media_kit_fork/media_kit_fork.dart';
import '../models/message.dart';
class VoiceMessageBubble extends StatefulWidget {
final Message message;
final Color backgroundColor;
final Color textColor;
final Color metaColor;
final bool isOutgoing;
const VoiceMessageBubble({
super.key,
required this.message,
required this.backgroundColor,
required this.textColor,
required this.metaColor,
required this.isOutgoing,
});
@override
State<VoiceMessageBubble> createState() => _VoiceMessageBubbleState();
}
class _VoiceMessageBubbleState extends State<VoiceMessageBubble> {
late final Player _player;
StreamSubscription<Duration>? _durationSubscription;
StreamSubscription<bool>? _completeSubscription;
Duration _duration = Duration.zero;
@override
void initState() {
super.initState();
_player = Player();
final voicePath = widget.message.voicePath;
if (voicePath != null && voicePath.isNotEmpty) {
_player.open(Media(Uri.file(voicePath).toString()), play: false);
}
_durationSubscription = _player.stream.duration.listen((value) {
if (!mounted) return;
if (value > Duration.zero && value != _duration) {
setState(() {
_duration = value;
});
}
});
_completeSubscription = _player.stream.completed.listen((completed) {
if (!completed) return;
_player.seek(Duration.zero);
_player.pause();
});
}
@override
void dispose() {
_durationSubscription?.cancel();
_completeSubscription?.cancel();
_player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final hasAudio = widget.message.voicePath != null && widget.message.voicePath!.isNotEmpty;
final fallbackDuration = Duration(milliseconds: widget.message.voiceDurationMs ?? 0);
final displayDuration = _duration > Duration.zero ? _duration : fallbackDuration;
return StreamBuilder<bool>(
stream: _player.stream.playing,
initialData: false,
builder: (context, playingSnapshot) {
final isPlaying = playingSnapshot.data ?? false;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconButton(
icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
color: widget.textColor,
onPressed: hasAudio
? () {
if (isPlaying) {
_player.pause();
} else {
_player.play();
}
}
: null,
),
Expanded(
child: StreamBuilder<Duration>(
stream: _player.stream.position,
initialData: Duration.zero,
builder: (context, positionSnapshot) {
final position = positionSnapshot.data ?? Duration.zero;
final progress = displayDuration.inMilliseconds > 0
? position.inMilliseconds / displayDuration.inMilliseconds
: 0.0;
return LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
backgroundColor: widget.metaColor.withValues(alpha: 0.2),
valueColor: AlwaysStoppedAnimation<Color>(widget.textColor),
minHeight: 4,
);
},
),
),
const SizedBox(width: 8),
Text(
_formatDuration(displayDuration),
style: TextStyle(
color: widget.metaColor,
fontSize: 11,
),
),
],
),
],
);
},
);
}
String _formatDuration(Duration duration) {
final totalSeconds = duration.inSeconds;
final minutes = totalSeconds ~/ 60;
final seconds = totalSeconds % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}