feat: внятный показ обрыва сессии
This commit is contained in:
@@ -9,6 +9,7 @@ import '../../../core/storage/app_database.dart';
|
|||||||
import '../../../core/utils/format.dart';
|
import '../../../core/utils/format.dart';
|
||||||
import '../../../backend/modules/calls.dart';
|
import '../../../backend/modules/calls.dart';
|
||||||
import '../../widgets/komet_avatar.dart';
|
import '../../widgets/komet_avatar.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
class CallsTab extends StatefulWidget {
|
class CallsTab extends StatefulWidget {
|
||||||
const CallsTab({super.key});
|
const CallsTab({super.key});
|
||||||
@@ -260,14 +261,20 @@ class _CallsTabState extends State<CallsTab> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
|
||||||
child: Text(
|
child: Column(
|
||||||
'Звонки',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: TextStyle(
|
children: [
|
||||||
color: cs.onSurface,
|
Text(
|
||||||
fontSize: 24,
|
'Звонки',
|
||||||
fontWeight: FontWeight.w700,
|
style: TextStyle(
|
||||||
fontFamily: 'Outfit',
|
color: cs.onSurface,
|
||||||
),
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontFamily: 'Outfit',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const ConnectionStatusLine(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../../../backend/modules/messages.dart' show ContactCache;
|
|||||||
import '../../../core/cache/info_cache.dart';
|
import '../../../core/cache/info_cache.dart';
|
||||||
import '../../../core/storage/app_database.dart';
|
import '../../../core/storage/app_database.dart';
|
||||||
import '../../../core/utils/format.dart';
|
import '../../../core/utils/format.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/komet_avatar.dart';
|
import '../../widgets/komet_avatar.dart';
|
||||||
|
|
||||||
@@ -190,6 +191,8 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: bg,
|
backgroundColor: bg,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _isLoading ? _buildShimmer(cs) : _buildScrollBody(cs),
|
child: _isLoading ? _buildShimmer(cs) : _buildScrollBody(cs),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import '../../../core/config/komet_settings.dart';
|
|||||||
import '../../../models/attachment.dart';
|
import '../../../models/attachment.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/online_dot.dart';
|
import '../../widgets/online_dot.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/message_bubble.dart';
|
import '../../widgets/message_bubble.dart';
|
||||||
import '../../widgets/theme_reveal.dart';
|
import '../../widgets/theme_reveal.dart';
|
||||||
import '../../widgets/message_actions_overlay.dart';
|
import '../../widgets/message_actions_overlay.dart';
|
||||||
@@ -110,6 +111,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
StreamSubscription<UploadEvent>? _uploadSub;
|
StreamSubscription<UploadEvent>? _uploadSub;
|
||||||
StreamSubscription<Packet>? _pushSub;
|
StreamSubscription<Packet>? _pushSub;
|
||||||
StreamSubscription<MessageEvent>? _messageEventSub;
|
StreamSubscription<MessageEvent>? _messageEventSub;
|
||||||
|
StreamSubscription<SessionState>? _connSub;
|
||||||
final Map<String, ValueNotifier<Map<String, dynamic>?>> _reactionNotifiers =
|
final Map<String, ValueNotifier<Map<String, dynamic>?>> _reactionNotifiers =
|
||||||
{};
|
{};
|
||||||
final Map<String, ValueNotifier<List<double>>> _photoUploadProgress = {};
|
final Map<String, ValueNotifier<List<double>>> _photoUploadProgress = {};
|
||||||
@@ -209,6 +211,10 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_messageEventSub = ChatsModule.messageEvents
|
_messageEventSub = ChatsModule.messageEvents
|
||||||
.where((e) => e.chatId == widget.chatId)
|
.where((e) => e.chatId == widget.chatId)
|
||||||
.listen(_onMessageEvent);
|
.listen(_onMessageEvent);
|
||||||
|
_connSub = api.stateStream.listen((_) {
|
||||||
|
if (mounted) _recomputeHeaderStatus();
|
||||||
|
});
|
||||||
|
debugForceOffline.addListener(_recomputeHeaderStatus);
|
||||||
PresenceFetch.revision.addListener(_onPresenceChanged);
|
PresenceFetch.revision.addListener(_onPresenceChanged);
|
||||||
_floatingDateAnimController = AnimationController(
|
_floatingDateAnimController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -544,6 +550,8 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_uploadSub?.cancel();
|
_uploadSub?.cancel();
|
||||||
_pushSub?.cancel();
|
_pushSub?.cancel();
|
||||||
_messageEventSub?.cancel();
|
_messageEventSub?.cancel();
|
||||||
|
_connSub?.cancel();
|
||||||
|
debugForceOffline.removeListener(_recomputeHeaderStatus);
|
||||||
for (final n in _reactionNotifiers.values) {
|
for (final n in _reactionNotifiers.values) {
|
||||||
n.dispose();
|
n.dispose();
|
||||||
}
|
}
|
||||||
@@ -1360,6 +1368,8 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _headerStatus() {
|
String _headerStatus() {
|
||||||
|
final conn = connectionStatusLabel(api.state);
|
||||||
|
if (conn != null) return conn;
|
||||||
if (_typingUserIds.isNotEmpty) return 'Печатает...';
|
if (_typingUserIds.isNotEmpty) return 'Печатает...';
|
||||||
if (widget.chatType == 'CHAT') {
|
if (widget.chatType == 'CHAT') {
|
||||||
final count = _participantsCount ?? chat?.participants.length ?? 0;
|
final count = _participantsCount ?? chat?.participants.length ?? 0;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../../../core/utils/format.dart';
|
|||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/komet_avatar.dart';
|
import '../../widgets/komet_avatar.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/swipe_route.dart';
|
import '../../widgets/swipe_route.dart';
|
||||||
import '../chats/chat_screen.dart';
|
import '../chats/chat_screen.dart';
|
||||||
|
|
||||||
@@ -127,6 +128,8 @@ class _ContactProfileScreenState extends State<ContactProfileScreen> {
|
|||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: _loading
|
child: _loading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../../../core/storage/app_database.dart';
|
|||||||
import '../../../backend/modules/contacts.dart';
|
import '../../../backend/modules/contacts.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
import '../../widgets/komet_avatar.dart';
|
import '../../widgets/komet_avatar.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/sheet_helpers.dart';
|
import '../../widgets/sheet_helpers.dart';
|
||||||
import 'contact_profile_screen.dart';
|
import 'contact_profile_screen.dart';
|
||||||
|
|
||||||
@@ -168,14 +169,21 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Column(
|
||||||
'Контакты',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: TextStyle(
|
mainAxisSize: MainAxisSize.min,
|
||||||
color: cs.onSurface,
|
children: [
|
||||||
fontSize: 24,
|
Text(
|
||||||
fontWeight: FontWeight.w700,
|
'Контакты',
|
||||||
fontFamily: 'Outfit',
|
style: TextStyle(
|
||||||
),
|
color: cs.onSurface,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontFamily: 'Outfit',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const ConnectionStatusLine(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../../../backend/modules/webapp.dart';
|
|||||||
import '../../../core/utils/webview_support.dart';
|
import '../../../core/utils/webview_support.dart';
|
||||||
import '../../../main.dart' show digitalIdModule;
|
import '../../../main.dart' show digitalIdModule;
|
||||||
import '../../../models/digital_id.dart';
|
import '../../../models/digital_id.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
import '../webapp/web_app_screen.dart';
|
import '../webapp/web_app_screen.dart';
|
||||||
|
|
||||||
@@ -155,6 +156,8 @@ class _DigitalIdScreenState extends State<DigitalIdScreen> {
|
|||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
|
|
||||||
import '../../../backend/modules/webapp.dart';
|
import '../../../backend/modules/webapp.dart';
|
||||||
import '../../../main.dart' show webAppModule, digitalIdModule;
|
import '../../../main.dart' show webAppModule, digitalIdModule;
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/webview_permission_prompt.dart';
|
import '../../widgets/webview_permission_prompt.dart';
|
||||||
|
|
||||||
Future<void> resetDigitalIdWebData() async {
|
Future<void> resetDigitalIdWebData() async {
|
||||||
@@ -218,6 +219,8 @@ class _DigitalIdWebScreenState extends State<DigitalIdWebScreen> {
|
|||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/app_icon.dart';
|
import '../../../core/config/app_icon.dart';
|
||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
@@ -46,7 +47,7 @@ class _AppIconScreenState extends State<AppIconScreen> {
|
|||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(
|
appBar: ConnectionTitleBar(
|
||||||
titleText: 'Иконка приложения',
|
titleText: 'Иконка приложения',
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/app_bubble_behavior.dart';
|
import '../../../core/config/app_bubble_behavior.dart';
|
||||||
import '../../../core/config/app_bubble_shape.dart';
|
import '../../../core/config/app_bubble_shape.dart';
|
||||||
import '../../../core/config/app_pill_gradient.dart';
|
import '../../../core/config/app_pill_gradient.dart';
|
||||||
@@ -86,7 +87,10 @@ class _AppearanceScreenState extends State<AppearanceScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(titleText: 'Внешний вид', backgroundColor: cs.surface),
|
appBar: ConnectionTitleBar(
|
||||||
|
titleText: 'Внешний вид',
|
||||||
|
backgroundColor: cs.surface,
|
||||||
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import '../../../backend/modules/upload_manager.dart';
|
|||||||
import '../../../core/storage/app_database.dart';
|
import '../../../core/storage/app_database.dart';
|
||||||
import '../../../core/utils/format.dart';
|
import '../../../core/utils/format.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/sheet_helpers.dart';
|
import '../../widgets/sheet_helpers.dart';
|
||||||
@@ -315,7 +316,7 @@ class _CloudStorageScreenState extends State<CloudStorageScreen>
|
|||||||
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
||||||
onPressed: _onBack,
|
onPressed: _onBack,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: ConnectionTitleText(
|
||||||
'Облачное хранилище',
|
'Облачное хранилище',
|
||||||
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import 'app_icon_screen.dart';
|
import 'app_icon_screen.dart';
|
||||||
@@ -80,7 +81,7 @@ class CustomizationScreen extends StatelessWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(
|
appBar: ConnectionTitleBar(
|
||||||
titleText: 'Кастомизация',
|
titleText: 'Кастомизация',
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import '../../widgets/sheet_helpers.dart';
|
|||||||
import '../../widgets/login_success_screen.dart';
|
import '../../widgets/login_success_screen.dart';
|
||||||
import '../calls/call_screen.dart';
|
import '../calls/call_screen.dart';
|
||||||
import '../../../core/calls/call_controller.dart';
|
import '../../../core/calls/call_controller.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../digital_id/digital_id_web_screen.dart';
|
import '../digital_id/digital_id_web_screen.dart';
|
||||||
|
|
||||||
class DebugMenuScreen extends StatefulWidget {
|
class DebugMenuScreen extends StatefulWidget {
|
||||||
@@ -205,6 +206,8 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
@@ -368,6 +371,64 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: debugForceOffline,
|
||||||
|
builder: (context, offline, _) {
|
||||||
|
return GlossyPill(
|
||||||
|
color: cs.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
depth: 6,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 17,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.wifi_off,
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
size: 22,
|
||||||
|
weight: 400,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Офлайн (тест)',
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurface,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'Показать индикаторы соединения во всех '
|
||||||
|
'экранах, не разрывая реальную сессию',
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: offline,
|
||||||
|
onChanged: (v) => debugForceOffline.value = v,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import '../../../core/utils/format.dart';
|
|||||||
import '../../../main.dart' show accountModule;
|
import '../../../main.dart' show accountModule;
|
||||||
import '../../../backend/modules/account.dart' show SessionInfo;
|
import '../../../backend/modules/account.dart' show SessionInfo;
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/sheet_helpers.dart';
|
import '../../widgets/sheet_helpers.dart';
|
||||||
import 'web_qr_scan_screen.dart';
|
import 'web_qr_scan_screen.dart';
|
||||||
@@ -327,7 +328,7 @@ class _DevicesScreenState extends State<DevicesScreen>
|
|||||||
icon: const Icon(Symbols.chevron_left, size: 28),
|
icon: const Icon(Symbols.chevron_left, size: 28),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: ConnectionTitleText(
|
||||||
'Устройства',
|
'Устройства',
|
||||||
style: GoogleFonts.outfit(
|
style: GoogleFonts.outfit(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import '../../../core/storage/app_database.dart';
|
|||||||
import '../../../core/utils/image_utils.dart';
|
import '../../../core/utils/image_utils.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import '../../../main.dart' show accountModule, fileUploader, KometApp;
|
import '../../../main.dart' show accountModule, fileUploader, KometApp;
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
|
|
||||||
class EditProfileScreen extends StatefulWidget {
|
class EditProfileScreen extends StatefulWidget {
|
||||||
@@ -170,7 +171,7 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
|
|||||||
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: ConnectionTitleText(
|
||||||
l10n?.editProfileTitle ?? 'Edit Profile',
|
l10n?.editProfileTitle ?? 'Edit Profile',
|
||||||
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import '../../../core/config/app_fonts.dart';
|
import '../../../core/config/app_fonts.dart';
|
||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
|
|
||||||
@@ -139,7 +140,10 @@ class _FontSettingsScreenState extends State<FontSettingsScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(titleText: 'Шрифты', backgroundColor: cs.surface),
|
appBar: ConnectionTitleBar(
|
||||||
|
titleText: 'Шрифты',
|
||||||
|
backgroundColor: cs.surface,
|
||||||
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import '../../../core/storage/app_database.dart';
|
import '../../../core/storage/app_database.dart';
|
||||||
import '../../../core/storage/token_storage.dart';
|
import '../../../core/storage/token_storage.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/section_header.dart';
|
import '../../widgets/section_header.dart';
|
||||||
@@ -60,7 +61,7 @@ class _InfoScreenState extends State<InfoScreen> {
|
|||||||
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
icon: Icon(Symbols.arrow_back, color: cs.onSurface),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: ConnectionTitleText(
|
||||||
l10n?.infoTitle ?? 'Info',
|
l10n?.infoTitle ?? 'Info',
|
||||||
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/komet_settings.dart';
|
import '../../../core/config/komet_settings.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
@@ -16,7 +17,7 @@ class KometSettingsScreen extends StatelessWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(titleText: 'Komet', backgroundColor: cs.surface),
|
appBar: ConnectionTitleBar(titleText: 'Komet', backgroundColor: cs.surface),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/app_message_actions_style.dart';
|
import '../../../core/config/app_message_actions_style.dart';
|
||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
@@ -14,7 +15,7 @@ class MessageActionsScreen extends StatelessWidget {
|
|||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(
|
appBar: ConnectionTitleBar(
|
||||||
titleText: 'Меню действий',
|
titleText: 'Меню действий',
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/section_header.dart';
|
import '../../widgets/section_header.dart';
|
||||||
import '../../widgets/sheet_helpers.dart';
|
import '../../widgets/sheet_helpers.dart';
|
||||||
@@ -89,7 +90,10 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(titleText: 'Уведомления', backgroundColor: cs.surface),
|
appBar: ConnectionTitleBar(
|
||||||
|
titleText: 'Уведомления',
|
||||||
|
backgroundColor: cs.surface,
|
||||||
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/app_cache_extent.dart';
|
import '../../../core/config/app_cache_extent.dart';
|
||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
@@ -91,7 +91,7 @@ class _PerformanceScreenState extends State<PerformanceScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(
|
appBar: ConnectionTitleBar(
|
||||||
titleText: 'Производительность',
|
titleText: 'Производительность',
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../../../backend/modules/account.dart'
|
|||||||
import '../../../core/storage/app_database.dart';
|
import '../../../core/storage/app_database.dart';
|
||||||
import '../../widgets/confirm_dialog.dart';
|
import '../../widgets/confirm_dialog.dart';
|
||||||
import '../../widgets/custom_notification.dart';
|
import '../../widgets/custom_notification.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/sheet_helpers.dart';
|
import '../../widgets/sheet_helpers.dart';
|
||||||
import 'password_entry_screen.dart';
|
import 'password_entry_screen.dart';
|
||||||
@@ -197,7 +198,7 @@ class _SecurityScreenState extends State<SecurityScreen>
|
|||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
ConnectionTitleText(
|
||||||
'Безопасность',
|
'Безопасность',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: cs.onSurface,
|
color: cs.onSurface,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import '../../../core/utils/format.dart';
|
|||||||
import '../../../core/utils/haptics.dart';
|
import '../../../core/utils/haptics.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/glossy_pill.dart';
|
import '../../widgets/glossy_pill.dart';
|
||||||
import '../../widgets/info_action_sheet.dart';
|
import '../../widgets/info_action_sheet.dart';
|
||||||
import '../../widgets/komet_avatar.dart';
|
import '../../widgets/komet_avatar.dart';
|
||||||
@@ -565,6 +566,9 @@ class _SettingsTabState extends State<SettingsTab> {
|
|||||||
),
|
),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: ConnectionStatusLine(textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.edit,
|
Symbols.edit,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import '../../../core/storage/spoofing_service.dart';
|
|||||||
import '../../../core/storage/token_storage.dart';
|
import '../../../core/storage/token_storage.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/info_action_sheet.dart';
|
import '../../widgets/info_action_sheet.dart';
|
||||||
import '../../widgets/section_header.dart';
|
import '../../widgets/section_header.dart';
|
||||||
import '../auth/login_screen.dart';
|
import '../auth/login_screen.dart';
|
||||||
@@ -438,7 +439,10 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(l10n.spoofScreenTitle), centerTitle: true),
|
appBar: AppBar(
|
||||||
|
title: ConnectionTitleText(l10n.spoofScreenTitle),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:m3e_collection/m3e_collection.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
import '../../../core/config/app_amoled.dart';
|
import '../../../core/config/app_amoled.dart';
|
||||||
import '../../../core/config/app_theme_mode.dart';
|
import '../../../core/config/app_theme_mode.dart';
|
||||||
import '../../../core/config/app_theme_schedule.dart';
|
import '../../../core/config/app_theme_schedule.dart';
|
||||||
@@ -18,7 +19,7 @@ class ThemeSettingsScreen extends StatelessWidget {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
appBar: AppBarM3E(titleText: 'Тема', backgroundColor: cs.surface),
|
appBar: ConnectionTitleBar(titleText: 'Тема', backgroundColor: cs.surface),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
|
|
||||||
class WebQrScanScreen extends StatefulWidget {
|
class WebQrScanScreen extends StatefulWidget {
|
||||||
const WebQrScanScreen({super.key});
|
const WebQrScanScreen({super.key});
|
||||||
|
|
||||||
@@ -58,6 +60,8 @@ class _WebQrScanScreenState extends State<WebQrScanScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
import '../../../backend/modules/webapp.dart';
|
import '../../../backend/modules/webapp.dart';
|
||||||
|
import '../../widgets/connection_status.dart';
|
||||||
import '../../widgets/webview_permission_prompt.dart';
|
import '../../widgets/webview_permission_prompt.dart';
|
||||||
|
|
||||||
class WebAppScreen extends StatefulWidget {
|
class WebAppScreen extends StatefulWidget {
|
||||||
@@ -67,6 +68,8 @@ class _WebAppScreenState extends State<WebAppScreen> {
|
|||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||||
|
floatingActionButton: const ConnectionSpinner(),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: cs.surface,
|
backgroundColor: cs.surface,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:m3e_collection/m3e_collection.dart';
|
||||||
|
|
||||||
|
import '../../backend/api.dart';
|
||||||
|
import '../../main.dart' show api;
|
||||||
|
|
||||||
|
final ValueNotifier<bool> debugForceOffline = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
String? connectionStatusLabel(SessionState state) {
|
||||||
|
if (debugForceOffline.value) return 'Ожидание сети...';
|
||||||
|
return switch (state) {
|
||||||
|
SessionState.online => null,
|
||||||
|
SessionState.connecting || SessionState.connected => 'Соединение...',
|
||||||
|
SessionState.disconnected => 'Ожидание сети...',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionStatusBuilder extends StatefulWidget {
|
||||||
|
final Widget Function(BuildContext context, String? label) builder;
|
||||||
|
|
||||||
|
const ConnectionStatusBuilder({super.key, required this.builder});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConnectionStatusBuilder> createState() =>
|
||||||
|
_ConnectionStatusBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConnectionStatusBuilderState extends State<ConnectionStatusBuilder> {
|
||||||
|
late SessionState _state = api.state;
|
||||||
|
StreamSubscription<SessionState>? _sub;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_sub = api.stateStream.listen((s) {
|
||||||
|
if (mounted && s != _state) setState(() => _state = s);
|
||||||
|
});
|
||||||
|
debugForceOffline.addListener(_onOverrideChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOverrideChanged() {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_sub?.cancel();
|
||||||
|
debugForceOffline.removeListener(_onOverrideChanged);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) =>
|
||||||
|
widget.builder(context, connectionStatusLabel(_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionStatusLine extends StatelessWidget {
|
||||||
|
final TextAlign? textAlign;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
|
const ConnectionStatusLine({
|
||||||
|
super.key,
|
||||||
|
this.textAlign,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return ConnectionStatusBuilder(
|
||||||
|
builder: (context, label) => AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 220),
|
||||||
|
switchInCurve: Curves.easeOut,
|
||||||
|
switchOutCurve: Curves.easeIn,
|
||||||
|
transitionBuilder: (child, animation) => SizeTransition(
|
||||||
|
sizeFactor: animation,
|
||||||
|
axisAlignment: -1,
|
||||||
|
child: FadeTransition(opacity: animation, child: child),
|
||||||
|
),
|
||||||
|
child: label == null
|
||||||
|
? const SizedBox(width: double.infinity, key: ValueKey('online'))
|
||||||
|
: Padding(
|
||||||
|
key: const ValueKey('offline'),
|
||||||
|
padding: padding,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
textAlign: textAlign,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant.withValues(alpha: 0.55),
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionTitleText extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final TextStyle? style;
|
||||||
|
final TextAlign? textAlign;
|
||||||
|
|
||||||
|
const ConnectionTitleText(
|
||||||
|
this.title, {
|
||||||
|
super.key,
|
||||||
|
this.style,
|
||||||
|
this.textAlign,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ConnectionStatusBuilder(
|
||||||
|
builder: (context, label) => Text(
|
||||||
|
label ?? title,
|
||||||
|
style: style,
|
||||||
|
textAlign: textAlign,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionTitleBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final String titleText;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
|
||||||
|
const ConnectionTitleBar({
|
||||||
|
super.key,
|
||||||
|
required this.titleText,
|
||||||
|
this.backgroundColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(64);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ConnectionStatusBuilder(
|
||||||
|
builder: (context, label) => AppBarM3E(
|
||||||
|
titleText: label ?? titleText,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionSpinner extends StatelessWidget {
|
||||||
|
const ConnectionSpinner({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return ConnectionStatusBuilder(
|
||||||
|
builder: (context, label) => AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 220),
|
||||||
|
switchInCurve: Curves.easeOutBack,
|
||||||
|
switchOutCurve: Curves.easeIn,
|
||||||
|
transitionBuilder: (child, animation) =>
|
||||||
|
ScaleTransition(scale: animation, child: child),
|
||||||
|
child: label == null
|
||||||
|
? const SizedBox.shrink(key: ValueKey('online'))
|
||||||
|
: Container(
|
||||||
|
key: const ValueKey('offline'),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: cs.surfaceContainerHighest,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.18),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.4,
|
||||||
|
color: cs.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user