Files
votianlt/app/lib/chats_view.dart
T
Sven b642f42954 feat: Auftragsdetails und Stationsabschluss für Version 0.9.24
Preisanzeige in der App entfernen und Auftragsbezeichnungen vereinheitlichen.

Stationsabschlüsse und Benachrichtigungen absichern sowie Historie um Aufgabenbeschreibungen und ausführende Nutzer erweitern.

Regressionstests ergänzen und App sowie Backend auf Version 0.9.24 anheben.
2026-10-01 11:31:59 +02:00

190 lines
5.4 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'app_theme.dart';
import 'l10n/app_localizations.dart';
import 'l10n/localization_helpers.dart';
import 'models/chat.dart';
import 'services/chat_service.dart';
import 'widgets/offline_banner.dart';
class ChatsView extends StatefulWidget {
const ChatsView({super.key});
@override
State<ChatsView> createState() => _ChatsViewState();
}
class _ChatsViewState extends State<ChatsView> {
final ChatService _chatService = ChatService();
List<Chat> _chats = const [];
StreamSubscription<List<Chat>>? _chatSubscription;
bool _isInitializing = true;
@override
void initState() {
super.initState();
_initializeChats();
}
Future<void> _initializeChats() async {
await _chatService.initialize();
if (!mounted) return;
setState(() {
_chats = _chatService.currentChats;
_isInitializing = false;
});
_chatSubscription = _chatService.chatsStream.listen((chats) {
if (!mounted) return;
setState(() {
_chats = chats;
});
});
}
@override
void dispose() {
_chatSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context).chats)),
body: Column(
children: [const OfflineBanner(), Expanded(child: _buildBody())],
),
);
}
Widget _buildBody() {
if (_isInitializing) {
return const Center(child: CircularProgressIndicator());
}
if (_chats.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.chat_outlined,
size: 64,
color: AppColors.textMuted,
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context).noChatsAvailable,
style: const TextStyle(fontSize: 16, color: AppColors.textMuted),
),
],
),
);
}
return ListView.builder(
itemCount: _chats.length,
itemBuilder: (context, index) {
final chat = _chats[index];
return _buildChatTile(chat);
},
);
}
Widget _buildChatTile(Chat chat) {
final isJobChat = chat.type == ChatType.jobSpecific;
final hasMessages = chat.messages.isNotEmpty;
final previewText =
hasMessages
? chat.lastMessagePreview
: AppLocalizations.of(context).noMessagesYet;
final timeLabel = hasMessages ? _formatTime(chat.lastMessageTime) : '--';
final jobId = chat.jobId?.trim();
final jobNumber = chat.jobNumber?.trim();
final showJobId = isJobChat && jobId != null && jobId.isNotEmpty;
return Card(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: ListTile(
leading: CircleAvatar(
backgroundColor:
isJobChat ? AppColors.primarySoft : AppColors.secondarySoft,
child: Icon(
isJobChat ? Icons.work : Icons.support_agent,
color: isJobChat ? AppColors.primaryStrong : AppColors.secondary,
),
),
title: Text(() {
if (isJobChat) {
if (jobNumber != null && jobNumber.isNotEmpty) {
return 'Auftrag $jobNumber';
}
if (showJobId) {
return 'Auftrag $jobId';
}
}
return localizedChatTitle(context, chat);
}(), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
subtitle: Text(
previewText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, color: AppColors.textMuted),
),
trailing: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
timeLabel,
style: const TextStyle(fontSize: 12, color: AppColors.textMuted),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color:
isJobChat ? AppColors.primarySoft : AppColors.secondarySoft,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isJobChat ? AppColors.primary : AppColors.secondary,
),
),
child: Text(
isJobChat ? 'AUFTRAG' : 'ALLG',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color:
isJobChat ? AppColors.primaryStrong : AppColors.secondary,
),
),
),
],
),
onTap: () {
Navigator.of(context).pushNamed('/chat_details', arguments: chat);
},
),
);
}
String _formatTime(DateTime dateTime) {
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inDays > 0) {
return '${difference.inDays}T';
} else if (difference.inHours > 0) {
return '${difference.inHours}h';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes}m';
} else {
return 'jetzt';
}
}
}