мультиаккаунгтинг(багованный, но он есть), избранное коректно отображается, убрана кнопка ответить в канале, добавлена поддержка видеокружков и голосовых сообщений

This commit is contained in:
needle10
2025-11-18 23:13:55 +03:00
parent e5b97208ad
commit 2d11f1cba2
14 changed files with 1803 additions and 218 deletions

View File

@@ -27,6 +27,8 @@ import 'package:gwid/downloads_screen.dart';
import 'package:gwid/user_id_lookup_screen.dart';
import 'package:gwid/widgets/message_preview_dialog.dart';
import 'package:gwid/services/chat_read_settings_service.dart';
import 'package:gwid/services/account_manager.dart';
import 'package:gwid/models/account.dart';
class SearchResult {
final Chat chat;
@@ -90,6 +92,7 @@ class _ChatsScreenState extends State<ChatsScreen>
String _connectionStatus = 'connecting';
StreamSubscription<void>? _connectionStatusSubscription;
StreamSubscription<String>? _connectionStateSubscription;
bool _isAccountsExpanded = false;
@override
void initState() {
@@ -158,6 +161,21 @@ class _ChatsScreenState extends State<ChatsScreen>
_isProfileLoading = true;
});
try {
final accountManager = AccountManager();
await accountManager.initialize();
final currentAccount = accountManager.currentAccount;
if (currentAccount?.profile != null && mounted) {
setState(() {
_myProfile = currentAccount!.profile;
_isProfileLoading = false;
});
return;
}
} catch (e) {
print('Ошибка загрузки профиля из AccountManager: $e');
}
final cachedProfileData = ApiService.instance.lastChatsPayload?['profile'];
if (cachedProfileData != null && mounted) {
setState(() {
@@ -1735,93 +1753,258 @@ class _ChatsScreenState extends State<ChatsScreen>
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: double.infinity,
FutureBuilder<List<Account>>(
future: _loadAccounts(),
builder: (context, accountsSnapshot) {
final accounts = accountsSnapshot.data ?? [];
final accountManager = AccountManager();
final currentAccount = accountManager.currentAccount;
final hasMultipleAccounts = accounts.length > 1;
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 16.0,
left: 16.0,
right: 16.0,
bottom: 16.0,
),
decoration: BoxDecoration(color: colors.primaryContainer),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 30, // Чуть крупнее
backgroundColor: colors.primary,
backgroundImage:
_isProfileLoading || _myProfile?.photoBaseUrl == null
? null
: NetworkImage(_myProfile!.photoBaseUrl!),
child: _isProfileLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
return Column(
children: [
Container(
width: double.infinity,
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 16.0,
left: 16.0,
right: 16.0,
bottom: 16.0,
),
decoration: BoxDecoration(color: colors.primaryContainer),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 30, // Чуть крупнее
backgroundColor: colors.primary,
backgroundImage:
_isProfileLoading ||
_myProfile?.photoBaseUrl == null
? null
: NetworkImage(_myProfile!.photoBaseUrl!),
child: _isProfileLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: (_myProfile?.photoBaseUrl == null
? Text(
_myProfile
?.displayName
.isNotEmpty ==
true
? _myProfile!.displayName[0]
.toUpperCase()
: '?',
style: TextStyle(
color: colors.onPrimary,
fontSize: 28, // Крупнее
),
)
: null),
),
IconButton(
icon: Icon(
isDarkMode
? Icons.brightness_7
: Icons.brightness_4, // Солнце / Луна
color: colors.onPrimaryContainer,
size: 26,
),
onPressed: () {
themeProvider.toggleTheme();
},
tooltip: isDarkMode
? 'Светлая тема'
: 'Темная тема',
),
],
),
const SizedBox(height: 12),
Text(
_myProfile?.displayName ?? 'Загрузка...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colors.onPrimaryContainer,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
_myProfile?.formattedPhone ?? '',
style: TextStyle(
color: colors.onPrimaryContainer.withOpacity(
0.8,
),
fontSize: 14,
),
),
),
InkWell(
onTap: () {
setState(() {
_isAccountsExpanded = !_isAccountsExpanded;
});
},
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Icon(
_isAccountsExpanded
? Icons.expand_less
: Icons.expand_more,
color: colors.onPrimaryContainer,
size: 24,
),
),
),
],
),
],
),
),
ClipRect(
child: AnimatedSize(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubic,
child: _isAccountsExpanded
? Column(
children: [
if (hasMultipleAccounts)
...accounts.map((account) {
final isCurrent =
account.id == currentAccount?.id;
return ListTile(
leading: CircleAvatar(
radius: 20,
backgroundColor: isCurrent
? colors.primary
: colors.surfaceVariant,
backgroundImage:
account.avatarUrl != null
? NetworkImage(account.avatarUrl!)
: null,
child: account.avatarUrl == null
? Text(
account.displayName.isNotEmpty
? account.displayName[0]
.toUpperCase()
: '?',
style: TextStyle(
color: isCurrent
? colors.onPrimary
: colors.onSurfaceVariant,
fontSize: 16,
),
)
: null,
),
title: Text(
account.displayName,
style: TextStyle(
fontWeight: isCurrent
? FontWeight.bold
: FontWeight.normal,
),
),
subtitle: account.displayPhone.isNotEmpty
? Text(account.displayPhone)
: null,
trailing: isCurrent
? Icon(
Icons.check_circle,
color: colors.primary,
size: 20,
)
: null,
onTap: isCurrent
? null
: () async {
Navigator.pop(context);
try {
await ApiService.instance
.switchAccount(account.id);
if (mounted) {
setState(() {
_isAccountsExpanded = false;
_loadMyProfile();
_chatsFuture = (() async {
try {
await ApiService
.instance
.waitUntilOnline();
return ApiService
.instance
.getChatsAndContacts();
} catch (e) {
print(
'Ошибка получения чатов: $e',
);
rethrow;
}
})();
});
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(
SnackBar(
content: Text(
'Ошибка переключения аккаунта: $e',
),
backgroundColor:
colors.error,
),
);
}
}
},
);
}).toList(),
ListTile(
leading: const Icon(Icons.add_circle_outline),
title: const Text('Добавить аккаунт'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const PhoneEntryScreen(),
),
);
},
),
],
)
: (_myProfile?.photoBaseUrl == null
? Text(
_myProfile?.displayName.isNotEmpty == true
? _myProfile!.displayName[0]
.toUpperCase()
: '?',
style: TextStyle(
color: colors.onPrimary,
fontSize: 28, // Крупнее
),
)
: null),
: const SizedBox.shrink(),
),
IconButton(
icon: Icon(
isDarkMode
? Icons.brightness_7
: Icons.brightness_4, // Солнце / Луна
color: colors.onPrimaryContainer,
size: 26,
),
onPressed: () {
themeProvider.toggleTheme();
},
tooltip: isDarkMode ? 'Светлая тема' : 'Темная тема',
),
],
),
const SizedBox(height: 12),
Text(
_myProfile?.displayName ?? 'Загрузка...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colors.onPrimaryContainer,
),
),
const SizedBox(height: 4),
Text(
_myProfile?.formattedPhone ?? '',
style: TextStyle(
color: colors.onPrimaryContainer.withOpacity(0.8),
fontSize: 14,
),
),
],
),
],
);
},
),
Expanded(
child: Column(
children: [
_buildAccountsSection(context, colors),
ListTile(
leading: const Icon(Icons.person_outline),
title: const Text('Мой профиль'),
@@ -1905,6 +2088,16 @@ class _ChatsScreenState extends State<ChatsScreen>
);
}
Widget _buildAccountsSection(BuildContext context, ColorScheme colors) {
return const SizedBox.shrink();
}
Future<List<Account>> _loadAccounts() async {
final accountManager = AccountManager();
await accountManager.initialize();
return accountManager.accounts;
}
Widget _buildSearchResults() {
final colors = Theme.of(context).colorScheme;
@@ -2002,10 +2195,23 @@ class _ChatsScreenState extends State<ChatsScreen>
final participantCount =
chat.participantsCount ?? chat.participantIds.length;
final Contact contactToUse = isSavedMessages
? Contact(
id: chat.id,
name: "Избранное",
firstName: "",
lastName: "",
photoBaseUrl: null,
description: null,
isBlocked: false,
isBlockedByMe: false,
)
: contact;
if (widget.onChatSelected != null) {
widget.onChatSelected!(
chat,
contact,
contactToUse,
isGroupChat,
isChannel,
participantCount,
@@ -2015,7 +2221,7 @@ class _ChatsScreenState extends State<ChatsScreen>
MaterialPageRoute(
builder: (context) => ChatScreen(
chatId: chat.id,
contact: contact,
contact: contactToUse,
myId: chat.ownerId,
isGroupChat: isGroupChat,
isChannel: isChannel,
@@ -3587,18 +3793,28 @@ class _ChatsScreenState extends State<ChatsScreen>
}
}
final Contact contactFallback =
contact ??
Contact(
id: chat.id,
name: title,
firstName: "",
lastName: "",
photoBaseUrl: avatarUrl,
description: isChannel ? chat.description : null,
isBlocked: false,
isBlockedByMe: false,
);
final Contact contactFallback = isSavedMessages
? Contact(
id: chat.id,
name: "Избранное",
firstName: "",
lastName: "",
photoBaseUrl: null,
description: null,
isBlocked: false,
isBlockedByMe: false,
)
: contact ??
Contact(
id: chat.id,
name: title,
firstName: "",
lastName: "",
photoBaseUrl: avatarUrl,
description: isChannel ? chat.description : null,
isBlocked: false,
isBlockedByMe: false,
);
final participantCount =
chat.participantsCount ?? chat.participantIds.length;