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