add WebDAV

add Auto check updates

Optimize more details
This commit is contained in:
chen08209
2024-05-20 15:15:09 +08:00
parent c4b470ffaf
commit 91d30c0f0e
65 changed files with 2866 additions and 1565 deletions

View File

@@ -3,28 +3,25 @@ import 'dart:ui';
import 'package:flutter/material.dart';
const appName = "FlClash";
const coreName = "clash.meta";
const packageName = "FlClash";
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb";
const profilesDirectoryName = "profiles";
const localhost = "127.0.0.1";
const clashConfigKey = "clash_config";
const configKey = "config";
const listItemPadding = EdgeInsets.symmetric(horizontal: 16);
const double dialogCommonWidth = 300;
const repository = "chen08209/FlClash";
const maxMobileWidth = 600;
const maxLaptopWidth = 840;
final filter = ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
tileMode: TileMode.mirror,
);
class AppConstant {
final packageName = "com.follow.clash";
final name = "FlClash";
final httpTimeoutDuration = const Duration(milliseconds: 5000);
final moreDuration = const Duration(milliseconds: 100);
final defaultUpdateDuration = const Duration(days: 1);
final mmdbFileName = "geoip.metadb";
final profilesDirectoryName = "profiles";
final configFileName = "config.yaml";
final localhost = "127.0.0.1";
final clashKey = "clash";
final configKey = "config";
final listItemPadding = const EdgeInsets.symmetric(horizontal: 16);
final dialogCommonWidth = 300;
final repository = "chen08209/FlClash";
final filter = ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
tileMode: TileMode.mirror,
);
final defaultPrimaryColor = Colors.brown;
}
final appConstant = AppConstant();
const defaultPrimaryColor = Colors.brown;

View File

@@ -11,8 +11,6 @@ extension BuildContextExtension on BuildContext {
return MediaQuery.of(this).size.width;
}
bool get isMobile => width < 600;
ColorScheme get colorScheme => Theme.of(this).colorScheme;
TextTheme get textTheme => Theme.of(this).textTheme;

107
lib/common/dav_client.dart Normal file
View File

@@ -0,0 +1,107 @@
import 'dart:async';
import 'dart:convert';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:path/path.dart';
import 'package:webdav_client/webdav_client.dart';
class DAVClient {
late Client client;
Completer<bool> pingCompleter = Completer();
DAVClient(DAV dav) {
client = newClient(
dav.uri,
user: dav.user,
password: dav.password,
);
client.setHeaders(
{
'accept-charset': 'utf-8',
'Content-Type': 'text/xml',
},
);
client.setConnectTimeout(8000);
client.setSendTimeout(8000);
client.setReceiveTimeout(8000);
pingCompleter.complete(_ping());
}
Future<bool> _ping() async {
try {
await client.ping();
await client.mkdir("/$appName");
await client.mkdir("/$appName/$profilesDirectoryName");
return true;
} catch (_) {
return false;
}
}
get root => "/$appName";
get remoteConfig => "$root/$configKey.json";
get remoteClashConfig => "$root/$clashConfigKey.json";
get remoteProfiles => "$root/$profilesDirectoryName";
backup() async {
final appController = globalState.appController;
final config = appController.config;
final clashConfig = appController.clashConfig;
await client.mkdir("$root");
client.write(
remoteConfig,
utf8.encode(
json.encode(config.toJson()),
),
);
client.write(
remoteClashConfig,
utf8.encode(
json.encode(clashConfig.toJson()),
),
);
await client.remove(remoteProfiles);
for (final profile in config.profiles) {
final path = await appPath.getProfilePath(profile.id);
if (path == null) continue;
await client.writeFromFile(
path,
"$remoteProfiles/${basename(path)}",
);
}
return true;
}
recovery({required RecoveryOption recoveryOption}) async {
final profiles = await client.readDir(remoteProfiles);
final profilesPath = await appPath.getProfilesPath();
for (final file in profiles) {
await client.read2File(
"$remoteProfiles/${file.name}",
join(
profilesPath,
file.name,
),
);
}
final configRaw = utf8.decode((await client.read(remoteConfig)));
final clashConfigRaw = utf8.decode(await client.read(remoteClashConfig));
final config = Config.fromJson(json.decode(configRaw));
final clashConfig = ClashConfig.fromJson(json.decode(clashConfigRaw));
if(recoveryOption == RecoveryOption.onlyProfiles){
globalState.appController.config.update(config, RecoveryOption.onlyProfiles);
}else{
globalState.appController.config.update(config, RecoveryOption.all);
globalState.appController.clashConfig.update(clashConfig);
}
await globalState.appController.applyProfile();
globalState.appController.savePreferences();
return true;
}
}

View File

@@ -10,7 +10,7 @@ class AutoLaunch {
AutoLaunch._internal() {
launchAtStartup.setup(
appName: appConstant.name,
appName: appName,
appPath: Platform.resolvedExecutable,
);
}

View File

@@ -147,6 +147,26 @@ class Other {
}
});
}
String? getFileNameForDisposition(String? disposition) {
if (disposition == null) return null;
final parseValue = HeaderValue.parse(disposition);
final parameters = parseValue.parameters;
final key = parameters.keys
.firstWhere((key) => key.startsWith("filename"), orElse: () => '');
if (key.isEmpty) return null;
if (key == "filename*") {
return Uri.decodeComponent((parameters[key] ?? "").split("'").last);
} else {
return parameters[key];
}
}
double getViewWidth(){
final view = WidgetsBinding.instance.platformDispatcher.views.first;
final size = view.physicalSize / view.devicePixelRatio;
return size.width;
}
}
final other = Other();

View File

@@ -26,14 +26,9 @@ class AppPath {
return directory.path;
}
Future<String> getConfigPath() async {
final directory = await applicationSupportDirectoryCompleter.future;
return join(directory.path, appConstant.configFileName);
}
Future<String> getProfilesPath() async {
final directory = await applicationSupportDirectoryCompleter.future;
return join(directory.path, appConstant.profilesDirectoryName);
return join(directory.path, profilesDirectoryName);
}
Future<String?> getProfilePath(String? id) async {
@@ -44,7 +39,7 @@ class AppPath {
Future<String> getMMDBPath() async {
var directory = await applicationSupportDirectoryCompleter.future;
return join(directory.path, appConstant.mmdbFileName);
return join(directory.path, mmdbFileName);
}
}

View File

@@ -23,9 +23,9 @@ class Picker {
}
final file = filePickerResult?.files.first;
if (file == null) {
return Result.error(message: appLocalizations.pleaseUploadFile);
return Result.error(appLocalizations.pleaseUploadFile);
}
return Result.success(data: file);
return Result.success(file);
}
Future<Result<String>> pickerConfigQRCode() async {
@@ -34,9 +34,9 @@ class Picker {
if (bytes == null) return Result.error();
final result = await other.parseQRCode(bytes);
if (result == null || !result.isUrl) {
return Result.error(message: appLocalizations.pleaseUploadValidQrcode);
return Result.error(appLocalizations.pleaseUploadValidQrcode);
}
return Result.success(data: result);
return Result.success(result);
}
}

View File

@@ -22,7 +22,7 @@ class Preferences {
Future<ClashConfig?> getClashConfig() async {
final preferences = await sharedPreferencesCompleter.future;
final clashConfigString = preferences.getString(appConstant.clashKey);
final clashConfigString = preferences.getString(clashConfigKey);
if (clashConfigString == null) return null;
final clashConfigMap = json.decode(clashConfigString);
try {
@@ -35,14 +35,14 @@ class Preferences {
Future<bool> saveClashConfig(ClashConfig clashConfig) async {
final preferences = await sharedPreferencesCompleter.future;
return preferences.setString(
appConstant.clashKey,
clashConfigKey,
json.encode(clashConfig),
);
}
Future<Config?> getConfig() async {
final preferences = await sharedPreferencesCompleter.future;
final configString = preferences.getString(appConstant.configKey);
final configString = preferences.getString(configKey);
if (configString == null) return null;
final configMap = json.decode(configString);
try {
@@ -55,7 +55,7 @@ class Preferences {
Future<bool> saveConfig(Config config) async {
final preferences = await sharedPreferencesCompleter.future;
return preferences.setString(
appConstant.configKey,
configKey,
json.encode(config),
);
}

View File

@@ -6,21 +6,21 @@ import '../models/models.dart';
class Request {
static Future<Result<Response>> getFileResponseForUrl(String url) async {
final headers = {'User-Agent': appConstant.name};
final headers = {'User-Agent': coreName};
try {
final response = await get(Uri.parse(url), headers: headers).timeout(
appConstant.httpTimeoutDuration,
httpTimeoutDuration,
);
return Result.success(data: response);
return Result.success(response);
} catch (err) {
return Result.error(message: err.toString());
return Result.error(err.toString());
}
}
static Future<Result<String>> checkForUpdate() async {
final response = await get(
Uri.parse(
"https://api.github.com/repos/${appConstant.repository}/releases/latest",
"https://api.github.com/repos/$repository/releases/latest",
),
);
if (response.statusCode != 200) return Result.error();
@@ -31,6 +31,6 @@ class Request {
final hasUpdate =
other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0;
if (!hasUpdate) return Result.error();
return Result.success(data: body['body']);
return Result.success(body['body']);
}
}