feat(frontend): flossy стиль. Выбор градиент/без

This commit is contained in:
Jganenok
2026-06-12 00:13:13 +07:00
parent 213c57e524
commit e4ed1c10e1
26 changed files with 1729 additions and 1153 deletions
+41 -4
View File
@@ -39,6 +39,10 @@ class CallSession {
bool _peerMuted = false; bool _peerMuted = false;
bool _peerVideo = false; bool _peerVideo = false;
bool _mediaConnected = false; bool _mediaConnected = false;
bool _remoteDescSet = false;
bool _ownRemoteStream = false;
final List<RTCIceCandidate> _pendingCandidates = [];
Future<void> _tail = Future.value();
final CallInfo info = CallInfo(); final CallInfo info = CallInfo();
@@ -81,11 +85,15 @@ class CallSession {
info.region = ws2Config.uri.host; info.region = ws2Config.uri.host;
final signaling = Ws2Signaling(ws2Config); final signaling = Ws2Signaling(ws2Config);
_signaling = signaling; _signaling = signaling;
signaling.notifications.listen(_onNotification, onError: (_) => _end()); signaling.notifications.listen(_enqueue, onError: (_) => _end());
signaling.done.then((_) => _end()); signaling.done.then((_) => _end());
await signaling.connect(); await signaling.connect();
} }
void _enqueue(Map<String, dynamic> msg) {
_tail = _tail.then((_) => _onNotification(msg)).catchError((_) {});
}
Future<void> _onNotification(Map<String, dynamic> msg) async { Future<void> _onNotification(Map<String, dynamic> msg) async {
_applyPeerMedia(msg); _applyPeerMedia(msg);
switch (msg['notification']) { switch (msg['notification']) {
@@ -195,7 +203,10 @@ class CallSession {
Future<void> _pushRemoteTrack(MediaStreamTrack track) async { Future<void> _pushRemoteTrack(MediaStreamTrack track) async {
var stream = _remoteStreamRef; var stream = _remoteStreamRef;
stream ??= await createLocalMediaStream('komet_remote'); if (stream == null) {
stream = await createLocalMediaStream('komet_remote');
_ownRemoteStream = true;
}
_remoteStreamRef = stream; _remoteStreamRef = stream;
if (!stream.getTracks().any((t) => t.id == track.id)) { if (!stream.getTracks().any((t) => t.id == track.id)) {
try { try {
@@ -322,6 +333,8 @@ class CallSession {
} }
await pc.setRemoteDescription(RTCSessionDescription(desc, type)); await pc.setRemoteDescription(RTCSessionDescription(desc, type));
_remoteDescSet = true;
await _flushCandidates();
if (type == 'offer') { if (type == 'offer') {
final answer = await pc.createAnswer({}); final answer = await pc.createAnswer({});
@@ -348,11 +361,30 @@ class CallSession {
final candidate = data['candidate']; final candidate = data['candidate'];
if (candidate is Map) { if (candidate is Map) {
_applyRemoteCandidate(candidate['candidate']); _applyRemoteCandidate(candidate['candidate']);
await pc.addCandidate(RTCIceCandidate( final ice = RTCIceCandidate(
candidate['candidate'] as String?, candidate['candidate'] as String?,
candidate['sdpMid'] as String?, candidate['sdpMid'] as String?,
candidate['sdpMLineIndex'] as int?, candidate['sdpMLineIndex'] as int?,
)); );
if (_remoteDescSet) {
try {
await pc.addCandidate(ice);
} catch (_) {}
} else {
_pendingCandidates.add(ice);
}
}
}
Future<void> _flushCandidates() async {
final pc = _pc;
if (pc == null || _pendingCandidates.isEmpty) return;
final pending = List<RTCIceCandidate>.from(_pendingCandidates);
_pendingCandidates.clear();
for (final c in pending) {
try {
await pc.addCandidate(c);
} catch (_) {}
} }
} }
@@ -411,6 +443,11 @@ class CallSession {
} }
await _localStream?.dispose(); await _localStream?.dispose();
await _pc?.close(); await _pc?.close();
if (_ownRemoteStream) {
try {
await _remoteStreamRef?.dispose();
} catch (_) {}
}
await _signaling?.close(); await _signaling?.close();
if (!_state.isClosed) await _state.close(); if (!_state.isClosed) await _state.close();
if (!_remoteStream.isClosed) await _remoteStream.close(); if (!_remoteStream.isClosed) await _remoteStream.close();
+18
View File
@@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppPillGradient {
static const prefKey = 'app_pill_gradient';
static final ValueNotifier<bool> current = ValueNotifier(true);
static Future<bool> load() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(prefKey) ?? true;
}
static Future<void> save(bool value) async {
current.value = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(prefKey, value);
}
}
+26
View File
@@ -0,0 +1,26 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum VisualStyle { materialYou, glossy }
class AppVisualStyle {
static const prefKey = 'app_visual_style';
static final ValueNotifier<VisualStyle> current =
ValueNotifier(VisualStyle.materialYou);
static Future<VisualStyle> load() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(prefKey) == 'glossy'
? VisualStyle.glossy
: VisualStyle.materialYou;
}
static Future<void> save(VisualStyle value) async {
current.value = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
prefKey,
value == VisualStyle.glossy ? 'glossy' : 'materialYou',
);
}
}
+36 -20
View File
@@ -20,6 +20,7 @@ import '../../../core/calls/call_controller.dart';
import '../../../core/calls/call_info.dart'; import '../../../core/calls/call_info.dart';
import '../../../core/calls/call_session.dart'; import '../../../core/calls/call_session.dart';
import '../../../core/utils/format.dart'; import '../../../core/utils/format.dart';
import '../../widgets/glossy_pill.dart';
const Color _kEndRed = Color(0xFFE5484D); const Color _kEndRed = Color(0xFFE5484D);
const Color _kAcceptGreen = Color(0xFF2EC36B); const Color _kAcceptGreen = Color(0xFF2EC36B);
@@ -283,6 +284,11 @@ class _CallScreenState extends State<CallScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = _darkScheme(context); final cs = _darkScheme(context);
final avatar = _buildAvatar(cs);
final name = _buildName(cs);
final status = _buildStatus(cs);
final peerBar = _peerStateBar(cs);
final controls = _buildControls(cs);
return Theme( return Theme(
data: Theme.of(context).copyWith(colorScheme: cs), data: Theme.of(context).copyWith(colorScheme: cs),
@@ -296,16 +302,29 @@ class _CallScreenState extends State<CallScreen>
backgroundColor: cs.surface, backgroundColor: cs.surface,
body: AnimatedBuilder( body: AnimatedBuilder(
animation: _videoController, animation: _videoController,
builder: (context, _) => _buildBody(cs), builder: (context, _) => _buildBody(
cs,
avatar: avatar,
name: name,
status: status,
peerBar: peerBar,
controls: controls,
),
), ),
), ),
), ),
); );
} }
Widget _buildBody(ColorScheme cs) { Widget _buildBody(
ColorScheme cs, {
required Widget avatar,
required Widget name,
required Widget status,
required Widget? peerBar,
required Widget controls,
}) {
final t = Curves.easeInOut.transform(_videoController.value); final t = Curves.easeInOut.transform(_videoController.value);
final peerBar = _peerStateBar(cs);
final showVideo = t > 0.001 && _remoteRenderer.srcObject != null; final showVideo = t > 0.001 && _remoteRenderer.srcObject != null;
return Stack( return Stack(
@@ -351,17 +370,17 @@ class _CallScreenState extends State<CallScreen>
children: [ children: [
_buildTopBar(cs, t), _buildTopBar(cs, t),
const Spacer(flex: 2), const Spacer(flex: 2),
_collapse(t, _buildAvatar(cs)), _collapse(t, avatar),
SizedBox(height: 36 * (1 - t)), SizedBox(height: 36 * (1 - t)),
_collapse(t, _buildName(cs)), _collapse(t, name),
SizedBox(height: 12 * (1 - t)), SizedBox(height: 12 * (1 - t)),
_collapse(t, _buildStatus(cs)), _collapse(t, status),
if (peerBar != null) ...[ if (peerBar != null) ...[
SizedBox(height: 14 * (1 - t)), SizedBox(height: 14 * (1 - t)),
_collapse(t, peerBar), _collapse(t, peerBar),
], ],
const Spacer(flex: 5), const Spacer(flex: 5),
_buildControls(cs), controls,
const SizedBox(height: 24), const SizedBox(height: 24),
], ],
), ),
@@ -479,12 +498,11 @@ class _CallScreenState extends State<CallScreen>
} }
Widget _statePill(ColorScheme cs, IconData icon, String label) { Widget _statePill(ColorScheme cs, IconData icon, String label) {
return Container( return GlossyPill(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(100),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
decoration: BoxDecoration( depth: 5,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(100),
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -763,15 +781,13 @@ class _CallButton extends StatelessWidget {
SizedBox( SizedBox(
width: 62, width: 62,
height: 62, height: 62,
child: Material( child: GlossyPill(
color: background, color: background,
shape: const CircleBorder(), borderRadius: BorderRadius.circular(31),
clipBehavior: Clip.antiAlias, onTap: onTap,
child: InkWell( depth: 9,
onTap: onTap, child: Center(
child: Center( child: Icon(icon, color: foreground, size: 26, fill: 1),
child: Icon(icon, color: foreground, size: 26, fill: 1),
),
), ),
), ),
), ),
@@ -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/glossy_pill.dart';
import '../../widgets/komet_avatar.dart'; import '../../widgets/komet_avatar.dart';
class _MemberInfo { class _MemberInfo {
@@ -331,12 +332,11 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
Widget _actionBtn(ColorScheme cs, IconData icon, String label) { Widget _actionBtn(ColorScheme cs, IconData icon, String label) {
return Expanded( return Expanded(
child: Container( child: GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -400,41 +400,41 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
String value, { String value, {
bool isLink = false, bool isLink = false,
}) { }) {
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), padding: const EdgeInsets.fromLTRB(16, 12, 16, 14),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(14), width: double.infinity,
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( label,
label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
color: isLink ? const Color(0xFF007AFF) : cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
), ),
), const SizedBox(height: 4),
], Text(
value,
style: TextStyle(
color: isLink ? const Color(0xFF007AFF) : cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
), ),
); );
} }
Widget _linkCard(ColorScheme cs, String link) { Widget _linkCard(ColorScheme cs, String link) {
return Container( return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.fromLTRB(16, 12, 8, 14), padding: const EdgeInsets.fromLTRB(16, 12, 8, 14),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -473,38 +473,41 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
const int collapsedLines = 3; const int collapsedLines = 3;
final isLong = desc.length > 120; final isLong = desc.length > 120;
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(14), width: double.infinity,
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( 'Описание',
'Описание', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
const SizedBox(height: 4),
Text(
desc,
style: TextStyle(color: cs.onSurface, fontSize: 15, height: 1.4),
maxLines: (_descExpanded || !isLong) ? null : collapsedLines,
overflow: (_descExpanded || !isLong) ? null : TextOverflow.ellipsis,
),
if (isLong) ...[
const SizedBox(height: 6),
GestureDetector(
onTap: () => setState(() => _descExpanded = !_descExpanded),
child: Text(
_descExpanded ? 'Свернуть' : 'Ещё',
style: const TextStyle(color: Color(0xFF007AFF), fontSize: 13),
),
), ),
const SizedBox(height: 4),
Text(
desc,
style: TextStyle(color: cs.onSurface, fontSize: 15, height: 1.4),
maxLines: (_descExpanded || !isLong) ? null : collapsedLines,
overflow:
(_descExpanded || !isLong) ? null : TextOverflow.ellipsis,
),
if (isLong) ...[
const SizedBox(height: 6),
GestureDetector(
onTap: () => setState(() => _descExpanded = !_descExpanded),
child: Text(
_descExpanded ? 'Свернуть' : 'Ещё',
style:
const TextStyle(color: Color(0xFF007AFF), fontSize: 13),
),
),
],
], ],
], ),
), ),
); );
} }
@@ -668,41 +671,41 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
} }
Widget _buildInfoRowsCard(ColorScheme cs) { Widget _buildInfoRowsCard(ColorScheme cs) {
return Container( return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), padding: const EdgeInsets.fromLTRB(16, 12, 16, 14),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
),
child: _buildAllInfoRows(cs), child: _buildAllInfoRows(cs),
); );
} }
Widget _infoCard(ColorScheme cs, String label, String value) { Widget _infoCard(ColorScheme cs, String label, String value) {
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), padding: const EdgeInsets.fromLTRB(16, 12, 16, 14),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(14), width: double.infinity,
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( label,
label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
), ),
), const SizedBox(height: 4),
], Text(
value,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
), ),
); );
} }
@@ -10,6 +10,7 @@ import 'chat_screen.dart';
import 'create_group_flow.dart'; import 'create_group_flow.dart';
import '../../widgets/adaptive_shell.dart'; import '../../widgets/adaptive_shell.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/sheet_helpers.dart'; import '../../widgets/sheet_helpers.dart';
import '../../widgets/swipe_route.dart'; import '../../widgets/swipe_route.dart';
import '../../widgets/sliding_pill_nav.dart'; import '../../widgets/sliding_pill_nav.dart';
@@ -1228,43 +1229,44 @@ class _ChatListScreenState extends State<ChatListScreen>
), ),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 3, 20, 8), padding: const EdgeInsets.fromLTRB(20, 3, 20, 8),
child: Container( child: GlossyPill(
height: 44, color: cs.surfaceContainerHighest,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(50),
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(50),
),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
), ),
child: Row( depth: 6,
children: [ child: SizedBox(
Icon( height: 44,
Symbols.search, child: Row(
color: cs.outline, children: [
size: 20, Icon(
weight: 400, Symbols.search,
), color: cs.outline,
const SizedBox(width: 10), size: 20,
Expanded( weight: 400,
child: TextField( ),
style: TextStyle( const SizedBox(width: 10),
color: cs.onSurface, Expanded(
fontSize: 15, child: TextField(
), style: TextStyle(
decoration: InputDecoration( color: cs.onSurface,
hintText: 'Поиск',
hintStyle: TextStyle(
color: cs.outline,
fontSize: 15, fontSize: 15,
), ),
border: InputBorder.none, decoration: InputDecoration(
isDense: true, hintText: 'Поиск',
contentPadding: EdgeInsets.zero, hintStyle: TextStyle(
color: cs.outline,
fontSize: 15,
),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
), ),
), ),
), ],
], ),
), ),
), ),
), ),
@@ -1805,18 +1807,25 @@ class _ChatListScreenState extends State<ChatListScreen>
Positioned( Positioned(
right: 20, right: 20,
bottom: bottomInset + 90, bottom: bottomInset + 90,
child: FloatingActionButton( child: GlossyPill(
onPressed: _toggleFab, onTap: _toggleFab,
backgroundColor: cs.primaryContainer, color: cs.primaryContainer,
elevation: 4, borderRadius: BorderRadius.circular(28),
shape: const CircleBorder(), elevated: true,
child: Transform.rotate( depth: 12,
angle: val * (pi / 4), child: SizedBox(
child: Icon( width: 56,
Symbols.add, height: 56,
color: cs.onPrimaryContainer, child: Center(
size: 28, child: Transform.rotate(
weight: 400, angle: val * (pi / 4),
child: Icon(
Symbols.add,
color: cs.onPrimaryContainer,
size: 28,
weight: 400,
),
),
), ),
), ),
), ),
@@ -2025,20 +2034,20 @@ class _ChatListScreenState extends State<ChatListScreen>
); );
} }
}, },
child: Container( child: GlossyPill(
alignment: Alignment.center, color: isSelected ? cs.primaryContainer : cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(50),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( depth: 4,
color: isSelected ? cs.primaryContainer : cs.surfaceContainerHigh, child: Center(
borderRadius: BorderRadius.circular(50), child: Text(
), title,
child: Text( textAlign: TextAlign.center,
title, style: TextStyle(
textAlign: TextAlign.center, color: isSelected ? cs.onPrimaryContainer : cs.primary,
style: TextStyle( fontSize: 13,
color: isSelected ? cs.onPrimaryContainer : cs.primary, fontWeight: FontWeight.w500,
fontSize: 13, ),
fontWeight: FontWeight.w500,
), ),
), ),
), ),
@@ -2372,38 +2381,27 @@ class _ChatListScreenState extends State<ChatListScreen>
Widget _buildFabMenuItem(IconData icon, String title, {VoidCallback? onTap}) { Widget _buildFabMenuItem(IconData icon, String title, {VoidCallback? onTap}) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Container( return SizedBox(
width: 220, width: 220,
decoration: BoxDecoration( child: GlossyPill(
onTap: onTap,
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(100), borderRadius: BorderRadius.circular(100),
boxShadow: [ elevated: true,
BoxShadow( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: Colors.black.withValues(alpha: 0.2), child: Row(
blurRadius: 10, children: [
offset: const Offset(0, 4), Icon(icon, color: cs.onSurface, size: 22),
), const SizedBox(width: 12),
], Text(
), title,
child: InkWell( style: TextStyle(
onTap: onTap, color: cs.onSurface,
borderRadius: BorderRadius.circular(100), fontSize: 14,
child: Padding( fontWeight: FontWeight.w500,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Icon(icon, color: cs.onSurface, size: 22),
const SizedBox(width: 12),
Text(
title,
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.w500,
),
), ),
], ),
), ],
), ),
), ),
); );
+310 -138
View File
@@ -32,7 +32,9 @@ 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 '../../../core/config/app_pranks.dart';
import '../../../core/config/app_visual_style.dart';
import '../../../models/attachment.dart'; import '../../../models/attachment.dart';
import '../../widgets/glossy_pill.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';
@@ -172,6 +174,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
super.initState(); super.initState();
_messageController.addListener(_onTextChanged); _messageController.addListener(_onTextChanged);
_scrollController.addListener(_onScrollForDate); _scrollController.addListener(_onScrollForDate);
AppVisualStyle.current.addListener(_onVisualStyleChanged);
_shimmerController = AnimationController( _shimmerController = AnimationController(
vsync: this, vsync: this,
duration: const Duration(milliseconds: 1500), duration: const Duration(milliseconds: 1500),
@@ -430,6 +433,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
void dispose() { void dispose() {
_messageController.removeListener(_onTextChanged); _messageController.removeListener(_onTextChanged);
_scrollController.removeListener(_onScrollForDate); _scrollController.removeListener(_onScrollForDate);
AppVisualStyle.current.removeListener(_onVisualStyleChanged);
_floatingDateTimer?.cancel(); _floatingDateTimer?.cancel();
_floatingDateCurved.dispose(); _floatingDateCurved.dispose();
_floatingDateAnimController.dispose(); _floatingDateAnimController.dispose();
@@ -660,6 +664,132 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
} catch (_) {} } catch (_) {}
} }
void _onVisualStyleChanged() {
if (mounted) setState(() {});
}
PreferredSizeWidget _materialAppBar(ColorScheme cs) {
return PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatInfoScreen(
chatId: widget.chatId,
name: widget.name,
imageUrl: widget.imageUrl,
chatType: widget.chatType,
),
),
),
child: AppBar(
backgroundColor: cs.surfaceContainerHigh,
foregroundColor: cs.onSurface,
elevation: 0,
surfaceTintColor: Colors.transparent,
iconTheme: IconThemeData(color: cs.onSurface),
leading: IconButton(
icon: Icon(
widget.embedded ? Symbols.close : Symbols.arrow_back,
weight: 400,
),
onPressed: () {
if (widget.embedded) {
widget.onClose?.call();
} else {
Navigator.pop(context);
}
},
),
titleSpacing: 0,
title: Row(
children: [
if (widget.imageUrl.isNotEmpty)
CircleAvatar(
radius: 18,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
),
)
else
CircleAvatar(
radius: 18,
backgroundColor: cs.primaryContainer,
child: Text(
widget.name.isNotEmpty ? widget.name[0].toUpperCase() : '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 12,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
widget.name,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Outfit',
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (chat?.isOfficial ?? false) ...[
const SizedBox(width: 4),
Icon(
Symbols.verified,
color: cs.primary,
size: 16,
weight: 600,
fill: 1,
),
],
],
),
ValueListenableBuilder<String>(
valueListenable: _headerStatusNotifier,
builder: (context, status, _) => Text(
status,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
],
),
actions: [
IconButton(
icon: const Icon(Symbols.call, weight: 400),
onPressed: _startCall,
),
IconButton(
icon: const Icon(Symbols.more_vert, weight: 400),
onPressed: () {},
),
],
),
),
);
}
Future<void> _startCall() async { Future<void> _startCall() async {
if (widget.chatType != 'DIALOG') { if (widget.chatType != 'DIALOG') {
showCustomNotification(context, 'Звонки доступны только в диалогах'); showCustomNotification(context, 'Звонки доступны только в диалогах');
@@ -1127,127 +1257,170 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
), ),
child: Scaffold( child: Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: PreferredSize( appBar: AppVisualStyle.current.value == VisualStyle.glossy
preferredSize: Size.fromHeight(kToolbarHeight), ? AppBar(
child: InkWell( backgroundColor: Colors.transparent,
onTap: () => Navigator.push( surfaceTintColor: Colors.transparent,
context, elevation: 0,
MaterialPageRoute( toolbarHeight: 76,
builder: (context) => ChatInfoScreen( automaticallyImplyLeading: false,
chatId: widget.chatId, titleSpacing: 0,
name: widget.name, title: Padding(
imageUrl: widget.imageUrl, padding: const EdgeInsets.fromLTRB(10, 4, 10, 8),
chatType: widget.chatType, child: Row(
), children: [
), SizedBox(
), width: 56,
child: AppBar( height: 56,
backgroundColor: cs.surfaceContainerHigh, child: GlossyPill(
foregroundColor: cs.onSurface, onTap: () {
elevation: 0, if (widget.embedded) {
surfaceTintColor: Colors.transparent, widget.onClose?.call();
iconTheme: IconThemeData(color: cs.onSurface), } else {
leading: IconButton( Navigator.pop(context);
icon: Icon( }
widget.embedded ? Symbols.close : Symbols.arrow_back, },
weight: 400, child: Center(
), child: Icon(
onPressed: () { widget.embedded
if (widget.embedded) { ? Symbols.close
widget.onClose?.call(); : Symbols.arrow_back,
} else { color: cs.onSurface,
Navigator.pop(context); weight: 500,
} size: 24,
},
),
titleSpacing: 0,
title: Row(
children: [
if (widget.imageUrl.isNotEmpty)
CircleAvatar(
radius: 18,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
), ),
) ),
else ),
CircleAvatar( ),
radius: 18, const SizedBox(width: 8),
backgroundColor: cs.primaryContainer, Expanded(
child: Text( child: GlossyPill(
widget.name.isNotEmpty onTap: () => Navigator.push(
? widget.name[0].toUpperCase() context,
: '?', MaterialPageRoute(
style: TextStyle( builder: (context) => ChatInfoScreen(
color: cs.onPrimaryContainer, chatId: widget.chatId,
fontSize: 12, name: widget.name,
imageUrl: widget.imageUrl,
chatType: widget.chatType,
), ),
), ),
), ),
const SizedBox(width: 12), padding: const EdgeInsets.fromLTRB(6, 6, 16, 6),
Expanded( child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( if (widget.imageUrl.isNotEmpty)
mainAxisSize: MainAxisSize.min, CircleAvatar(
children: [ radius: 22,
Flexible( backgroundImage: CachedNetworkImageProvider(
child: Text( widget.imageUrl,
widget.name, maxWidth: 144,
style: TextStyle( maxHeight: 144,
color: cs.onSurface, ),
fontSize: 16, )
fontWeight: FontWeight.w600, else
fontFamily: 'Outfit', CircleAvatar(
), radius: 22,
maxLines: 1, backgroundColor: cs.primaryContainer,
overflow: TextOverflow.ellipsis, child: Text(
widget.name.isNotEmpty
? widget.name[0].toUpperCase()
: '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Outfit',
), ),
), ),
if (chat?.isOfficial ?? false) ...[ ),
const SizedBox(width: 4), const SizedBox(width: 12),
Icon( Expanded(
Symbols.verified, child: Column(
color: cs.primary, mainAxisSize: MainAxisSize.min,
size: 16, crossAxisAlignment: CrossAxisAlignment.start,
weight: 600, children: [
fill: 1, Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
widget.name,
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.w600,
fontFamily: 'Outfit',
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (chat?.isOfficial ?? false) ...[
const SizedBox(width: 4),
Icon(
Symbols.verified,
color: cs.primary,
size: 16,
weight: 600,
fill: 1,
),
],
],
),
ValueListenableBuilder<String>(
valueListenable: _headerStatusNotifier,
builder: (context, status, _) => Text(
status,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 13,
fontWeight: FontWeight.w400,
),
),
), ),
], ],
],
),
ValueListenableBuilder<String>(
valueListenable: _headerStatusNotifier,
builder: (context, status, _) => Text(
status,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w400,
),
), ),
), ),
], ],
), ),
), ),
],
),
actions: [
IconButton(
icon: const Icon(Symbols.call, weight: 400),
onPressed: _startCall,
), ),
IconButton( const SizedBox(width: 8),
icon: const Icon(Symbols.more_vert, weight: 400), GlossyPill(
onPressed: () {}, padding: const EdgeInsets.symmetric(horizontal: 2),
child: SizedBox(
height: 56,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
Symbols.call,
weight: 500,
color: cs.onSurface,
),
onPressed: _startCall,
),
IconButton(
icon: Icon(
Symbols.more_vert,
weight: 500,
color: cs.onSurface,
),
onPressed: () {},
),
],
),
),
), ),
], ],
), ),
), ),
), )
: _materialAppBar(cs),
body: Column( body: Column(
children: [ children: [
Expanded( Expanded(
@@ -1531,22 +1704,21 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
return SafeArea( return SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
child: GestureDetector( child: GlossyPill(
onTap: () {}, onTap: () {},
child: Container( color: Color.alphaBlend(
cs.surfaceContainerHighest.withValues(alpha: 0.92),
cs.surface,
),
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.symmetric(vertical: 16),
depth: 8,
borderSide: BorderSide(
color: cs.outlineVariant.withValues(alpha: 0.5),
width: 0.5,
),
child: SizedBox(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Color.alphaBlend(
cs.surfaceContainerHighest.withValues(alpha: 0.92),
cs.surface,
),
borderRadius: BorderRadius.circular(28),
border: Border.all(
color: cs.outlineVariant.withValues(alpha: 0.5),
width: 0.5,
),
),
child: Center( child: Center(
child: Text( child: Text(
'Отключить уведомления', 'Отключить уведомления',
@@ -1576,19 +1748,18 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
minHeight: 54, minHeight: 54,
maxHeight: 180, maxHeight: 180,
), ),
decoration: BoxDecoration( child: GlossyPill(
color: Color.alphaBlend( color: Color.alphaBlend(
cs.surfaceContainerHighest.withValues(alpha: 0.92), cs.surfaceContainerHighest.withValues(alpha: 0.92),
cs.surface, cs.surface,
), ),
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
border: Border.all( depth: 8,
borderSide: BorderSide(
color: cs.outlineVariant.withValues(alpha: 0.5), color: cs.outlineVariant.withValues(alpha: 0.5),
width: 0.5, width: 0.5,
), ),
), child: Stack(
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
AnimatedBuilder( AnimatedBuilder(
@@ -1693,6 +1864,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
), ),
], ],
), ),
),
), ),
), ),
AnimatedBuilder( AnimatedBuilder(
@@ -1725,23 +1897,23 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
}, },
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: _hasText, valueListenable: _hasText,
builder: (context, hasText, _) => Container( builder: (context, hasText, _) => GlossyPill(
width: 54, color: hasText
height: 54, ? cs.primary
alignment: Alignment.center, : cs.surfaceContainerHighest,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(27),
color: hasText onTap: hasText ? _sendMessage : null,
? cs.primary depth: 8,
: cs.surfaceContainerHighest, child: SizedBox(
shape: BoxShape.circle, width: 54,
), height: 54,
child: GestureDetector( child: Center(
onTap: hasText ? _sendMessage : null, child: Icon(
child: Icon( hasText ? Symbols.send : Symbols.mic,
hasText ? Symbols.send : Symbols.mic, color: hasText ? cs.onPrimary : cs.onSurface,
color: hasText ? cs.onPrimary : cs.onSurface, size: 24,
size: 24, weight: 400,
weight: 400, ),
), ),
), ),
), ),
@@ -6,6 +6,7 @@ import '../../../core/storage/app_database.dart';
import '../../../core/storage/token_storage.dart'; import '../../../core/storage/token_storage.dart';
import '../../../core/utils/format.dart'; import '../../../core/utils/format.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/komet_avatar.dart'; import '../../widgets/komet_avatar.dart';
import '../../widgets/swipe_route.dart'; import '../../widgets/swipe_route.dart';
import '../chats/chat_screen.dart'; import '../chats/chat_screen.dart';
@@ -215,12 +216,11 @@ class _ContactProfileScreenState extends State<ContactProfileScreen> {
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: actions[i].onTap, onTap: actions[i].onTap,
child: Container( child: GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(14),
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -324,24 +324,25 @@ class _ContactProfileScreenState extends State<ContactProfileScreen> {
if (rows.isEmpty) return const SizedBox.shrink(); if (rows.isEmpty) return const SizedBox.shrink();
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(20),
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column( depth: 6,
children: [ child: SizedBox(
for (var i = 0; i < rows.length; i++) ...[ width: double.infinity,
if (i > 0) child: Column(
Divider( children: [
height: 1, for (var i = 0; i < rows.length; i++) ...[
color: cs.outlineVariant.withValues(alpha: 0.3), if (i > 0)
), Divider(
rows[i], height: 1,
color: cs.outlineVariant.withValues(alpha: 0.3),
),
rows[i],
],
], ],
], ),
), ),
); );
} }
@@ -5,6 +5,7 @@ import 'package:material_symbols_icons/symbols.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';
import '../../widgets/glossy_pill.dart';
class AppIconScreen extends StatefulWidget { class AppIconScreen extends StatefulWidget {
const AppIconScreen({super.key}); const AppIconScreen({super.key});
@@ -55,51 +56,50 @@ class _AppIconScreenState extends State<AppIconScreen> {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120), padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [ children: [
Material( GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 12),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 12), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Внешний вид иконки', 'Внешний вид иконки',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
),
), ),
const SizedBox(height: 4), ),
Text( const SizedBox(height: 4),
AppIconConfig.isSupported Text(
? 'На Android приложение закроется — лаунчер подхватит новую иконку. На iOS — мгновенно с системным диалогом.' AppIconConfig.isSupported
: 'Доступно только на Android и iOS', ? 'На Android приложение закроется — лаунчер подхватит новую иконку. На iOS — мгновенно с системным диалогом.'
style: TextStyle( : 'Доступно только на Android и iOS',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
height: 1.35, fontSize: 13,
), height: 1.35,
), ),
const SizedBox(height: 12), ),
ValueListenableBuilder<AppIcon>( const SizedBox(height: 12),
valueListenable: AppIconConfig.current, ValueListenableBuilder<AppIcon>(
builder: (context, current, _) { valueListenable: AppIconConfig.current,
return Column( builder: (context, current, _) {
children: [ return Column(
for (final icon in AppIcon.values) children: [
_IconTile( for (final icon in AppIcon.values)
icon: icon, _IconTile(
selected: current == icon, icon: icon,
onTap: () => _select(icon), selected: current == icon,
), onTap: () => _select(icon),
], ),
); ],
}, );
), },
], ),
), ],
), ),
), ),
], ],
@@ -6,9 +6,12 @@ import 'package:material_symbols_icons/symbols.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_visual_style.dart';
import '../../../core/utils/bubble_radius.dart'; import '../../../core/utils/bubble_radius.dart';
import '../../../core/utils/haptics.dart'; import '../../../core/utils/haptics.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/glossy_pill.dart';
class AppearanceScreen extends StatefulWidget { class AppearanceScreen extends StatefulWidget {
const AppearanceScreen({super.key}); const AppearanceScreen({super.key});
@@ -83,10 +86,7 @@ class _AppearanceScreenState extends State<AppearanceScreen> {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBarM3E( appBar: AppBarM3E(titleText: 'Внешний вид', backgroundColor: cs.surface),
titleText: 'Внешний вид',
backgroundColor: cs.surface,
),
body: SafeArea( body: SafeArea(
top: false, top: false,
child: ListView( child: ListView(
@@ -107,6 +107,10 @@ class _AppearanceScreenState extends State<AppearanceScreen> {
_BubbleShapeCard(onChanged: _onStyleChanged), _BubbleShapeCard(onChanged: _onStyleChanged),
const SizedBox(height: 12), const SizedBox(height: 12),
_BubbleBehaviorCard(onChanged: _onBehaviorChanged), _BubbleBehaviorCard(onChanged: _onBehaviorChanged),
const SizedBox(height: 12),
const _VisualStyleCard(),
const SizedBox(height: 12),
const _GradientToggleCard(),
], ],
), ),
), ),
@@ -114,6 +118,115 @@ class _AppearanceScreenState extends State<AppearanceScreen> {
} }
} }
class _VisualStyleCard extends StatelessWidget {
const _VisualStyleCard();
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 20),
depth: 6,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Визуал',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
'Material You или объёмные Glossy-капсулы',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
const SizedBox(height: 16),
ValueListenableBuilder<VisualStyle>(
valueListenable: AppVisualStyle.current,
builder: (context, current, _) {
return SegmentedButton<VisualStyle>(
segments: const [
ButtonSegment(
value: VisualStyle.materialYou,
label: Text('Material You'),
),
ButtonSegment(
value: VisualStyle.glossy,
label: Text('Glossy'),
),
],
selected: {current},
onSelectionChanged: (set) {
if (set.isNotEmpty) {
Haptics.selection();
AppVisualStyle.save(set.first);
}
},
);
},
),
],
),
);
}
}
class _GradientToggleCard extends StatelessWidget {
const _GradientToggleCard();
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.fromLTRB(20, 14, 12, 14),
depth: 6,
child: Row(
children: [
Icon(Symbols.blur_on, color: cs.onSurface, size: 24, weight: 500),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Градиент',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
'Объём и блики в Glossy-капсулах',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
],
),
),
ValueListenableBuilder<bool>(
valueListenable: AppPillGradient.current,
builder: (context, value, _) => Switch(
value: value,
onChanged: (v) {
Haptics.selection();
AppPillGradient.save(v);
},
),
),
],
),
);
}
}
class _PreviewSection extends StatefulWidget { class _PreviewSection extends StatefulWidget {
final ValueNotifier<Color> color; final ValueNotifier<Color> color;
final ValueNotifier<bool> isSystem; final ValueNotifier<bool> isSystem;
@@ -162,9 +275,9 @@ class _PreviewSectionState extends State<_PreviewSection> {
valueListenable: widget.color, valueListenable: widget.color,
builder: (context, color, _) { builder: (context, color, _) {
return Theme( return Theme(
data: Theme.of(context).copyWith( data: Theme.of(
colorScheme: _schemeFor(color, brightness), context,
), ).copyWith(colorScheme: _schemeFor(color, brightness)),
child: const _ChatPreview(), child: const _ChatPreview(),
); );
}, },
@@ -204,25 +317,23 @@ class _ChatPreview extends StatelessWidget {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return ListenableBuilder( return ListenableBuilder(
listenable: Listenable.merge( listenable: Listenable.merge([
[AppBubbleShape.current, AppBubbleBehavior.current], AppBubbleShape.current,
), AppBubbleBehavior.current,
]),
builder: (context, _) { builder: (context, _) {
final style = AppBubbleShape.current.value; final style = AppBubbleShape.current.value;
final behavior = AppBubbleBehavior.current.value; final behavior = AppBubbleBehavior.current.value;
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerLow,
color: cs.surfaceContainerLow, borderRadius: BorderRadius.circular(28),
borderRadius: BorderRadius.circular(28),
border: Border.all(color: cs.outlineVariant.withValues(alpha: 0.5)),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
for (var i = 0; i < _messages.length; i++) ...[ for (var i = 0; i < _messages.length; i++) ...[
if (i > 0) if (i > 0) SizedBox(height: _messages[i].isTop ? 8 : 2),
SizedBox(height: _messages[i].isTop ? 8 : 2),
_PreviewBubble( _PreviewBubble(
text: _messages[i].text, text: _messages[i].text,
isMe: _messages[i].isMe, isMe: _messages[i].isMe,
@@ -313,10 +424,10 @@ class _ColorPickerCard extends StatelessWidget {
Widget _buildBody(ColorScheme cs, Color col, bool sys) { Widget _buildBody(ColorScheme cs, Color col, bool sys) {
final swatchColor = sys ? cs.primary : col; final swatchColor = sys ? cs.primary : col;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
clipBehavior: Clip.antiAlias, depth: 6,
child: Column( child: Column(
children: [ children: [
InkWell( InkWell(
@@ -385,10 +496,7 @@ class _ColorPickerCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_HueStripPicker( _HueStripPicker(color: col, onChanged: onColorChanged),
color: col,
onChanged: onColorChanged,
),
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
@@ -402,11 +510,17 @@ class _ColorPickerCard extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Symbols.auto_awesome, size: 18, weight: 500), Icon(
Symbols.auto_awesome,
size: 18,
weight: 500,
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(sys Text(
? 'Системный цвет активен' sys
: 'Сбросить на системный'), ? 'Системный цвет активен'
: 'Сбросить на системный',
),
], ],
), ),
), ),
@@ -431,53 +545,52 @@ class _BubbleShapeCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 20),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 20), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Форма сообщения', 'Форма сообщения',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
),
), ),
const SizedBox(height: 4), ),
Text( const SizedBox(height: 4),
'Скругление углов пузырей', Text(
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), 'Скругление углов пузырей',
), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
const SizedBox(height: 16), ),
ValueListenableBuilder<BubbleStyle>( const SizedBox(height: 16),
valueListenable: AppBubbleShape.current, ValueListenableBuilder<BubbleStyle>(
builder: (context, current, _) { valueListenable: AppBubbleShape.current,
return SegmentedButton<BubbleStyle>( builder: (context, current, _) {
segments: const [ return SegmentedButton<BubbleStyle>(
ButtonSegment( segments: const [
value: BubbleStyle.mobile, ButtonSegment(
label: Text('TG Mobile'), value: BubbleStyle.mobile,
icon: Icon(Symbols.smartphone), label: Text('TG Mobile'),
), icon: Icon(Symbols.smartphone),
ButtonSegment( ),
value: BubbleStyle.desktop, ButtonSegment(
label: Text('TG Desktop'), value: BubbleStyle.desktop,
icon: Icon(Symbols.desktop_windows), label: Text('TG Desktop'),
), icon: Icon(Symbols.desktop_windows),
], ),
selected: {current}, ],
onSelectionChanged: (set) { selected: {current},
if (set.isNotEmpty) onChanged(set.first); onSelectionChanged: (set) {
}, if (set.isNotEmpty) onChanged(set.first);
); },
}, );
), },
], ),
), ],
), ),
); );
} }
@@ -492,53 +605,52 @@ class _BubbleBehaviorCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 20),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 20), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Поведение сообщения', 'Поведение сообщения',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
),
), ),
const SizedBox(height: 4), ),
Text( const SizedBox(height: 4),
'Меняется ли форма пузыря по соседям в группе', Text(
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), 'Меняется ли форма пузыря по соседям в группе',
), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
const SizedBox(height: 16), ),
ValueListenableBuilder<BubbleBehavior>( const SizedBox(height: 16),
valueListenable: AppBubbleBehavior.current, ValueListenableBuilder<BubbleBehavior>(
builder: (context, current, _) { valueListenable: AppBubbleBehavior.current,
return SegmentedButton<BubbleBehavior>( builder: (context, current, _) {
segments: const [ return SegmentedButton<BubbleBehavior>(
ButtonSegment( segments: const [
value: BubbleBehavior.mutable, ButtonSegment(
label: Text('Изменяемая'), value: BubbleBehavior.mutable,
icon: Icon(Symbols.auto_fix), label: Text('Изменяемая'),
), icon: Icon(Symbols.auto_fix),
ButtonSegment( ),
value: BubbleBehavior.immutable, ButtonSegment(
label: Text('Неизменяемая'), value: BubbleBehavior.immutable,
icon: Icon(Symbols.lock), label: Text('Неизменяемая'),
), icon: Icon(Symbols.lock),
], ),
selected: {current}, ],
onSelectionChanged: (set) { selected: {current},
if (set.isNotEmpty) onChanged(set.first); onSelectionChanged: (set) {
}, if (set.isNotEmpty) onChanged(set.first);
); },
}, );
), },
], ),
), ],
), ),
); );
} }
@@ -14,6 +14,7 @@ import '../../../core/storage/app_database.dart';
import '../../../core/utils/format.dart'; import '../../../core/utils/format.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/sheet_helpers.dart'; import '../../widgets/sheet_helpers.dart';
enum _EnvState { loading, notConfigured, ready } enum _EnvState { loading, notConfigured, ready }
@@ -656,16 +657,11 @@ class _CornerAction extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: Container( child: GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: cs.outlineVariant.withValues(alpha: 0.5),
width: 0.5,
),
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -855,15 +851,10 @@ class _CloudFileCard extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: AspectRatio( child: AspectRatio(
aspectRatio: 1.0, aspectRatio: 1.0,
child: Container( child: GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerLow,
color: cs.surfaceContainerLow, borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(16), depth: 6,
border: Border.all(
color: cs.outlineVariant.withValues(alpha: 0.5),
width: 0.5,
),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -3,6 +3,7 @@ import 'package:m3e_collection/m3e_collection.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import '../../../core/utils/haptics.dart'; import '../../../core/utils/haptics.dart';
import '../../widgets/glossy_pill.dart';
import 'app_icon_screen.dart'; import 'app_icon_screen.dart';
import 'appearance_screen.dart'; import 'appearance_screen.dart';
import 'font_settings_screen.dart'; import 'font_settings_screen.dart';
@@ -118,18 +119,16 @@ class _CategoryCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
clipBehavior: Clip.antiAlias, padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 18),
child: InkWell( depth: 6,
onTap: onTap, onTap: onTap,
child: Padding( child: Row(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 18), children: [
child: Row( Container(
children: [ width: 48,
Container(
width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: cs.primaryContainer, color: cs.primaryContainer,
@@ -172,8 +171,6 @@ class _CategoryCard extends StatelessWidget {
Icon(Symbols.chevron_right, color: cs.outline, size: 22), Icon(Symbols.chevron_right, color: cs.outline, size: 22),
], ],
), ),
),
),
); );
} }
} }
@@ -17,6 +17,7 @@ import '../../../core/utils/logger.dart';
import '../../../core/utils/media_cache.dart'; import '../../../core/utils/media_cache.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/sheet_helpers.dart'; 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';
@@ -249,57 +250,54 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
: ValueListenableBuilder<bool>( : ValueListenableBuilder<bool>(
valueListenable: appState.fpsOverlayEnabled, valueListenable: appState.fpsOverlayEnabled,
builder: (context, fpsOn, _) { builder: (context, fpsOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.speed,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.speed, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment:
), CrossAxisAlignment.start,
const SizedBox(width: 16), children: [
Expanded( Text(
child: Column( 'Оверлей FPS',
crossAxisAlignment: style: TextStyle(
CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Оверлей FPS',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Показ текущего фреймрейта поверх интерфейса', Text(
style: TextStyle( 'Показ текущего фреймрейта поверх интерфейса',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: fpsOn, Switch(
onChanged: (v) { value: fpsOn,
appState.setFpsOverlayEnabled(v); onChanged: (v) {
}, appState.setFpsOverlayEnabled(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -314,59 +312,56 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
: ValueListenableBuilder<bool>( : ValueListenableBuilder<bool>(
valueListenable: appState.vpnBypassEnabled, valueListenable: appState.vpnBypassEnabled,
builder: (context, bypassOn, _) { builder: (context, bypassOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.vpn_key_off,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.vpn_key_off, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment:
), CrossAxisAlignment.start,
const SizedBox(width: 16), children: [
Expanded( Text(
child: Column( 'Обход VPN',
crossAxisAlignment: style: TextStyle(
CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Обход VPN',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Если обнаружен VPN (tun-интерфейс), ' Text(
'подключаться напрямую через Wi-Fi или ' 'Если обнаружен VPN (tun-интерфейс), '
'моб. сеть в обход туннеля. Только Android', 'подключаться напрямую через Wi-Fi или '
style: TextStyle( 'моб. сеть в обход туннеля. Только Android',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: bypassOn, Switch(
onChanged: (v) { value: bypassOn,
appState.setVpnBypassEnabled(v); onChanged: (v) {
}, appState.setVpnBypassEnabled(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -381,60 +376,57 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
: ValueListenableBuilder<bool>( : ValueListenableBuilder<bool>(
valueListenable: appState.tlsInsecureEnabled, valueListenable: appState.tlsInsecureEnabled,
builder: (context, insecureOn, _) { builder: (context, insecureOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.gpp_bad,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.gpp_bad, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment:
), CrossAxisAlignment.start,
const SizedBox(width: 16), children: [
Expanded( Text(
child: Column( 'Отключить проверку TLS',
crossAxisAlignment: style: TextStyle(
CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Отключить проверку TLS',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Принимать любой сертификат сервера. ' Text(
'Только для отладки через MitM-прокси — ' 'Принимать любой сертификат сервера. '
'соединение становится уязвимым к ' 'Только для отладки через MitM-прокси — '
'перехвату трафика', 'соединение становится уязвимым к '
style: TextStyle( 'перехвату трафика',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: insecureOn, Switch(
onChanged: (v) { value: insecureOn,
appState.setTlsInsecureEnabled(v); onChanged: (v) {
}, appState.setTlsInsecureEnabled(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -447,58 +439,55 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: AppSwipeBackDesktop.current, valueListenable: AppSwipeBackDesktop.current,
builder: (context, swipeOn, _) { builder: (context, swipeOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.swipe_right,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.swipe_right, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 16), Text(
Expanded( 'Свайп-назад в десктоп-режиме',
child: Column( style: TextStyle(
crossAxisAlignment: CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Свайп-назад в десктоп-режиме',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Включает жест «провести от левого края, чтобы ' Text(
'закрыть» внутри встроенной панели чата на ' 'Включает жест «провести от левого края, чтобы '
'десктопе — для тестирования курсором', 'закрыть» внутри встроенной панели чата на '
style: TextStyle( 'десктопе — для тестирования курсором',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: swipeOn, Switch(
onChanged: (v) { value: swipeOn,
AppSwipeBackDesktop.save(v); onChanged: (v) {
}, AppSwipeBackDesktop.save(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -511,48 +500,45 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: AppPranks.current, valueListenable: AppPranks.current,
builder: (context, pranksOn, _) { builder: (context, pranksOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.auto_awesome,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.auto_awesome, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 16), Text(
Expanded( 'Приколь4ики',
child: Column( style: TextStyle(
crossAxisAlignment: CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Приколь4ики',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
], ),
), ],
), ),
Switch( ),
value: pranksOn, Switch(
onChanged: (v) { value: pranksOn,
AppPranks.save(v); onChanged: (v) {
}, AppPranks.save(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -565,58 +551,55 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: AppDigitalIdNative.current, valueListenable: AppDigitalIdNative.current,
builder: (context, native, _) { builder: (context, native, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.badge,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.badge, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 16), Text(
Expanded( 'Нативный Цифровой ID',
child: Column( style: TextStyle(
crossAxisAlignment: CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Нативный Цифровой ID',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
native Text(
? 'Нативный экран (REST ext-api.max.ru)' native
: 'Оригинальная страница в WebView', ? 'Нативный экран (REST ext-api.max.ru)'
style: TextStyle( : 'Оригинальная страница в WebView',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: native, Switch(
onChanged: (v) { value: native,
AppDigitalIdNative.save(v); onChanged: (v) {
}, AppDigitalIdNative.save(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -689,56 +672,53 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: AppStories.current, valueListenable: AppStories.current,
builder: (context, storiesOn, _) { builder: (context, storiesOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.amp_stories,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.amp_stories, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 16), Text(
Expanded( 'Истории',
child: Column( style: TextStyle(
crossAxisAlignment: CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Истории',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Отображение ленты историй в списке чатов', Text(
style: TextStyle( 'Отображение ленты историй в списке чатов',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: storiesOn, Switch(
onChanged: (v) { value: storiesOn,
AppStories.save(v); onChanged: (v) {
}, AppStories.save(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -751,56 +731,53 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
child: ValueListenableBuilder<bool>( child: ValueListenableBuilder<bool>(
valueListenable: AppLinkPreview.current, valueListenable: AppLinkPreview.current,
builder: (context, linkPreviewOn, _) { builder: (context, linkPreviewOn, _) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 17,
), ),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric( children: [
horizontal: 20, Icon(
vertical: 17, Symbols.link,
), color: cs.onSurfaceVariant,
child: Row( size: 22,
children: [ weight: 400,
Icon( ),
Symbols.link, const SizedBox(width: 16),
color: cs.onSurfaceVariant, Expanded(
size: 22, child: Column(
weight: 400, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 16), Text(
Expanded( 'Предпросмотр ссылок',
child: Column( style: TextStyle(
crossAxisAlignment: CrossAxisAlignment.start, color: cs.onSurface,
children: [ fontSize: 16,
Text( fontWeight: FontWeight.w500,
'Предпросмотр ссылок',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(height: 2), ),
Text( const SizedBox(height: 2),
'Карточки с превью для ссылок в сообщениях', Text(
style: TextStyle( 'Карточки с превью для ссылок в сообщениях',
color: cs.onSurfaceVariant, style: TextStyle(
fontSize: 13, color: cs.onSurfaceVariant,
), fontSize: 13,
), ),
], ),
), ],
), ),
Switch( ),
value: linkPreviewOn, Switch(
onChanged: (v) { value: linkPreviewOn,
AppLinkPreview.save(v); onChanged: (v) {
}, AppLinkPreview.save(v);
), },
], ),
), ],
), ),
); );
}, },
@@ -1007,11 +984,10 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: Container( child: GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -1084,11 +1060,10 @@ class _DebugMenuScreenState extends State<DebugMenuScreen> {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: Container( child: GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -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/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';
@@ -352,14 +353,14 @@ class _DevicesScreenState extends State<DevicesScreen>
} }
Widget _buildPromoCard(BuildContext context, ColorScheme cs) { Widget _buildPromoCard(BuildContext context, ColorScheme cs) {
return Container( return Padding(
margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), child: GlossyPill(
decoration: BoxDecoration(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20),
child: Center( depth: 6,
child: Center(
child: Column( child: Column(
children: [ children: [
Container( Container(
@@ -414,19 +415,20 @@ class _DevicesScreenState extends State<DevicesScreen>
), ),
], ],
), ),
),
), ),
); );
} }
Widget _buildDevicesList(BuildContext context, ColorScheme cs) { Widget _buildDevicesList(BuildContext context, ColorScheme cs) {
return Container( return Padding(
margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 8), child: GlossyPill(
decoration: BoxDecoration(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), padding: const EdgeInsets.symmetric(vertical: 8),
child: Column( depth: 6,
child: Column(
children: [ children: [
if (_isLoading) if (_isLoading)
...List.generate(5, (index) => _buildShimmerItem(cs)) ...List.generate(5, (index) => _buildShimmerItem(cs))
@@ -475,6 +477,7 @@ class _DevicesScreenState extends State<DevicesScreen>
), ),
], ],
], ],
),
), ),
); );
} }
@@ -671,15 +674,16 @@ class _DevicesScreenState extends State<DevicesScreen>
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeOutQuart, curve: Curves.easeOutQuart,
child: isExpanded && details != null child: isExpanded && details != null
? Container( ? Padding(
width: double.infinity, padding: const EdgeInsets.only(top: 12),
margin: const EdgeInsets.only(top: 12), child: GlossyPill(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.onSurface.withValues(alpha: 0.04), color: cs.onSurface.withValues(alpha: 0.04),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), padding: const EdgeInsets.all(12),
child: Stack( depth: 6,
child: SizedBox(
width: double.infinity,
child: Stack(
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -740,6 +744,8 @@ class _DevicesScreenState extends State<DevicesScreen>
), ),
), ),
], ],
),
),
), ),
) )
: const SizedBox(width: double.infinity, height: 0), : const SizedBox(width: double.infinity, height: 0),
@@ -6,6 +6,7 @@ import '../../../core/config/app_fonts.dart';
import '../../../core/utils/haptics.dart'; import '../../../core/utils/haptics.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
class FontSettingsScreen extends StatefulWidget { class FontSettingsScreen extends StatefulWidget {
const FontSettingsScreen({super.key}); const FontSettingsScreen({super.key});
@@ -138,10 +139,7 @@ class _FontSettingsScreenState extends State<FontSettingsScreen> {
return Scaffold( return Scaffold(
backgroundColor: cs.surface, backgroundColor: cs.surface,
appBar: AppBarM3E( appBar: AppBarM3E(titleText: 'Шрифты', backgroundColor: cs.surface),
titleText: 'Шрифты',
backgroundColor: cs.surface,
),
body: SafeArea( body: SafeArea(
top: false, top: false,
child: ListView( child: ListView(
@@ -217,42 +215,44 @@ class _PreviewCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(28), width: double.infinity,
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Text(
Text( 'ПРЕДПРОСМОТР',
'ПРЕДПРОСМОТР', style: TextStyle(
style: TextStyle( color: cs.primary,
color: cs.primary, fontSize: 12,
fontSize: 12, fontWeight: FontWeight.w700,
fontWeight: FontWeight.w700, letterSpacing: 1.2,
letterSpacing: 1.2, ),
), ),
), const SizedBox(height: 16),
const SizedBox(height: 16), Text(
Text( 'Съешь ещё этих мягких булок',
'Съешь ещё этих мягких булок', style: AppFonts.sample(fontId, fontSize: 22).copyWith(
style: AppFonts.sample(fontId, fontSize: 22).copyWith( color: cs.onSurface,
color: cs.onSurface, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600, height: 1.25,
height: 1.25, ),
), ),
), const SizedBox(height: 10),
const SizedBox(height: 10), Text(
Text( 'The quick brown fox 0123',
'The quick brown fox 0123', style: AppFonts.sample(
style: AppFonts.sample(fontId, fontSize: 15).copyWith( fontId,
color: cs.onSurfaceVariant, fontSize: 15,
).copyWith(color: cs.onSurfaceVariant),
), ),
), ],
], ),
), ),
); );
} }
@@ -316,10 +316,7 @@ class _FontOption extends StatelessWidget {
: (font.isSystem ? Symbols.smartphone : Symbols.font_download), : (font.isSystem ? Symbols.smartphone : Symbols.font_download),
fill: selected ? 1 : 0, fill: selected ? 1 : 0,
), ),
label: Text( label: Text(font.label, style: AppFonts.sample(font.id, fontSize: 16)),
font.label,
style: AppFonts.sample(font.id, fontSize: 16),
),
); );
if (onDelete == null) { if (onDelete == null) {
@@ -357,12 +354,11 @@ class _FontSizeControl extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
final isDefault = (scale - AppFonts.defaultScale).abs() < 0.001; final isDefault = (scale - AppFonts.defaultScale).abs() < 0.001;
return Container( return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
padding: const EdgeInsets.fromLTRB(20, 16, 12, 16), padding: const EdgeInsets.fromLTRB(20, 16, 12, 16),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
),
child: Column( child: Column(
children: [ children: [
Row( Row(
@@ -378,9 +374,8 @@ class _FontSizeControl extends StatelessWidget {
value: AppFonts.clampScale(scale), value: AppFonts.clampScale(scale),
min: AppFonts.minScale, min: AppFonts.minScale,
max: AppFonts.maxScale, max: AppFonts.maxScale,
divisions: divisions: ((AppFonts.maxScale - AppFonts.minScale) / 0.05)
((AppFonts.maxScale - AppFonts.minScale) / 0.05) .round(),
.round(),
onChanged: onChanged, onChanged: onChanged,
onChangeEnd: onChangeEnd, onChangeEnd: onChangeEnd,
), ),
+16 -16
View File
@@ -5,6 +5,7 @@ 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/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/section_header.dart'; import '../../widgets/section_header.dart';
class InfoScreen extends StatefulWidget { class InfoScreen extends StatefulWidget {
@@ -170,14 +171,14 @@ class _InfoScreenState extends State<InfoScreen> {
} }
Widget _buildRow(String key, String label, String value, ColorScheme cs) { Widget _buildRow(String key, String label, String value, ColorScheme cs) {
return Container( return Padding(
margin: const EdgeInsets.only(bottom: 1), padding: const EdgeInsets.only(bottom: 1),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), child: GlossyPill(
decoration: BoxDecoration(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row( depth: 6,
child: Row(
children: [ children: [
Expanded( Expanded(
flex: 2, flex: 2,
@@ -204,27 +205,26 @@ class _InfoScreenState extends State<InfoScreen> {
), ),
), ),
], ],
),
), ),
); );
} }
Widget _buildListRow(List? items, ColorScheme cs) { Widget _buildListRow(List? items, ColorScheme cs) {
if (items == null || items.isEmpty) { if (items == null || items.isEmpty) {
return Container( return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
),
child: Text('-', style: TextStyle(color: cs.onSurfaceVariant)), child: Text('-', style: TextStyle(color: cs.onSurfaceVariant)),
); );
} }
return Container( return GlossyPill(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
),
child: Wrap( child: Wrap(
spacing: 8, spacing: 8,
runSpacing: 4, runSpacing: 4,
@@ -4,6 +4,7 @@ import 'package:material_symbols_icons/symbols.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';
class MessageActionsScreen extends StatelessWidget { class MessageActionsScreen extends StatelessWidget {
const MessageActionsScreen({super.key}); const MessageActionsScreen({super.key});
@@ -52,12 +53,12 @@ class _StyleCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 12),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 12), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@@ -97,7 +98,6 @@ class _StyleCard extends StatelessWidget {
), ),
], ],
), ),
),
); );
} }
} }
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart'; import 'package:m3e_collection/m3e_collection.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.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';
@@ -164,11 +165,10 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
} }
Widget _card(ColorScheme cs, List<Widget> children) { Widget _card(ColorScheme cs, List<Widget> children) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column(children: children), child: Column(children: children),
); );
} }
@@ -4,6 +4,7 @@ import '../../../main.dart' show accountModule;
import '../../../backend/modules/account.dart' show TwoFactorDetails; import '../../../backend/modules/account.dart' show TwoFactorDetails;
import '../../../core/storage/app_database.dart'; import '../../../core/storage/app_database.dart';
import '../../widgets/custom_notification.dart'; import '../../widgets/custom_notification.dart';
import '../../widgets/glossy_pill.dart';
class PasswordEntryScreen extends StatefulWidget { class PasswordEntryScreen extends StatefulWidget {
const PasswordEntryScreen({super.key}); const PasswordEntryScreen({super.key});
@@ -185,11 +186,10 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
} }
Widget _buildSetupSection(ColorScheme cs) { Widget _buildSetupSection(ColorScheme cs) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: [ children: [
_buildHeaderTile( _buildHeaderTile(
@@ -217,14 +217,14 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
} }
Widget _buildPasswordGate(ColorScheme cs) { Widget _buildPasswordGate(ColorScheme cs) {
return Container( return GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(20),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(20), width: double.infinity,
), child: Column(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
@@ -296,20 +296,21 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
), ),
], ],
), ),
),
); );
} }
Widget _buildManageSection(ColorScheme cs) { Widget _buildManageSection(ColorScheme cs) {
return Column( return Column(
children: [ children: [
Container( GlossyPill(
width: double.infinity, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(20),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( depth: 6,
color: cs.surfaceContainerHigh, child: SizedBox(
borderRadius: BorderRadius.circular(20), width: double.infinity,
), child: Row(
child: Row(
children: [ children: [
Container( Container(
width: 48, width: 48,
@@ -360,13 +361,13 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
), ),
], ],
), ),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: [ children: [
_buildActionRow( _buildActionRow(
@@ -4,6 +4,7 @@ import 'package:m3e_collection/m3e_collection.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';
import '../../widgets/confirm_dialog.dart'; import '../../widgets/confirm_dialog.dart';
import '../../widgets/glossy_pill.dart';
class PerformanceScreen extends StatefulWidget { class PerformanceScreen extends StatefulWidget {
const PerformanceScreen({super.key}); const PerformanceScreen({super.key});
@@ -100,55 +101,54 @@ class _PerformanceScreenState extends State<PerformanceScreen> {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120), padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [ children: [
Material( GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 20),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 20), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Кеш сообщений', 'Кеш сообщений',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
'Сколько пикселей сообщений держать построенными за пределами видимой области.',
style: TextStyle(color: hint, fontSize: 13, height: 1.3),
),
const SizedBox(height: 18),
Text(
'Текущий cacheExtent: ${_value.round()}',
style: TextStyle(color: hint, fontSize: 12),
),
const SizedBox(height: 4),
Slider(
value: _value,
min: AppCacheExtent.min,
max: AppCacheExtent.max,
onChanged: _onChanged,
onChangeEnd: _onChangeEnd,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Меньше потребление',
style: TextStyle(color: hint, fontSize: 11),
), ),
), Text(
const SizedBox(height: 4), 'Больше FPS',
Text( style: TextStyle(color: hint, fontSize: 11),
'Сколько пикселей сообщений держать построенными за пределами видимой области.', ),
style: TextStyle(color: hint, fontSize: 13, height: 1.3), ],
), ),
const SizedBox(height: 18), ],
Text(
'Текущий cacheExtent: ${_value.round()}',
style: TextStyle(color: hint, fontSize: 12),
),
const SizedBox(height: 4),
Slider(
value: _value,
min: AppCacheExtent.min,
max: AppCacheExtent.max,
onChanged: _onChanged,
onChangeEnd: _onChangeEnd,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Меньше потребление',
style: TextStyle(color: hint, fontSize: 11),
),
Text(
'Больше FPS',
style: TextStyle(color: hint, fontSize: 11),
),
],
),
],
),
), ),
), ),
], ],
@@ -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/glossy_pill.dart';
import '../../widgets/sheet_helpers.dart'; import '../../widgets/sheet_helpers.dart';
import 'password_entry_screen.dart'; import 'password_entry_screen.dart';
@@ -238,11 +239,10 @@ class _SecurityScreenState extends State<SecurityScreen>
} }
Widget _buildTopSection(ColorScheme cs) { Widget _buildTopSection(ColorScheme cs) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: [ children: [
_buildPasswordRow(cs), _buildPasswordRow(cs),
@@ -336,11 +336,10 @@ class _SecurityScreenState extends State<SecurityScreen>
Widget _buildPrivacySettings(ColorScheme cs) { Widget _buildPrivacySettings(ColorScheme cs) {
final isSafeMode = _privacyConfig?.safeMode ?? false; final isSafeMode = _privacyConfig?.safeMode ?? false;
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@@ -725,11 +724,10 @@ class _SecurityScreenState extends State<SecurityScreen>
} }
Widget _buildConfidentialSection(ColorScheme cs) { Widget _buildConfidentialSection(ColorScheme cs) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: [ children: [
_buildSwitchRow( _buildSwitchRow(
@@ -771,11 +769,10 @@ class _SecurityScreenState extends State<SecurityScreen>
Widget _buildBlacklistSection(ColorScheme cs) { Widget _buildBlacklistSection(ColorScheme cs) {
final count = _blockedContacts.length; final count = _blockedContacts.length;
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@@ -10,6 +10,7 @@ import '../../../core/storage/token_storage.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/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';
import '../../widgets/sheet_helpers.dart'; import '../../widgets/sheet_helpers.dart';
@@ -631,11 +632,10 @@ class _SettingsTabState extends State<SettingsTab> {
ColorScheme cs, { ColorScheme cs, {
required List<_SettingsItem> items, required List<_SettingsItem> items,
}) { }) {
return Container( return GlossyPill(
decoration: BoxDecoration( color: cs.surfaceContainerHigh,
color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20), depth: 6,
),
child: Column( child: Column(
children: List.generate(items.length, (index) { children: List.generate(items.length, (index) {
final item = items[index]; final item = items[index];
@@ -7,6 +7,7 @@ import '../../../core/config/app_theme_mode.dart';
import '../../../core/config/app_theme_schedule.dart'; import '../../../core/config/app_theme_schedule.dart';
import '../../../core/utils/haptics.dart'; import '../../../core/utils/haptics.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/glossy_pill.dart';
class ThemeSettingsScreen extends StatelessWidget { class ThemeSettingsScreen extends StatelessWidget {
const ThemeSettingsScreen({super.key}); const ThemeSettingsScreen({super.key});
@@ -49,16 +50,16 @@ class _ThemeModeCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 12),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 12), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Режим темы', 'Режим темы',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
@@ -94,7 +95,6 @@ class _ThemeModeCard extends StatelessWidget {
), ),
], ],
), ),
),
); );
} }
} }
@@ -176,15 +176,15 @@ class _AmoledCardState extends State<_AmoledCard> {
return Listener( return Listener(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onPointerDown: (e) => _lastPointerPosition = e.position, onPointerDown: (e) => _lastPointerPosition = e.position,
child: Material( child: GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 14, 12, 14),
padding: const EdgeInsets.fromLTRB(20, 14, 12, 14), depth: 6,
child: Row( child: Row(
children: [ children: [
Icon( Icon(
Symbols.contrast, Symbols.contrast,
color: cs.onSurface, color: cs.onSurface,
size: 24, size: 24,
weight: 500, weight: 500,
@@ -231,7 +231,6 @@ class _AmoledCardState extends State<_AmoledCard> {
], ],
), ),
), ),
),
); );
} }
} }
@@ -249,16 +248,16 @@ class _ScheduleCard extends StatelessWidget {
return AnimatedOpacity( return AnimatedOpacity(
opacity: enabled ? 1 : 0.5, opacity: enabled ? 1 : 0.5,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Material( child: GlossyPill(
color: cs.surfaceContainerHigh, color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
child: Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 20),
padding: const EdgeInsets.fromLTRB(20, 18, 20, 20), depth: 6,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Расписание', 'Расписание',
style: TextStyle( style: TextStyle(
color: cs.onSurface, color: cs.onSurface,
fontSize: 16, fontSize: 16,
@@ -313,7 +312,6 @@ class _ScheduleCard extends StatelessWidget {
), ),
], ],
), ),
),
), ),
); );
}, },
@@ -339,17 +337,15 @@ class _TimeRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Material( return GlossyPill(
color: cs.surfaceContainerHighest, color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: InkWell( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
borderRadius: BorderRadius.circular(16), depth: 6,
onTap: enabled ? () => _pick(context) : null, onTap: enabled ? () => _pick(context) : null,
child: Padding( child: Row(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), children: [
child: Row( Icon(icon, color: cs.onSurface, size: 22, weight: 500),
children: [
Icon(icon, color: cs.onSurface, size: 22, weight: 500),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
@@ -372,8 +368,6 @@ class _TimeRow extends StatelessWidget {
), ),
], ],
), ),
),
),
); );
} }
+189
View File
@@ -0,0 +1,189 @@
import 'package:flutter/material.dart';
import '../../core/config/app_pill_gradient.dart';
import '../../core/config/app_visual_style.dart';
class _GlossyParts {
final bool dark;
final Gradient fill;
final Border rim;
final Gradient topSheen;
final Gradient bottomShade;
const _GlossyParts({
required this.dark,
required this.fill,
required this.rim,
required this.topSheen,
required this.bottomShade,
});
}
class GlossyDecor {
static final Map<Color, _GlossyParts> _cache = {};
static _GlossyParts _parts(Color base) {
final cached = _cache[base];
if (cached != null) return cached;
if (_cache.length > 64) _cache.clear();
final hsl = HSLColor.fromColor(base);
final dark = hsl.lightness < 0.5;
Color shift(double d) =>
hsl.withLightness((hsl.lightness + d).clamp(0.0, 1.0)).toColor();
final parts = _GlossyParts(
dark: dark,
fill: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [shift(dark ? 0.06 : 0.05), base, shift(dark ? -0.05 : -0.07)],
stops: const [0.0, 0.5, 1.0],
),
rim: Border.all(
color: Colors.white.withValues(alpha: dark ? 0.08 : 0.6),
width: 0.8,
),
topSheen: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.center,
colors: [
Colors.white.withValues(alpha: dark ? 0.11 : 0.45),
Colors.white.withValues(alpha: 0.0),
],
),
bottomShade: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.center,
colors: [
Colors.black.withValues(alpha: dark ? 0.2 : 0.07),
Colors.black.withValues(alpha: 0.0),
],
),
);
_cache[base] = parts;
return parts;
}
static bool isDark(Color base) => _parts(base).dark;
static Gradient fillGradient(Color base) => _parts(base).fill;
static Border rimBorder(Color base) => _parts(base).rim;
static Gradient topSheen(Color base) => _parts(base).topSheen;
static Gradient bottomShade(Color base) => _parts(base).bottomShade;
static BoxShadow dropShadow(Color base, double depth) {
final dark = _parts(base).dark;
return BoxShadow(
color: Colors.black.withValues(alpha: dark ? 0.5 : 0.22),
blurRadius: depth * 1.6,
spreadRadius: -depth * 0.3,
offset: Offset(0, depth * 0.6),
);
}
}
class GlossyPill extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final BorderRadius borderRadius;
final Color? color;
final VoidCallback? onTap;
final double depth;
final bool elevated;
final BorderSide? borderSide;
const GlossyPill({
super.key,
required this.child,
this.padding = EdgeInsets.zero,
BorderRadius? borderRadius,
this.color,
this.onTap,
this.depth = 10,
this.elevated = false,
this.borderSide,
}) : borderRadius =
borderRadius ?? const BorderRadius.all(Radius.circular(100));
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<VisualStyle>(
valueListenable: AppVisualStyle.current,
builder: (context, style, _) {
if (style == VisualStyle.materialYou) return _flat(context);
return ValueListenableBuilder<bool>(
valueListenable: AppPillGradient.current,
builder: (context, gradient, _) => _glossy(context, gradient),
);
},
);
}
Widget _flat(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final base = color ?? cs.surfaceContainerHigh;
final content = Padding(padding: padding, child: child);
return Material(
color: base,
elevation: elevated ? 3 : 0,
shadowColor: Colors.black.withValues(alpha: 0.4),
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: borderRadius,
side: borderSide ?? BorderSide.none,
),
clipBehavior: Clip.antiAlias,
child: onTap == null ? content : InkWell(onTap: onTap, child: content),
);
}
Widget _glossy(BuildContext context, bool gradient) {
final cs = Theme.of(context).colorScheme;
final base = color ?? cs.surfaceContainerHigh;
final content = Padding(padding: padding, child: child);
return RepaintBoundary(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius,
color: gradient ? null : base,
gradient: gradient ? GlossyDecor.fillGradient(base) : null,
border: GlossyDecor.rimBorder(base),
boxShadow: [GlossyDecor.dropShadow(base, depth)],
),
child: ClipRRect(
borderRadius: borderRadius,
child: Stack(
fit: StackFit.passthrough,
children: [
if (gradient) ...[
Positioned.fill(
child: IgnorePointer(
child: DecoratedBox(
decoration:
BoxDecoration(gradient: GlossyDecor.topSheen(base)),
),
),
),
Positioned.fill(
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: GlossyDecor.bottomShade(base),
),
),
),
),
],
if (onTap == null)
content
else
Material(
type: MaterialType.transparency,
child: InkWell(onTap: onTap, child: content),
),
],
),
),
),
);
}
}
+46 -4
View File
@@ -1,5 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../core/config/app_pill_gradient.dart';
import '../../core/config/app_visual_style.dart';
import 'glossy_pill.dart';
class PillNavItem { class PillNavItem {
final IconData icon; final IconData icon;
final String label; final String label;
@@ -74,17 +78,43 @@ class SlidingPillNav extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<VisualStyle>(
valueListenable: AppVisualStyle.current,
builder: (context, style, _) {
if (style != VisualStyle.glossy) {
return _buildNav(context, glossy: false, gradient: false);
}
return ValueListenableBuilder<bool>(
valueListenable: AppPillGradient.current,
builder: (context, gradient, _) =>
_buildNav(context, glossy: true, gradient: gradient),
);
},
);
}
Widget _buildNav(
BuildContext context, {
required bool glossy,
required bool gradient,
}) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
final visualSel = position.round().clamp(0, items.length - 1); final visualSel = position.round().clamp(0, items.length - 1);
final base = backgroundColor ?? cs.surfaceContainerHigh;
final useGradient = glossy && gradient;
return Container( return Container(
height: height, height: height,
padding: const EdgeInsets.symmetric(horizontal: 2), padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor ?? cs.surfaceContainerHigh, color: useGradient ? null : base,
gradient: useGradient ? GlossyDecor.fillGradient(base) : null,
borderRadius: BorderRadius.circular(34), borderRadius: BorderRadius.circular(34),
border: borderColor != null border: glossy
? Border.all(color: borderColor!, width: 0.5) ? GlossyDecor.rimBorder(base)
: null, : (borderColor != null
? Border.all(color: borderColor!, width: 0.5)
: null),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.5), color: Colors.black.withValues(alpha: 0.5),
@@ -96,6 +126,18 @@ class SlidingPillNav extends StatelessWidget {
child: Stack( child: Stack(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
if (useGradient)
Positioned.fill(
child: IgnorePointer(
child: ClipRRect(
borderRadius: BorderRadius.circular(34),
child: DecoratedBox(
decoration:
BoxDecoration(gradient: GlossyDecor.topSheen(base)),
),
),
),
),
AnimatedPositioned( AnimatedPositioned(
duration: animationDuration, duration: animationDuration,
curve: Curves.easeOutCubic, curve: Curves.easeOutCubic,
+6
View File
@@ -23,6 +23,8 @@ import 'core/config/app_pranks.dart';
import 'core/config/app_stories.dart'; import 'core/config/app_stories.dart';
import 'core/config/app_link_preview.dart'; import 'core/config/app_link_preview.dart';
import 'core/config/app_media_cache.dart'; import 'core/config/app_media_cache.dart';
import 'core/config/app_pill_gradient.dart';
import 'core/config/app_visual_style.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 'core/config/app_digital_id_mode.dart'; import 'core/config/app_digital_id_mode.dart';
@@ -94,6 +96,8 @@ void main() async {
final cacheExtentFuture = AppCacheExtent.load(); final cacheExtentFuture = AppCacheExtent.load();
final themeModeFuture = AppThemeModeConfig.load(); final themeModeFuture = AppThemeModeConfig.load();
final amoledFuture = AppAmoled.load(); final amoledFuture = AppAmoled.load();
final pillGradientFuture = AppPillGradient.load();
final visualStyleFuture = AppVisualStyle.load();
final themeScheduleFuture = AppThemeSchedule.load(); final themeScheduleFuture = AppThemeSchedule.load();
final messageActionsFuture = AppMessageActionsStyle.load(); final messageActionsFuture = AppMessageActionsStyle.load();
final swipeBackFuture = AppSwipeBackDesktop.load(); final swipeBackFuture = AppSwipeBackDesktop.load();
@@ -126,6 +130,8 @@ void main() async {
AppCacheExtent.current.value = await cacheExtentFuture; AppCacheExtent.current.value = await cacheExtentFuture;
AppThemeModeConfig.current.value = await themeModeFuture; AppThemeModeConfig.current.value = await themeModeFuture;
AppAmoled.current.value = await amoledFuture; AppAmoled.current.value = await amoledFuture;
AppPillGradient.current.value = await pillGradientFuture;
AppVisualStyle.current.value = await visualStyleFuture;
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;