Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
926bf400b8 Fix windows some issues
Optimize overwrite handle

Optimize access control page

Optimize some details
2025-12-05 10:56:02 +08:00
27 changed files with 182 additions and 402 deletions

View File

@@ -22,7 +22,7 @@ jobs:
os: ubuntu-22.04
arch: amd64
- platform: macos
os: macos-15-intel
os: macos-13
arch: amd64
- platform: macos
os: macos-latest

View File

@@ -54,17 +54,13 @@ object State {
suspend fun handleSyncState() {
runLock.withLock {
try {
Service.bind()
runTime = Service.getRunTime()
val runState = when (runTime == 0L) {
true -> RunState.STOP
false -> RunState.START
}
runStateFlow.tryEmit(runState)
} catch (_: Exception) {
runStateFlow.tryEmit(RunState.STOP)
Service.bind()
runTime = Service.getRunTime()
val runState = when (runTime == 0L) {
true -> RunState.STOP
false -> RunState.START
}
runStateFlow.tryEmit(runState)
}
}
@@ -164,7 +160,10 @@ object State {
if (servicePlugin == null) {
return@launch
}
val options = servicePlugin?.handleGetVpnOptions() ?: return@launch
val options = servicePlugin?.handleGetVpnOptions()
if (options == null) {
return@launch
}
appPlugin?.prepare(options.enable) {
runTime = Service.startService(options, runTime)
runStateFlow.tryEmit(RunState.START)

View File

@@ -50,11 +50,7 @@ class CommonService : Service(), IBaseService,
}
override fun start() {
try {
loader.load()
} catch (_: Exception) {
stop()
}
loader.load()
}
override fun stop() {

View File

@@ -234,13 +234,9 @@ class VpnService : SystemVpnService(), IBaseService,
}
override fun start() {
try {
loader.load()
State.options?.let {
handleStart(it)
}
} catch (_: Exception) {
stop()
loader.load()
State.options?.let {
handleStart(it)
}
}

View File

@@ -449,7 +449,6 @@
"externalFetch": "External fetch",
"confirmForceCrashCore": "Are you sure you want to force crash the core?",
"confirmClearAllData": "Are you sure you want to clear all data?",
"loading": "Loading...",
"loadTest": "Load test",
"yearsAgo": "{count, plural, =1{1 year ago} other{{count} years ago}}",
"monthsAgo": "{count, plural, =1{1 month ago} other{{count} months ago}}",

View File

@@ -450,7 +450,6 @@
"externalFetch": "外部取得",
"confirmForceCrashCore": "コアを強制的にクラッシュさせてもよろしいですか?",
"confirmClearAllData": "すべてのデータをクリアしてもよろしいですか?",
"loading": "読み込み中...",
"loadTest": "読み込みテスト",
"yearsAgo": "{count}年前",
"monthsAgo": "{count}ヶ月前",

View File

@@ -450,7 +450,6 @@
"externalFetch": "Внешнее получение",
"confirmForceCrashCore": "Вы уверены, что хотите принудительно аварийно завершить работу ядра?",
"confirmClearAllData": "Вы уверены, что хотите очистить все данные?",
"loading": "Загрузка...",
"loadTest": "Тест загрузки",
"yearsAgo": "{count, plural, one{{count} год назад} few{{count} года назад} many{{count} лет назад} other{{count} года назад}}",
"monthsAgo": "{count, plural, one{{count} месяц назад} few{{count} месяца назад} many{{count} месяцев назад} other{{count} месяца назад}}",

View File

@@ -450,7 +450,6 @@
"externalFetch": "外部获取",
"confirmForceCrashCore": "确定要强制崩溃核心?",
"confirmClearAllData": "确定要清除所有数据?",
"loading": "加载中...",
"loadTest": "加载测试",
"yearsAgo": "{count} 年前",
"monthsAgo": "{count} 个月前",

View File

@@ -988,7 +988,7 @@ class AppController {
final realSilence = needLoading == true ? true : silence;
try {
if (needLoading) {
_ref.read(loadingProvider.notifier).start();
_ref.read(loadingProvider.notifier).value = true;
}
final res = await futureFunction();
return res;
@@ -1004,7 +1004,7 @@ class AppController {
}
return null;
} finally {
_ref.read(loadingProvider.notifier).stop();
_ref.read(loadingProvider.notifier).value = false;
}
}
}

View File

@@ -197,7 +197,7 @@ extension KeyboardModifierExt on KeyboardModifier {
enum HotAction { start, view, mode, proxy, tun }
enum ProxiesIconStyle { none, standard, icon }
enum ProxiesIconStyle { standard, none, icon }
enum FontFamily {
twEmoji('Twemoji'),

View File

@@ -445,7 +445,6 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("List"),
"listen": MessageLookupByLibrary.simpleMessage("Listen"),
"loadTest": MessageLookupByLibrary.simpleMessage("Load test"),
"loading": MessageLookupByLibrary.simpleMessage("Loading..."),
"local": MessageLookupByLibrary.simpleMessage("Local"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Backup local data to local",

View File

@@ -334,7 +334,6 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("リスト"),
"listen": MessageLookupByLibrary.simpleMessage("リスン"),
"loadTest": MessageLookupByLibrary.simpleMessage("読み込みテスト"),
"loading": MessageLookupByLibrary.simpleMessage("読み込み中..."),
"local": MessageLookupByLibrary.simpleMessage("ローカル"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("ローカルにデータをバックアップ"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("ファイルからデータを復元"),

View File

@@ -464,7 +464,6 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("Список"),
"listen": MessageLookupByLibrary.simpleMessage("Слушать"),
"loadTest": MessageLookupByLibrary.simpleMessage("Тест загрузки"),
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
"local": MessageLookupByLibrary.simpleMessage("Локальный"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage(
"Резервное копирование локальных данных на локальный диск",

View File

@@ -298,7 +298,6 @@ class MessageLookup extends MessageLookupByLibrary {
"list": MessageLookupByLibrary.simpleMessage("列表"),
"listen": MessageLookupByLibrary.simpleMessage("监听"),
"loadTest": MessageLookupByLibrary.simpleMessage("加载测试"),
"loading": MessageLookupByLibrary.simpleMessage("加载中..."),
"local": MessageLookupByLibrary.simpleMessage("本地"),
"localBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到本地"),
"localRecoveryDesc": MessageLookupByLibrary.simpleMessage("通过文件恢复数据"),

View File

@@ -3509,11 +3509,6 @@ class AppLocalizations {
);
}
/// `Loading...`
String get loading {
return Intl.message('Loading...', name: 'loading', desc: '', args: []);
}
/// `Load test`
String get loadTest {
return Intl.message('Load test', name: 'loadTest', desc: '', args: []);

View File

@@ -6,7 +6,6 @@ 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/loading.dart';
import 'package:fl_clash/widgets/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -92,106 +91,94 @@ class StatusManagerState extends State<StatusManager> {
final top = ref.watch(overlayTopOffsetProvider);
return Container(
margin: EdgeInsets.only(
top: top + MediaQuery.of(context).viewPadding.top + 8,
top: top + MediaQuery.of(context).viewPadding.top,
),
child: child,
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LoadingIndicator(),
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,
),
),
),
],
),
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: 50,
),
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
messages.last.text,
maxLines: 3,
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(),
],
),
),
@@ -208,13 +195,11 @@ class LoadingIndicator extends ConsumerWidget {
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),
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(animation),
child: child,
@@ -222,37 +207,29 @@ class LoadingIndicator extends ConsumerWidget {
},
child: loading && isMobileView
? Container(
height: 54,
margin: EdgeInsets.only(top: 8, left: 14, right: 14),
height: 36,
margin: EdgeInsets.only(top: 8),
child: Material(
elevation: 3,
color: context.colorScheme.surfaceContainer,
surfaceTintColor: context.colorScheme.surfaceTint,
shape: const RoundedSuperellipseBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
borderRadius: BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18),
),
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,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 10, height: 36),
SizedBox(
width: 36,
height: 36,
child: Padding(
padding: EdgeInsets.all(6),
child: CircularProgressIndicator(),
),
SizedBox(
height: 32,
width: 32,
child: CommonCircleLoading(),
),
],
),
),
],
),
),
)

View File

@@ -36,17 +36,11 @@ abstract class Package with _$Package {
}
extension PackagesExt on List<Package> {
List<Package> getViewList({
List<Package> getSortList({
required List<String> pinedList,
required AccessSortType sortType,
required bool isFilterSystemApp,
required bool isFilterNonInternetApp,
}) {
return where(
(item) =>
(isFilterSystemApp ? item.system == false : true) &&
(isFilterNonInternetApp ? item.internet == true : true),
).sorted((a, b) {
return sorted((a, b) {
final isSelectA = pinedList.contains(a.packageName);
final isSelectB = pinedList.contains(b.packageName);

View File

@@ -233,8 +233,8 @@ const _$ProxiesLayoutEnumMap = {
};
const _$ProxiesIconStyleEnumMap = {
ProxiesIconStyle.none: 'none',
ProxiesIconStyle.standard: 'standard',
ProxiesIconStyle.none: 'none',
ProxiesIconStyle.icon: 'icon',
};

View File

@@ -51,13 +51,11 @@ class _EditorPageState extends ConsumerState<EditorPage> {
late TextEditingController _titleController;
late FocusNode _focusNode;
late bool readOnly = false;
late final SelectionToolbarController _toolbarController;
@override
void initState() {
super.initState();
readOnly = widget.onSave == null;
_toolbarController = ContextMenuControllerImpl(readOnly);
_focusNode = FocusNode(canRequestFocus: !readOnly);
_controller = CodeLineEditingController.fromText(widget.content);
_findController = CodeFindController(_controller);
@@ -91,7 +89,6 @@ class _EditorPageState extends ConsumerState<EditorPage> {
@override
void dispose() {
_toolbarController.hide(context);
_findController.dispose();
_controller.dispose();
_focusNode.dispose();
@@ -256,7 +253,6 @@ class _EditorPageState extends ConsumerState<EditorPage> {
),
body: CodeEditor(
readOnly: readOnly,
autofocus: false,
findController: _findController,
findBuilder: (context, controller, readOnly) => FindPanel(
controller: controller,
@@ -272,7 +268,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
child: child,
);
},
toolbarController: _toolbarController,
toolbarController: ContextMenuControllerImpl(readOnly),
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
@@ -312,7 +308,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
}
}
const double _kDefaultFindPanelHeight = 52;
const double _kDefaultFindPanelHeight = 56;
class FindPanel extends StatelessWidget implements PreferredSizeWidget {
final CodeFindController controller;
@@ -404,7 +400,6 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
);
if (isMobileView) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [bar, SizedBox(height: 4), _buildFindInput(context, value)],
);
@@ -412,42 +407,45 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
return bar;
}
Widget _buildFindInput(BuildContext context, CodeFindValue value) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 8,
Stack _buildFindInput(BuildContext context, CodeFindValue value) {
return Stack(
alignment: Alignment.center,
children: [
Flexible(
child: _buildTextField(
context: context,
onSubmitted: () {
if (value.result == null) {
return;
}
controller.nextMatch();
controller.findInputFocusNode.requestFocus();
},
controller: controller.findInputController,
focusNode: controller.findInputFocusNode,
),
),
_buildCheckText(
_buildTextField(
context: context,
text: 'Aa',
isSelected: value.option.caseSensitive,
onPressed: () {
controller.toggleCaseSensitive();
onSubmitted: () {
if (value.result == null) {
return;
}
controller.nextMatch();
controller.findInputFocusNode.requestFocus();
},
controller: controller.findInputController,
focusNode: controller.findInputFocusNode,
),
_buildCheckText(
context: context,
text: '.*',
isSelected: value.option.regex,
onPressed: () {
controller.toggleRegex();
},
Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 8,
children: [
_buildCheckText(
context: context,
text: 'Aa',
isSelected: value.option.caseSensitive,
onPressed: () {
controller.toggleCaseSensitive();
},
),
_buildCheckText(
context: context,
text: '.*',
isSelected: value.option.regex,
onPressed: () {
controller.toggleRegex();
},
),
SizedBox(width: 4),
],
),
SizedBox(width: 4),
],
);
}
@@ -458,21 +456,18 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
required FocusNode focusNode,
required VoidCallback onSubmitted,
}) {
return SizedBox(
height: globalState.measure.bodyMediumHeight + 8 * 2,
child: TextField(
maxLines: 1,
focusNode: focusNode,
style: context.textTheme.bodyMedium,
decoration: InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12),
),
onSubmitted: (_) {
onSubmitted();
},
controller: controller,
return TextField(
maxLines: 1,
focusNode: focusNode,
style: context.textTheme.bodyMedium,
decoration: InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12),
),
onSubmitted: (_) {
onSubmitted();
},
controller: controller,
);
}
@@ -570,10 +565,6 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
} else if (controller.selectedText.isEmpty) {
_removeOverLayEntry();
}
if (menus.isEmpty) {
_removeOverLayEntry();
return SizedBox();
}
return TextSelectionToolbar(
anchorAbove: anchors.primaryAnchor,
anchorBelow: anchors.secondaryAnchor ?? Offset.zero,

View File

@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
@@ -292,41 +290,11 @@ class BackBlock extends _$BackBlock with AutoDisposeNotifierMixin {
@riverpod
class Loading extends _$Loading with AutoDisposeNotifierMixin {
DateTime? _start;
Timer? _timer;
@override
bool build() {
return globalState.appState.loading;
}
void start() {
_timer?.cancel();
_timer = null;
_start = DateTime.now();
value = true;
}
Future<void> stop() async {
if (_start == null) {
value = false;
return;
}
final startedAt = _start!;
final elapsed = DateTime.now().difference(_start!).inMilliseconds;
const minDuration = 1000;
if (elapsed >= minDuration) {
value = false;
return;
}
_timer = Timer(Duration(milliseconds: minDuration - elapsed), () {
if (_start != startedAt) {
return;
}
value = false;
});
}
@override
onUpdate(value) {
globalState.appState = globalState.appState.copyWith(loading: value);

View File

@@ -32,10 +32,10 @@ class _AccessViewState extends ConsumerState<AccessView> {
super.initState();
_controller = ScrollController();
_completer.complete(globalState.appController.getPackages());
final accessControl = ref
.read(vpnSettingProvider.select((state) => state.accessControl))
.copyWith();
WidgetsBinding.instance.addPostFrameCallback((_) {
final accessControl = ref.read(
vpnSettingProvider.select((state) => state.accessControl),
);
ref.read(accessControlStateProvider.notifier).value = accessControl;
_isInit = true;
});
@@ -157,35 +157,11 @@ class _AccessViewState extends ConsumerState<AccessView> {
}
}
AccessControl _getRealAccessControl(AccessControl accessControl) {
final packages = ref.read(packagesProvider);
if (packages.isEmpty) {
return accessControl;
}
final viewPackageNames = packages
.getViewList(
pinedList: [],
sortType: accessControl.sort,
isFilterSystemApp: accessControl.isFilterSystemApp,
isFilterNonInternetApp: accessControl.isFilterNonInternetApp,
)
.map((item) => item.packageName)
.toSet()
.toList();
return accessControl.copyWithNewList(
accessControl.currentList.intersection(viewPackageNames),
);
}
void _handleSave() {
final accessControl = ref.read(accessControlStateProvider);
ref
.read(vpnSettingProvider.notifier)
.update(
(state) => state.copyWith(
accessControl: _getRealAccessControl(accessControl),
),
);
.update((state) => state.copyWith(accessControl: accessControl));
}
Widget _buildConfirm() {
@@ -194,8 +170,7 @@ class _AccessViewState extends ConsumerState<AccessView> {
final accessControl = ref.watch(accessControlStateProvider);
final noSave = ref.watch(
vpnSettingProvider.select(
(state) =>
state.accessControl == _getRealAccessControl(accessControl),
(state) => state.accessControl == accessControl,
),
);
if (noSave) {
@@ -382,13 +357,8 @@ class _AccessViewState extends ConsumerState<AccessView> {
_pinedList ??= accessControl.currentList;
}
}
final viewPackages = packages
.getViewList(
pinedList: _pinedList ?? [],
sortType: accessControl.sort,
isFilterNonInternetApp: accessControl.isFilterNonInternetApp,
isFilterSystemApp: accessControl.isFilterSystemApp,
)
final packagesSorted = packages
.getSortList(pinedList: _pinedList ?? [], sortType: accessControl.sort)
.where(
(package) =>
package.label.toLowerCase().contains(query) ||
@@ -397,8 +367,8 @@ class _AccessViewState extends ConsumerState<AccessView> {
.toList();
final mode = accessControl.mode;
final currentList = accessControl.currentList;
final viewPackageNameList = viewPackages.map((e) => e.packageName).toList();
final valueList = currentList.intersection(viewPackageNameList);
final packageNameList = packagesSorted.map((e) => e.packageName).toList();
final valueList = currentList.intersection(packageNameList);
return CommonScaffold(
key: _scaffoldKey,
searchState: AppBarSearchState(onSearch: _onSearch, autoAddSearch: false),
@@ -413,7 +383,7 @@ class _AccessViewState extends ConsumerState<AccessView> {
SizedBox(height: 8),
Expanded(
child: _buildContent(
packages: viewPackages,
packages: packagesSorted,
valueList: valueList,
),
),
@@ -421,8 +391,8 @@ class _AccessViewState extends ConsumerState<AccessView> {
),
),
floatingActionButton: _buildSelectedAllButton(
isSelectedAll: valueList.length == viewPackageNameList.length,
allValueList: viewPackageNameList,
isSelectedAll: valueList.length == packageNameList.length,
allValueList: packageNameList,
),
);
}

View File

@@ -128,7 +128,9 @@ class _NetworkDetectionState extends ConsumerState<NetworkDetection> {
padding: const EdgeInsets.all(2),
child: const AspectRatio(
aspectRatio: 1,
child: CommonCircleLoading(),
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
),

View File

@@ -43,7 +43,7 @@ class ProxiesSetting extends StatelessWidget {
String _getTextWithProxiesIconStyle(ProxiesIconStyle style) {
return switch (style) {
ProxiesIconStyle.standard => appLocalizations.standard,
ProxiesIconStyle.none => appLocalizations.none,
ProxiesIconStyle.none => appLocalizations.noIcon,
ProxiesIconStyle.icon => appLocalizations.onlyIcon,
};
}

View File

@@ -1,95 +0,0 @@
import 'package:flutter/material.dart';
class CommonCircleLoading extends StatefulWidget {
final Color? color;
const CommonCircleLoading({super.key, this.color});
@override
State<CommonCircleLoading> createState() => _CommonCircleLoadingState();
}
class _CommonCircleLoadingState extends State<CommonCircleLoading>
with TickerProviderStateMixin {
late AnimationController _rotateController;
late AnimationController _pointsController;
late Animation<double> _pointsAnimation;
@override
void initState() {
super.initState();
_rotateController = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
)..repeat();
_pointsController = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat(reverse: true);
_pointsAnimation = Tween<double>(begin: 3.0, end: 9.0).animate(
CurvedAnimation(parent: _pointsController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_rotateController.dispose();
_pointsController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final color = widget.color ?? Theme.of(context).colorScheme.primary;
return RepaintBoundary(
child: RotationTransition(
turns: _rotateController,
child: SizedBox.expand(
child: AnimatedBuilder(
animation: _pointsController,
builder: (context, child) {
return CustomPaint(
painter: _StarPainter(
points: _pointsAnimation.value,
color: color,
),
);
},
),
),
),
);
}
}
class _StarPainter extends CustomPainter {
final double points;
final Color color;
final Paint _paint;
_StarPainter({required this.points, required this.color})
: _paint = Paint()..color = color;
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final starBorder = StarBorder(
points: points,
innerRadiusRatio: 0.8,
pointRounding: 0.5,
valleyRounding: 0.1,
squash: 0.5,
);
final path = starBorder.getOuterPath(rect);
canvas.drawPath(path, _paint);
}
@override
bool shouldRepaint(covariant _StarPainter oldDelegate) {
return oldDelegate.points != points || oldDelegate.color != color;
}
}

View File

@@ -17,7 +17,6 @@ export 'input.dart';
export 'keep_scope.dart';
export 'line_chart.dart';
export 'list.dart';
export 'loading.dart';
export 'notification.dart';
export 'null_status.dart';
export 'open_container.dart';

View File

@@ -1675,11 +1675,10 @@ packages:
yaml_writer:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: "2a38859d8b9707f99a55433974a799c413477a5b"
url: "https://github.com/chen08209/yaml_writer"
source: git
name: yaml_writer
sha256: "69651cd7238411179ac32079937d4aa9a2970150d6b2ae2c6fe6de09402a5dc5"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
sdks:
dart: ">=3.9.0 <4.0.0"

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.91+2025121901
version: 0.8.91+2025112801
environment:
sdk: '>=3.8.0 <4.0.0'
@@ -60,10 +60,7 @@ dependencies:
flutter_cache_manager: ^3.4.1
crypto: ^3.0.3
yaml: ^3.1.3
yaml_writer:
git:
url: https://github.com/chen08209/yaml_writer
ref: master
yaml_writer: ^2.1.0
super_sliver_list: ^0.4.1
dev_dependencies:
flutter_test: