Compare commits

..

1 Commits

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

Optimize access control page

Optimize some details
2025-12-06 14:23:11 +08:00
33 changed files with 177 additions and 1383 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

@@ -1,954 +0,0 @@
## v0.8.90
- Fix android tile service
- Support append system DNS
- Fix some issues
- Update changelog
## v0.8.89
- Fix some issues
- Optimize Windows service mode
- Update core
- Update changelog
## v0.8.88
- Add android separates the core process
- Support core status check and force restart
- Optimize proxies page and access page
- Update flutter and pub dependencies
- Update go version
- Optimize more details
- Update changelog
## v0.8.87
- Optimize desktop view
- Optimize logs, requests, connection pages
- Optimize windows tray auto hide
- Optimize some details
- Update core
- Update changelog
## v0.8.86
- Fix windows tun issues
- Optimize android get system dns
- Optimize more details
- Update changelog
## v0.8.85
- Support override script
- Support proxies search
- Support svg display
- Optimize config persistence
- Add some scenes auto close connections
- Update core
- Optimize more details
## v0.8.84
- Fix windows service verify issues
- Update changelog
## v0.8.83
- Add windows server mode start process verify
- Add linux deb dependencies
- Add backup recovery strategy select
- Support custom text scaling
- Optimize the display of different text scale
- Optimize windows setup experience
- Optimize startTun performance
- Optimize android tv experience
- Optimize default option
- Optimize computed text size
- Optimize hyperOS freeform window
- Add developer mode
- Update core
- Optimize more details
- Add issues template
- Update changelog
## v0.8.82
- Optimize android vpn performance
- Add custom primary color and color scheme
- Add linux nad windows arm release
- Optimize requests and logs page
- Fix map input page delete issues
- Update changelog
## v0.8.81
- Add rule override
- Update core
- Optimize more details
- Update changelog
## v0.8.80
- Optimize dashboard performance
- Fix some issues
- Fix unselected proxy group delay issues
- Fix asn url issues
- Update changelog
## v0.8.79
- Fix tab delay view issues
- Fix tray action issues
- Fix get profile redirect client ua issues
- Fix proxy card delay view issues
- Add Russian, Japanese adaptation
- Fix some issues
- Update changelog
## v0.8.78
- Fix list form input view issues
- Fix traffic view issues
- Update changelog
## v0.8.77
- Optimize performance
- Update core
- Optimize core stability
- Fix linux tun authority check error
- Fix some issues
- Fix scroll physics error
- Update changelog
## v0.8.75
- Add windows storage corruption detection
- Fix core crash caused by windows resource manager restart
- Optimize logs, requests, access to pages
- Fix macos bypass domain issues
- Update changelog
## v0.8.74
- Fix some issues
- Update changelog
## v0.8.73
- Update popup menu
- Add file editor
- Fix android service issues
- Optimize desktop background performance
- Optimize android main process performance
- Optimize delay test
- Optimize vpn protect
- Update changelog
## v0.8.72
- Update core
- Fix some issues
- Update changelog
## v0.8.71
- Remake dashboard
- Optimize theme
- Optimize more details
- Update flutter version
- Update changelog
## v0.8.70
- Support better window position memory
- Add windows arm64 and linux arm64 build script
- Optimize some details
## v0.8.69
- Remake desktop
- Optimize change proxy
- Optimize network check
- Fix fallback issues
- Optimize lots of details
- Update change.yaml
- Fix android tile issues
- Fix windows tray issues
- Support setting bypassDomain
- Update flutter version
- Fix android service issues
- Fix macos dock exit button issues
- Add route address setting
- Optimize provider view
- Update changelog
- Update CHANGELOG.md
## v0.8.67
- Add android shortcuts
- Fix init params issues
- Fix dynamic color issues
- Optimize navigator animate
- Optimize window init
- Optimize fab
- Optimize save
## v0.8.66
- Fix the collapse issues
- Add fontFamily options
## v0.8.65
- Update core version
- Update flutter version
- Optimize ip check
- Optimize url-test
## v0.8.64
- Update release message
- Init auto gen changelog
- Fix windows tray issues
- Fix urltest issues
- Add auto changelog
- Fix windows admin auto launch issues
- Add android vpn options
- Support proxies icon configuration
- Optimize android immersion display
- Fix some issues
- Optimize ip detection
- Support android vpn ipv6 inbound switch
- Support log export
- Optimize more details
- Fix android system dns issues
- Optimize dns default option
- Fix some issues
- Update readme
## v0.8.60
- Fix build error2
- Fix build error
- Support desktop hotkey
- Support android ipv6 inbound
- Support android system dns
- fix some bugs
## v0.8.59
- Fix delete profile error
## v0.8.58
- Fix submit error 2
- Fix submit error
- Optimize DNS strategy
- Fix the problem that the tray is not displayed in some cases
- Optimize tray
- Update core
- Fix some error
## v0.8.57
- Fix tun update issues
- Add DNS override
- Fixed some bugs
- Optimize more detail
- Add Hosts override
## v0.8.56
- fix android tip error
- fix windows auto launch error
## v0.8.55
- Fix windows tray issues
- Optimize windows logic
- Optimize app logic
- Support windows administrator auto launch
- Support android close vpn
## v0.8.53
- Change flutter version
- Support profiles sort
- Support windows country flags display
- Optimize proxies page and profiles page columns
## v0.8.52
- Update flutter version
- Update version
- Update timeout time
- Update access control page
- Fix bug
## v0.8.51
- Optimize provider page
- Optimize delay test
- Support local backup and recovery
- Fix android tile service issues
## v0.8.49
- Fix linux core build error
- Add proxy-only traffic statistics
- Update core
- Optimize more details
- Merge pull request #140 from txyyh/main
- 添加自建 F-Droid 仓库相关 workflow
- Rename readme fingerprint
- Rename workflow deploy repo name
- Add download guide to README
- Add push release files to fdroid-repo
## v0.8.48
- Optimize proxies page
- Fix ua issues
- Optimize more details
## v0.8.47
- Fix windows build error
## v0.8.46
- Update app icon
- Fix desktop backup error
- Optimize request ua
- Change android icon
- Optimize dashboard
## v0.8.44
- Remove request validate certificate
- Sync core
## v0.8.43
- Fix windows error
## v0.8.42
- Fix setup.dart error
- Fix android system proxy not effective
- Add macos arm64
## v0.8.41
- Optimize proxies page
- Support mouse drag scroll
- Adjust desktop ui
- Revert "Fix android vpn issues"
- This reverts commit 891977408e6938e2acd74e9b9adb959c48c79988.
## v0.8.40
- Fix android vpn issues
- Fix android vpn issues
- Rollback partial modification
## v0.8.39
- Fix the problem that ui can't be synchronized when android vpn is occupied by an external
- Override default socksPort,port
## v0.8.38
- Fix fab issues
## v0.8.37
- Update version
- Fix the problem that vpn cannot be started in some cases
- Fix the problem that geodata url does not take effect
## v0.8.36
- Update ua
- Fix change outbound mode without check ip issues
- Separate android ui and vpn
- Fix url validate issues 2
- Add android hidden from the recent task
- Add geoip file
- Support modify geoData URL
## v0.8.35
- Fix url validate issues
- Fix check ip performance problem
- Optimize resources page
## v0.8.34
- Add ua selector
- Support modify test url
- Optimize android proxy
- Fix the error that async proxy provider could not selected the proxy
## v0.8.33
- Fix android proxy error
- Fix submit error
- Add windows tun
- Optimize android proxy
- Optimize change profile
- Update application ua
- Optimize delay test
## v0.8.32
- Fix android repeated request notification issues
## v0.8.31
- Fix memory overflow issues
## v0.8.30
- Optimize proxies expansion panel 2
- Fix android scan qrcode error
## v0.8.29
- Optimize proxies expansion panel
- Fix text error
## v0.8.28
- Optimize proxy
- Optimize delayed sorting performance
- Add expansion panel proxies page
- Support to adjust the proxy card size
- Support to adjust proxies columns number
- Fix autoRun show issues
- Fix Android 10 issues
- Optimize ip show
## v0.8.26
- Add intranet IP display
- Add connections page
- Add search in connections, requests
- Add keyword search in connections, requests, logs
- Add basic viewing editing capabilities
- Optimize update profile
## v0.8.25
- Update version
- Fix the problem of excessive memory usage in traffic usage.
- Add lightBlue theme color
- Fix start unable to update profile issues
- Fix flashback caused by process
## v0.8.23
- Add build version
- Optimize quick start
- Update system default option
## v0.8.22
- Update build.yml
- Fix android vpn close issues
- Add requests page
- Fix checkUpdate dark mode style error
- Fix quickStart error open app
- Add memory proxies tab index
- Support hidden group
- Optimize logs
- Fix externalController hot load error
## v0.8.21
- Add tcp concurrent switch
- Add system proxy switch
- Add geodata loader switch
- Add external controller switch
- Add auto gc on trim memory
- Fix android notification error
## v0.8.20
- Fix ipv6 error
- Fix android udp direct error
- Add ipv6 switch
- Add access all selected button
- Remove android low version splash
## v0.8.19
- Update version
- Add allowBypass
- Fix Android only pick .text file issues
## v0.8.18
- Fix search issues
## v0.8.17
- Fix LoadBalance, Relay load error
- Fix build.yml4
- Fix build.yml3
- Fix build.yml2
- Fix build.yml
- Add search function at access control
- Fix the issues with the profile add button to cover the edit button
- Adapt LoadBalance and Relay
- Add arm
- Fix android notification icon error
## v0.8.16
- Add one-click update all profiles
- Add expire show
## v0.8.15
- Temp remove tun mode
- Remove macos in workflow
- Change go version
## v0.8.14
- Update Version
- Fix tun unable to open
## v0.8.13
- Optimize delay test2
- Optimize delay test
- Add check ip
- add check ip request
## v0.8.12
- Fix the problem that the download of remote resources failed after GeodataMode was turned on, which caused the
application to flash back.
- Fix edit profile error
- Fix quickStart change proxy error
- Fix core version
## v0.8.10
- Fix core version
## v0.8.9
- Update file_picker
- Add resources page
- Optimize more detail
- Add access selected sorted
- Fix notification duplicate creation issue
- Fix AccessControl click issue
## v0.8.7
- Fix Workflow
- Fix Linux unable to open
- Update README.md 3
- Create LICENSE
- Update README.md 2
- Update README.md
- Optimize workFlow
## v0.8.6
- optimize checkUpdate
## v0.8.5
- Fix submit error
## v0.8.4
- add WebDAV
- add Auto check updates
- Optimize more details
- optimize delayTest
## v0.8.2
- upgrade flutter version
## v0.8.1
- Update kernel
- Add import profile via QR code image
## v0.8.0
- Add compatibility mode and adapt clash scheme.
## v0.7.14
- update Version
- Reconstruction application proxy logic
## v0.7.13
- Fix Tab destroy error
## v0.7.12
- Optimize repeat healthcheck
## v0.7.11
- Optimize Direct mode ui
## v0.7.10
- Optimize Healthcheck
- Remove proxies position animation, improve performance
- Add Telegram Link
- Update healthcheck policy
- New Check URLTest
- Fix the problem of invalid auto-selection
## v0.7.8
- New Async UpdateConfig
- add changeProfileDebounce
- Update Workflow
- Fix ChangeProfile block
- Fix Release Message Error
## v0.7.7
- Update Selector 2
## v0.7.6
- Update Version
- Fix Proxies Select Error
## v0.7.5
- Fix the problem that the proxy group is empty in global mode.
- Fix the problem that the proxy group is empty in global mode.
## v0.7.4
- Add ProxyProvider2
## v0.7.3
- Add ProxyProvider
- Update Version
- Update ProxyGroup Sort
- Fix Android quickStart VpnService some problems
## v0.7.1
- Update version
- Set Android notification low importance
- Fix the issue that VpnService can't be closed correctly in special cases
- Fix the problem that TileService is not destroyed correctly in some cases
- Adjust tab animation defaults
- Add Telegram in README_zh_CN.md
- Add Telegram
## v0.7.0
- update mobile_scanner
- Initial commit

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

@@ -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

@@ -42,7 +42,8 @@ class Preferences {
Future<bool> saveConfig(Config config) async {
final preferences = await sharedPreferencesCompleter.future;
return preferences?.setString(configKey, json.encode(config)) ?? false;
return await preferences?.setString(configKey, json.encode(config)) ??
false;
}
Future<void> clearClashConfig() async {

View File

@@ -105,8 +105,7 @@ class Tray {
subMenuItems.add(
MenuItem.checkbox(
label: proxy.name,
checked:
globalState.getSelectedProxyName(group.name) == proxy.name,
checked: trayState.selectedMap[group.name] == proxy.name,
onClick: (_) {
final appController = globalState.appController;
appController.updateCurrentSelectedMap(group.name, proxy.name);

View File

@@ -56,11 +56,7 @@ class AppController {
}
void savePreferencesDebounce() {
debouncer.call(
FunctionTag.savePreferences,
savePreferences,
duration: Duration(seconds: 3),
);
debouncer.call(FunctionTag.savePreferences, savePreferences);
}
void changeProxyDebounce(String groupName, String proxyName) {
@@ -69,7 +65,7 @@ class AppController {
String proxyName,
) async {
await changeProxy(groupName: groupName, proxyName: proxyName);
updateGroupsDebounce();
await updateGroups();
}, args: [groupName, proxyName]);
}
@@ -376,7 +372,6 @@ class AppController {
Future<void> updateGroups() async {
try {
commonPrint.log('updateGroups');
_ref.read(groupsProvider.notifier).value = await retry(
task: () async {
final sortType = _ref.read(
@@ -993,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;
@@ -1009,7 +1004,7 @@ class AppController {
}
return null;
} finally {
_ref.read(loadingProvider.notifier).stop();
_ref.read(loadingProvider.notifier).value = false;
}
}
}

View File

@@ -280,7 +280,6 @@ enum FunctionTag {
logs,
requests,
autoScrollToEnd,
loadedProvider,
}
enum DashboardWidget {

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

@@ -87,9 +87,7 @@ class _CoreContainerState extends ConsumerState<CoreManager>
ref
.read(providersProvider.notifier)
.setProvider(await coreController.getExternalProvider(providerName));
debouncer.call(FunctionTag.loadedProvider, () async {
globalState.appController.updateGroupsDebounce();
}, duration: const Duration(milliseconds: 5000));
globalState.appController.updateGroupsDebounce();
super.onLoaded(providerName);
}

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

@@ -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

@@ -1097,7 +1097,7 @@ final class LoadingProvider extends $NotifierProvider<Loading, bool> {
}
}
String _$loadingHash() => r'd3d9e6b203fecbef89d468b6ecf173a98a6a26a9';
String _$loadingHash() => r'a0a09132a78495616785461cdc2a8b412c19b51b';
abstract class _$Loading extends $Notifier<bool> {
bool build();

View File

@@ -90,7 +90,7 @@ final class CurrentGroupsStateProvider
}
String _$currentGroupsStateHash() =>
r'dbf8f02606a31486c99d7b89d19914cd5a1fc496';
r'6222c006e1970e7435268d32903b9019cf1a4351';
@ProviderFor(navigationItemsState)
const navigationItemsStateProvider = NavigationItemsStateProvider._();
@@ -719,7 +719,7 @@ final class FilterGroupsStateProvider
}
}
String _$filterGroupsStateHash() => r'7de7a4603ca5ed7c39a00351af43144eb6c21404';
String _$filterGroupsStateHash() => r'c50aafbb50f98a66e21fc069d22031351d93a0ab';
final class FilterGroupsStateFamily extends $Family
with $FunctionalFamilyOverride<GroupsState, String> {

View File

@@ -51,16 +51,7 @@ GroupsState currentGroupsState(Ref ref) {
final mode = ref.watch(
patchClashConfigProvider.select((state) => state.mode),
);
final groups = ref.watch(
groupsProvider.select(
(state) => state.map((item) {
return item.copyWith(
now: '',
all: item.all.map((proxy) => proxy.copyWith(now: '')).toList(),
);
}),
),
);
final groups = ref.watch(groupsProvider);
return GroupsState(
value: switch (mode) {
Mode.direct => [],
@@ -299,7 +290,7 @@ GroupsState filterGroupsState(Ref ref, String query) {
})
.where((group) => group.all.isNotEmpty)
.toList();
return currentGroups.copyWith(value: groups);
return GroupsState(value: groups);
}
@riverpod

View File

@@ -367,12 +367,6 @@ class GlobalState {
);
}
String getSelectedProxyName(String groupName) {
final group = appState.groups.getGroup(groupName);
final proxyName = config.currentProfile?.selectedMap[groupName];
return group?.getCurrentSelectedName(proxyName ?? '') ?? '';
}
Future<String> setupConfig({
required SetupState setupState,
required ClashConfig patchConfig,

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

@@ -6,7 +6,7 @@ import 'package:fl_clash/state.dart';
double get listHeaderHeight {
final measure = globalState.measure;
return 20 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight + 2;
return 20 + measure.titleMediumHeight + 4 + measure.bodyMediumHeight;
}
double getItemHeight(ProxyCardType proxyCardType) {

View File

@@ -40,10 +40,8 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
}
if (!stringListEquality.equals(prev?.a, next.a)) {
_destroyTabController();
final groupNames = next.a;
final currentGroupName = next.b;
final index = groupNames.indexWhere((item) => item == currentGroupName);
_updateTabController(groupNames.length, index);
final index = next.a.indexWhere((item) => item == next.b);
_updateTabController(next.a.length, index);
}
}, fireImmediately: true);
}
@@ -129,22 +127,22 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
}
void _tabControllerListener([int? index]) {
int? groupIndex = index;
if (groupIndex == -1) {
return;
}
final appController = globalState.appController;
if (groupIndex == null) {
final currentIndex = _tabController?.index;
groupIndex = currentIndex;
}
final currentGroups = appController.getCurrentGroups();
if (groupIndex == null || groupIndex > currentGroups.length) {
return;
}
final currentGroup = currentGroups[groupIndex];
WidgetsBinding.instance.addPostFrameCallback((_) {
int? groupIndex = index;
if (groupIndex == -1) {
return;
}
final appController = globalState.appController;
if (groupIndex == null) {
final currentIndex = _tabController?.index;
groupIndex = currentIndex;
}
final currentGroups = appController.getCurrentGroups();
if (groupIndex == null || groupIndex > currentGroups.length) {
return;
}
final currentGroup = currentGroups[groupIndex];
appController.updateCurrentGroupName(currentGroup.name);
globalState.appController.updateCurrentGroupName(currentGroup.name);
});
}
@@ -155,8 +153,8 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
}
void _updateTabController(int length, int index) {
_destroyTabController();
if (length == 0) {
_destroyTabController();
return;
}
final realIndex = index == -1 ? 0 : index;
@@ -172,15 +170,26 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
@override
Widget build(BuildContext context) {
ref.watch(themeSettingProvider.select((state) => state.textScale));
final state = ref.watch(proxiesTabStateProvider.select((state) => state));
final state = ref.watch(proxiesTabStateProvider);
final groups = state.groups;
if (groups.isEmpty || _tabController == null) {
if (groups.isEmpty) {
return NullStatus(
illustration: ProxyEmptyIllustration(),
label: appLocalizations.nullTip(appLocalizations.proxies),
);
}
_keyMap = {};
final ProxyGroupViewKeyMap keyMap = {};
final children = groups.map((group) {
final key = GlobalObjectKey<_ProxyGroupViewState>(group.name);
keyMap[group.name] = key;
return ProxyGroupView(
key: key,
group: group,
columns: state.columns,
cardType: state.proxyCardType,
);
}).toList();
_keyMap = keyMap;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -236,7 +245,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
context.colorScheme.surface.opacity10,
context.colorScheme.surface,
],
stops: const [0.0, 0.5],
stops: const [0.0, 0.1],
),
),
child: _buildMoreButton(),
@@ -244,21 +253,7 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
for (final group in groups)
ProxyGroupView(
key: _keyMap.updateCacheValue(
group.name,
() => GlobalObjectKey<_ProxyGroupViewState>(group.name),
),
group: group,
columns: state.columns,
cardType: state.proxyCardType,
),
],
),
child: TabBarView(controller: _tabController, children: children),
),
],
);

View File

@@ -40,8 +40,6 @@ class CommonTargetIcon extends StatelessWidget {
}
}
final _cacheMange = DefaultCacheManager();
class ImageCacheWidget extends StatefulWidget {
final String src;
final Widget defaultWidget;
@@ -57,45 +55,22 @@ class ImageCacheWidget extends StatefulWidget {
}
class _ImageCacheWidgetState extends State<ImageCacheWidget> {
final ValueNotifier<File?> _imageNotifier = ValueNotifier(null);
late Future<File> _imageFuture;
@override
void initState() {
super.initState();
_getImageFormCache();
}
void _getImageFormCache() async {
final src = widget.src;
final cacheFile = await _cacheMange.getFileFromCache(src);
if (!mounted) {
return;
}
if (cacheFile != null) {
_imageNotifier.value = cacheFile.file;
if (cacheFile.validTill.isAfter(DateTime.now())) {
return;
}
}
if (!mounted) {
return;
}
_imageNotifier.value = (await _cacheMange.downloadFile(src, key: src)).file;
}
@override
void dispose() {
_imageNotifier.dispose();
super.dispose();
_imageFuture = DefaultCacheManager().getSingleFile(widget.src);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<File?>(
valueListenable: _imageNotifier,
builder: (_, data, _) {
return FutureBuilder<File>(
future: _imageFuture,
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null) {
return widget.defaultWidget;
return SizedBox();
}
return widget.src.isSvg
? SvgPicture.file(

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

@@ -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+2025122201
version: 0.8.91+2025121601
environment:
sdk: '>=3.8.0 <4.0.0'