Compare commits
3 Commits
v0.8.92-pr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
672eaccd35 | ||
|
|
2fbb96f5c1 | ||
|
|
243b3037d9 |
7
.run/main.dart.run.xml
Normal file
7
.run/main.dart.run.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||||
|
<option name="additionalArgs" value="--dart-define-from-file env.json" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,3 +1,23 @@
|
|||||||
|
## v0.8.92
|
||||||
|
|
||||||
|
- Add sqlite store
|
||||||
|
|
||||||
|
- Optimize android quick action
|
||||||
|
|
||||||
|
- Optimize backup and restore
|
||||||
|
|
||||||
|
- Optimize more details
|
||||||
|
|
||||||
|
## v0.8.91
|
||||||
|
|
||||||
|
- Fix windows some issues
|
||||||
|
|
||||||
|
- Optimize overwrite handle
|
||||||
|
|
||||||
|
- Optimize access control page
|
||||||
|
|
||||||
|
- Optimize some details
|
||||||
|
|
||||||
## v0.8.90
|
## v0.8.90
|
||||||
|
|
||||||
- Fix android tile service
|
- Fix android tile service
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -1,10 +0,0 @@
|
|||||||
android_arm64:
|
|
||||||
dart ./setup.dart android --arch arm64
|
|
||||||
macos_arm64:
|
|
||||||
dart ./setup.dart macos --arch arm64
|
|
||||||
android_app:
|
|
||||||
dart ./setup.dart android
|
|
||||||
android_arm64_core:
|
|
||||||
dart ./setup.dart android --arch arm64 --out core
|
|
||||||
macos_arm64_core:
|
|
||||||
dart ./setup.dart macos --arch arm64 --out core
|
|
||||||
@@ -475,5 +475,6 @@
|
|||||||
"restoreFromFileDesc": "Restore data via file",
|
"restoreFromFileDesc": "Restore data via file",
|
||||||
"restoreOnlyConfig": "Restore configuration files only",
|
"restoreOnlyConfig": "Restore configuration files only",
|
||||||
"restoreAllData": "Restore all data",
|
"restoreAllData": "Restore all data",
|
||||||
"addProfile": "Add Profile"
|
"addProfile": "Add Profile",
|
||||||
|
"delayTest": "Delay Test"
|
||||||
}
|
}
|
||||||
@@ -476,5 +476,6 @@
|
|||||||
"restoreFromFileDesc": "ファイルを介してデータを復元する",
|
"restoreFromFileDesc": "ファイルを介してデータを復元する",
|
||||||
"restoreOnlyConfig": "設定ファイルのみを復元する",
|
"restoreOnlyConfig": "設定ファイルのみを復元する",
|
||||||
"restoreAllData": "すべてのデータを復元する",
|
"restoreAllData": "すべてのデータを復元する",
|
||||||
"addProfile": "プロファイルを追加"
|
"addProfile": "プロファイルを追加",
|
||||||
|
"delayTest": "遅延テスト"
|
||||||
}
|
}
|
||||||
@@ -484,5 +484,6 @@
|
|||||||
"restoreFromFileDesc": "Восстановить данные из файла",
|
"restoreFromFileDesc": "Восстановить данные из файла",
|
||||||
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
|
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
|
||||||
"restoreAllData": "Восстановить все данные",
|
"restoreAllData": "Восстановить все данные",
|
||||||
"addProfile": "Добавить профиль"
|
"addProfile": "Добавить профиль",
|
||||||
|
"delayTest": "Тест задержки"
|
||||||
}
|
}
|
||||||
@@ -476,5 +476,6 @@
|
|||||||
"restoreFromFileDesc": "通过文件恢复数据",
|
"restoreFromFileDesc": "通过文件恢复数据",
|
||||||
"restoreOnlyConfig": "仅恢复配置文件",
|
"restoreOnlyConfig": "仅恢复配置文件",
|
||||||
"restoreAllData": "恢复所有数据",
|
"restoreAllData": "恢复所有数据",
|
||||||
"addProfile": "添加配置"
|
"addProfile": "添加配置",
|
||||||
|
"delayTest": "延迟测试"
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule core/Clash.Meta updated: 4d4492c38c...c27f82fbdf
43
core/hub.go
43
core/hub.go
@@ -99,27 +99,47 @@ func handleValidateConfig(path string) string {
|
|||||||
func handleGetProxies() ProxiesData {
|
func handleGetProxies() ProxiesData {
|
||||||
runLock.Lock()
|
runLock.Lock()
|
||||||
defer runLock.Unlock()
|
defer runLock.Unlock()
|
||||||
var all = []string{"GLOBAL"}
|
|
||||||
all = append(all, config.ProxyList...)
|
nameList := config.GetProxyNameList()
|
||||||
|
|
||||||
proxies := make(map[string]constant.Proxy)
|
proxies := make(map[string]constant.Proxy)
|
||||||
|
|
||||||
for name, proxy := range tunnel.Proxies() {
|
for name, proxy := range tunnel.Proxies() {
|
||||||
proxies[name] = proxy
|
proxies[name] = proxy
|
||||||
}
|
}
|
||||||
for _, p := range tunnel.Providers() {
|
for _, p := range tunnel.Providers() {
|
||||||
for _, proxy := range p.Proxies() {
|
for _, proxy := range p.Proxies() {
|
||||||
name := proxy.Name()
|
proxies[proxy.Name()] = proxy
|
||||||
proxies[name] = proxy
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
types := []constant.AdapterType{
|
hasGlobal := false
|
||||||
constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance,
|
allNames := make([]string, 0, len(nameList)+1)
|
||||||
|
|
||||||
|
for _, name := range nameList {
|
||||||
|
if name == "GLOBAL" {
|
||||||
|
hasGlobal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ok := proxies[name]
|
||||||
|
if !ok || p == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch p.Type() {
|
||||||
|
case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance:
|
||||||
|
allNames = append(allNames, name)
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nextAll := slices.DeleteFunc(all, func(name string) bool {
|
|
||||||
return !slices.Contains(types, proxies[name].Type())
|
if !hasGlobal {
|
||||||
})
|
if p, ok := proxies["GLOBAL"]; ok && p != nil {
|
||||||
|
allNames = append([]string{"GLOBAL"}, allNames...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ProxiesData{
|
return ProxiesData{
|
||||||
All: nextAll,
|
All: allNames,
|
||||||
Proxies: proxies,
|
Proxies: proxies,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,6 +532,9 @@ func handleDelFile(path string, result ActionResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSetupConfig(bytes []byte) string {
|
func handleSetupConfig(bytes []byte) string {
|
||||||
|
if !isInit {
|
||||||
|
return "not initialized"
|
||||||
|
}
|
||||||
var params = defaultSetupParams()
|
var params = defaultSetupParams()
|
||||||
err := UnmarshalJson(bytes, params)
|
err := UnmarshalJson(bytes, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ const helperPort = 47890;
|
|||||||
const maxTextScale = 1.4;
|
const maxTextScale = 1.4;
|
||||||
const minTextScale = 0.8;
|
const minTextScale = 0.8;
|
||||||
final baseInfoEdgeInsets = EdgeInsets.symmetric(
|
final baseInfoEdgeInsets = EdgeInsets.symmetric(
|
||||||
vertical: 16.ap,
|
vertical: 16.mAp,
|
||||||
horizontal: 16.ap,
|
horizontal: 16.mAp,
|
||||||
);
|
);
|
||||||
final listHeaderPadding = EdgeInsets.only(
|
final listHeaderPadding = EdgeInsets.only(
|
||||||
left: 16.ap,
|
left: 16.mAp,
|
||||||
right: 8.ap,
|
right: 8.mAp,
|
||||||
top: 24.ap,
|
top: 24.mAp,
|
||||||
bottom: 8.ap,
|
bottom: 8.mAp,
|
||||||
);
|
);
|
||||||
|
|
||||||
const watchExecution = true;
|
const watchExecution = true;
|
||||||
@@ -102,7 +102,8 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
|
|||||||
const defaultPrimaryColor = 0XFFD8C0C3;
|
const defaultPrimaryColor = 0XFFD8C0C3;
|
||||||
|
|
||||||
double getWidgetHeight(num lines) {
|
double getWidgetHeight(num lines) {
|
||||||
return max(lines * 80 + (lines - 1) * 16, 0).ap;
|
final space = 14.mAp;
|
||||||
|
return max(lines * (80.ap + space) - space, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLength = 1000;
|
const maxLength = 1000;
|
||||||
|
|||||||
@@ -19,10 +19,19 @@ class Migration {
|
|||||||
required Future<Config> Function(MigrationData data) sync,
|
required Future<Config> Function(MigrationData data) sync,
|
||||||
}) async {
|
}) async {
|
||||||
_oldVersion = await preferences.getVersion();
|
_oldVersion = await preferences.getVersion();
|
||||||
MigrationData data = MigrationData(configMap: configMap);
|
|
||||||
if (_oldVersion == currentVersion) {
|
if (_oldVersion == currentVersion) {
|
||||||
return Config.realFromJson(configMap);
|
try {
|
||||||
|
return Config.realFromJson(configMap);
|
||||||
|
} catch (_) {
|
||||||
|
final isV0 = configMap?['proxiesStyle'] != null;
|
||||||
|
if (isV0) {
|
||||||
|
_oldVersion = 0;
|
||||||
|
} else {
|
||||||
|
throw 'Local data is damaged. A reset is required to fix this issue.';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MigrationData data = MigrationData(configMap: configMap);
|
||||||
if (_oldVersion == 0 && configMap != null) {
|
if (_oldVersion == 0 && configMap != null) {
|
||||||
final clashConfigMap = await preferences.getClashConfigMap();
|
final clashConfigMap = await preferences.getClashConfigMap();
|
||||||
if (clashConfigMap != null) {
|
if (clashConfigMap != null) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/common.dart';
|
import 'package:fl_clash/models/common.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
@@ -20,6 +22,10 @@ extension NumExt on num {
|
|||||||
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
|
return this * (1 + (globalState.theme.textScaleFactor - 1) * 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double get mAp {
|
||||||
|
return this * min((1 + (globalState.theme.textScaleFactor - 1) * 0.5), 1);
|
||||||
|
}
|
||||||
|
|
||||||
TrafficShow get traffic {
|
TrafficShow get traffic {
|
||||||
final units = TrafficUnit.values;
|
final units = TrafficUnit.values;
|
||||||
var size = toDouble();
|
var size = toDouble();
|
||||||
@@ -51,7 +57,7 @@ extension NumExt on num {
|
|||||||
|
|
||||||
extension DoubleExt on double {
|
extension DoubleExt on double {
|
||||||
bool moreOrEqual(double value) {
|
bool moreOrEqual(double value) {
|
||||||
return this > value || (value - this).abs() < precisionErrorTolerance + 2;
|
return this > value || (value - this).abs() < precisionErrorTolerance + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -576,14 +576,14 @@ Future<MigrationData> _restoreTask(RootIsolateToken token) async {
|
|||||||
final scripts = results[1].cast<Script>();
|
final scripts = results[1].cast<Script>();
|
||||||
final profilesMigration = profiles.map(
|
final profilesMigration = profiles.map(
|
||||||
(item) => VM2(
|
(item) => VM2(
|
||||||
_getProfilePath(restoreDirPath, item.fileName),
|
_getProfilePath(restoreDirPath, item.id.toString()),
|
||||||
_getProfilePath(homeDirPath, item.fileName),
|
_getProfilePath(homeDirPath, item.id.toString()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final scriptsMigration = scripts.map(
|
final scriptsMigration = scripts.map(
|
||||||
(item) => VM2(
|
(item) => VM2(
|
||||||
_getScriptPath(restoreDirPath, item.fileName),
|
_getScriptPath(restoreDirPath, item.id.toString()),
|
||||||
_getScriptPath(homeDirPath, item.fileName),
|
_getScriptPath(homeDirPath, item.id.toString()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await _copyWithMapList([...profilesMigration, ...scriptsMigration]);
|
await _copyWithMapList([...profilesMigration, ...scriptsMigration]);
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ class Tray {
|
|||||||
return system.isWindows ? 'ico' : 'png';
|
return system.isWindows ? 'ico' : 'png';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> destroy() async {
|
||||||
|
await trayManager.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
String getTryIcon({required bool isStart, required bool tunEnable}) {
|
String getTryIcon({required bool isStart, required bool tunEnable}) {
|
||||||
if (system.isMacOS || !isStart) {
|
if (system.isMacOS || !isStart) {
|
||||||
return 'assets/images/icon/status_1.$trayIconSuffix';
|
return 'assets/images/icon/status_1.$trayIconSuffix';
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
await windowManager.close();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:fl_clash/plugins/app.dart';
|
|||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:fl_clash/widgets/dialog.dart';
|
import 'package:fl_clash/widgets/dialog.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -41,26 +40,28 @@ class AppController {
|
|||||||
|
|
||||||
extension InitControllerExt on AppController {
|
extension InitControllerExt on AppController {
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
try {
|
FlutterError.onError = (details) {
|
||||||
updateTray();
|
commonPrint.log(
|
||||||
autoUpdateProfiles();
|
'exception: ${details.exception} stack: ${details.stack}',
|
||||||
autoCheckUpdate();
|
logLevel: LogLevel.warning,
|
||||||
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
|
);
|
||||||
if (!_ref.read(appSettingProvider).silentLaunch) {
|
};
|
||||||
window?.show();
|
updateTray();
|
||||||
} else {
|
autoUpdateProfiles();
|
||||||
window?.hide();
|
autoCheckUpdate();
|
||||||
}
|
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
|
||||||
await _handleFailedPreference();
|
if (!_ref.read(appSettingProvider).silentLaunch) {
|
||||||
await _handlerDisclaimer();
|
window?.show();
|
||||||
await _showCrashlyticsTip();
|
} else {
|
||||||
await _connectCore();
|
window?.hide();
|
||||||
await _initCore();
|
|
||||||
await _initStatus();
|
|
||||||
_ref.read(initProvider.notifier).value = true;
|
|
||||||
} catch (e) {
|
|
||||||
commonPrint.log('init error: $e');
|
|
||||||
}
|
}
|
||||||
|
await _handleFailedPreference();
|
||||||
|
await _handlerDisclaimer();
|
||||||
|
await _showCrashlyticsTip();
|
||||||
|
await _connectCore();
|
||||||
|
await _initCore();
|
||||||
|
await _initStatus();
|
||||||
|
_ref.read(initProvider.notifier).value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleFailedPreference() async {
|
Future<void> _handleFailedPreference() async {
|
||||||
@@ -137,6 +138,11 @@ extension InitControllerExt on AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initStatus() async {
|
Future<void> _initStatus() async {
|
||||||
|
if (!globalState.needInitStatus) {
|
||||||
|
commonPrint.log('init status cancel');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commonPrint.log('init status');
|
||||||
if (system.isAndroid) {
|
if (system.isAndroid) {
|
||||||
await globalState.updateStartTime();
|
await globalState.updateStartTime();
|
||||||
}
|
}
|
||||||
@@ -549,18 +555,18 @@ extension SetupControllerExt on AppController {
|
|||||||
|
|
||||||
Future<void> updateStatus(bool isStart, {bool isInit = false}) async {
|
Future<void> updateStatus(bool isStart, {bool isInit = false}) async {
|
||||||
if (isStart) {
|
if (isStart) {
|
||||||
final res = await tryStartCore(true);
|
|
||||||
if (res) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
|
final res = await tryStartCore(true);
|
||||||
|
if (res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!_ref.read(initProvider)) {
|
if (!_ref.read(initProvider)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await globalState.handleStart([updateRunTime, updateTraffic]);
|
await globalState.handleStart([updateRunTime, updateTraffic]);
|
||||||
applyProfileDebounce(force: true, silence: true);
|
applyProfileDebounce(force: true, silence: true);
|
||||||
} else {
|
} else {
|
||||||
_ref.read(runTimeProvider.notifier).value = 0;
|
globalState.needInitStatus = false;
|
||||||
await applyProfile(
|
await applyProfile(
|
||||||
force: true,
|
force: true,
|
||||||
preloadInvoke: () async {
|
preloadInvoke: () async {
|
||||||
@@ -608,6 +614,18 @@ extension SetupControllerExt on AppController {
|
|||||||
_ref.read(checkIpNumProvider.notifier).add();
|
_ref.read(checkIpNumProvider.notifier).add();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tryCheckIp() {
|
||||||
|
final isTimeout = _ref.read(
|
||||||
|
networkDetectionProvider.select(
|
||||||
|
(state) => state.ipInfo == null && state.isLoading == false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!isTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ref.read(checkIpNumProvider.notifier).add();
|
||||||
|
}
|
||||||
|
|
||||||
void applyProfileDebounce({bool silence = false, bool force = false}) {
|
void applyProfileDebounce({bool silence = false, bool force = false}) {
|
||||||
debouncer.call(FunctionTag.applyProfile, (silence, force) {
|
debouncer.call(FunctionTag.applyProfile, (silence, force) {
|
||||||
applyProfile(silence: silence, force: force);
|
applyProfile(silence: silence, force: force);
|
||||||
@@ -635,9 +653,14 @@ extension SetupControllerExt on AppController {
|
|||||||
bool force = false,
|
bool force = false,
|
||||||
VoidCallback? preloadInvoke,
|
VoidCallback? preloadInvoke,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!force && !await needSetup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await loadingRun(
|
await loadingRun(
|
||||||
() async {
|
() async {
|
||||||
await _applyProfile(force, preloadInvoke);
|
await _setupConfig(preloadInvoke);
|
||||||
|
await updateGroups();
|
||||||
|
await updateProviders();
|
||||||
},
|
},
|
||||||
silence: true,
|
silence: true,
|
||||||
tag: !silence ? LoadingTag.proxies : null,
|
tag: !silence ? LoadingTag.proxies : null,
|
||||||
@@ -707,13 +730,8 @@ extension SetupControllerExt on AppController {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setupConfig([
|
Future<void> _setupConfig([VoidCallback? preloadInvoke]) async {
|
||||||
bool force = false,
|
commonPrint.log('setup ===>');
|
||||||
VoidCallback? preloadInvoke,
|
|
||||||
]) async {
|
|
||||||
if (!force && !await needSetup()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var profile = _ref.read(currentProfileProvider);
|
var profile = _ref.read(currentProfileProvider);
|
||||||
final nextProfile = await profile?.checkAndUpdateAndCopy();
|
final nextProfile = await profile?.checkAndUpdateAndCopy();
|
||||||
if (nextProfile != null) {
|
if (nextProfile != null) {
|
||||||
@@ -731,9 +749,6 @@ extension SetupControllerExt on AppController {
|
|||||||
globalState.lastSetupState = setupState;
|
globalState.lastSetupState = setupState;
|
||||||
if (system.isAndroid) {
|
if (system.isAndroid) {
|
||||||
globalState.lastVpnState = _ref.read(vpnStateProvider);
|
globalState.lastVpnState = _ref.read(vpnStateProvider);
|
||||||
}
|
|
||||||
|
|
||||||
if (system.isAndroid) {
|
|
||||||
preferences.saveShareState(this.sharedState);
|
preferences.saveShareState(this.sharedState);
|
||||||
}
|
}
|
||||||
final config = await getProfile(
|
final config = await getProfile(
|
||||||
@@ -753,15 +768,6 @@ extension SetupControllerExt on AppController {
|
|||||||
}
|
}
|
||||||
addCheckIp();
|
addCheckIp();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _applyProfile([
|
|
||||||
bool force = false,
|
|
||||||
VoidCallback? preloadInvoke,
|
|
||||||
]) async {
|
|
||||||
await _setupConfig(force, preloadInvoke);
|
|
||||||
await updateGroups();
|
|
||||||
await updateProviders();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CoreControllerExt on AppController {
|
extension CoreControllerExt on AppController {
|
||||||
@@ -793,9 +799,6 @@ extension CoreControllerExt on AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<bool>> _requestAdmin(bool enableTun) async {
|
Future<Result<bool>> _requestAdmin(bool enableTun) async {
|
||||||
if (system.isWindows && kDebugMode) {
|
|
||||||
return Result.success(false);
|
|
||||||
}
|
|
||||||
final realTunEnable = _ref.read(realTunEnableProvider);
|
final realTunEnable = _ref.read(realTunEnableProvider);
|
||||||
if (enableTun != realTunEnable && realTunEnable == false) {
|
if (enableTun != realTunEnable && realTunEnable == false) {
|
||||||
final code = await system.authorizeCore();
|
final code = await system.authorizeCore();
|
||||||
@@ -815,8 +818,8 @@ extension CoreControllerExt on AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restartCore([bool start = false]) async {
|
Future<void> restartCore([bool start = false]) async {
|
||||||
globalState.isUserDisconnected = true;
|
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||||
await coreController.shutdown();
|
await coreController.shutdown(true);
|
||||||
await _connectCore();
|
await _connectCore();
|
||||||
await _initCore();
|
await _initCore();
|
||||||
if (start || _ref.read(isStartProvider)) {
|
if (start || _ref.read(isStartProvider)) {
|
||||||
@@ -830,7 +833,7 @@ extension CoreControllerExt on AppController {
|
|||||||
if (coreController.isCompleted) {
|
if (coreController.isCompleted) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await restartCore();
|
await restartCore(start);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,11 +859,12 @@ extension SystemControllerExt on AppController {
|
|||||||
system.exit();
|
system.exit();
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
if (needSave) {
|
await Future.wait([
|
||||||
await preferences.saveConfig(config);
|
if (needSave) preferences.saveConfig(config),
|
||||||
}
|
if (macOS != null) macOS!.updateDns(true),
|
||||||
await proxy?.stopProxy();
|
if (proxy != null) proxy!.stopProxy(),
|
||||||
await macOS?.updateDns(true);
|
if (tray != null) tray!.destroy(),
|
||||||
|
]);
|
||||||
await coreController.destroy();
|
await coreController.destroy();
|
||||||
commonPrint.log('exit');
|
commonPrint.log('exit');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1176,8 +1180,8 @@ extension CommonControllerExt on AppController {
|
|||||||
}
|
}
|
||||||
final res = await futureFunction();
|
final res = await futureFunction();
|
||||||
return res;
|
return res;
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
commonPrint.log('$title===> $e', logLevel: LogLevel.warning);
|
commonPrint.log('$title ===> $e, $s', logLevel: LogLevel.warning);
|
||||||
if (silence) {
|
if (silence) {
|
||||||
globalState.showNotifier(e.toString());
|
globalState.showNotifier(e.toString());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ class CoreController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> shutdown() async {
|
Future<void> shutdown(bool isUser) async {
|
||||||
await _interface.shutdown();
|
await _interface.shutdown(isUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<bool> get isInit => _interface.isInit;
|
FutureOr<bool> get isInit => _interface.isInit;
|
||||||
@@ -201,9 +201,10 @@ class CoreController {
|
|||||||
final profilePath = await appPath.getProfilePath(id.toString());
|
final profilePath = await appPath.getProfilePath(id.toString());
|
||||||
final res = await _interface.getConfig(profilePath);
|
final res = await _interface.getConfig(profilePath);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
res.data['rules'] = res.data['rule'];
|
final data = Map<String, dynamic>.from(res.data);
|
||||||
res.data.remove('rule');
|
data['rules'] = data['rule'];
|
||||||
return res.data;
|
data.remove('rule');
|
||||||
|
return data;
|
||||||
} else {
|
} else {
|
||||||
throw res.message;
|
throw res.message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ mixin CoreInterface {
|
|||||||
|
|
||||||
Future<String> preload();
|
Future<String> preload();
|
||||||
|
|
||||||
Future<bool> shutdown();
|
Future<bool> shutdown(bool isUser);
|
||||||
|
|
||||||
Future<bool> get isInit;
|
Future<bool> get isInit;
|
||||||
|
|
||||||
@@ -98,9 +98,9 @@ abstract class CoreHandlerInterface with CoreInterface {
|
|||||||
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
|
commonPrint.log('Invoke ${method.name} ${DateTime.now()} $data');
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.handleWatch(
|
return await utils.handleWatch(
|
||||||
function: () async {
|
function: () async {
|
||||||
return await invoke(method: method, data: data, timeout: timeout);
|
return await invoke<T>(method: method, data: data, timeout: timeout);
|
||||||
},
|
},
|
||||||
onWatch: (data, elapsedMilliseconds) {
|
onWatch: (data, elapsedMilliseconds) {
|
||||||
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
|
commonPrint.log('Invoke ${method.name} ${elapsedMilliseconds}ms');
|
||||||
@@ -131,7 +131,7 @@ abstract class CoreHandlerInterface with CoreInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> shutdown();
|
Future<bool> shutdown(bool isUser);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> get isInit async {
|
Future<bool> get isInit async {
|
||||||
@@ -163,8 +163,8 @@ abstract class CoreHandlerInterface with CoreInterface {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result> getConfig(String path) async {
|
Future<Result> getConfig(String path) async {
|
||||||
return await _invoke<Result>(method: ActionMethod.getConfig, data: path) ??
|
final res = await _invoke(method: ActionMethod.getConfig, data: path);
|
||||||
Result.success({});
|
return res ?? Result.success({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ class CoreLib extends CoreHandlerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> shutdown() async {
|
Future<bool> shutdown(_) async {
|
||||||
await service?.shutdown();
|
if (!_connectedCompleter.isCompleted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
_connectedCompleter = Completer();
|
_connectedCompleter = Completer();
|
||||||
return true;
|
return service?.shutdown() ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
|
|
||||||
Completer<Socket> _socketCompleter = Completer();
|
Completer<Socket> _socketCompleter = Completer();
|
||||||
|
|
||||||
|
Completer<bool> _shutdownCompleter = Completer();
|
||||||
|
|
||||||
final Map<String, Completer> _callbackCompleterMap = {};
|
final Map<String, Completer> _callbackCompleterMap = {};
|
||||||
|
|
||||||
Process? _process;
|
Process? _process;
|
||||||
@@ -35,6 +37,9 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
if (result.id?.isEmpty == true) {
|
if (result.id?.isEmpty == true) {
|
||||||
coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
|
coreEventManager.sendEvent(CoreEvent.fromJson(result.data));
|
||||||
}
|
}
|
||||||
|
if (completer?.isCompleted == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
completer?.complete(data);
|
completer?.complete(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +81,9 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
})
|
})
|
||||||
.onDone(() {
|
.onDone(() {
|
||||||
_handleInvokeCrashEvent();
|
_handleInvokeCrashEvent();
|
||||||
|
if (!_shutdownCompleter.isCompleted) {
|
||||||
|
_shutdownCompleter.complete(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +95,7 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
|
|
||||||
Future<void> start() async {
|
Future<void> start() async {
|
||||||
if (_process != null) {
|
if (_process != null) {
|
||||||
await shutdown();
|
await shutdown(false);
|
||||||
}
|
}
|
||||||
final serverSocket = await _serverCompleter.future;
|
final serverSocket = await _serverCompleter.future;
|
||||||
final arg = system.isWindows
|
final arg = system.isWindows
|
||||||
@@ -113,7 +121,7 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
@override
|
@override
|
||||||
destroy() async {
|
destroy() async {
|
||||||
final server = await _serverCompleter.future;
|
final server = await _serverCompleter.future;
|
||||||
await shutdown();
|
await shutdown(false);
|
||||||
await server.close();
|
await server.close();
|
||||||
await _deleteSocketFile();
|
await _deleteSocketFile();
|
||||||
return true;
|
return true;
|
||||||
@@ -135,12 +143,16 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
if (_socketCompleter.isCompleted) {
|
if (_socketCompleter.isCompleted) {
|
||||||
final socket = await _socketCompleter.future;
|
final socket = await _socketCompleter.future;
|
||||||
_socketCompleter = Completer();
|
_socketCompleter = Completer();
|
||||||
socket.close();
|
await socket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
shutdown() async {
|
shutdown(bool isUser) async {
|
||||||
|
if (!_socketCompleter.isCompleted && _process == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_shutdownCompleter = Completer();
|
||||||
await _destroySocket();
|
await _destroySocket();
|
||||||
_clearCompleter();
|
_clearCompleter();
|
||||||
if (system.isWindows) {
|
if (system.isWindows) {
|
||||||
@@ -148,7 +160,11 @@ class CoreService extends CoreHandlerInterface {
|
|||||||
}
|
}
|
||||||
_process?.kill();
|
_process?.kill();
|
||||||
_process = null;
|
_process = null;
|
||||||
return true;
|
if (isUser) {
|
||||||
|
return _shutdownCompleter.future;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearCompleter() {
|
void _clearCompleter() {
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"defaultText": MessageLookupByLibrary.simpleMessage("Default"),
|
"defaultText": MessageLookupByLibrary.simpleMessage("Default"),
|
||||||
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
|
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
|
||||||
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
|
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
|
||||||
|
"delayTest": MessageLookupByLibrary.simpleMessage("Delay Test"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
||||||
"deleteMultipTip": m1,
|
"deleteMultipTip": m1,
|
||||||
"deleteTip": m2,
|
"deleteTip": m2,
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
|
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
|
||||||
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
|
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
|
||||||
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
|
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
|
||||||
|
"delayTest": MessageLookupByLibrary.simpleMessage("遅延テスト"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("削除"),
|
"delete": MessageLookupByLibrary.simpleMessage("削除"),
|
||||||
"deleteMultipTip": m1,
|
"deleteMultipTip": m1,
|
||||||
"deleteTip": m2,
|
"deleteTip": m2,
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
|
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
|
||||||
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
|
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
|
||||||
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
|
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
|
||||||
|
"delayTest": MessageLookupByLibrary.simpleMessage("Тест задержки"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
|
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
|
||||||
"deleteMultipTip": m1,
|
"deleteMultipTip": m1,
|
||||||
"deleteTip": m2,
|
"deleteTip": m2,
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
|
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
|
||||||
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
|
||||||
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
|
||||||
|
"delayTest": MessageLookupByLibrary.simpleMessage("延迟测试"),
|
||||||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||||
"deleteMultipTip": m1,
|
"deleteMultipTip": m1,
|
||||||
"deleteTip": m2,
|
"deleteTip": m2,
|
||||||
|
|||||||
@@ -3743,6 +3743,11 @@ class AppLocalizations {
|
|||||||
String get addProfile {
|
String get addProfile {
|
||||||
return Intl.message('Add Profile', name: 'addProfile', desc: '', args: []);
|
return Intl.message('Add Profile', name: 'addProfile', desc: '', args: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Delay Test`
|
||||||
|
String get delayTest {
|
||||||
|
return Intl.message('Delay Test', name: 'delayTest', desc: '', args: []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fl_clash/pages/error.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -9,11 +10,22 @@ import 'application.dart';
|
|||||||
import 'common/common.dart';
|
import 'common/common.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
try {
|
||||||
final version = await system.version;
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final container = await globalState.init(version);
|
final version = await system.version;
|
||||||
HttpOverrides.global = FlClashHttpOverrides();
|
final container = await globalState.init(version);
|
||||||
runApp(
|
HttpOverrides.global = FlClashHttpOverrides();
|
||||||
UncontrolledProviderScope(container: container, child: const Application()),
|
runApp(
|
||||||
);
|
UncontrolledProviderScope(
|
||||||
|
container: container,
|
||||||
|
child: const Application(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
return runApp(
|
||||||
|
MaterialApp(
|
||||||
|
home: InitErrorScreen(error: e, stack: s),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ class _AppStateManagerState extends ConsumerState<AppStateManager>
|
|||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
render?.resume();
|
render?.resume();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
appController.tryCheckIp();
|
||||||
if (system.isAndroid) {
|
if (system.isAndroid) {
|
||||||
appController.addCheckIp();
|
|
||||||
appController.tryStartCore();
|
appController.tryStartCore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,16 +98,14 @@ class _CoreContainerState extends ConsumerState<CoreManager>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onCrash(String message) async {
|
Future<void> onCrash(String message) async {
|
||||||
if (!globalState.isUserDisconnected &&
|
|
||||||
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
|
||||||
context.showNotifier(message);
|
|
||||||
}
|
|
||||||
globalState.isUserDisconnected = false;
|
|
||||||
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
|
if (ref.read(coreStatusProvider) != CoreStatus.connected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
|
||||||
await coreController.shutdown();
|
if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
||||||
|
context.showNotifier(message);
|
||||||
|
}
|
||||||
|
await coreController.shutdown(false);
|
||||||
super.onCrash(message);
|
super.onCrash(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/common.dart';
|
import 'package:fl_clash/models/common.dart';
|
||||||
@@ -125,7 +127,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
|
final res = utf8.decode(file.bytes?.toList() ?? []);
|
||||||
_controller.text = res;
|
_controller.text = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
121
lib/pages/error.dart
Normal file
121
lib/pages/error.dart
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import 'package:fl_clash/common/color.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class InitErrorScreen extends StatelessWidget {
|
||||||
|
final Object error;
|
||||||
|
final StackTrace stack;
|
||||||
|
|
||||||
|
const InitErrorScreen({super.key, required this.error, required this.stack});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Init Failed'),
|
||||||
|
backgroundColor: colorScheme.error,
|
||||||
|
foregroundColor: colorScheme.onError,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.report_problem,
|
||||||
|
color: colorScheme.error,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'The application encountered a critical error during startup and cannot continue.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildSectionLabel('Error Details:'),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.errorContainer.opacity50,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: colorScheme.error.opacity50),
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
error.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: colorScheme.onErrorContainer,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildSectionLabel('Stack Trace:'),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.grey[900]
|
||||||
|
: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey.opacity50),
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
stack.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'monospace', // Makes code easier to read
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 80),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
onPressed: () => _copyToClipboard(context),
|
||||||
|
label: const Text('Copy Details'),
|
||||||
|
icon: const Icon(Icons.copy),
|
||||||
|
backgroundColor: colorScheme.error,
|
||||||
|
foregroundColor: colorScheme.onError,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSectionLabel(String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _copyToClipboard(BuildContext context) {
|
||||||
|
final text = '=== ERROR ===\n$error\n\n=== STACK TRACE ===\n$stack';
|
||||||
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Error details copied to clipboard'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export 'editor.dart';
|
||||||
|
export 'error.dart';
|
||||||
export 'home.dart';
|
export 'home.dart';
|
||||||
export 'scan.dart';
|
export 'scan.dart';
|
||||||
export 'editor.dart';
|
|
||||||
@@ -1822,7 +1822,7 @@ final class NetworkDetectionProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$networkDetectionHash() => r'29770de6a7ea2ac04b6584145d5200a7d43e45b0';
|
String _$networkDetectionHash() => r'501babec2bbf2a38e4fef96cf20c76e9352bc5ee';
|
||||||
|
|
||||||
abstract class _$NetworkDetection extends $Notifier<NetworkDetectionState> {
|
abstract class _$NetworkDetection extends $Notifier<NetworkDetectionState> {
|
||||||
NetworkDetectionState build();
|
NetworkDetectionState build();
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
|
|
||||||
import 'common/common.dart';
|
import 'common/common.dart';
|
||||||
import 'database/database.dart';
|
import 'database/database.dart';
|
||||||
import 'enum/enum.dart';
|
|
||||||
import 'l10n/l10n.dart';
|
import 'l10n/l10n.dart';
|
||||||
import 'models/models.dart';
|
import 'models/models.dart';
|
||||||
|
|
||||||
@@ -39,10 +38,10 @@ class GlobalState {
|
|||||||
late Measure measure;
|
late Measure measure;
|
||||||
late CommonTheme theme;
|
late CommonTheme theme;
|
||||||
late Color accentColor;
|
late Color accentColor;
|
||||||
|
bool needInitStatus = true;
|
||||||
CorePalette? corePalette;
|
CorePalette? corePalette;
|
||||||
DateTime? startTime;
|
DateTime? startTime;
|
||||||
UpdateTasks tasks = [];
|
UpdateTasks tasks = [];
|
||||||
bool isUserDisconnected = false;
|
|
||||||
SetupState? lastSetupState;
|
SetupState? lastSetupState;
|
||||||
VpnState? lastVpnState;
|
VpnState? lastVpnState;
|
||||||
|
|
||||||
@@ -56,12 +55,6 @@ class GlobalState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<ProviderContainer> init(int version) async {
|
Future<ProviderContainer> init(int version) async {
|
||||||
FlutterError.onError = (details) {
|
|
||||||
commonPrint.log(
|
|
||||||
'exception: ${details.exception} stack: ${details.stack}',
|
|
||||||
logLevel: LogLevel.warning,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
coreSHA256 = const String.fromEnvironment('CORE_SHA256');
|
coreSHA256 = const String.fromEnvironment('CORE_SHA256');
|
||||||
isPre = const String.fromEnvironment('APP_ENV') != 'stable';
|
isPre = const String.fromEnvironment('APP_ENV') != 'stable';
|
||||||
await _initDynamicColor();
|
await _initDynamicColor();
|
||||||
@@ -95,9 +88,7 @@ class GlobalState {
|
|||||||
configMap,
|
configMap,
|
||||||
sync: (data) async {
|
sync: (data) async {
|
||||||
final newConfigMap = data.configMap;
|
final newConfigMap = data.configMap;
|
||||||
final config = newConfigMap != null
|
final config = Config.realFromJson(newConfigMap);
|
||||||
? Config.fromJson(newConfigMap)
|
|
||||||
: Config(themeProps: defaultThemeProps);
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
database.restore(data.profiles, data.scripts, data.rules, data.links),
|
database.restore(data.profiles, data.scripts, data.rules, data.links),
|
||||||
preferences.saveConfig(config),
|
preferences.saveConfig(config),
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dashboardState = ref.watch(dashboardStateProvider);
|
final dashboardState = ref.watch(dashboardStateProvider);
|
||||||
final columns = max(4 * ((dashboardState.contentWidth / 280).ceil()), 8);
|
final columns = max(4 * ((dashboardState.contentWidth / 280).ceil()), 8);
|
||||||
final spacing = 14.ap;
|
final spacing = 14.mAp;
|
||||||
final children = [
|
final children = [
|
||||||
...dashboardState.dashboardWidgets
|
...dashboardState.dashboardWidgets
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
@@ -41,45 +41,54 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
|
|||||||
final color = context.colorScheme.onSurfaceVariant.opacity80;
|
final color = context.colorScheme.onSurfaceVariant.opacity80;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: getWidgetHeight(2),
|
height: getWidgetHeight(2),
|
||||||
child: CommonCard(
|
child: RepaintBoundary(
|
||||||
onPressed: () {},
|
child: CommonCard(
|
||||||
info: Info(
|
onPressed: () {},
|
||||||
label: appLocalizations.networkSpeed,
|
child: Consumer(
|
||||||
iconData: Icons.speed_sharp,
|
builder: (_, ref, _) {
|
||||||
),
|
final traffics = ref.watch(trafficsProvider).list;
|
||||||
child: Consumer(
|
return Column(
|
||||||
builder: (_, ref, _) {
|
children: [
|
||||||
final traffics = ref.watch(trafficsProvider).list;
|
Padding(
|
||||||
return Stack(
|
padding: baseInfoEdgeInsets.copyWith(bottom: 0),
|
||||||
children: [
|
child: Row(
|
||||||
Positioned.fill(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsets.all(
|
Flexible(
|
||||||
16,
|
child: InfoHeader(
|
||||||
).copyWith(bottom: 0, left: 0, right: 0),
|
padding: EdgeInsets.zero,
|
||||||
child: LineChart(
|
info: Info(
|
||||||
gradient: true,
|
label: appLocalizations.networkSpeed,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
iconData: Icons.speed_sharp,
|
||||||
points: _getPoints(traffics),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
_getLastTraffic(traffics).speedText,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Flexible(
|
||||||
Positioned(
|
child: Padding(
|
||||||
top: 0,
|
padding: EdgeInsets.all(
|
||||||
right: 0,
|
16,
|
||||||
child: Transform.translate(
|
).copyWith(bottom: 0, left: 0, right: 0),
|
||||||
offset: Offset(-16, -20),
|
child: LineChart(
|
||||||
child: Text(
|
gradient: true,
|
||||||
_getLastTraffic(traffics).speedText,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
points: _getPoints(traffics),
|
||||||
color: color,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ class _StartButtonState extends ConsumerState<StartButton>
|
|||||||
parent: _controller!,
|
parent: _controller!,
|
||||||
curve: Curves.easeOutBack,
|
curve: Curves.easeOutBack,
|
||||||
);
|
);
|
||||||
ref.listenManual(runTimeProvider.select((state) => state != null), (
|
ref.listenManual(isStartProvider, (prev, next) {
|
||||||
prev,
|
|
||||||
next,
|
|
||||||
) {
|
|
||||||
if (next != isStart) {
|
if (next != isStart) {
|
||||||
isStart = next;
|
isStart = next;
|
||||||
updateController();
|
updateController();
|
||||||
@@ -55,7 +52,7 @@ class _StartButtonState extends ConsumerState<StartButton>
|
|||||||
isStart = !isStart;
|
isStart = !isStart;
|
||||||
updateController();
|
updateController();
|
||||||
debouncer.call(FunctionTag.updateStatus, () {
|
debouncer.call(FunctionTag.updateStatus, () {
|
||||||
appController.updateStatus(isStart);
|
appController.updateStatus(isStart, isInit: !ref.read(initProvider));
|
||||||
}, duration: commonDuration);
|
}, duration: commonDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class _EditProfileViewState extends State<EditProfileView> {
|
|||||||
late final TextEditingController _labelController;
|
late final TextEditingController _labelController;
|
||||||
late final TextEditingController _urlController;
|
late final TextEditingController _urlController;
|
||||||
late final TextEditingController _autoUpdateDurationController;
|
late final TextEditingController _autoUpdateDurationController;
|
||||||
late final bool _autoUpdate;
|
late bool _autoUpdate;
|
||||||
String? _rawText;
|
String? _rawText;
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
final _fileInfoNotifier = ValueNotifier<FileInfo?>(null);
|
final _fileInfoNotifier = ValueNotifier<FileInfo?>(null);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:fl_clash/controller.dart';
|
|||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/features/overwrite/rule.dart';
|
import 'package:fl_clash/features/overwrite/rule.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
|
import 'package:fl_clash/pages/editor.dart';
|
||||||
import 'package:fl_clash/providers/database.dart';
|
import 'package:fl_clash/providers/database.dart';
|
||||||
import 'package:fl_clash/providers/providers.dart';
|
import 'package:fl_clash/providers/providers.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
@@ -28,10 +29,33 @@ class _OverwriteViewState extends ConsumerState<OverwriteView> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _handlePreview() async {
|
||||||
|
final profile = ref.read(profileProvider(widget.profileId));
|
||||||
|
if (profile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final configMap = await appController.getProfileWithId(profile.id);
|
||||||
|
final content = await encodeYamlTask(configMap);
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final previewPage = EditorPage(title: profile.realLabel, content: content);
|
||||||
|
BaseNavigator.push<String>(context, previewPage);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CommonScaffold(
|
return CommonScaffold(
|
||||||
title: appLocalizations.override,
|
title: appLocalizations.override,
|
||||||
|
actions: [
|
||||||
|
CommonMinFilledButtonTheme(
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: _handlePreview,
|
||||||
|
child: Text(appLocalizations.preview),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
],
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [_Title(widget.profileId), _Content(widget.profileId)],
|
slivers: [_Title(widget.profileId), _Content(widget.profileId)],
|
||||||
),
|
),
|
||||||
@@ -341,7 +365,9 @@ class _ScriptContent extends ConsumerWidget {
|
|||||||
|
|
||||||
void _handleChange(WidgetRef ref, int scriptId) {
|
void _handleChange(WidgetRef ref, int scriptId) {
|
||||||
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
|
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
|
||||||
return state.copyWith(scriptId: scriptId);
|
return state.copyWith(
|
||||||
|
scriptId: state.scriptId == scriptId ? null : scriptId,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class ProfilesView extends StatefulWidget {
|
|||||||
|
|
||||||
class _ProfilesViewState extends State<ProfilesView> {
|
class _ProfilesViewState extends State<ProfilesView> {
|
||||||
Function? applyConfigDebounce;
|
Function? applyConfigDebounce;
|
||||||
|
bool _isUpdating = false;
|
||||||
|
|
||||||
void _handleShowAddExtendPage() {
|
void _handleShowAddExtendPage() {
|
||||||
showExtend(
|
showExtend(
|
||||||
@@ -40,6 +41,10 @@ class _ProfilesViewState extends State<ProfilesView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateProfiles(List<Profile> profiles) async {
|
Future<void> _updateProfiles(List<Profile> profiles) async {
|
||||||
|
if (_isUpdating == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isUpdating = true;
|
||||||
final List<UpdatingMessage> messages = [];
|
final List<UpdatingMessage> messages = [];
|
||||||
final updateProfiles = profiles.map<Future>((profile) async {
|
final updateProfiles = profiles.map<Future>((profile) async {
|
||||||
if (profile.type == ProfileType.file) return;
|
if (profile.type == ProfileType.file) return;
|
||||||
@@ -55,6 +60,7 @@ class _ProfilesViewState extends State<ProfilesView> {
|
|||||||
if (messages.isNotEmpty) {
|
if (messages.isNotEmpty) {
|
||||||
globalState.showAllUpdatingMessagesDialog(messages);
|
globalState.showAllUpdatingMessagesDialog(messages);
|
||||||
}
|
}
|
||||||
|
_isUpdating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(List<Profile> profiles) {
|
List<Widget> _buildActions(List<Profile> profiles) {
|
||||||
@@ -86,10 +92,10 @@ class _ProfilesViewState extends State<ProfilesView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFAB() {
|
Widget _buildFAB() {
|
||||||
return FloatingActionButton(
|
return CommonFloatingActionButton(
|
||||||
heroTag: null,
|
|
||||||
onPressed: _handleShowAddExtendPage,
|
onPressed: _handleShowAddExtendPage,
|
||||||
child: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
label: context.appLocalizations.addProfile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ class _ProfilesViewState extends State<ProfilesView> {
|
|||||||
builder: (_, ref, _) {
|
builder: (_, ref, _) {
|
||||||
final isLoading = ref.watch(loadingProvider(LoadingTag.profiles));
|
final isLoading = ref.watch(loadingProvider(LoadingTag.profiles));
|
||||||
final state = ref.watch(profilesStateProvider);
|
final state = ref.watch(profilesStateProvider);
|
||||||
final spacing = 14.ap;
|
final spacing = 14.mAp;
|
||||||
return CommonScaffold(
|
return CommonScaffold(
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
title: appLocalizations.profiles,
|
title: appLocalizations.profiles,
|
||||||
|
|||||||
@@ -410,19 +410,15 @@ class _DelayTestButtonState extends State<DelayTestButton>
|
|||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _controller.view,
|
animation: _controller.view,
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return SizedBox(
|
return FadeTransition(
|
||||||
width: 56,
|
opacity: _animation,
|
||||||
height: 56,
|
child: ScaleTransition(scale: _animation, child: child),
|
||||||
child: FadeTransition(
|
|
||||||
opacity: _animation,
|
|
||||||
child: ScaleTransition(scale: _animation, child: child),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: FloatingActionButton(
|
child: CommonFloatingActionButton(
|
||||||
heroTag: null,
|
|
||||||
onPressed: _healthcheck,
|
onPressed: _healthcheck,
|
||||||
child: const Icon(Icons.network_ping),
|
label: appLocalizations.delayTest,
|
||||||
|
icon: const Icon(Icons.network_ping),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:fl_clash/widgets/inherited.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ScrollOverBuilder extends StatefulWidget {
|
class ScrollOverBuilder extends StatefulWidget {
|
||||||
@@ -35,58 +36,18 @@ class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class ProxiesActionsBuilder extends StatelessWidget {
|
class FloatingActionButtonExtendedBuilder extends StatelessWidget {
|
||||||
// final Widget? child;
|
final Widget Function(bool isExtend) builder;
|
||||||
// final Widget Function(
|
|
||||||
// ProxiesActionsState state,
|
|
||||||
// Widget? child,
|
|
||||||
// ) builder;
|
|
||||||
//
|
|
||||||
// const ProxiesActionsBuilder({
|
|
||||||
// super.key,
|
|
||||||
// required this.child,
|
|
||||||
// required this.builder,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return Selector<AppState, ProxiesActionsState>(
|
|
||||||
// selector: (_, appState) => ProxiesActionsState(
|
|
||||||
// isCurrent: appState.currentLabel == "proxies",
|
|
||||||
// hasProvider: appState.providers.isNotEmpty,
|
|
||||||
// ),
|
|
||||||
// builder: (_, state, child) => builder(state, child),
|
|
||||||
// child: child,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class ActiveBuilder extends StatelessWidget {
|
const FloatingActionButtonExtendedBuilder({super.key, required this.builder});
|
||||||
// final String label;
|
|
||||||
// final StateAndChildWidgetBuilder<bool> builder;
|
@override
|
||||||
// final Widget? child;
|
Widget build(BuildContext context) {
|
||||||
//
|
final isExtended =
|
||||||
// const ActiveBuilder({
|
CommonScaffoldFabExtendedProvider.of(context)?.isExtended ?? true;
|
||||||
// super.key,
|
return builder(isExtended);
|
||||||
// required this.label,
|
}
|
||||||
// required this.builder,
|
}
|
||||||
// required this.child,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return Selector<AppState, bool>(
|
|
||||||
// selector: (_, appState) => appState.currentLabel == label,
|
|
||||||
// builder: (_, state, child) {
|
|
||||||
// return builder(
|
|
||||||
// state,
|
|
||||||
// child,
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// child: child,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
typedef StateWidgetBuilder<T> = Widget Function(T state);
|
typedef StateWidgetBuilder<T> = Widget Function(T state);
|
||||||
|
|
||||||
|
|||||||
56
lib/widgets/button.dart
Normal file
56
lib/widgets/button.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:fl_clash/common/common.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'builder.dart';
|
||||||
|
|
||||||
|
class CommonFloatingActionButton extends StatelessWidget {
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final Icon icon;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const CommonFloatingActionButton({
|
||||||
|
super.key,
|
||||||
|
this.onPressed,
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
|
||||||
|
.copyWith(
|
||||||
|
extendedIconLabelSpacing: 0,
|
||||||
|
extendedPadding: EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: FloatingActionButtonExtendedBuilder(
|
||||||
|
builder: (isExtended) {
|
||||||
|
return FloatingActionButton.extended(
|
||||||
|
heroTag: null,
|
||||||
|
icon: icon,
|
||||||
|
onPressed: onPressed,
|
||||||
|
isExtended: true,
|
||||||
|
label: AnimatedSize(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
duration: midDuration,
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
duration: midDuration,
|
||||||
|
opacity: isExtended ? 1.0 : 0.4,
|
||||||
|
curve: Curves.linear,
|
||||||
|
child: isExtended
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: Text(label, softWrap: false),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ class InfoHeader extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
EdgeInsetsGeometry nextPadding = (padding ?? baseInfoEdgeInsets);
|
EdgeInsetsGeometry nextPadding = (padding ?? baseInfoEdgeInsets);
|
||||||
if (actions.isNotEmpty) {
|
if (actions.isNotEmpty) {
|
||||||
nextPadding = nextPadding.subtract(EdgeInsets.symmetric(vertical: 8.ap));
|
nextPadding = nextPadding.subtract(EdgeInsets.symmetric(vertical: 8.mAp));
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: nextPadding,
|
padding: nextPadding,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:fl_clash/common/common.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
typedef WrapBuilder = Widget Function(Widget child);
|
typedef WrapBuilder = Widget Function(Widget child);
|
||||||
|
|
||||||
@@ -27,10 +28,10 @@ class Grid extends MultiChildRenderObjectWidget {
|
|||||||
TextDirection? textDirection,
|
TextDirection? textDirection,
|
||||||
this.mainAxisExtent,
|
this.mainAxisExtent,
|
||||||
List<Widget>? children,
|
List<Widget>? children,
|
||||||
}) : crossAxisCount = crossAxisCount ?? 1,
|
}) : crossAxisCount = crossAxisCount ?? 1,
|
||||||
axisDirection = axisDirection ?? AxisDirection.down,
|
axisDirection = axisDirection ?? AxisDirection.down,
|
||||||
textDirection = textDirection ?? TextDirection.ltr,
|
textDirection = textDirection ?? TextDirection.ltr,
|
||||||
super(children: children ?? const []);
|
super(children: children ?? const []);
|
||||||
|
|
||||||
const Grid.baseGap({
|
const Grid.baseGap({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -42,15 +43,15 @@ class Grid extends MultiChildRenderObjectWidget {
|
|||||||
double? mainAxisExtent,
|
double? mainAxisExtent,
|
||||||
List<Widget>? children,
|
List<Widget>? children,
|
||||||
}) : this(
|
}) : this(
|
||||||
key: key,
|
key: key,
|
||||||
mainAxisSpacing: mainAxisSpacing,
|
mainAxisSpacing: mainAxisSpacing,
|
||||||
crossAxisSpacing: crossAxisSpacing,
|
crossAxisSpacing: crossAxisSpacing,
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
axisDirection: axisDirection,
|
axisDirection: axisDirection,
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
mainAxisExtent: mainAxisExtent,
|
mainAxisExtent: mainAxisExtent,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) {
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
@@ -65,10 +66,7 @@ class Grid extends MultiChildRenderObjectWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRenderObject(
|
void updateRenderObject(BuildContext context, RenderGrid renderObject) {
|
||||||
BuildContext context,
|
|
||||||
RenderGrid renderObject,
|
|
||||||
) {
|
|
||||||
renderObject
|
renderObject
|
||||||
..mainAxisSpacing = mainAxisSpacing
|
..mainAxisSpacing = mainAxisSpacing
|
||||||
..mainAxisExtent = mainAxisExtent
|
..mainAxisExtent = mainAxisExtent
|
||||||
@@ -90,12 +88,12 @@ class RenderGrid extends RenderBox
|
|||||||
required AxisDirection axisDirection,
|
required AxisDirection axisDirection,
|
||||||
required TextDirection textDirection,
|
required TextDirection textDirection,
|
||||||
double? mainAxisExtent,
|
double? mainAxisExtent,
|
||||||
}) : _crossAxisCount = crossAxisCount,
|
}) : _crossAxisCount = crossAxisCount,
|
||||||
_crossAxisSpacing = crossAxisSpacing,
|
_crossAxisSpacing = crossAxisSpacing,
|
||||||
_mainAxisSpacing = mainAxisSpacing,
|
_mainAxisSpacing = mainAxisSpacing,
|
||||||
_axisDirection = axisDirection,
|
_axisDirection = axisDirection,
|
||||||
_textDirection = textDirection,
|
_textDirection = textDirection,
|
||||||
_mainAxisExtent = mainAxisExtent;
|
_mainAxisExtent = mainAxisExtent;
|
||||||
|
|
||||||
int _crossAxisCount;
|
int _crossAxisCount;
|
||||||
|
|
||||||
@@ -214,15 +212,10 @@ class RenderGrid extends RenderBox
|
|||||||
GridParentData childParentData,
|
GridParentData childParentData,
|
||||||
int crossAxisCount,
|
int crossAxisCount,
|
||||||
) {
|
) {
|
||||||
return math.min(
|
return math.min(childParentData.crossAxisCellCount ?? 1, crossAxisCount);
|
||||||
childParentData.crossAxisCellCount ?? 1,
|
|
||||||
crossAxisCount,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Size _computeSize({
|
Size _computeSize({required BoxConstraints constraints}) {
|
||||||
required BoxConstraints constraints,
|
|
||||||
}) {
|
|
||||||
final crossAxisExtent = mainAxis == Axis.vertical
|
final crossAxisExtent = mainAxis == Axis.vertical
|
||||||
? constraints.maxWidth
|
? constraints.maxWidth
|
||||||
: constraints.maxHeight;
|
: constraints.maxHeight;
|
||||||
@@ -245,11 +238,13 @@ class RenderGrid extends RenderBox
|
|||||||
? BoxConstraints.tightFor(width: crossAxisExtent)
|
? BoxConstraints.tightFor(width: crossAxisExtent)
|
||||||
: BoxConstraints.tightFor(height: crossAxisExtent);
|
: BoxConstraints.tightFor(height: crossAxisExtent);
|
||||||
_layoutChild(child, childConstraints, parentUsesSize: true);
|
_layoutChild(child, childConstraints, parentUsesSize: true);
|
||||||
mainAxisExtent =
|
mainAxisExtent = mainAxis == Axis.vertical
|
||||||
mainAxis == Axis.vertical ? child.size.height : child.size.width;
|
? child.size.height
|
||||||
|
: child.size.width;
|
||||||
} else {
|
} else {
|
||||||
final mainAxisCellCount = childParentData.mainAxisCellCount ?? 1;
|
final mainAxisCellCount = childParentData.mainAxisCellCount ?? 1;
|
||||||
mainAxisExtent = (this.mainAxisExtent ?? stride) * mainAxisCellCount -
|
mainAxisExtent =
|
||||||
|
(this.mainAxisExtent ?? stride) * mainAxisCellCount -
|
||||||
mainAxisSpacing;
|
mainAxisSpacing;
|
||||||
childParentData.realMainAxisExtent = mainAxisExtent;
|
childParentData.realMainAxisExtent = mainAxisExtent;
|
||||||
final childSize = mainAxis == Axis.vertical
|
final childSize = mainAxis == Axis.vertical
|
||||||
@@ -265,9 +260,7 @@ class RenderGrid extends RenderBox
|
|||||||
? Offset(crossAxisOffset, mainAxisOffset)
|
? Offset(crossAxisOffset, mainAxisOffset)
|
||||||
: Offset(mainAxisOffset, crossAxisOffset);
|
: Offset(mainAxisOffset, crossAxisOffset);
|
||||||
childParentData.offset = offset;
|
childParentData.offset = offset;
|
||||||
|
|
||||||
final nextOffset = mainAxisOffset + mainAxisExtent + mainAxisSpacing;
|
final nextOffset = mainAxisOffset + mainAxisExtent + mainAxisSpacing;
|
||||||
|
|
||||||
for (int i = 0; i < crossAxisCellCount; i++) {
|
for (int i = 0; i < crossAxisCellCount; i++) {
|
||||||
offsets[origin.crossAxisIndex + i] = nextOffset;
|
offsets[origin.crossAxisIndex + i] = nextOffset;
|
||||||
}
|
}
|
||||||
@@ -281,7 +274,8 @@ class RenderGrid extends RenderBox
|
|||||||
final childParentData = _getParentData(child);
|
final childParentData = _getParentData(child);
|
||||||
final offset = childParentData.offset;
|
final offset = childParentData.offset;
|
||||||
final crossAxisOffset = offset.getCrossAxisOffset(mainAxis);
|
final crossAxisOffset = offset.getCrossAxisOffset(mainAxis);
|
||||||
final mainAxisOffset = mainAxisExtent -
|
final mainAxisOffset =
|
||||||
|
mainAxisExtent -
|
||||||
offset.getMainAxisOffset(mainAxis) -
|
offset.getMainAxisOffset(mainAxis) -
|
||||||
childParentData.realMainAxisExtent!;
|
childParentData.realMainAxisExtent!;
|
||||||
final newOffset = mainAxis == Axis.vertical
|
final newOffset = mainAxis == Axis.vertical
|
||||||
@@ -365,15 +359,11 @@ class GridItem extends ParentDataWidget<GridParentData> {
|
|||||||
@override
|
@override
|
||||||
Type get debugTypicalAncestorWidgetClass => GridItem;
|
Type get debugTypicalAncestorWidgetClass => GridItem;
|
||||||
|
|
||||||
GridItem wrap({
|
GridItem wrap({required WrapBuilder builder}) {
|
||||||
required WrapBuilder builder,
|
|
||||||
}) {
|
|
||||||
return GridItem(
|
return GridItem(
|
||||||
mainAxisCellCount: mainAxisCellCount,
|
mainAxisCellCount: mainAxisCellCount,
|
||||||
crossAxisCellCount: crossAxisCellCount,
|
crossAxisCellCount: crossAxisCellCount,
|
||||||
child: builder(
|
child: builder(child),
|
||||||
child,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,11 +385,13 @@ _Origin _getOrigin(List<double> offsets, int crossAxisCount) {
|
|||||||
}
|
}
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int span = 0;
|
int span = 0;
|
||||||
for (int j = 0;
|
for (
|
||||||
span < crossAxisCount &&
|
int j = 0;
|
||||||
j < length &&
|
span < crossAxisCount &&
|
||||||
length - j >= crossAxisCount - span;
|
j < length &&
|
||||||
j++) {
|
length - j >= crossAxisCount - span;
|
||||||
|
j++
|
||||||
|
) {
|
||||||
if (offset.moreOrEqual(offsets[j])) {
|
if (offset.moreOrEqual(offsets[j])) {
|
||||||
span++;
|
span++;
|
||||||
if (span == crossAxisCount) {
|
if (span == crossAxisCount) {
|
||||||
|
|||||||
41
lib/widgets/inherited.dart
Normal file
41
lib/widgets/inherited.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CommonScaffoldBackActionProvider extends InheritedWidget {
|
||||||
|
final VoidCallback? backAction;
|
||||||
|
|
||||||
|
const CommonScaffoldBackActionProvider({
|
||||||
|
super.key,
|
||||||
|
required this.backAction,
|
||||||
|
required super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
static CommonScaffoldBackActionProvider? of(BuildContext context) {
|
||||||
|
return context
|
||||||
|
.dependOnInheritedWidgetOfExactType<CommonScaffoldBackActionProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(CommonScaffoldBackActionProvider oldWidget) =>
|
||||||
|
backAction != oldWidget.backAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommonScaffoldFabExtendedProvider extends InheritedWidget {
|
||||||
|
final bool isExtended;
|
||||||
|
|
||||||
|
const CommonScaffoldFabExtendedProvider({
|
||||||
|
super.key,
|
||||||
|
required this.isExtended,
|
||||||
|
required super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
static CommonScaffoldFabExtendedProvider? of(BuildContext context) {
|
||||||
|
return context
|
||||||
|
.dependOnInheritedWidgetOfExactType<
|
||||||
|
CommonScaffoldFabExtendedProvider
|
||||||
|
>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(CommonScaffoldFabExtendedProvider oldWidget) =>
|
||||||
|
isExtended != oldWidget.isExtended;
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ import 'package:fl_clash/enum/enum.dart';
|
|||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:fl_clash/widgets/pop_scope.dart';
|
import 'package:fl_clash/widgets/pop_scope.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'chip.dart';
|
import 'chip.dart';
|
||||||
|
import 'inherited.dart';
|
||||||
|
|
||||||
typedef OnKeywordsUpdateCallback = void Function(List<String> keywords);
|
typedef OnKeywordsUpdateCallback = void Function(List<String> keywords);
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@ class CommonScaffold extends StatefulWidget {
|
|||||||
|
|
||||||
class CommonScaffoldState extends State<CommonScaffold> {
|
class CommonScaffoldState extends State<CommonScaffold> {
|
||||||
late final ValueNotifier<AppBarState> _appBarState;
|
late final ValueNotifier<AppBarState> _appBarState;
|
||||||
final ValueNotifier<Widget?> _floatingActionButton = ValueNotifier(null);
|
|
||||||
final ValueNotifier<bool> _loadingNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _loadingNotifier = ValueNotifier(false);
|
||||||
|
final ValueNotifier<bool> _isFabExtendedNotifier = ValueNotifier(true);
|
||||||
final ValueNotifier<List<String>> _keywordsNotifier = ValueNotifier([]);
|
final ValueNotifier<List<String>> _keywordsNotifier = ValueNotifier([]);
|
||||||
final _textController = TextEditingController();
|
final _textController = TextEditingController();
|
||||||
|
|
||||||
@@ -83,12 +85,6 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
_updateSearchState((state) => state?.copyWith(query: ''));
|
_updateSearchState((state) => state?.copyWith(query: ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
set floatingActionButton(Widget? floatingActionButton) {
|
|
||||||
if (_floatingActionButton.value != floatingActionButton) {
|
|
||||||
_floatingActionButton.value = floatingActionButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSearchingAppBarTheme(Widget child) {
|
Widget _buildSearchingAppBarTheme(Widget child) {
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
final ColorScheme colorScheme = theme.colorScheme;
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
@@ -156,7 +152,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_appBarState.dispose();
|
_appBarState.dispose();
|
||||||
_textController.dispose();
|
_textController.dispose();
|
||||||
_floatingActionButton.dispose();
|
_isFabExtendedNotifier.dispose();
|
||||||
_loadingNotifier.dispose();
|
_loadingNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -350,17 +346,31 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
);
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: _buildAppBar(backActionProvider?.backAction),
|
appBar: _buildAppBar(backActionProvider?.backAction),
|
||||||
body: body,
|
body: NotificationListener<UserScrollNotification>(
|
||||||
|
child: body,
|
||||||
|
onNotification: (notification) {
|
||||||
|
if (notification.direction == ScrollDirection.reverse) {
|
||||||
|
_isFabExtendedNotifier.value = false;
|
||||||
|
} else if (notification.direction == ScrollDirection.forward) {
|
||||||
|
_isFabExtendedNotifier.value = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
),
|
||||||
resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset,
|
resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset,
|
||||||
backgroundColor: widget.backgroundColor,
|
backgroundColor: widget.backgroundColor,
|
||||||
floatingActionButton:
|
floatingActionButton: widget.floatingActionButton != null
|
||||||
widget.floatingActionButton ??
|
? ValueListenableBuilder<bool>(
|
||||||
ValueListenableBuilder<Widget?>(
|
valueListenable: _isFabExtendedNotifier,
|
||||||
valueListenable: _floatingActionButton,
|
builder: (_, isExtended, child) {
|
||||||
builder: (_, value, _) {
|
return CommonScaffoldFabExtendedProvider(
|
||||||
return value ?? SizedBox();
|
isExtended: isExtended,
|
||||||
},
|
child: child!,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
child: widget.floatingActionButton,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,25 +382,6 @@ List<Widget> genActions(List<Widget> actions, {double? space}) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommonScaffoldBackActionProvider extends InheritedWidget {
|
|
||||||
final VoidCallback? backAction;
|
|
||||||
|
|
||||||
const CommonScaffoldBackActionProvider({
|
|
||||||
super.key,
|
|
||||||
required this.backAction,
|
|
||||||
required super.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
static CommonScaffoldBackActionProvider? of(BuildContext context) {
|
|
||||||
return context
|
|
||||||
.dependOnInheritedWidgetOfExactType<CommonScaffoldBackActionProvider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool updateShouldNotify(CommonScaffoldBackActionProvider oldWidget) =>
|
|
||||||
backAction != oldWidget.backAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseScaffold extends StatelessWidget {
|
class BaseScaffold extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export 'activate_box.dart';
|
export 'activate_box.dart';
|
||||||
export 'animate_grid.dart';
|
export 'animate_grid.dart';
|
||||||
export 'builder.dart';
|
export 'builder.dart';
|
||||||
|
export 'button.dart';
|
||||||
export 'card.dart';
|
export 'card.dart';
|
||||||
export 'chip.dart';
|
export 'chip.dart';
|
||||||
export 'color_scheme_box.dart';
|
export 'color_scheme_box.dart';
|
||||||
@@ -13,6 +14,7 @@ export 'fade_box.dart';
|
|||||||
export 'float_layout.dart';
|
export 'float_layout.dart';
|
||||||
export 'grid.dart';
|
export 'grid.dart';
|
||||||
export 'icon.dart';
|
export 'icon.dart';
|
||||||
|
export 'inherited.dart';
|
||||||
export 'input.dart';
|
export 'input.dart';
|
||||||
export 'keep_scope.dart';
|
export 'keep_scope.dart';
|
||||||
export 'line_chart.dart';
|
export 'line_chart.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: fl_clash
|
name: fl_clash
|
||||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.8.92+2026012301
|
version: 0.8.92+2026020201
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
|
|
||||||
|
|||||||
25
setup.dart
25
setup.dart
@@ -369,6 +369,15 @@ class BuildCommand extends Command {
|
|||||||
.map((e) => e.arch!)
|
.map((e) => e.arch!)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
Future<void> _buildEnvFile(String env, {String? coreSha256}) async {
|
||||||
|
final data = {
|
||||||
|
'APP_ENV': env,
|
||||||
|
if (coreSha256 != null) 'CORE_SHA256': coreSha256,
|
||||||
|
};
|
||||||
|
final envFile = File(join(current, 'env.json'))..create();
|
||||||
|
await envFile.writeAsString(json.encode(data));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _getLinuxDependencies(Arch arch) async {
|
Future<void> _getLinuxDependencies(Arch arch) async {
|
||||||
await Build.exec(Build.getExecutable('sudo apt update -y'));
|
await Build.exec(Build.getExecutable('sudo apt update -y'));
|
||||||
await Build.exec(
|
await Build.exec(
|
||||||
@@ -412,7 +421,7 @@ class BuildCommand extends Command {
|
|||||||
await Build.exec(
|
await Build.exec(
|
||||||
name: name,
|
name: name,
|
||||||
Build.getExecutable(
|
Build.getExecutable(
|
||||||
'flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose$args --build-dart-define=APP_ENV=$env',
|
'flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose,dart-define-from-file=env.json$args',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -448,21 +457,23 @@ class BuildCommand extends Command {
|
|||||||
mode: mode,
|
mode: mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
String? coreSha256;
|
||||||
|
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
coreSha256 = await Build.calcSha256(corePaths.first);
|
||||||
|
await Build.buildHelper(target, coreSha256);
|
||||||
|
}
|
||||||
|
await _buildEnvFile(env, coreSha256: coreSha256);
|
||||||
if (out != 'app') {
|
if (out != 'app') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case Target.windows:
|
case Target.windows:
|
||||||
final token = target != Target.android
|
|
||||||
? await Build.calcSha256(corePaths.first)
|
|
||||||
: null;
|
|
||||||
Build.buildHelper(target, token!);
|
|
||||||
_buildDistributor(
|
_buildDistributor(
|
||||||
target: target,
|
target: target,
|
||||||
targets: 'exe,zip',
|
targets: 'exe,zip',
|
||||||
args:
|
args: ' --description $archName',
|
||||||
' --description $archName --build-dart-define=CORE_SHA256=$token',
|
|
||||||
env: env,
|
env: env,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user