remove voice code make optimizations. Fix channels race conditions. add reply function

This commit is contained in:
zach
2025-12-30 19:27:25 -07:00
parent 6ff950d426
commit baf92ef672
582 changed files with 814 additions and 179108 deletions
+46
View File
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
/// A centered empty state display with icon, title, and optional subtitle/action.
class EmptyState extends StatelessWidget {
final IconData icon;
final String title;
final String? subtitle;
final Widget? action;
const EmptyState({
super.key,
required this.icon,
required this.title,
this.subtitle,
this.action,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
title,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
textAlign: TextAlign.center,
),
],
if (action != null) ...[
const SizedBox(height: 24),
action!,
],
],
),
);
}
}
-134
View File
@@ -1,134 +0,0 @@
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')}';
}
}