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