Files
MWClash/lib/manager/status_manager.dart
chen08209 dbd8fd89fe Add sqlite store
Optimize android quick action

Optimize backup and restore

Optimize more details
2026-01-26 01:31:32 +08:00

262 lines
10 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/fade_box.dart';
import 'package:fl_clash/widgets/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class StatusManager extends StatefulWidget {
final Widget child;
const StatusManager({super.key, required this.child});
@override
State<StatusManager> createState() => StatusManagerState();
}
class StatusManagerState extends State<StatusManager> {
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
final _bufferMessages = Queue<CommonMessage>();
final _activeTimers = <String, Timer>{};
bool _isDisplayingMessage = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_messagesNotifier.dispose();
for (final timer in _activeTimers.values) {
timer.cancel();
}
_activeTimers.clear();
_bufferMessages.clear();
super.dispose();
}
void message(String text, {MessageActionState? actionState}) {
final commonMessage = CommonMessage(
id: utils.uuidV4,
text: text,
actionState: actionState,
);
_bufferMessages.add(commonMessage);
commonPrint.log('message: $text');
_processQueue();
}
void _cancelMessage(String id) {
_bufferMessages.removeWhere((msg) => msg.id == id);
if (_activeTimers.containsKey(id)) {
_removeMessage(id);
}
}
void _processQueue() {
if (_isDisplayingMessage || _bufferMessages.isEmpty) {
return;
}
_isDisplayingMessage = true;
final message = _bufferMessages.removeFirst();
_messagesNotifier.value = List.from(_messagesNotifier.value)..add(message);
final timer = Timer(message.duration, () {
_removeMessage(message.id);
});
_activeTimers[message.id] = timer;
}
void _removeMessage(String id) {
_activeTimers.remove(id)?.cancel();
final currentMessages = List<CommonMessage>.from(_messagesNotifier.value);
currentMessages.removeWhere((msg) => msg.id == id);
_messagesNotifier.value = currentMessages;
_isDisplayingMessage = false;
_processQueue();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
Consumer(
builder: (_, ref, child) {
final top = ref.watch(overlayTopOffsetProvider);
return Container(
margin: EdgeInsets.only(
top: top + MediaQuery.of(context).viewPadding.top + 8,
),
child: child,
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: AnimatedSize(
duration: animateDuration,
child: ValueListenableBuilder(
valueListenable: _messagesNotifier,
builder: (_, messages, _) {
return FadeThroughBox(
alignment: Alignment.centerRight,
child: messages.isEmpty
? SizedBox()
: LayoutBuilder(
key: Key(messages.last.id),
builder: (_, constraints) {
return Dismissible(
key: ValueKey(messages.last.id),
onDismissed: (_) {
_cancelMessage(messages.last.id);
},
child: Card(
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(
Radius.circular(14),
),
),
elevation: 10,
color: context
.colorScheme
.surfaceContainerHigh,
child: Container(
width: min(constraints.maxWidth, 500),
constraints: BoxConstraints(
minHeight: 54,
),
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
messages.last.text,
maxLines: 3,
style: context
.textTheme
.labelLarge
?.copyWith(
color: context
.colorScheme
.onSurfaceVariant,
),
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: 16),
if (messages.last.actionState !=
null)
CommonMinFilledButtonTheme(
child: FilledButton.tonal(
onPressed: () async {
_cancelMessage(
messages.last.id,
);
messages.last.actionState!
.action();
},
child: Text(
messages
.last
.actionState!
.actionText,
),
),
),
],
),
),
),
);
},
),
);
},
),
),
),
// LoadingIndicator(),
],
),
),
],
);
}
}
// class LoadingIndicator extends ConsumerWidget {
// const LoadingIndicator({super.key});
//
// @override
// Widget build(BuildContext context, ref) {
// final loading = ref.watch(loadingProvider);
// final isMobileView = ref.watch(isMobileViewProvider);
// return AnimatedSwitcher(
// switchInCurve: Curves.easeIn,
// switchOutCurve: Curves.easeOut,
// duration: midDuration,
// transitionBuilder: (Widget child, Animation<double> animation) {
// return SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(1, 0),
// end: Offset.zero,
// ).animate(animation),
// child: child,
// );
// },
// child: loading && isMobileView
// ? Container(
// height: 54,
// margin: EdgeInsets.only(top: 8, left: 14, right: 14),
// child: Material(
// elevation: 3,
// color: context.colorScheme.surfaceContainer,
// surfaceTintColor: context.colorScheme.surfaceTint,
// shape: const RoundedSuperellipseBorder(
// borderRadius: BorderRadius.all(Radius.circular(14)),
// ),
// child: Padding(
// padding: EdgeInsets.symmetric(horizontal: 16),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// spacing: 12,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Flexible(
// child: Text(
// context.appLocalizations.loading,
// style: context.textTheme.labelLarge?.copyWith(
// color: context.colorScheme.onSurfaceVariant,
// ),
// ),
// ),
// SizedBox(
// height: 32,
// width: 32,
// child: CommonCircleLoading(),
// ),
// ],
// ),
// ),
// ),
// )
// : SizedBox(),
// );
// }
// }