From 07d39ffa24e044f807bce0b2af5947c17ec2dfdc Mon Sep 17 00:00:00 2001 From: Jganenok Date: Wed, 13 May 2026 21:26:46 +0700 Subject: [PATCH] =?UTF-8?q?=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1=D0=BD=D0=B5=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backend/modules/account.dart | 120 ++++++ lib/backend/modules/chats.dart | 22 + lib/core/storage/app_database.dart | 14 + lib/core/storage/spoofing_service.dart | 2 +- .../screens/chats/chat_info_screen.dart | 379 ++++++++++++++++-- .../screens/profile/debug_menu_screen.dart | 132 +++++- lib/frontend/screens/profile/info_screen.dart | 286 +++++++++++++ .../screens/profile/security_screen.dart | 25 +- .../screens/profile/settings_tab.dart | 21 +- lib/l10n/app_en.arb | 57 ++- lib/l10n/app_localizations.dart | 324 +++++++++++++++ lib/l10n/app_localizations_en.dart | 162 ++++++++ lib/l10n/app_localizations_ru.dart | 162 ++++++++ lib/l10n/app_ru.arb | 57 ++- 14 files changed, 1724 insertions(+), 39 deletions(-) create mode 100644 lib/frontend/screens/profile/info_screen.dart diff --git a/lib/backend/modules/account.dart b/lib/backend/modules/account.dart index 4c5c32d..8d6f6b8 100644 --- a/lib/backend/modules/account.dart +++ b/lib/backend/modules/account.dart @@ -27,6 +27,7 @@ class PrivacyConfig { final String chatsInvite; final bool pushNewContacts; final bool unsafeFiles; + final String phoneNumberPrivacy; final String inactiveTtl; final bool showReadMark; final bool altKeyboard; @@ -48,6 +49,7 @@ class PrivacyConfig { required this.chatsInvite, required this.pushNewContacts, required this.unsafeFiles, + required this.phoneNumberPrivacy, required this.inactiveTtl, required this.showReadMark, required this.altKeyboard, @@ -71,6 +73,7 @@ class PrivacyConfig { chatsInvite: map['CHATS_INVITE']?.toString() ?? 'CONTACTS', pushNewContacts: map['PUSH_NEW_CONTACTS'] ?? false, unsafeFiles: map['UNSAFE_FILES'] ?? true, + phoneNumberPrivacy: map['PHONE_NUMBER_PRIVACY']?.toString() ?? 'ALL', inactiveTtl: map['INACTIVE_TTL']?.toString() ?? '6M', showReadMark: map['SHOW_READ_MARK'] ?? true, altKeyboard: map['ALT_KEYBOARD'] ?? false, @@ -94,6 +97,7 @@ class PrivacyConfig { 'CHATS_INVITE': chatsInvite, 'PUSH_NEW_CONTACTS': pushNewContacts, 'UNSAFE_FILES': unsafeFiles, + 'PHONE_NUMBER_PRIVACY': phoneNumberPrivacy, 'INACTIVE_TTL': inactiveTtl, 'SHOW_READ_MARK': showReadMark, 'ALT_KEYBOARD': altKeyboard, @@ -125,6 +129,7 @@ class PrivacyConfig { chatsInvite: 'CONTACTS', pushNewContacts: false, unsafeFiles: true, + phoneNumberPrivacy: 'ALL', inactiveTtl: '6M', showReadMark: true, altKeyboard: false, @@ -946,6 +951,12 @@ class AccountModule { logger.w('Папки чатов: $e'); } + try { + await _saveLoginInfo(data, profile.id); + } catch (e) { + logger.w('Info: $e'); + } + return LoginResult( profile: profile, updatedToken: updatedToken, @@ -980,6 +991,115 @@ class AccountModule { } } + Future _saveLoginInfo( + Map data, + int accountId, + ) async { + final contact = data['profile']?['contact'] as Map?; + final videoChatHistory = data['videoChatHistory']; + final chats = data['chats'] as List?; + final config = data['config'] as Map?; + final serverConfig = config?['server'] as Map?; + final userConfig = config?['user'] as Map?; + final yMap = serverConfig?['y-map'] as Map?; + final whiteListLinks = serverConfig?['white-list-links'] as List?; + final fileUploadUnsupported = serverConfig?['file-upload-unsupported-types'] as List?; + final time = data['time'] as int?; + + final info = { + 'registrationTime': contact?['registrationTime'], + 'country': contact?['country'], + 'videoChatHistory': videoChatHistory, + 'updateTime': contact?['updateTime'], + 'id': contact?['id'], + 'chatMarker': chats != null && chats.isNotEmpty + ? _extractChatMarker(chats.cast()) + : null, + 'time': time, + 'server': serverConfig != null + ? _extractServerInfo(serverConfig, yMap, whiteListLinks, fileUploadUnsupported) + : null, + 'user': userConfig != null ? _extractUserConfig(userConfig) : null, + }; + + await AppDatabase.saveLoginInfo(accountId, jsonEncode(info)); + } + + Map _extractChatMarker(List chats) { + int? latestTime; + for (final chat in chats) { + final lastEventTime = chat['lastEventTime'] as int?; + if (lastEventTime != null && (latestTime == null || lastEventTime > latestTime)) { + latestTime = lastEventTime; + } + } + return {'chatMarker': latestTime}; + } + + Map _extractServerInfo( + Map serverConfig, + Map? yMap, + List? whiteListLinks, + List? fileUploadUnsupported, + ) { + return { + 'account-removal-enabled': serverConfig['account-removal-enabled'], + 'image-size': serverConfig['image-size'], + 'gce': serverConfig['gce'], + 'gcce': serverConfig['gcce'], + 'max-msg-length': serverConfig['max-msg-length'], + 'quotes-enabled': serverConfig['quotes-enabled'], + 'calls-endpoint': serverConfig['calls-endpoint'], + 'send-location-enabled': serverConfig['send-location-enabled'], + 'lgce': serverConfig['lgce'], + 'wud': serverConfig['wud'], + 'video-msg-enabled': serverConfig['video-msg-enabled'], + 'grse': serverConfig['grse'], + 'edit-timeout': serverConfig['edit-timeout'], + 'image-quality': serverConfig['image-quality'], + 'unsafe-files-alert': serverConfig['unsafe-files-alert'], + 'account-nickname-enabled': serverConfig['account-nickname-enabled'], + 'mentions_entity_names_limit': serverConfig['mentions_entity_names_limit'], + 'reactions-enabled': serverConfig['reactions-enabled'], + 'y-map': yMap != null ? { + 'tile': yMap['tile'], + 'geocoder': yMap['geocoder'], + 'static': yMap['static'], + } : null, + 'white-list-links': whiteListLinks, + 'file-upload-unsupported-types': fileUploadUnsupported, + }; + } + + Map _extractUserConfig(Map userConfig) { + return { + 'CHATS_PUSH_NOTIFICATION': userConfig['CHATS_PUSH_NOTIFICATION'], + 'PUSH_DETAILS': userConfig['PUSH_DETAILS'], + 'PUSH_SOUND': userConfig['PUSH_SOUND'], + 'PHONE_NUMBER_PRIVACY': userConfig['PHONE_NUMBER_PRIVACY'], + 'INACTIVE_TTL': userConfig['INACTIVE_TTL'], + 'SHOW_READ_MARK': userConfig['SHOW_READ_MARK'], + 'AUDIO_TRANSCRIPTION_ENABLED': userConfig['AUDIO_TRANSCRIPTION_ENABLED'], + 'SEARCH_BY_PHONE': userConfig['SEARCH_BY_PHONE'], + 'INCOMING_CALL': userConfig['INCOMING_CALL'], + 'DOUBLE_TAP_REACTION_DISABLED': userConfig['DOUBLE_TAP_REACTION_DISABLED'], + 'SAFE_MODE_NO_PIN': userConfig['SAFE_MODE_NO_PIN'], + 'CHATS_PUSH_SOUND': userConfig['CHATS_PUSH_SOUND'], + 'DOUBLE_TAP_REACTION_VALUE': userConfig['DOUBLE_TAP_REACTION_VALUE'], + 'FAMILY_PROTECTION': userConfig['FAMILY_PROTECTION'], + 'HIDDEN': userConfig['HIDDEN'], + 'CHATS_INVITE': userConfig['CHATS_INVITE'], + 'PUSH_NEW_CONTACTS': userConfig['PUSH_NEW_CONTACTS'], + 'UNSAFE_FILES': userConfig['UNSAFE_FILES'], + 'DONT_DISTURB_UNTIL': userConfig['DONT_DISTURB_UNTIL'], + 'ALT_KEYBOARD': userConfig['ALT_KEYBOARD'], + 'CONTENT_LEVEL_ACCESS': userConfig['CONTENT_LEVEL_ACCESS'], + 'STICKERS_SUGGEST': userConfig['STICKERS_SUGGEST'], + 'SAFE_MODE': userConfig['SAFE_MODE'], + 'M_CALL_PUSH_NOTIFICATION': userConfig['M_CALL_PUSH_NOTIFICATION'], + }; + } + Future _requestCodeInternal( String phone, AuthRequestType type, diff --git a/lib/backend/modules/chats.dart b/lib/backend/modules/chats.dart index 7ee2864..f47aa70 100644 --- a/lib/backend/modules/chats.dart +++ b/lib/backend/modules/chats.dart @@ -1,7 +1,9 @@ import 'dart:convert'; +import '../../core/protocol/opcode_map.dart'; import '../../core/storage/app_database.dart'; import '../../core/utils/logger.dart'; +import '../api.dart'; Map _parseParticipants(dynamic raw) { try { @@ -304,4 +306,24 @@ class ChatsModule { final name = nameRaw; return name['name'] as String?; } + + static Future?> getChatInfo(Api api, int chatId) async { + final packet = await api.sendRequest(Opcode.chatInfo, { + 'chatIds': [chatId], + }); + if (packet.isError) return null; + final payload = packet.payload as Map?; + final chats = payload?['chats'] as List?; + if (chats == null || chats.isEmpty) return null; + return Map.from(chats.first as Map); + } + + static Future searchById(Api api, int userId) async { + final packet = await api.sendRequest(Opcode.publicSearch, { + 'query': userId.toString(), + 'from': 0, + 'count': 10, + }); + return packet.payload; + } } diff --git a/lib/core/storage/app_database.dart b/lib/core/storage/app_database.dart index 53bbb91..7283d9b 100644 --- a/lib/core/storage/app_database.dart +++ b/lib/core/storage/app_database.dart @@ -124,6 +124,7 @@ abstract class SyncKey { static const configHash = 'config_hash'; static const chatCacheFingerprint = 'chat_cache_fingerprint'; static const serverTime = 'server_time'; + static const loginInfo = 'login_info'; } class AppDatabase { @@ -392,6 +393,19 @@ class AppDatabase { return rows.first['value'] as String; } + static Future saveLoginInfo(int accountId, String jsonInfo) async { + final db = await _instance; + await db.insert('sync_state', { + 'account_id': accountId, + 'key': SyncKey.loginInfo, + 'value': jsonInfo, + }, conflictAlgorithm: ConflictAlgorithm.replace); + } + + static Future getLoginInfo(int accountId) async { + return getSyncValue(accountId, SyncKey.loginInfo); + } + static Future close() async { await _db?.close(); _db = null; diff --git a/lib/core/storage/spoofing_service.dart b/lib/core/storage/spoofing_service.dart index b58cce2..c27240b 100644 --- a/lib/core/storage/spoofing_service.dart +++ b/lib/core/storage/spoofing_service.dart @@ -1,7 +1,7 @@ import 'package:shared_preferences/shared_preferences.dart'; class SpoofingService { - static const String hardcodedAppVersion = '26.8.1'; + static const String hardcodedAppVersion = '26.15.3'; static const int hardcodedBuildNumber = 6606; static Future?> getSpoofedSessionData() async { diff --git a/lib/frontend/screens/chats/chat_info_screen.dart b/lib/frontend/screens/chats/chat_info_screen.dart index c50ae93..70a4ae7 100644 --- a/lib/frontend/screens/chats/chat_info_screen.dart +++ b/lib/frontend/screens/chats/chat_info_screen.dart @@ -1,4 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import '../../../core/protocol/opcode_map.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../main.dart' as main; +import '../../widgets/custom_notification.dart'; class ChatInfoScreen extends StatefulWidget { final int chatId; @@ -13,49 +18,367 @@ class ChatInfoScreen extends StatefulWidget { required this.imageUrl, required this.chatType, }); + @override State createState() => _ChatInfoScreenState(); } -class _ChatInfoScreenState extends State{ + +class _ChatInfoScreenState extends State + with TickerProviderStateMixin { + bool _isLoading = true; + Map? _chatData; + late AnimationController _shimmerController; + + @override + void initState() { + super.initState(); + _shimmerController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1500), + )..repeat(); + _loadChatData(); + } + + @override + void dispose() { + _shimmerController.dispose(); + super.dispose(); + } + + Future _loadChatData() async { + try { + final packet = await main.api.sendRequest(Opcode.chatInfo, { + 'chatIds': [widget.chatId], + }); + + final payload = packet.payload as Map?; + if (payload == null) { + if (mounted) setState(() => _isLoading = false); + return; + } + + final errorField = payload['error']; + if (errorField != null) { + String errorMsg = 'Error'; + if (errorField is Map) { + errorMsg = errorField['localizedMessage'] ?? errorField['message'] ?? errorField.toString(); + } else if (errorField is String) { + errorMsg = errorField; + } + if (mounted) showCustomNotification(context, errorMsg); + setState(() => _isLoading = false); + return; + } + + final chats = payload['chats'] as List?; + if (chats != null && chats.isNotEmpty) { + _chatData = Map.from(chats.first as Map); + } else if (chats != null && chats.isEmpty) { + if (mounted) showCustomNotification(context, 'No data found'); + } + + if (mounted) setState(() => _isLoading = false); + } catch (e) { + if (mounted) { + showCustomNotification(context, 'Error: $e'); + setState(() => _isLoading = false); + } + } + } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; + final l10n = AppLocalizations.of(context); return Scaffold( backgroundColor: cs.surface, appBar: AppBar( - + backgroundColor: cs.surface, + elevation: 0, + leading: IconButton( + icon: Icon(Symbols.arrow_back, color: cs.onSurface), + onPressed: () => Navigator.pop(context), + ), + title: Text( + l10n?.chatInfoTitle ?? 'Info', + style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600), + ), ), - body: - SizedBox( - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (widget.imageUrl.isNotEmpty) - CircleAvatar( - radius: 36, - backgroundImage: NetworkImage(widget.imageUrl), - ) - else - CircleAvatar( - radius: 36, - backgroundColor: cs.primaryContainer, - child: Text( - widget.name.isNotEmpty ? widget.name[0].toUpperCase() : '?', - style: TextStyle(color: cs.onPrimaryContainer, fontSize: 12), - ), - ), - Text( - widget.name, - style: TextStyle(color: Colors.white, fontSize: 24, height: 1.3), + body: _isLoading + ? _buildShimmer(cs) + : _chatData == null + ? Center( + child: Text( + 'No data', + style: TextStyle(color: cs.onSurfaceVariant), + ), + ) + : _buildContent(cs, l10n), + ); + } + + Widget _buildShimmer(ColorScheme cs) { + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Center( + child: Container( + width: 72, + height: 72, + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + shape: BoxShape.circle, ), - - ], + ), + ), + const SizedBox(height: 12), + Center( + child: Container( + width: 120, + height: 20, + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + const SizedBox(height: 24), + ...List.generate( + 10, + (_) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Container( + height: 48, + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ], + ); + } + + Widget _buildContent(ColorScheme cs, AppLocalizations? l10n) { + final chat = _chatData!; + final type = chat['type'] as String? ?? ''; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + Center( + child: Container( + width: 72, + height: 72, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: cs.primaryContainer, + ), + child: widget.imageUrl.isNotEmpty + ? ClipOval( + child: Image.network(widget.imageUrl, fit: BoxFit.cover), + ) + : Center( + child: Text( + widget.name.isNotEmpty + ? widget.name[0].toUpperCase() + : '?', + style: TextStyle( + color: cs.onPrimaryContainer, + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + const SizedBox(height: 12), + Center( + child: Text( + widget.name, + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 24), + + if (type == 'CHANNEL') ...[ + _buildSectionTitle('Channel', cs), + _buildRow( + l10n?.chatInfoSubscribers ?? 'subscribers:', + (chat['participantsCount'] as int?)?.toString() ?? '-', + cs, + ), + if ((chat['link'] as String?)?.isNotEmpty ?? false) + _buildRow( + l10n?.chatInfoLink ?? 'link:', + chat['link'] as String, + cs, + ), + _buildRow( + l10n?.chatInfoOfficial ?? 'official:', + (chat['options']?['OFFICIAL'] as bool?)?.toString() ?? '-', + cs, + ), + _buildRow( + l10n?.chatInfoComments ?? 'comments:', + (chat['options']?['COMMENTS'] as bool?)?.toString() ?? '-', + cs, + ), + _buildRow( + l10n?.chatInfoAplus ?? 'approved by Roskomnadzor:', + (chat['options']?['A_PLUS_CHANNEL'] as bool?)?.toString() ?? '-', + cs, + ), + _buildRow( + l10n?.chatInfoSignAdmin ?? 'admin signature:', + (chat['options']?['SIGN_ADMIN'] as bool?)?.toString() ?? '-', + cs, + ), + if ((chat['modified'] as int?) != null) + _buildRow( + l10n?.chatInfoLastChanged ?? 'last changed:', + _formatTs(chat['modified'] as int), + cs, + ), + if ((chat['created'] as int?) != null) + _buildRow( + l10n?.chatInfoCreated ?? 'created:', + _formatTs(chat['created'] as int), + cs, + ), + ], + + if (type == 'CHAT') ...[ + _buildSectionTitle('Chat', cs), + _buildRow( + l10n?.chatInfoMembers ?? 'members:', + (chat['participantsCount'] as int?)?.toString() ?? '-', + cs, + ), + if ((chat['hasBots'] as bool?) ?? false) + _buildRow( + l10n?.chatInfoHasBots ?? 'has bots:', + 'true', + cs, + ), + if ((chat['blockedParticipantsCount'] as int?) != null && + chat['blockedParticipantsCount'] > 0) + _buildRow( + l10n?.chatInfoBlockedCount ?? 'blocked in group:', + (chat['blockedParticipantsCount'] as int).toString(), + cs, + ), + _buildRow( + l10n?.chatInfoOfficialStatus ?? 'official status:', + (chat['options']?['OFFICIAL'] as bool?)?.toString() ?? '-', + cs, + ), + if ((chat['modified'] as int?) != null) + _buildRow( + l10n?.chatInfoLastChanged ?? 'last changed:', + _formatTs(chat['modified'] as int), + cs, + ), + if ((chat['joinTime'] as int?) != null && chat['joinTime'] != 1) + _buildRow( + l10n?.chatInfoJoined ?? 'joined:', + _formatTs(chat['joinTime'] as int), + cs, + ), + if ((chat['created'] as int?) != null) + _buildRow( + l10n?.chatInfoGroupCreated ?? 'group created:', + _formatTs(chat['created'] as int), + cs, + ), + if ((chat['owner'] as int?) != null) + _buildRow( + l10n?.chatInfoGroupOwner ?? 'group owner:', + (chat['owner'] as int).toString(), + cs, + ), + ], + + if (type == 'DIALOG') ...[ + if ((chat['created'] as int?) != null && + chat['created'] != 0 && + chat['created'] != 1) + _buildRow( + l10n?.chatInfoDialogStarted ?? 'dialog started:', + _formatTs(chat['created'] as int), + cs, + ), + ], + + const SizedBox(height: 120), + ], + ); + } + + Widget _buildSectionTitle(String title, ColorScheme cs) { + return Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8, left: 4, right: 4), + child: Text( + title, + style: TextStyle( + color: cs.primary, + fontSize: 13, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, ), ), ); } -} + Widget _buildRow(String label, String value, ColorScheme cs) { + return Container( + margin: const EdgeInsets.only(bottom: 1), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Text( + label, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 3, + child: Text( + value, + style: TextStyle( + color: cs.onSurface, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.end, + ), + ), + ], + ), + ); + } + + String _formatTs(int ts) { + if (ts < 1000000000000) return ts.toString(); + final dt = DateTime.fromMillisecondsSinceEpoch(ts); + return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ' + '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}'; + } +} \ No newline at end of file diff --git a/lib/frontend/screens/profile/debug_menu_screen.dart b/lib/frontend/screens/profile/debug_menu_screen.dart index 5f7de26..fc43f31 100644 --- a/lib/frontend/screens/profile/debug_menu_screen.dart +++ b/lib/frontend/screens/profile/debug_menu_screen.dart @@ -1,11 +1,55 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; - +import '../../../backend/modules/chats.dart'; +import '../../../core/utils/logger.dart'; import '../../../main.dart'; -class DebugMenuScreen extends StatelessWidget { +class DebugMenuScreen extends StatefulWidget { const DebugMenuScreen({super.key}); + @override + State createState() => _DebugMenuScreenState(); +} + +class _DebugMenuScreenState extends State { + final _idController = TextEditingController(); + String? _searchResult; + bool _isSearching = false; + + @override + void dispose() { + _idController.dispose(); + super.dispose(); + } + + Future _search() async { + final id = int.tryParse(_idController.text); + if (id == null) return; + setState(() { + _isSearching = true; + _searchResult = null; + }); + try { + final result = await ChatsModule.searchById(api, id); + logger.i('searchById result: $result'); + if (!mounted) return; + if (result is Map && result.containsKey('error')) { + final errorMsg = result['localizedMessage'] ?? result['message'] ?? result['error'] ?? 'Error'; + setState(() => _searchResult = 'Error: $errorMsg'); + } else if (result is Map) { + setState(() => _searchResult = result.toString()); + } else { + setState(() => _searchResult = result?.toString() ?? 'null'); + } + } catch (e) { + if (mounted) { + setState(() => _searchResult = 'Exception: $e'); + } + } finally { + if (mounted) setState(() => _isSearching = false); + } + } + @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; @@ -115,10 +159,92 @@ class DebugMenuScreen extends StatelessWidget { ), ), ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), + child: Container( + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Поиск по ID (opcode 60)', + style: TextStyle( + color: cs.onSurface, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextField( + controller: _idController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: 'Введите user ID', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + onSubmitted: (_) => _search(), + ), + ), + const SizedBox(width: 12), + FilledButton( + onPressed: _isSearching ? null : _search, + child: _isSearching + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : const Icon(Symbols.search, size: 20), + ), + ], + ), + if (_searchResult != null) ...[ + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + constraints: const BoxConstraints(maxHeight: 400), + child: SingleChildScrollView( + child: Text( + _searchResult!, + style: TextStyle( + color: cs.onSurface, + fontSize: 12, + fontFamily: 'monospace', + ), + ), + ), + ), + ], + ], + ), + ), + ), + ), const SliverToBoxAdapter(child: SizedBox(height: 120)), ], ), ), ); } -} +} \ No newline at end of file diff --git a/lib/frontend/screens/profile/info_screen.dart b/lib/frontend/screens/profile/info_screen.dart new file mode 100644 index 0000000..715ccd1 --- /dev/null +++ b/lib/frontend/screens/profile/info_screen.dart @@ -0,0 +1,286 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import '../../../core/storage/app_database.dart'; +import '../../../core/storage/token_storage.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../widgets/custom_notification.dart'; + +class InfoScreen extends StatefulWidget { + const InfoScreen({super.key}); + + @override + State createState() => _InfoScreenState(); +} + +class _InfoScreenState extends State { + bool _isLoading = true; + Map? _info; + + @override + void initState() { + super.initState(); + _loadData(); + } + + Future _loadData() async { + try { + final accountId = await TokenStorage.getActiveAccountId(); + if (accountId == null) { + if (mounted) setState(() => _isLoading = false); + return; + } + final jsonStr = await AppDatabase.getLoginInfo(accountId); + if (jsonStr != null) { + setState(() => _info = jsonDecode(jsonStr) as Map); + } + if (mounted) setState(() => _isLoading = false); + } catch (e) { + if (mounted) { + showCustomNotification(context, 'Error: $e'); + setState(() => _isLoading = false); + } + } + } + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final l10n = AppLocalizations.of(context); + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + leading: IconButton( + icon: Icon(Symbols.arrow_back, color: cs.onSurface), + onPressed: () => Navigator.pop(context), + ), + title: Text( + l10n?.infoTitle ?? 'Info', + style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600), + ), + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _info == null + ? Center( + child: Text( + 'No data', + style: TextStyle(color: cs.onSurfaceVariant), + ), + ) + : _buildContent(cs, l10n!), + ); + } + + Widget _buildContent(ColorScheme cs, AppLocalizations l10n) { + final info = _info!; + final server = info['server'] as Map?; + final user = info['user'] as Map?; + final yMap = server?['y-map'] as Map?; + + final accountKeys = { + 'registrationTime': l10n.infoRegistrationTime, + 'country': l10n.infoCountry, + 'videoChatHistory': l10n.infoVideoChatHistory, + 'updateTime': l10n.infoUpdateTime, + 'id': l10n.infoId, + 'chatMarker': l10n.infoChatMarker, + }; + + final serverKeys = { + 'account-removal-enabled': l10n.infoAccountRemovalEnabled, + 'image-size': l10n.infoImageSize, + 'gce': l10n.infoGce, + 'gcce': l10n.infoGcce, + 'max-msg-length': l10n.infoMaxMsgLength, + 'quotes-enabled': l10n.infoQuotesEnabled, + 'calls-endpoint': l10n.infoCallsEndpoint, + 'send-location-enabled': l10n.infoSendLocationEnabled, + 'lgce': l10n.infoLgce, + 'wud': l10n.infoWud, + 'video-msg-enabled': l10n.infoVideoMsgEnabled, + 'grse': l10n.infoGrse, + 'edit-timeout': l10n.infoEditTimeout, + 'image-quality': l10n.infoImageQuality, + 'unsafe-files-alert': l10n.infoUnsafeFilesAlert, + 'account-nickname-enabled': l10n.infoAccountNicknameEnabled, + 'mentions_entity_names_limit': l10n.infoMentionsEntityNamesLimit, + 'reactions-enabled': l10n.infoReactionsEnabled, + }; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildSectionTitle(l10n.infoAccountSection, cs), + ...accountKeys.entries.map((e) => _buildRow(e.key, e.value, _formatValue(info[e.key]), cs)), + + const SizedBox(height: 16), + _buildSectionTitle(l10n.infoServerSection, cs), + ...serverKeys.entries.map((e) => _buildRow(e.key, e.value, _formatValue(server?[e.key]), cs)), + + const SizedBox(height: 8), + _buildSectionTitle(l10n.infoYMapSection, cs), + _buildRow('tile', l10n.infoTile, yMap?['tile']?.toString() ?? '-', cs), + _buildRow('geocoder', l10n.infoGeocoder, yMap?['geocoder']?.toString() ?? '-', cs), + _buildRow('static', l10n.infoStatic, yMap?['static']?.toString() ?? '-', cs), + + const SizedBox(height: 8), + _buildSectionTitle(l10n.infoFileUploadTypes, cs), + _buildListRow(server?['file-upload-unsupported-types'] as List?, cs), + + const SizedBox(height: 8), + _buildSectionTitle(l10n.infoWhiteListLinks, cs), + _buildListRow(server?['white-list-links'] as List?, cs), + + const SizedBox(height: 8), + _buildSectionTitle(l10n.infoUserSection, cs), + if (user != null) + ...user.entries + .where((e) => e.value != null) + .map((e) => _buildRow(e.key, e.key, e.value.toString(), cs)), + + const SizedBox(height: 120), + ], + ); + } + + Widget _buildSectionTitle(String title, ColorScheme cs) { + return Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8, left: 4, right: 4), + child: Text( + title, + style: TextStyle( + color: cs.primary, + fontSize: 13, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ); + } + + Widget _buildRow(String key, String label, String value, ColorScheme cs) { + return Container( + margin: const EdgeInsets.only(bottom: 1), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Text( + label, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 3, + child: Text( + value, + style: TextStyle( + color: cs.onSurface, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.end, + ), + ), + ], + ), + ); + } + + Widget _buildListRow(List? items, ColorScheme cs) { + if (items == null || items.isEmpty) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + child: Text('-', style: TextStyle(color: cs.onSurfaceVariant)), + ); + } + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + child: Wrap( + spacing: 8, + runSpacing: 4, + children: items + .map( + (item) => Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + item.toString(), + style: TextStyle(fontSize: 13, color: cs.onSurface), + ), + ), + ) + .toList(), + ), + ); + } + + String _formatValue(dynamic value) { + if (value == null) return '-'; + if (value is Map && value.containsKey('chatMarker')) { + final ts = value['chatMarker'] as int?; + if (ts != null) { + final dt = DateTime.fromMillisecondsSinceEpoch(ts); + return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ' + '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}'; + } + return '-'; + } + if (value is int && value > 1000000000000) { + final dt = DateTime.fromMillisecondsSinceEpoch(value); + return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ' + '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}'; + } + if (value is int && value > 86400) { + final weeks = value ~/ 604800; + final days = (value % 604800) ~/ 86400; + if (weeks > 0) { + return '$weeks ${_w(weeks)} ${days > 0 ? '$days ${_d(days)}' : ''}'.trim(); + } + final h = value ~/ 3600; + final m = (value % 3600) ~/ 60; + if (h > 0) return '${h}h ${m}m'; + return '${m}m'; + } + return value.toString(); + } + + String _w(int n) { + final m = n % 10; + if (m == 1 && n != 11) return 'нед'; + if ((m == 2 || m == 3 || m == 4) && (n < 10 || n > 20)) return 'нед'; + return 'нед'; + } + + String _d(int n) { + final m = n % 10; + if (m == 1 && n != 11) return 'дн'; + if ((m == 2 || m == 3 || m == 4) && (n < 10 || n > 20)) return 'дн'; + return 'дн'; + } +} \ No newline at end of file diff --git a/lib/frontend/screens/profile/security_screen.dart b/lib/frontend/screens/profile/security_screen.dart index 676396e..e65ab62 100644 --- a/lib/frontend/screens/profile/security_screen.dart +++ b/lib/frontend/screens/profile/security_screen.dart @@ -222,6 +222,7 @@ class _SecurityScreenState extends State case 'CONTACTS': return 'Мои контакты'; case 'NONE': + case 'NOBODY': return 'Никто'; default: return value; @@ -481,9 +482,31 @@ class _SecurityScreenState extends State icon: Icons.visibility_off_outlined, label: 'Видеть статус «в сети»', value: _privacyConfig?.hidden == true ? 'Никто' : 'Мои контакты', - isLast: true, + isLast: false, onTap: () => _showHiddenStatusSheet(context, cs), ), + _buildOptionRow( + cs, + icon: Symbols.contact_page, + label: 'Видеть мой номер', + value: _getPrivacyLabel( + _privacyConfig?.phoneNumberPrivacy ?? 'ALL', + ), + isLast: true, + onTap: () => _showOptionSheet( + context, + cs, + title: 'Видеть мой номер', + currentValue: _privacyConfig?.phoneNumberPrivacy ?? 'ALL', + options: const [ + ('ALL', 'Все'), + ('CONTACTS', 'Мои контакты'), + ('NOBODY', 'Никто'), + ], + onSelect: (value) => + _updateSetting('PHONE_NUMBER_PRIVACY', value), + ), + ), ], ], ), diff --git a/lib/frontend/screens/profile/settings_tab.dart b/lib/frontend/screens/profile/settings_tab.dart index 2389838..3804ac8 100644 --- a/lib/frontend/screens/profile/settings_tab.dart +++ b/lib/frontend/screens/profile/settings_tab.dart @@ -8,6 +8,7 @@ import '../../../l10n/app_localizations.dart'; import '../auth/proxy_settings_sheet.dart'; import 'debug_menu_screen.dart'; import 'devices_screen.dart'; +import 'info_screen.dart'; import 'security_screen.dart'; import 'spoof_screen.dart'; @@ -96,15 +97,27 @@ class _SettingsTabState extends State { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: _buildSection( +child: _buildSection( context, cs, - items: const [ - _SettingsItem(icon: Symbols.badge, label: 'Цифровой ID'), - _SettingsItem( + items: [ + const _SettingsItem(icon: Symbols.badge, label: 'Цифровой ID'), + const _SettingsItem( icon: Symbols.language, label: 'Войти в Сферум', ), + _SettingsItem( + icon: Symbols.info, + label: 'Info', + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const InfoScreen(), + ), + ); + }, + ), ], ), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 806d0c5..174459b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -104,5 +104,60 @@ } } }, - "profileMenuSpoof": "Spoofing" + "profileMenuSpoof": "Spoofing", + "infoTitle": "Info", + "infoAccountSection": "Account", + "infoServerSection": "Server", + "infoUserSection": "User", + "infoYMapSection": "Y-Map", + "infoFileUploadTypes": "file-upload-unsupported-types", + "infoWhiteListLinks": "white-list-links", + "infoRegistrationTime": "registrationTime", + "infoCountry": "country", + "infoVideoChatHistory": "videoChatHistory", + "infoUpdateTime": "updateTime", + "infoId": "id", + "infoChatMarker": "chatMarker", + "infoAccountRemovalEnabled": "account-removal-enabled", + "infoImageSize": "image-size", + "infoGce": "gce", + "infoGcce": "gcce", + "infoMaxMsgLength": "max-msg-length", + "infoQuotesEnabled": "quotes-enabled", + "infoCallsEndpoint": "calls-endpoint", + "infoSendLocationEnabled": "send-location-enabled", + "infoLgce": "lgce", + "infoWud": "wud", + "infoVideoMsgEnabled": "video-msg-enabled", + "infoGrse": "grse", + "infoEditTimeout": "edit-timeout", + "infoImageQuality": "image-quality", + "infoUnsafeFilesAlert": "unsafe-files-alert", + "infoAccountNicknameEnabled": "account-nickname-enabled", + "infoMentionsEntityNamesLimit": "mentions_entity_names_limit", + "infoReactionsEnabled": "reactions-enabled", + "infoTile": "tile", + "infoGeocoder": "geocoder", + "infoStatic": "static", + "chatInfoSubscribers": "subscribers:", + "chatInfoInvitedBy": "invited by:", + "chatInfoLink": "link:", + "chatInfoOfficial": "official:", + "chatInfoComments": "comments:", + "chatInfoAplus": "approved by Roskomnadzor:", + "chatInfoSignAdmin": "admin signature:", + "chatInfoLastChanged": "last changed:", + "chatInfoJoinTime": "joined:", + "chatInfoCreated": "created:", + "chatInfoTitle": "Info", + "chatInfoMembers": "members:", + "chatInfoLastSeen": "last seen recently", + "chatInfoHasBots": "has bots:", + "chatInfoBlockedCount": "blocked in group:", + "chatInfoOfficialStatus": "official status:", + "chatInfoLastChanged": "last changed:", + "chatInfoJoined": "joined:", + "chatInfoGroupCreated": "group created:", + "chatInfoGroupOwner": "group owner:", + "chatInfoDialogStarted": "dialog started:" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 5bd5013..15fbdfb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -631,6 +631,330 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Spoofing'** String get profileMenuSpoof; + + /// No description provided for @infoTitle. + /// + /// In en, this message translates to: + /// **'Info'** + String get infoTitle; + + /// No description provided for @infoAccountSection. + /// + /// In en, this message translates to: + /// **'Account'** + String get infoAccountSection; + + /// No description provided for @infoServerSection. + /// + /// In en, this message translates to: + /// **'Server'** + String get infoServerSection; + + /// No description provided for @infoUserSection. + /// + /// In en, this message translates to: + /// **'User'** + String get infoUserSection; + + /// No description provided for @infoYMapSection. + /// + /// In en, this message translates to: + /// **'Y-Map'** + String get infoYMapSection; + + /// No description provided for @infoFileUploadTypes. + /// + /// In en, this message translates to: + /// **'file-upload-unsupported-types'** + String get infoFileUploadTypes; + + /// No description provided for @infoWhiteListLinks. + /// + /// In en, this message translates to: + /// **'white-list-links'** + String get infoWhiteListLinks; + + /// No description provided for @infoRegistrationTime. + /// + /// In en, this message translates to: + /// **'registrationTime'** + String get infoRegistrationTime; + + /// No description provided for @infoCountry. + /// + /// In en, this message translates to: + /// **'country'** + String get infoCountry; + + /// No description provided for @infoVideoChatHistory. + /// + /// In en, this message translates to: + /// **'videoChatHistory'** + String get infoVideoChatHistory; + + /// No description provided for @infoUpdateTime. + /// + /// In en, this message translates to: + /// **'updateTime'** + String get infoUpdateTime; + + /// No description provided for @infoId. + /// + /// In en, this message translates to: + /// **'id'** + String get infoId; + + /// No description provided for @infoChatMarker. + /// + /// In en, this message translates to: + /// **'chatMarker'** + String get infoChatMarker; + + /// No description provided for @infoAccountRemovalEnabled. + /// + /// In en, this message translates to: + /// **'account-removal-enabled'** + String get infoAccountRemovalEnabled; + + /// No description provided for @infoImageSize. + /// + /// In en, this message translates to: + /// **'image-size'** + String get infoImageSize; + + /// No description provided for @infoGce. + /// + /// In en, this message translates to: + /// **'gce'** + String get infoGce; + + /// No description provided for @infoGcce. + /// + /// In en, this message translates to: + /// **'gcce'** + String get infoGcce; + + /// No description provided for @infoMaxMsgLength. + /// + /// In en, this message translates to: + /// **'max-msg-length'** + String get infoMaxMsgLength; + + /// No description provided for @infoQuotesEnabled. + /// + /// In en, this message translates to: + /// **'quotes-enabled'** + String get infoQuotesEnabled; + + /// No description provided for @infoCallsEndpoint. + /// + /// In en, this message translates to: + /// **'calls-endpoint'** + String get infoCallsEndpoint; + + /// No description provided for @infoSendLocationEnabled. + /// + /// In en, this message translates to: + /// **'send-location-enabled'** + String get infoSendLocationEnabled; + + /// No description provided for @infoLgce. + /// + /// In en, this message translates to: + /// **'lgce'** + String get infoLgce; + + /// No description provided for @infoWud. + /// + /// In en, this message translates to: + /// **'wud'** + String get infoWud; + + /// No description provided for @infoVideoMsgEnabled. + /// + /// In en, this message translates to: + /// **'video-msg-enabled'** + String get infoVideoMsgEnabled; + + /// No description provided for @infoGrse. + /// + /// In en, this message translates to: + /// **'grse'** + String get infoGrse; + + /// No description provided for @infoEditTimeout. + /// + /// In en, this message translates to: + /// **'edit-timeout'** + String get infoEditTimeout; + + /// No description provided for @infoImageQuality. + /// + /// In en, this message translates to: + /// **'image-quality'** + String get infoImageQuality; + + /// No description provided for @infoUnsafeFilesAlert. + /// + /// In en, this message translates to: + /// **'unsafe-files-alert'** + String get infoUnsafeFilesAlert; + + /// No description provided for @infoAccountNicknameEnabled. + /// + /// In en, this message translates to: + /// **'account-nickname-enabled'** + String get infoAccountNicknameEnabled; + + /// No description provided for @infoMentionsEntityNamesLimit. + /// + /// In en, this message translates to: + /// **'mentions_entity_names_limit'** + String get infoMentionsEntityNamesLimit; + + /// No description provided for @infoReactionsEnabled. + /// + /// In en, this message translates to: + /// **'reactions-enabled'** + String get infoReactionsEnabled; + + /// No description provided for @infoTile. + /// + /// In en, this message translates to: + /// **'tile'** + String get infoTile; + + /// No description provided for @infoGeocoder. + /// + /// In en, this message translates to: + /// **'geocoder'** + String get infoGeocoder; + + /// No description provided for @infoStatic. + /// + /// In en, this message translates to: + /// **'static'** + String get infoStatic; + + /// No description provided for @chatInfoSubscribers. + /// + /// In en, this message translates to: + /// **'subscribers:'** + String get chatInfoSubscribers; + + /// No description provided for @chatInfoInvitedBy. + /// + /// In en, this message translates to: + /// **'invited by:'** + String get chatInfoInvitedBy; + + /// No description provided for @chatInfoLink. + /// + /// In en, this message translates to: + /// **'link:'** + String get chatInfoLink; + + /// No description provided for @chatInfoOfficial. + /// + /// In en, this message translates to: + /// **'official:'** + String get chatInfoOfficial; + + /// No description provided for @chatInfoComments. + /// + /// In en, this message translates to: + /// **'comments:'** + String get chatInfoComments; + + /// No description provided for @chatInfoAplus. + /// + /// In en, this message translates to: + /// **'approved by Roskomnadzor:'** + String get chatInfoAplus; + + /// No description provided for @chatInfoSignAdmin. + /// + /// In en, this message translates to: + /// **'admin signature:'** + String get chatInfoSignAdmin; + + /// No description provided for @chatInfoLastChanged. + /// + /// In en, this message translates to: + /// **'last changed:'** + String get chatInfoLastChanged; + + /// No description provided for @chatInfoJoinTime. + /// + /// In en, this message translates to: + /// **'joined:'** + String get chatInfoJoinTime; + + /// No description provided for @chatInfoCreated. + /// + /// In en, this message translates to: + /// **'created:'** + String get chatInfoCreated; + + /// No description provided for @chatInfoTitle. + /// + /// In en, this message translates to: + /// **'Info'** + String get chatInfoTitle; + + /// No description provided for @chatInfoMembers. + /// + /// In en, this message translates to: + /// **'members:'** + String get chatInfoMembers; + + /// No description provided for @chatInfoLastSeen. + /// + /// In en, this message translates to: + /// **'last seen recently'** + String get chatInfoLastSeen; + + /// No description provided for @chatInfoHasBots. + /// + /// In en, this message translates to: + /// **'has bots:'** + String get chatInfoHasBots; + + /// No description provided for @chatInfoBlockedCount. + /// + /// In en, this message translates to: + /// **'blocked in group:'** + String get chatInfoBlockedCount; + + /// No description provided for @chatInfoOfficialStatus. + /// + /// In en, this message translates to: + /// **'official status:'** + String get chatInfoOfficialStatus; + + /// No description provided for @chatInfoJoined. + /// + /// In en, this message translates to: + /// **'joined:'** + String get chatInfoJoined; + + /// No description provided for @chatInfoGroupCreated. + /// + /// In en, this message translates to: + /// **'group created:'** + String get chatInfoGroupCreated; + + /// No description provided for @chatInfoGroupOwner. + /// + /// In en, this message translates to: + /// **'group owner:'** + String get chatInfoGroupOwner; + + /// No description provided for @chatInfoDialogStarted. + /// + /// In en, this message translates to: + /// **'dialog started:'** + String get chatInfoDialogStarted; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index bfce511..0320c05 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -289,4 +289,166 @@ class AppLocalizationsEn extends AppLocalizations { @override String get profileMenuSpoof => 'Spoofing'; + + @override + String get infoTitle => 'Info'; + + @override + String get infoAccountSection => 'Account'; + + @override + String get infoServerSection => 'Server'; + + @override + String get infoUserSection => 'User'; + + @override + String get infoYMapSection => 'Y-Map'; + + @override + String get infoFileUploadTypes => 'file-upload-unsupported-types'; + + @override + String get infoWhiteListLinks => 'white-list-links'; + + @override + String get infoRegistrationTime => 'registrationTime'; + + @override + String get infoCountry => 'country'; + + @override + String get infoVideoChatHistory => 'videoChatHistory'; + + @override + String get infoUpdateTime => 'updateTime'; + + @override + String get infoId => 'id'; + + @override + String get infoChatMarker => 'chatMarker'; + + @override + String get infoAccountRemovalEnabled => 'account-removal-enabled'; + + @override + String get infoImageSize => 'image-size'; + + @override + String get infoGce => 'gce'; + + @override + String get infoGcce => 'gcce'; + + @override + String get infoMaxMsgLength => 'max-msg-length'; + + @override + String get infoQuotesEnabled => 'quotes-enabled'; + + @override + String get infoCallsEndpoint => 'calls-endpoint'; + + @override + String get infoSendLocationEnabled => 'send-location-enabled'; + + @override + String get infoLgce => 'lgce'; + + @override + String get infoWud => 'wud'; + + @override + String get infoVideoMsgEnabled => 'video-msg-enabled'; + + @override + String get infoGrse => 'grse'; + + @override + String get infoEditTimeout => 'edit-timeout'; + + @override + String get infoImageQuality => 'image-quality'; + + @override + String get infoUnsafeFilesAlert => 'unsafe-files-alert'; + + @override + String get infoAccountNicknameEnabled => 'account-nickname-enabled'; + + @override + String get infoMentionsEntityNamesLimit => 'mentions_entity_names_limit'; + + @override + String get infoReactionsEnabled => 'reactions-enabled'; + + @override + String get infoTile => 'tile'; + + @override + String get infoGeocoder => 'geocoder'; + + @override + String get infoStatic => 'static'; + + @override + String get chatInfoSubscribers => 'subscribers:'; + + @override + String get chatInfoInvitedBy => 'invited by:'; + + @override + String get chatInfoLink => 'link:'; + + @override + String get chatInfoOfficial => 'official:'; + + @override + String get chatInfoComments => 'comments:'; + + @override + String get chatInfoAplus => 'approved by Roskomnadzor:'; + + @override + String get chatInfoSignAdmin => 'admin signature:'; + + @override + String get chatInfoLastChanged => 'last changed:'; + + @override + String get chatInfoJoinTime => 'joined:'; + + @override + String get chatInfoCreated => 'created:'; + + @override + String get chatInfoTitle => 'Info'; + + @override + String get chatInfoMembers => 'members:'; + + @override + String get chatInfoLastSeen => 'last seen recently'; + + @override + String get chatInfoHasBots => 'has bots:'; + + @override + String get chatInfoBlockedCount => 'blocked in group:'; + + @override + String get chatInfoOfficialStatus => 'official status:'; + + @override + String get chatInfoJoined => 'joined:'; + + @override + String get chatInfoGroupCreated => 'group created:'; + + @override + String get chatInfoGroupOwner => 'group owner:'; + + @override + String get chatInfoDialogStarted => 'dialog started:'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 7a1c8b4..d7780cf 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -291,4 +291,166 @@ class AppLocalizationsRu extends AppLocalizations { @override String get profileMenuSpoof => 'Подмена данных'; + + @override + String get infoTitle => 'Info'; + + @override + String get infoAccountSection => 'Аккаунт'; + + @override + String get infoServerSection => 'Сервер'; + + @override + String get infoUserSection => 'Пользователь'; + + @override + String get infoYMapSection => 'Y-Map'; + + @override + String get infoFileUploadTypes => 'запрещённые типы файлов'; + + @override + String get infoWhiteListLinks => 'безопасные ссылки'; + + @override + String get infoRegistrationTime => 'Дата регистрации:'; + + @override + String get infoCountry => 'Регион аккаунта:'; + + @override + String get infoVideoChatHistory => 'videoChatHistory'; + + @override + String get infoUpdateTime => 'Последнее обновление аватарки:'; + + @override + String get infoId => 'id аккаунта:'; + + @override + String get infoChatMarker => 'chatMarker'; + + @override + String get infoAccountRemovalEnabled => 'Мгновенное удаление аккаунта:'; + + @override + String get infoImageSize => 'image-size'; + + @override + String get infoGce => 'gce'; + + @override + String get infoGcce => 'gcce'; + + @override + String get infoMaxMsgLength => 'макс. длина сообщения:'; + + @override + String get infoQuotesEnabled => 'quotes-enabled'; + + @override + String get infoCallsEndpoint => 'calls-endpoint'; + + @override + String get infoSendLocationEnabled => 'отправка гео.:'; + + @override + String get infoLgce => 'lgce'; + + @override + String get infoWud => 'wud'; + + @override + String get infoVideoMsgEnabled => 'Кружки:'; + + @override + String get infoGrse => 'grse'; + + @override + String get infoEditTimeout => 'Можно редактировать сообщение в течении:'; + + @override + String get infoImageQuality => 'image-quality'; + + @override + String get infoUnsafeFilesAlert => 'unsafe-files-alert'; + + @override + String get infoAccountNicknameEnabled => 'account-nickname-enabled'; + + @override + String get infoMentionsEntityNamesLimit => 'макс. кол-во упоминаний:'; + + @override + String get infoReactionsEnabled => 'reactions-enabled'; + + @override + String get infoTile => 'tile'; + + @override + String get infoGeocoder => 'geocoder'; + + @override + String get infoStatic => 'static'; + + @override + String get chatInfoSubscribers => 'подписчиков:'; + + @override + String get chatInfoInvitedBy => 'Приглашён от:'; + + @override + String get chatInfoLink => 'ссылка:'; + + @override + String get chatInfoOfficial => 'оффициальный:'; + + @override + String get chatInfoComments => 'комментарии:'; + + @override + String get chatInfoAplus => 'подтверждён Роскомнадзором:'; + + @override + String get chatInfoSignAdmin => 'Подпись админов:'; + + @override + String get chatInfoLastChanged => 'последнее изменение:'; + + @override + String get chatInfoJoinTime => 'заход в канал:'; + + @override + String get chatInfoCreated => 'канал создан:'; + + @override + String get chatInfoTitle => 'Информация'; + + @override + String get chatInfoMembers => 'участников:'; + + @override + String get chatInfoLastSeen => 'был(а) недавно'; + + @override + String get chatInfoHasBots => 'Есть боты:'; + + @override + String get chatInfoBlockedCount => 'в ЧС группы:'; + + @override + String get chatInfoOfficialStatus => 'Официальный статус:'; + + @override + String get chatInfoJoined => 'Зашли в:'; + + @override + String get chatInfoGroupCreated => 'Группа создана в:'; + + @override + String get chatInfoGroupOwner => 'Создатель группы:'; + + @override + String get chatInfoDialogStarted => 'ЛС начат в:'; } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 0f2378c..c103607 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -104,5 +104,60 @@ } } }, - "profileMenuSpoof": "Подмена данных" + "profileMenuSpoof": "Подмена данных", + "infoTitle": "Info", + "infoAccountSection": "Аккаунт", + "infoServerSection": "Сервер", + "infoUserSection": "Пользователь", + "infoYMapSection": "Y-Map", + "infoFileUploadTypes": "запрещённые типы файлов", + "infoWhiteListLinks": "безопасные ссылки", + "infoRegistrationTime": "Дата регистрации:", + "infoCountry": "Регион аккаунта:", + "infoVideoChatHistory": "videoChatHistory", + "infoUpdateTime": "Последнее обновление аватарки:", + "infoId": "id аккаунта:", + "infoChatMarker": "chatMarker", + "infoAccountRemovalEnabled": "Мгновенное удаление аккаунта:", + "infoImageSize": "image-size", + "infoGce": "gce", + "infoGcce": "gcce", + "infoMaxMsgLength": "макс. длина сообщения:", + "infoQuotesEnabled": "quotes-enabled", + "infoCallsEndpoint": "calls-endpoint", + "infoSendLocationEnabled": "отправка гео.:", + "infoLgce": "lgce", + "infoWud": "wud", + "infoVideoMsgEnabled": "Кружки:", + "infoGrse": "grse", + "infoEditTimeout": "Можно редактировать сообщение в течении:", + "infoImageQuality": "image-quality", + "infoUnsafeFilesAlert": "unsafe-files-alert", + "infoAccountNicknameEnabled": "account-nickname-enabled", + "infoMentionsEntityNamesLimit": "макс. кол-во упоминаний:", + "infoReactionsEnabled": "reactions-enabled", + "infoTile": "tile", + "infoGeocoder": "geocoder", + "infoStatic": "static", + "chatInfoSubscribers": "подписчиков:", + "chatInfoInvitedBy": "Приглашён от:", + "chatInfoLink": "ссылка:", + "chatInfoOfficial": "оффициальный:", + "chatInfoComments": "комментарии:", + "chatInfoAplus": "подтверждён Роскомнадзором:", + "chatInfoSignAdmin": "Подпись админов:", + "chatInfoLastChanged": "последнее изменение:", + "chatInfoJoinTime": "заход в канал:", + "chatInfoCreated": "канал создан:", + "chatInfoTitle": "Информация", + "chatInfoMembers": "участников:", + "chatInfoLastSeen": "был(а) недавно", + "chatInfoHasBots": "Есть боты:", + "chatInfoBlockedCount": "в ЧС группы:", + "chatInfoOfficialStatus": "Официальный статус:", + "chatInfoLastChanged": "последнее изменение:", + "chatInfoJoined": "Зашли в:", + "chatInfoGroupCreated": "Группа создана в:", + "chatInfoGroupOwner": "Создатель группы:", + "chatInfoDialogStarted": "ЛС начат в:" }