онэмашке
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class AppPranks {
|
||||||
|
static const prefKey = 'dev_pranks';
|
||||||
|
static const bool defaultValue = false;
|
||||||
|
|
||||||
|
static final ValueNotifier<bool> current = ValueNotifier(defaultValue);
|
||||||
|
|
||||||
|
static Future<bool> load() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getBool(prefKey) ?? defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> save(bool value) async {
|
||||||
|
current.value = value;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool(prefKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ import '../../../backend/modules/cloud_storage.dart';
|
|||||||
import '../../../backend/modules/folders.dart';
|
import '../../../backend/modules/folders.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 '../../../main.dart' show accountModule, api, messagesModule;
|
import '../../../main.dart'
|
||||||
|
show accountModule, api, messagesModule, appRouteObserver;
|
||||||
|
|
||||||
class _StoriesScrollPhysics extends BouncingScrollPhysics {
|
class _StoriesScrollPhysics extends BouncingScrollPhysics {
|
||||||
final bool Function() blockPositive;
|
final bool Function() blockPositive;
|
||||||
@@ -73,7 +74,7 @@ class ChatListScreen extends StatefulWidget {
|
|||||||
enum _DeleteKind { personalLike, ownerGroup, blocked }
|
enum _DeleteKind { personalLike, ownerGroup, blocked }
|
||||||
|
|
||||||
class _ChatListScreenState extends State<ChatListScreen>
|
class _ChatListScreenState extends State<ChatListScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin, RouteAware {
|
||||||
String? _selectedFolderId;
|
String? _selectedFolderId;
|
||||||
|
|
||||||
List<ChatFolder> _folders = [];
|
List<ChatFolder> _folders = [];
|
||||||
@@ -103,6 +104,9 @@ class _ChatListScreenState extends State<ChatListScreen>
|
|||||||
bool _isFabOpen = false;
|
bool _isFabOpen = false;
|
||||||
bool _storiesAnimClosing = false;
|
bool _storiesAnimClosing = false;
|
||||||
Timer? _contactRebuildTimer;
|
Timer? _contactRebuildTimer;
|
||||||
|
bool _deferReloads = false;
|
||||||
|
bool _reloadQueued = false;
|
||||||
|
Timer? _settleTimer;
|
||||||
bool get _isSelectionMode => _selectedChats.isNotEmpty;
|
bool get _isSelectionMode => _selectedChats.isNotEmpty;
|
||||||
bool? _foldersListKnown;
|
bool? _foldersListKnown;
|
||||||
|
|
||||||
@@ -447,22 +451,58 @@ class _ChatListScreenState extends State<ChatListScreen>
|
|||||||
_sessionState = state;
|
_sessionState = state;
|
||||||
});
|
});
|
||||||
if (state == SessionState.online) {
|
if (state == SessionState.online) {
|
||||||
_reloadChatsAndFolders();
|
_requestReload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_loginSub = accountModule.loginStatusStream.listen((status) {
|
_loginSub = accountModule.loginStatusStream.listen((status) {
|
||||||
if (status == LoginStatus.success) {
|
if (status == LoginStatus.success) {
|
||||||
_reloadChatsAndFolders();
|
_requestReload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ChatsModule.chatsChanged.addListener(_onChatsChanged);
|
ChatsModule.chatsChanged.addListener(_onChatsChanged);
|
||||||
_reloadChatsAndFolders();
|
_reloadChatsAndFolders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final route = ModalRoute.of(context);
|
||||||
|
if (route is PageRoute) {
|
||||||
|
appRouteObserver.subscribe(this, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPushNext() {
|
||||||
|
_deferReloads = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
_settleTimer?.cancel();
|
||||||
|
_settleTimer = Timer(const Duration(milliseconds: 420), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_deferReloads = false;
|
||||||
|
if (_reloadQueued) {
|
||||||
|
_reloadQueued = false;
|
||||||
|
_reloadChatsAndFolders();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requestReload() {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_deferReloads) {
|
||||||
|
_reloadQueued = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_reloadChatsAndFolders();
|
||||||
|
}
|
||||||
|
|
||||||
void _onChatsChanged() {
|
void _onChatsChanged() {
|
||||||
if (mounted) _reloadChatsAndFolders();
|
_requestReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _reloadChatsAndFolders() async {
|
Future<void> _reloadChatsAndFolders() async {
|
||||||
@@ -934,6 +974,8 @@ class _ChatListScreenState extends State<ChatListScreen>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
appRouteObserver.unsubscribe(this);
|
||||||
|
_settleTimer?.cancel();
|
||||||
ChatsModule.chatsChanged.removeListener(_onChatsChanged);
|
ChatsModule.chatsChanged.removeListener(_onChatsChanged);
|
||||||
_loginSub?.cancel();
|
_loginSub?.cancel();
|
||||||
_stateSub?.cancel();
|
_stateSub?.cancel();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' show File;
|
import 'dart:io' show File;
|
||||||
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@@ -22,8 +23,10 @@ import '../../../core/utils/haptics.dart';
|
|||||||
import '../../../core/config/app_cache_extent.dart';
|
import '../../../core/config/app_cache_extent.dart';
|
||||||
import '../../../core/config/app_message_actions_style.dart';
|
import '../../../core/config/app_message_actions_style.dart';
|
||||||
import '../../../core/config/app_swipe_back_desktop.dart';
|
import '../../../core/config/app_swipe_back_desktop.dart';
|
||||||
|
import '../../../core/config/app_pranks.dart';
|
||||||
import '../../../models/attachment.dart';
|
import '../../../models/attachment.dart';
|
||||||
import '../../widgets/message_bubble.dart';
|
import '../../widgets/message_bubble.dart';
|
||||||
|
import '../../widgets/theme_reveal.dart';
|
||||||
import '../../widgets/message_actions_overlay.dart';
|
import '../../widgets/message_actions_overlay.dart';
|
||||||
import '../../widgets/attachment_panel.dart';
|
import '../../widgets/attachment_panel.dart';
|
||||||
import '../../widgets/swipe_to_pop.dart';
|
import '../../widgets/swipe_to_pop.dart';
|
||||||
@@ -115,6 +118,14 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
int _otherStatus = 0;
|
int _otherStatus = 0;
|
||||||
int? _otherSeenTime;
|
int? _otherSeenTime;
|
||||||
int? _participantsCount;
|
int? _participantsCount;
|
||||||
|
|
||||||
|
bool _prankActive = false;
|
||||||
|
String? _prankBubbleId;
|
||||||
|
final GlobalKey _prankBubbleKey = GlobalKey();
|
||||||
|
final GlobalKey _prankCaptureKey = GlobalKey();
|
||||||
|
OverlayEntry? _prankRevealEntry;
|
||||||
|
AnimationController? _prankRevealController;
|
||||||
|
ui.Image? _prankRevealImage;
|
||||||
final ValueNotifier<String> _headerStatusNotifier = ValueNotifier('');
|
final ValueNotifier<String> _headerStatusNotifier = ValueNotifier('');
|
||||||
final ValueNotifier<int> _otherReadTime = ValueNotifier(0);
|
final ValueNotifier<int> _otherReadTime = ValueNotifier(0);
|
||||||
int _tempIdCounter = 0;
|
int _tempIdCounter = 0;
|
||||||
@@ -413,6 +424,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_typingTimers.clear();
|
_typingTimers.clear();
|
||||||
_headerStatusNotifier.dispose();
|
_headerStatusNotifier.dispose();
|
||||||
_otherReadTime.dispose();
|
_otherReadTime.dispose();
|
||||||
|
_finishPrankReveal();
|
||||||
_uploadStatus.dispose();
|
_uploadStatus.dispose();
|
||||||
_attachAnim.dispose();
|
_attachAnim.dispose();
|
||||||
_messageController.dispose();
|
_messageController.dispose();
|
||||||
@@ -454,6 +466,102 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
if (_otherReadTime.value != t) _otherReadTime.value = t;
|
if (_otherReadTime.value != t) _otherReadTime.value = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _checkPrankTrigger(CachedMessage msg) {
|
||||||
|
if (!AppPranks.current.value || _prankActive || _prankBubbleId != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((msg.text ?? '').trim().toUpperCase() != 'THE WORLD') return;
|
||||||
|
setState(() => _prankBubbleId = msg.id);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) _runPrankReveal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData _prankPinkTheme(ThemeData base) {
|
||||||
|
final cs = base.colorScheme;
|
||||||
|
return base.copyWith(
|
||||||
|
scaffoldBackgroundColor: const Color(0xFFFFF0F5),
|
||||||
|
colorScheme: cs.copyWith(
|
||||||
|
surface: const Color(0xFFFFF0F5),
|
||||||
|
surfaceContainerHigh: const Color(0xFFFFE3EC),
|
||||||
|
surfaceContainerHighest: const Color(0xFFFFD9E6),
|
||||||
|
primary: const Color(0xFFE8579A),
|
||||||
|
primaryContainer: const Color(0xFFFFD6E5),
|
||||||
|
onPrimaryContainer: const Color(0xFF7A1F4B),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _runPrankReveal() {
|
||||||
|
if (_prankActive) return;
|
||||||
|
final overlay = Navigator.of(context).overlay;
|
||||||
|
final captureCtx = _prankCaptureKey.currentContext;
|
||||||
|
final renderObject = captureCtx?.findRenderObject();
|
||||||
|
if (overlay == null || renderObject is! RenderRepaintBoundary) {
|
||||||
|
setState(() => _prankActive = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset center;
|
||||||
|
final bubbleBox =
|
||||||
|
_prankBubbleKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (bubbleBox != null && bubbleBox.attached) {
|
||||||
|
center = bubbleBox.localToGlobal(bubbleBox.size.center(Offset.zero));
|
||||||
|
} else {
|
||||||
|
final size = MediaQuery.sizeOf(context);
|
||||||
|
center = Offset(size.width / 2, size.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ui.Image snapshot;
|
||||||
|
try {
|
||||||
|
final dpr = math.min(MediaQuery.of(context).devicePixelRatio, 2.0);
|
||||||
|
snapshot = renderObject.toImageSync(pixelRatio: dpr);
|
||||||
|
} catch (_) {
|
||||||
|
setState(() => _prankActive = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_finishPrankReveal();
|
||||||
|
|
||||||
|
final controller = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 650),
|
||||||
|
);
|
||||||
|
final entry = ThemeRevealOverlay.build(
|
||||||
|
snapshot: snapshot,
|
||||||
|
center: center,
|
||||||
|
animation: controller,
|
||||||
|
);
|
||||||
|
|
||||||
|
_prankRevealController = controller;
|
||||||
|
_prankRevealEntry = entry;
|
||||||
|
_prankRevealImage = snapshot;
|
||||||
|
|
||||||
|
overlay.insert(entry);
|
||||||
|
setState(() => _prankActive = true);
|
||||||
|
Haptics.success();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.endOfFrame.then((_) {
|
||||||
|
if (_prankRevealController != controller) return;
|
||||||
|
controller.forward().then((_) {
|
||||||
|
if (_prankRevealController != controller) return;
|
||||||
|
_finishPrankReveal();
|
||||||
|
}, onError: (_) {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _finishPrankReveal() {
|
||||||
|
_prankRevealEntry?.remove();
|
||||||
|
_prankRevealEntry = null;
|
||||||
|
_prankRevealController?.dispose();
|
||||||
|
_prankRevealController = null;
|
||||||
|
final img = _prankRevealImage;
|
||||||
|
_prankRevealImage = null;
|
||||||
|
if (img != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => img.dispose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String? _effectiveStatus(CachedMessage msg) {
|
String? _effectiveStatus(CachedMessage msg) {
|
||||||
if (msg.senderId != _myId) return null;
|
if (msg.senderId != _myId) return null;
|
||||||
if (msg.status == 'sending' || msg.status == 'error') return msg.status;
|
if (msg.status == 'sending' || msg.status == 'error') return msg.status;
|
||||||
@@ -486,6 +594,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_clearTyping(message.senderId);
|
_clearTyping(message.senderId);
|
||||||
Haptics.tap();
|
Haptics.tap();
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
|
_checkPrankTrigger(message);
|
||||||
case MessageEditedEvent(:final message):
|
case MessageEditedEvent(:final message):
|
||||||
final idx = _messages.indexWhere((m) => m.id == message.id);
|
final idx = _messages.indexWhere((m) => m.id == message.id);
|
||||||
if (idx == -1) return;
|
if (idx == -1) return;
|
||||||
@@ -615,28 +724,32 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_messages.add(tempMessage);
|
_messages.add(tempMessage);
|
||||||
_messageController.clear();
|
_messageController.clear();
|
||||||
});
|
});
|
||||||
|
unawaited(_persistOutgoing(tempMessage));
|
||||||
|
|
||||||
// Instant tactile "whoosh" the moment the message leaves the composer,
|
// Instant tactile "whoosh" the moment the message leaves the composer,
|
||||||
// not after the network round-trip — feedback must feel immediate.
|
// not after the network round-trip — feedback must feel immediate.
|
||||||
Haptics.send();
|
Haptics.send();
|
||||||
|
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
|
_checkPrankTrigger(tempMessage);
|
||||||
|
|
||||||
final actualId = await messagesModule.sendMessage(_myId, widget.chatId, text);
|
final actualId = await messagesModule.sendMessage(_myId, widget.chatId, text);
|
||||||
|
|
||||||
final index = _messages.indexWhere((m) => m.id == tempId);
|
final index = _messages.indexWhere((m) => m.id == tempId);
|
||||||
if (index != -1 && mounted) {
|
if (index != -1 && mounted) {
|
||||||
|
final sent = CachedMessage(
|
||||||
|
id: actualId.isNotEmpty ? actualId : tempId,
|
||||||
|
accountId: _myId,
|
||||||
|
chatId: widget.chatId,
|
||||||
|
senderId: _myId,
|
||||||
|
text: text,
|
||||||
|
time: now,
|
||||||
|
status: 'sent',
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
_messages[index] = CachedMessage(
|
_messages[index] = sent;
|
||||||
id: actualId.isNotEmpty ? actualId : tempId,
|
|
||||||
accountId: _myId,
|
|
||||||
chatId: widget.chatId,
|
|
||||||
senderId: _myId,
|
|
||||||
text: text,
|
|
||||||
time: now,
|
|
||||||
status: 'sent',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
unawaited(_persistOutgoing(sent, removeId: tempId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat == null) {
|
if (chat == null) {
|
||||||
@@ -652,21 +765,32 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
Haptics.error();
|
Haptics.error();
|
||||||
final index = _messages.indexWhere((m) => m.id == tempId);
|
final index = _messages.indexWhere((m) => m.id == tempId);
|
||||||
if (index != -1 && mounted) {
|
if (index != -1 && mounted) {
|
||||||
|
final failed = CachedMessage(
|
||||||
|
id: tempId,
|
||||||
|
accountId: _myId,
|
||||||
|
chatId: widget.chatId,
|
||||||
|
senderId: _myId,
|
||||||
|
text: text,
|
||||||
|
time: now,
|
||||||
|
status: 'error',
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
_messages[index] = CachedMessage(
|
_messages[index] = failed;
|
||||||
id: tempId,
|
|
||||||
accountId: _myId,
|
|
||||||
chatId: widget.chatId,
|
|
||||||
senderId: _myId,
|
|
||||||
text: text,
|
|
||||||
time: now,
|
|
||||||
status: 'error',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
unawaited(_persistOutgoing(failed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _persistOutgoing(CachedMessage msg, {String? removeId}) async {
|
||||||
|
try {
|
||||||
|
if (removeId != null && removeId != msg.id) {
|
||||||
|
await AppDatabase.deleteMessage(_myId, widget.chatId, removeId);
|
||||||
|
}
|
||||||
|
await AppDatabase.saveMessages([msg.toDbRow()]);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadForwardedSenderNames() async {
|
Future<void> _loadForwardedSenderNames() async {
|
||||||
final forwardIds = <int>{};
|
final forwardIds = <int>{};
|
||||||
for (final msg in _messages) {
|
for (final msg in _messages) {
|
||||||
@@ -879,11 +1003,17 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cs = Theme.of(context).colorScheme;
|
final theme =
|
||||||
|
_prankActive ? _prankPinkTheme(Theme.of(context)) : Theme.of(context);
|
||||||
|
final cs = theme.colorScheme;
|
||||||
|
|
||||||
// TODO: Локализация
|
// TODO: Локализация
|
||||||
// TODO: Cклонения
|
// TODO: Cклонения
|
||||||
return ValueListenableBuilder<bool>(
|
return Theme(
|
||||||
|
data: theme,
|
||||||
|
child: RepaintBoundary(
|
||||||
|
key: _prankCaptureKey,
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
valueListenable: AppSwipeBackDesktop.current,
|
valueListenable: AppSwipeBackDesktop.current,
|
||||||
builder: (context, desktopSwipe, child) => SwipeToPop(
|
builder: (context, desktopSwipe, child) => SwipeToPop(
|
||||||
enabled: widget.embedded && desktopSwipe,
|
enabled: widget.embedded && desktopSwipe,
|
||||||
@@ -1039,6 +1169,8 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1114,10 +1246,13 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
)
|
)
|
||||||
: pressable;
|
: pressable;
|
||||||
|
|
||||||
return RepaintBoundary(
|
final builtItem = RepaintBoundary(
|
||||||
key: ValueKey('msg_${message.id}'),
|
key: ValueKey('msg_${message.id}'),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
return message.id == _prankBubbleId
|
||||||
|
? KeyedSubtree(key: _prankBubbleKey, child: builtItem)
|
||||||
|
: builtItem;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import '../../../backend/modules/chats.dart';
|
import '../../../backend/modules/chats.dart';
|
||||||
import '../../../core/config/app_swipe_back_desktop.dart';
|
import '../../../core/config/app_swipe_back_desktop.dart';
|
||||||
|
import '../../../core/config/app_pranks.dart';
|
||||||
import '../../../core/protocol/opcode_map.dart';
|
import '../../../core/protocol/opcode_map.dart';
|
||||||
import '../../../core/protocol/packet.dart';
|
import '../../../core/protocol/packet.dart';
|
||||||
import '../../../core/utils/logger.dart';
|
import '../../../core/utils/logger.dart';
|
||||||
@@ -398,6 +399,60 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: AppPranks.current,
|
||||||
|
builder: (context, pranksOn, _) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: cs.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 17,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.auto_awesome,
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
size: 22,
|
||||||
|
weight: 400,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Приколь4ики',
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurface,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: pranksOn,
|
||||||
|
onChanged: (v) {
|
||||||
|
AppPranks.save(v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import 'core/config/app_cache_extent.dart';
|
|||||||
import 'core/config/app_fonts.dart';
|
import 'core/config/app_fonts.dart';
|
||||||
import 'core/config/app_message_actions_style.dart';
|
import 'core/config/app_message_actions_style.dart';
|
||||||
import 'core/config/app_swipe_back_desktop.dart';
|
import 'core/config/app_swipe_back_desktop.dart';
|
||||||
|
import 'core/config/app_pranks.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';
|
||||||
import 'backend/modules/account.dart';
|
import 'backend/modules/account.dart';
|
||||||
@@ -43,6 +44,8 @@ final api = Api();
|
|||||||
final accountModule = AccountModule(api);
|
final accountModule = AccountModule(api);
|
||||||
final messagesModule = MessagesModule(api);
|
final messagesModule = MessagesModule(api);
|
||||||
final fileUploader = FileUploader(api: api, messages: messagesModule);
|
final fileUploader = FileUploader(api: api, messages: messagesModule);
|
||||||
|
final RouteObserver<PageRoute<dynamic>> appRouteObserver =
|
||||||
|
RouteObserver<PageRoute<dynamic>>();
|
||||||
|
|
||||||
Future<Locale> _loadInitialLocale() async {
|
Future<Locale> _loadInitialLocale() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -80,6 +83,7 @@ void main() async {
|
|||||||
final themeScheduleFuture = AppThemeSchedule.load();
|
final themeScheduleFuture = AppThemeSchedule.load();
|
||||||
final messageActionsFuture = AppMessageActionsStyle.load();
|
final messageActionsFuture = AppMessageActionsStyle.load();
|
||||||
final swipeBackFuture = AppSwipeBackDesktop.load();
|
final swipeBackFuture = AppSwipeBackDesktop.load();
|
||||||
|
final pranksFuture = AppPranks.load();
|
||||||
|
|
||||||
await api.connect();
|
await api.connect();
|
||||||
|
|
||||||
@@ -111,6 +115,7 @@ void main() async {
|
|||||||
AppThemeSchedule.current.value = await themeScheduleFuture;
|
AppThemeSchedule.current.value = await themeScheduleFuture;
|
||||||
AppMessageActionsStyle.current.value = await messageActionsFuture;
|
AppMessageActionsStyle.current.value = await messageActionsFuture;
|
||||||
AppSwipeBackDesktop.current.value = await swipeBackFuture;
|
AppSwipeBackDesktop.current.value = await swipeBackFuture;
|
||||||
|
AppPranks.current.value = await pranksFuture;
|
||||||
runApp(
|
runApp(
|
||||||
KometApp(
|
KometApp(
|
||||||
initialLocale: initialLocale,
|
initialLocale: initialLocale,
|
||||||
@@ -643,6 +648,7 @@ class KometAppState extends State<KometApp>
|
|||||||
theme: _lightTheme,
|
theme: _lightTheme,
|
||||||
darkTheme: _darkTheme,
|
darkTheme: _darkTheme,
|
||||||
navigatorKey: KometApp.navigatorKey,
|
navigatorKey: KometApp.navigatorKey,
|
||||||
|
navigatorObservers: [appRouteObserver],
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return ValueListenableBuilder<double>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: fontScale,
|
valueListenable: fontScale,
|
||||||
|
|||||||
Reference in New Issue
Block a user