Compare commits

..

44 Commits

Author SHA1 Message Date
chen08209
9100962939 cache 2026-03-25 19:10:08 +08:00
chen08209
2dbee3a22d cache 2026-03-23 19:25:17 +08:00
chen08209
1791ead92d cache 2026-03-23 13:58:51 +08:00
chen08209
71903feeeb cache 2026-03-23 10:24:45 +08:00
chen08209
7e234dc62d cache 2026-03-23 10:13:30 +08:00
chen08209
5d3122f5f4 cache 2026-03-22 15:16:12 +08:00
chen08209
d30eb6a7c6 cache 2026-03-20 21:52:06 +08:00
chen08209
b8dd8ada2e cache 2026-03-20 21:40:29 +08:00
chen08209
9e04b004ba cache 2026-03-20 21:20:35 +08:00
chen08209
c8a93029aa cache 2026-03-20 19:00:53 +08:00
chen08209
490e1bae87 cache 2026-03-20 18:55:29 +08:00
chen08209
ce3093c80b cache 2026-03-19 18:19:37 +08:00
chen08209
2023cecdfc cache 2026-03-19 17:42:41 +08:00
chen08209
b20efc32a5 cache 2026-03-19 15:30:39 +08:00
chen08209
b3123eb35d cache 2026-03-19 15:11:47 +08:00
chen08209
4be3a483fa cache 2026-03-17 16:17:39 +08:00
chen08209
e5c18d97cf cache 2026-03-15 01:10:43 +08:00
chen08209
b41ca491ae cache 2026-03-14 19:55:50 +08:00
chen08209
eabae2aa29 cache 2026-03-12 16:26:30 +08:00
chen08209
f846a97aa3 cache 2026-03-12 15:24:09 +08:00
chen08209
88d3c23cdc cache 2026-03-12 15:04:33 +08:00
chen08209
67402899a8 cache 2026-03-11 17:45:20 +08:00
chen08209
bc0bbc6ede cache 2026-03-11 17:19:32 +08:00
chen08209
f0417cac58 cache 2026-03-11 16:46:26 +08:00
chen08209
7b46347016 cache 2026-03-11 10:24:38 +08:00
chen08209
3f5e7b80bc cache 2026-03-10 19:24:23 +08:00
chen08209
8296302211 cache 2026-03-10 14:25:00 +08:00
chen08209
e200a89796 cache 2026-03-09 14:15:30 +08:00
chen08209
bc552b63bb cache 2026-03-05 17:29:05 +08:00
chen08209
50e0ae721e cache 2026-03-04 18:47:56 +08:00
chen08209
b61d985657 cache 2026-03-03 16:23:33 +08:00
chen08209
09ca576752 cache 2026-03-03 10:12:29 +08:00
chen08209
37f635a16a cache 2026-03-02 18:59:31 +08:00
chen08209
08376b7ae1 cache 2026-02-28 10:39:45 +08:00
chen08209
dfc59aac4b cache 2026-02-27 16:15:21 +08:00
chen08209
f94a1491bf cache 2026-02-14 22:00:05 +08:00
chen08209
0602146d50 cache 2026-02-12 22:28:48 +08:00
chen08209
b5e5d9c632 cache 2026-02-11 16:49:02 +08:00
chen08209
b742ab76bc cache 2026-02-06 15:38:01 +08:00
chen08209
c46168eff8 cache 2026-02-05 16:21:14 +08:00
chen08209
fa28b0d610 cache 2026-02-04 15:52:44 +08:00
chen08209
23b349c3df cache 2026-02-03 18:44:05 +08:00
chen08209
cb948446be cache 2026-02-02 10:15:11 +08:00
chen08209
db49cd81ce Add sqlite store
Optimize android quick action

Optimize backup and restore

Optimize more details
2026-02-02 10:15:11 +08:00
98 changed files with 7794 additions and 2780 deletions

View File

@@ -22,6 +22,7 @@
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
<application
android:name=".Application"

View File

@@ -85,22 +85,24 @@ class NotificationModule(private val service: Service) : Module() {
private val notificationBuilder: NotificationCompat.Builder by lazy {
val intent = Intent().setComponent(Components.MAIN_ACTIVITY)
with(
NotificationCompat.Builder(
service, GlobalState.NOTIFICATION_CHANNEL
)
) {
setSmallIcon(R.drawable.ic)
NotificationCompat.Builder(
service, GlobalState.NOTIFICATION_CHANNEL
).apply {
setSmallIcon(R.drawable.ic_service)
setContentTitle("FlClash")
setContentIntent(intent.toPendingIntent)
setPriority(NotificationCompat.PRIORITY_HIGH)
setCategory(NotificationCompat.CATEGORY_SERVICE)
setOngoing(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}
setOngoing(true)
setShowWhen(true)
setOnlyAlertOnce(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setRequestPromotedOngoing(true)
}
}
}

View File

@@ -475,5 +475,6 @@
"restoreFromFileDesc": "Restore data via file",
"restoreOnlyConfig": "Restore configuration files only",
"restoreAllData": "Restore all data",
"addProfile": "Add Profile"
"addProfile": "Add Profile",
"delayTest": "Delay Test"
}

View File

@@ -476,5 +476,6 @@
"restoreFromFileDesc": "ファイルを介してデータを復元する",
"restoreOnlyConfig": "設定ファイルのみを復元する",
"restoreAllData": "すべてのデータを復元する",
"addProfile": "プロファイルを追加"
"addProfile": "プロファイルを追加",
"delayTest": "遅延テスト"
}

View File

@@ -484,5 +484,6 @@
"restoreFromFileDesc": "Восстановить данные из файла",
"restoreOnlyConfig": "Восстановить только файлы конфигурации",
"restoreAllData": "Восстановить все данные",
"addProfile": "Добавить профиль"
"addProfile": "Добавить профиль",
"delayTest": "Тест задержки"
}

View File

@@ -476,5 +476,6 @@
"restoreFromFileDesc": "通过文件恢复数据",
"restoreOnlyConfig": "仅恢复配置文件",
"restoreAllData": "恢复所有数据",
"addProfile": "添加配置"
"addProfile": "添加配置",
"delayTest": "延迟测试"
}

View File

@@ -532,6 +532,9 @@ func handleDelFile(path string, result ActionResult) {
}
func handleSetupConfig(bytes []byte) string {
if !isInit {
return "not initialized"
}
var params = defaultSetupParams()
err := UnmarshalJson(bytes, params)
if err != nil {

View File

@@ -10,7 +10,6 @@ export 'file.dart';
export 'fixed.dart';
export 'function.dart';
export 'future.dart';
export 'hive.dart';
export 'http.dart';
export 'icons.dart';
export 'indexing.dart';

View File

@@ -20,15 +20,16 @@ const helperPort = 47890;
const maxTextScale = 1.4;
const minTextScale = 0.8;
final baseInfoEdgeInsets = EdgeInsets.symmetric(
vertical: 16.ap,
horizontal: 16.ap,
vertical: 16.mAp,
horizontal: 16.mAp,
);
final listHeaderPadding = EdgeInsets.only(
left: 16.ap,
right: 8.ap,
top: 24.ap,
bottom: 8.ap,
left: 16.mAp,
right: 8.mAp,
top: 24.mAp,
bottom: 8.mAp,
);
const sheetAppBarHeight = 68.0;
const watchExecution = true;
@@ -62,7 +63,7 @@ const defaultTestUrl = 'https://www.gstatic.com/generate_204';
final commonFilter = ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
tileMode: TileMode.mirror,
tileMode: TileMode.clamp,
);
const listEquality = ListEquality();
@@ -77,6 +78,7 @@ const scriptListEquality = ListEquality<Script>();
const externalProviderListEquality = ListEquality<ExternalProvider>();
const packageListEquality = ListEquality<Package>();
const profileListEquality = ListEquality<Profile>();
const proxyGroupsEquality = ListEquality<ProxyGroup>();
const hotKeyActionListEquality = ListEquality<HotKeyAction>();
const stringAndStringMapEquality = MapEquality<String, String>();
const stringAndStringMapEntryListEquality =
@@ -102,7 +104,8 @@ const profilesStoreKey = PageStorageKey<String>('profiles');
const defaultPrimaryColor = 0XFFD8C0C3;
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;

View File

@@ -1,7 +1,10 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/manager/manager.dart';
import 'package:fl_clash/models/state.dart';
import 'package:fl_clash/widgets/inherited.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:fl_clash/widgets/sheet.dart';
import 'package:flutter/material.dart';
extension BuildContextExtension on BuildContext {
@@ -9,6 +12,15 @@ extension BuildContextExtension on BuildContext {
return findAncestorStateOfType<CommonScaffoldState>();
}
double get sheetTopPadding {
final sheetType = SheetProvider.of(this)!.type;
if (sheetType == SheetType.bottomSheet) {
return sheetAppBarHeight;
} else {
return 10;
}
}
void showNotifier(String text, {MessageActionState? actionState}) {
return findAncestorStateOfType<StatusManagerState>()?.message(
text,

View File

@@ -1,13 +1,12 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
class Debouncer {
final Map<FunctionTag, Timer?> _operations = {};
final Map<dynamic, Timer?> _operations = {};
void call(
FunctionTag tag,
dynamic tag,
Function func, {
List<dynamic>? args,
Duration? duration,
@@ -30,10 +29,10 @@ class Debouncer {
}
class Throttler {
final Map<FunctionTag, Timer?> _operations = {};
final Map<dynamic, Timer?> _operations = {};
bool call(
FunctionTag tag,
dynamic tag,
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),

View File

View File

@@ -255,6 +255,10 @@ class Indexing {
.followedBy(generateNKeysBetween(c, b, n - mid - 1))
.toList();
}
List<String?> generateNKeys(int n) {
return generateNKeysBetween(null, null, n);
}
}
final indexing = Indexing();

View File

@@ -11,16 +11,25 @@ class Measure {
: _measureMap = {},
_textScaler = TextScaler.linear(textScaleFactor);
Size computeTextSize(Text text, {double maxWidth = double.infinity}) {
final textPainter = TextPainter(
text: TextSpan(text: text.data, style: text.style),
TextPainter computeText(Text text, {TextStyle? style, double? maxWidth}) {
return TextPainter(
text: TextSpan(text: text.data, style: text.style ?? style),
maxLines: text.maxLines,
textScaler: _textScaler,
textDirection: text.textDirection ?? TextDirection.ltr,
)..layout(maxWidth: maxWidth);
)..layout(maxWidth: maxWidth ?? double.infinity);
}
Size computeTextSize(Text text, {TextStyle? style, double? maxWidth}) {
final textPainter = computeText(text, style: style, maxWidth: maxWidth);
return textPainter.size;
}
bool computeTextIsOverflow(Text text, {TextStyle? style, double? maxWidth}) {
final textPainter = computeText(text, style: style, maxWidth: maxWidth);
return textPainter.didExceedMaxLines;
}
double get bodyMediumHeight {
return _measureMap.updateCacheValue(
'bodyMediumHeight',

View File

@@ -1,6 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'utils.dart';
mixin AutoDisposeNotifierMixin<T> on AnyNotifier<T, T> {
T get value => state;
@@ -41,3 +44,7 @@ mixin AsyncNotifierMixin<T> on AnyNotifier<AsyncValue<T>, T> {
state = AsyncData(value);
}
}
mixin UniqueKeyStateMixin<T extends StatefulWidget> on State<T> {
final key = utils.id;
}

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/state.dart';
@@ -20,6 +22,10 @@ extension NumExt on num {
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 {
final units = TrafficUnit.values;
var size = toDouble();
@@ -51,7 +57,7 @@ extension NumExt on num {
extension DoubleExt on double {
bool moreOrEqual(double value) {
return this > value || (value - this).abs() < precisionErrorTolerance + 2;
return this > value || (value - this).abs() < precisionErrorTolerance + 1;
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -610,3 +611,16 @@ String _getScriptPath(String root, String fileName) {
String _getProfilePath(String root, String fileName) {
return join(root, 'profiles', '$fileName.yaml');
}
Future<List<T>> mapListTask<T, S>(List<S> results, T Function(S) mapper) async {
return await compute<VM2<List<S>, T Function(S)>, List<T>>(
_mapListTask,
VM2(results, mapper),
);
}
Future<List<T>> _mapListTask<T, S>(VM2<List<S>, T Function(S)> vm2) async {
final results = vm2.a;
final mapper = vm2.b;
return results.map((item) => mapper(item)).toList();
}

View File

@@ -6,16 +6,15 @@ class CommonTheme {
final Map<String, Color> _colorMap;
final double textScaleFactor;
CommonTheme.of(
this.context,
this.textScaleFactor,
) : _colorMap = {};
CommonTheme.of(this.context, this.textScaleFactor) : _colorMap = {};
Color get darkenSecondaryContainer {
return _colorMap.updateCacheValue(
'darkenSecondaryContainer',
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1),
() => context.colorScheme.secondaryContainer.blendDarken(
context,
factor: 0.1,
),
);
}
@@ -31,16 +30,20 @@ class CommonTheme {
Color get darken2SecondaryContainer {
return _colorMap.updateCacheValue(
'darken2SecondaryContainer',
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.2),
() => context.colorScheme.secondaryContainer.blendDarken(
context,
factor: 0.2,
),
);
}
Color get darken3PrimaryContainer {
return _colorMap.updateCacheValue(
'darken3PrimaryContainer',
() => context.colorScheme.primaryContainer
.blendDarken(context, factor: 0.3),
() => context.colorScheme.primaryContainer.blendDarken(
context,
factor: 0.3,
),
);
}
}

View File

@@ -26,6 +26,10 @@ class Tray {
return system.isWindows ? 'ico' : 'png';
}
Future<void> destroy() async {
await trayManager.destroy();
}
String getTryIcon({required bool isStart, required bool tunEnable}) {
if (system.isMacOS || !isStart) {
return 'assets/images/icon/status_1.$trayIconSuffix';

View File

@@ -83,6 +83,7 @@ class Window {
}
Future<void> close() async {
await windowManager.close();
exit(0);
}

View File

@@ -138,6 +138,11 @@ extension InitControllerExt on AppController {
}
Future<void> _initStatus() async {
if (!globalState.needInitStatus) {
commonPrint.log('init status cancel');
return;
}
commonPrint.log('init status');
if (system.isAndroid) {
await globalState.updateStartTime();
}
@@ -205,6 +210,10 @@ extension StateControllerExt on AppController {
return _ref.read(isMobileViewProvider);
}
Size get viewSize {
return _ref.read(viewSizeProvider);
}
bool get isStart {
return _ref.read(isStartProvider);
}
@@ -225,10 +234,6 @@ extension StateControllerExt on AppController {
return _ref.read(getSelectedProxyNameProvider(groupName));
}
Future<SetupState> getSetupState(int profileId) async {
return await _ref.read(setupStateProvider(profileId).future);
}
String getRealTestUrl(String? url) {
return _ref.read(realTestUrlProvider(url));
}
@@ -550,9 +555,6 @@ extension SetupControllerExt on AppController {
Future<void> updateStatus(bool isStart, {bool isInit = false}) async {
if (isStart) {
_ref.read(runTimeProvider.notifier).update((state) {
return state ?? 0;
});
if (!isInit) {
final res = await tryStartCore(true);
if (res) {
@@ -564,6 +566,7 @@ extension SetupControllerExt on AppController {
await globalState.handleStart([updateRunTime, updateTraffic]);
applyProfileDebounce(force: true, silence: true);
} else {
globalState.needInitStatus = false;
await applyProfile(
force: true,
preloadInvoke: () async {
@@ -650,9 +653,14 @@ extension SetupControllerExt on AppController {
bool force = false,
VoidCallback? preloadInvoke,
}) async {
if (!force && !await needSetup()) {
return;
}
await loadingRun(
() async {
await _applyProfile(force, preloadInvoke);
await _setupConfig(preloadInvoke);
await updateGroups();
await updateProviders();
},
silence: true,
tag: !silence ? LoadingTag.proxies : null,
@@ -661,7 +669,7 @@ extension SetupControllerExt on AppController {
Future<Map<String, dynamic>> getProfile({
required SetupState setupState,
required ClashConfig patchConfig,
required PatchClashConfig patchConfig,
}) async {
final profileId = setupState.profileId;
if (profileId == null) {
@@ -722,13 +730,8 @@ extension SetupControllerExt on AppController {
return res;
}
Future<void> _setupConfig([
bool force = false,
VoidCallback? preloadInvoke,
]) async {
if (!force && !await needSetup()) {
return;
}
Future<void> _setupConfig([VoidCallback? preloadInvoke]) async {
commonPrint.log('setup ===>');
var profile = _ref.read(currentProfileProvider);
final nextProfile = await profile?.checkAndUpdateAndCopy();
if (nextProfile != null) {
@@ -746,9 +749,6 @@ extension SetupControllerExt on AppController {
globalState.lastSetupState = setupState;
if (system.isAndroid) {
globalState.lastVpnState = _ref.read(vpnStateProvider);
}
if (system.isAndroid) {
preferences.saveShareState(this.sharedState);
}
final config = await getProfile(
@@ -768,15 +768,6 @@ extension SetupControllerExt on AppController {
}
addCheckIp();
}
Future _applyProfile([
bool force = false,
VoidCallback? preloadInvoke,
]) async {
await _setupConfig(force, preloadInvoke);
await updateGroups();
await updateProviders();
}
}
extension CoreControllerExt on AppController {
@@ -842,7 +833,7 @@ extension CoreControllerExt on AppController {
if (coreController.isCompleted) {
return false;
}
await restartCore();
await restartCore(start);
return true;
}
@@ -868,11 +859,12 @@ extension SystemControllerExt on AppController {
system.exit();
});
try {
if (needSave) {
await preferences.saveConfig(config);
}
await proxy?.stopProxy();
await macOS?.updateDns(true);
await Future.wait([
if (needSave) preferences.saveConfig(config),
if (macOS != null) macOS!.updateDns(true),
if (proxy != null) proxy!.stopProxy(),
if (tray != null) tray!.destroy(),
]);
await coreController.destroy();
commonPrint.log('exit');
} finally {

View File

@@ -86,9 +86,7 @@ abstract class CoreHandlerInterface with CoreInterface {
Duration? timeout,
}) async {
try {
if (!completer.isCompleted) {
return null;
}
await completer.future.timeout(const Duration(seconds: 10));
} catch (e) {
commonPrint.log(
'Invoke pre ${method.name} timeout $e',

View File

@@ -0,0 +1,43 @@
part of 'database.dart';
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringListConverter extends TypeConverter<List<String>, String> {
const StringListConverter();
@override
List<String> fromSql(String fromDb) {
return List<String>.from(json.decode(fromDb));
}
@override
String toSql(List<String> value) {
return json.encode(value.toList());
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -8,21 +9,23 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
part 'converter.dart';
part 'generated/database.g.dart';
part 'groups.dart';
part 'links.dart';
part 'profiles.dart';
part 'rules.dart';
part 'scripts.dart';
@DriftDatabase(
tables: [Profiles, Scripts, Rules, ProfileRuleLinks],
daos: [ProfilesDao, ScriptsDao, RulesDao],
tables: [Profiles, Scripts, Rules, ProfileRuleLinks, ProxyGroups],
daos: [ProfilesDao, ScriptsDao, RulesDao, ProxyGroupsDao],
)
class Database extends _$Database {
Database([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;
int get schemaVersion => 2;
static LazyDatabase _openConnection() {
return LazyDatabase(() async {
@@ -31,6 +34,22 @@ class Database extends _$Database {
});
}
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: (m, from, to) async {
if (from < 2) {
await m.createTable(proxyGroups);
await _resetOrders();
}
},
);
}
Future<void> _resetOrders() async {
await rulesDao.resetOrders();
}
Future<void> restore(
List<Profile> profiles,
List<Script> scripts,
@@ -54,6 +73,17 @@ class Database extends _$Database {
});
}
}
Future<void> setProfileCustomData(
int profileId,
List<ProxyGroup> groups,
List<Rule> rules,
) async {
await batch((b) {
proxyGroupsDao.setAllWithBatch(profileId, b, groups);
rulesDao.setCustomRulesWithBatch(profileId, b, rules);
});
}
}
extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
@@ -61,9 +91,21 @@ extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
Batch batch,
Iterable<Insertable<Row>> items, {
required Expression<bool> Function(Tbl tbl) deleteFilter,
bool preDelete = false,
}) async {
if (preDelete) {
batch.deleteWhere(this, deleteFilter);
}
batch.insertAllOnConflictUpdate(this, items);
batch.deleteWhere(this, deleteFilter);
if (!preDelete) {
batch.deleteWhere(this, deleteFilter);
}
}
Selectable<int?> get count {
final countExp = countAll();
final query = select().addColumns([countExp]);
return query.map((row) => row.read(countExp));
}
Future<int> remove(Expression<bool> Function(Tbl tbl) filter) async {
@@ -75,4 +117,78 @@ extension TableInfoExt<Tbl extends Table, Row> on TableInfo<Tbl, Row> {
}
}
extension SimpleSelectStatementExt<T extends HasResultSet, D>
on SimpleSelectStatement<T, D> {
Selectable<int> get count {
final countExp = countAll();
final query = addColumns([countExp]);
return query.map((row) => row.read(countExp)!);
}
}
extension JoinedSelectStatementExt<T extends HasResultSet, D>
on JoinedSelectStatement<T, D> {
Selectable<int> get count {
final countExp = countAll();
addColumns([countExp]);
return map((row) => row.read(countExp)!);
}
}
// extension _AsyncMapPerSubscription<S> on Stream<S> {
// Stream<T> asyncMapPerSubscription<T>(FutureOr<T> Function(S) mapper) {
// return Stream.multi((listener) {
// late StreamSubscription<S> subscription;
//
// void onData(S original) {
// subscription.pause();
// Future.sync(() => mapper(original))
// .then(listener.addSync, onError: listener.addErrorSync)
// .whenComplete(subscription.resume);
// }
//
// subscription = listen(
// onData,
// onError: listener.addErrorSync,
// onDone: listener.closeSync,
// cancelOnError: false, // Determined by downstream subscription
// );
//
// listener
// ..onPause = subscription.pause
// ..onResume = subscription.resume
// ..onCancel = subscription.cancel;
// }, isBroadcast: isBroadcast);
// }
// }
// extension SelectableExt<T> on Selectable<T> {
// Selectable<N> isolateMap<N>(N Function(T) mapper) {
// return _IsolateMappedSelectable<T, N>(this, mapper);
// }
// }
//
// class _IsolateMappedSelectable<S, T> extends Selectable<T> {
// final Selectable<S> _source;
// final T Function(S) _mapper;
//
// _IsolateMappedSelectable(this._source, this._mapper);
//
// @override
// Future<List<T>> get() {
// return _source.get().then(_mapResults);
// }
//
// @override
// Stream<List<T>> watch() {
// return _AsyncMapPerSubscription(
// _source.watch(),
// ).asyncMapPerSubscription(_mapResults);
// }
//
// Future<List<T>> _mapResults(List<S> results) async {
// return mapListTask(results, _mapper);
// }
// }
final database = Database();

File diff suppressed because it is too large Load Diff

172
lib/database/groups.dart Normal file
View File

@@ -0,0 +1,172 @@
part of 'database.dart';
@DataClassName('RawProxyGroup')
@TableIndex(
name: 'idx_profile_name_order',
columns: {#profileId, #name, #order},
)
class ProxyGroups extends Table {
@override
String get tableName => 'proxy_groups';
IntColumn get profileId => integer().nullable().references(
Profiles,
#id,
onDelete: KeyAction.cascade,
)();
TextColumn get name => text()();
TextColumn get type => text()();
TextColumn get proxies =>
text().map(const StringListConverter()).nullable()();
TextColumn get use => text().map(const StringListConverter()).nullable()();
TextColumn get url => text().nullable()();
IntColumn get interval => integer().nullable()();
IntColumn get timeout => integer().nullable()();
IntColumn get maxFailedTimes => integer().nullable()();
BoolColumn get lazy => boolean().nullable()();
BoolColumn get disableUDP => boolean().nullable()();
TextColumn get filter => text().nullable()();
TextColumn get excludeFilter => text().nullable()();
TextColumn get excludeType => text().nullable()();
TextColumn get expectedStatus => text().nullable()();
BoolColumn get includeAll => boolean().nullable()();
BoolColumn get includeAllProxies => boolean().nullable()();
BoolColumn get includeAllProviders => boolean().nullable()();
BoolColumn get hidden => boolean().nullable()();
TextColumn get icon => text().nullable()();
TextColumn get order => text().nullable()();
@override
Set<Column> get primaryKey => {profileId, name};
}
@DriftAccessor(tables: [ProxyGroups])
class ProxyGroupsDao extends DatabaseAccessor<Database>
with _$ProxyGroupsDaoMixin {
ProxyGroupsDao(super.attachedDatabase);
Selectable<ProxyGroup> all(int profileId) {
final stmt = proxyGroups.select();
stmt.where((row) => row.profileId.equals(profileId));
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
]);
return stmt.map((item) => item.toProxyGroup());
}
Selectable<int> count(int profileId) {
final stmt = proxyGroups.select();
stmt.where((row) => row.profileId.equals(profileId));
stmt.orderBy([
(t) => OrderingTerm(expression: t.order, nulls: NullsOrder.last),
]);
return stmt.count;
}
Selectable<ProxyGroup> get(int profileId, String name) {
final stmt = proxyGroups.select();
stmt.where(
(row) => row.profileId.equals(profileId) & row.name.equals(name),
);
return stmt.map((item) => item.toProxyGroup());
}
Future<int> order(
int profileId, {
required ProxyGroup proxyGroup,
required String order,
}) async {
return await proxyGroups.insertOnConflictUpdate(
proxyGroup.toCompanion(profileId, order),
);
}
void setAllWithBatch(
int profileId,
Batch batch,
Iterable<ProxyGroup> proxyGroups,
) async {
final keys = indexing.generateNKeys(proxyGroups.length);
this.proxyGroups.setAll(
batch,
proxyGroups.mapIndexed(
(index, item) => item.toCompanion(profileId, keys[index]),
),
deleteFilter: (row) => row.profileId.equals(profileId),
preDelete: true,
);
}
}
extension RawProxyGroupExt on RawProxyGroup {
ProxyGroup toProxyGroup() {
return ProxyGroup(
name: name,
type: GroupType.parseProfileType(type),
proxies: proxies,
url: url,
interval: interval,
timeout: timeout,
maxFailedTimes: maxFailedTimes,
lazy: lazy,
disableUDP: disableUDP,
filter: filter,
excludeFilter: excludeFilter,
excludeType: excludeType,
expectedStatus: expectedStatus,
includeAll: includeAll,
includeAllProxies: includeAllProxies,
includeAllProviders: includeAllProviders,
hidden: hidden,
icon: icon,
order: order,
);
}
}
extension ProxyGroupsCompanionExt on ProxyGroup {
ProxyGroupsCompanion toCompanion(int profileId, [String? order]) {
return ProxyGroupsCompanion.insert(
profileId: Value(profileId),
name: name,
type: type.value,
proxies: Value(proxies),
url: Value(url),
interval: Value(interval),
timeout: Value(timeout),
maxFailedTimes: Value(maxFailedTimes),
lazy: Value(lazy),
disableUDP: Value(disableUDP),
filter: Value(filter),
excludeFilter: Value(excludeFilter),
excludeType: Value(excludeType),
expectedStatus: Value(expectedStatus),
includeAll: Value(includeAll),
includeAllProxies: Value(includeAllProxies),
includeAllProviders: Value(includeAllProviders),
hidden: Value(hidden),
icon: Value(icon),
order: Value(order ?? this.order),
);
}
}

View File

@@ -40,13 +40,13 @@ extension RawProfileRuleLinkExt on RawProfileRuleLink {
}
extension ProfileRuleLinksCompanionExt on ProfileRuleLink {
ProfileRuleLinksCompanion toCompanion() {
ProfileRuleLinksCompanion toCompanion([String? order]) {
return ProfileRuleLinksCompanion.insert(
id: key,
ruleId: ruleId,
scene: Value(scene),
profileId: Value(profileId),
order: Value(order),
order: Value(order ?? this.order),
);
}
}

View File

@@ -99,34 +99,6 @@ class ProfilesDao extends DatabaseAccessor<Database> with _$ProfilesDaoMixin {
}
}
class StringMapConverter extends TypeConverter<Map<String, String>, String> {
const StringMapConverter();
@override
Map<String, String> fromSql(String fromDb) {
return Map<String, String>.from(json.decode(fromDb));
}
@override
String toSql(Map<String, String> value) {
return json.encode(value);
}
}
class StringSetConverter extends TypeConverter<Set<String>, String> {
const StringSetConverter();
@override
Set<String> fromSql(String fromDb) {
return Set<String>.from(json.decode(fromDb));
}
@override
String toSql(Set<String> value) {
return json.encode(value.toList());
}
}
extension RawProfilExt on RawProfile {
Profile toProfile() {
return Profile(

View File

@@ -29,6 +29,18 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
return _get(profileId: profileId, scene: RuleScene.disabled);
}
Selectable<Rule> allProfileCustomRules(int profileId) {
return _get(profileId: profileId, scene: RuleScene.custom);
}
Selectable<int> profileCustomRulesCount(int profileId) {
final query = _getSelectStatement(
profileId: profileId,
scene: RuleScene.custom,
);
return query.count;
}
Selectable<Rule> allAddedRules(int profileId) {
final disabledIdsQuery = selectOnly(profileRuleLinks)
..addColumns([profileRuleLinks.ruleId])
@@ -65,6 +77,26 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
});
}
Future<void> resetOrders() async {
final stmt = profileRuleLinks.select();
stmt.orderBy([
(t) => OrderingTerm.asc(t.scene),
//v0.8.92 ordering desc
(t) => OrderingTerm.desc(t.order),
(t) => OrderingTerm.desc(t.id),
]);
final links = await stmt.map((item) => item.toLink()).get();
final keys = indexing.generateNKeys(links.length);
await batch((b) {
b.insertAllOnConflictUpdate(
profileRuleLinks,
links.mapIndexed((index, item) => item.toCompanion(keys[index])),
);
});
}
void restoreWithBatch(
Batch batch,
Iterable<Rule> rules,
@@ -76,9 +108,10 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
);
final ruleIds = rules.map((item) => item.id);
batch.deleteWhere(this.rules, (t) => t.id.isNotIn(ruleIds));
final keys = indexing.generateNKeys(links.length);
batch.insertAllOnConflictUpdate(
profileRuleLinks,
links.map((item) => item.toCompanion()),
links.mapIndexed((index, item) => item.toCompanion(keys[index])),
);
final linkKeys = links.map((item) => item.key);
batch.deleteWhere(profileRuleLinks, (t) => t.id.isNotIn(linkKeys));
@@ -96,16 +129,16 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putProfileCustomRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.custom);
}
Future<void> putProfileDisabledRule(int profileId, Rule rule) {
return _put(rule, profileId: profileId, scene: RuleScene.added);
}
Future<void> putGlobalRules(Iterable<Rule> rules) {
return _putAll(rules);
}
Future<void> setGlobalRules(Iterable<Rule> rules) {
return _set(rules);
void setCustomRulesWithBatch(int profileId, Batch b, Iterable<Rule> rules) {
_setWithBatch(b, rules, profileId: profileId, scene: RuleScene.custom);
}
Future<int> putDisabledLink(int profileId, int ruleId) async {
@@ -148,7 +181,23 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
);
}
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
Future<int> orderProfileCustomRule(
int profileId, {
required int ruleId,
required String order,
}) async {
return await _order(
ruleId: ruleId,
order: order,
profileId: profileId,
scene: RuleScene.custom,
);
}
JoinedSelectStatement<HasResultSet, dynamic> _getSelectStatement({
int? profileId,
RuleScene? scene,
}) {
final query = select(rules).join([
innerJoin(profileRuleLinks, profileRuleLinks.ruleId.equalsExp(rules.id)),
]);
@@ -160,10 +209,13 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
profileRuleLinks.scene.equalsValue(scene),
);
query.orderBy([
OrderingTerm.desc(profileRuleLinks.order),
OrderingTerm.desc(profileRuleLinks.id),
]);
query.orderBy([OrderingTerm.asc(profileRuleLinks.order)]);
return query;
}
Selectable<Rule> _get({int? profileId, RuleScene? scene}) {
final query = _getSelectStatement(profileId: profileId, scene: scene);
return query.map((row) {
return row.readTable(rules).toRule(row.read(profileRuleLinks.order));
@@ -198,6 +250,7 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
ruleId: rule.id,
profileId: profileId,
scene: scene,
order: rule.order,
).toCompanion(),
);
});
@@ -207,65 +260,43 @@ class RulesDao extends DatabaseAccessor<Database> with _$RulesDaoMixin {
await rules.deleteWhere((t) => t.id.isIn(ruleIds));
}
Future<void> _putAll(
void _setWithBatch(
Batch b,
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
});
}
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
Future<void> _set(
Iterable<Rule> rules, {
int? profileId,
RuleScene? scene,
}) async {
await batch((b) {
b.insertAllOnConflictUpdate(
this.rules,
rules.map((item) => item.toCompanion()),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
b.deleteWhere(
profileRuleLinks,
(t) =>
(profileId == null
? t.profileId.isNull()
: t.profileId.equals(profileId)) &
(scene == null ? const Constant(true) : t.scene.equalsValue(scene)),
);
final keys = indexing.generateNKeys(rules.length);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.map(
(item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(),
),
);
b.insertAllOnConflictUpdate(
profileRuleLinks,
rules.mapIndexed(
(index, item) => ProfileRuleLink(
ruleId: item.id,
profileId: profileId,
scene: scene,
).toCompanion(keys[index]),
),
);
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
b.deleteWhere(this.rules, (r) {
final linkedIds = selectOnly(profileRuleLinks);
linkedIds.addColumns([profileRuleLinks.ruleId]);
return r.id.isNotInQuery(linkedIds);
});
}
}

View File

@@ -45,7 +45,7 @@ enum GroupType {
Relay;
static GroupType parseProfileType(String type) {
return switch (type) {
return switch (type.toLowerCase()) {
'url-test' => URLTest,
'select' => Selector,
'fallback' => Fallback,
@@ -72,7 +72,13 @@ extension GroupTypeExtension on GroupType {
return GroupType.values[index];
}
String get value => GroupTypeExtension.valueList[index];
String get value => switch (this) {
GroupType.URLTest => 'url-test',
GroupType.Selector => 'select',
GroupType.Fallback => 'fallback',
GroupType.LoadBalance => 'load-balance',
GroupType.Relay => 'relay',
};
}
enum UsedProxy { GLOBAL, DIRECT, REJECT }
@@ -282,6 +288,7 @@ enum FunctionTag {
autoScrollToEnd,
loadedProvider,
saveSharedFile,
removeProxy,
}
enum DashboardWidget {
@@ -402,7 +409,7 @@ enum OverwriteType {
// none,
standard,
script,
// custom,
custom,
}
enum RuleTarget { DIRECT, REJECT, MATCH }
@@ -424,3 +431,43 @@ enum LoadingTag { profiles, backup_restore, access, proxies }
enum CoreStatus { connecting, connected, disconnected }
enum RuleScene { added, disabled, custom }
enum ItemPosition {
start,
middle,
end,
startAndEnd;
static ItemPosition get(int index, int length) {
ItemPosition position = ItemPosition.middle;
if (length == 1) {
position = ItemPosition.startAndEnd;
} else if (index == length - 1) {
position = ItemPosition.end;
} else if (index == 0) {
position = ItemPosition.start;
}
return position;
}
static ItemPosition calculateVisualPosition<T>(
int currentIndex,
List<T> items,
Set<T> deletedItems,
) {
final currentItem = items[currentIndex];
if (deletedItems.contains(currentItem)) {
return ItemPosition.middle;
}
final int visualLength = items.length - deletedItems.length;
if (visualLength <= 0) return ItemPosition.middle;
int deletedCountBeforeMe = 0;
for (int i = 0; i < currentIndex; i++) {
if (deletedItems.contains(items[i])) {
deletedCountBeforeMe++;
}
}
final int visualIndex = currentIndex - deletedCountBeforeMe;
return ItemPosition.get(visualIndex, visualLength);
}
}

View File

@@ -8,8 +8,11 @@ import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/input.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:fl_clash/widgets/text.dart';
import 'package:flutter/material.dart';
final ruleItemHeight = globalState.measure.bodyMediumHeight * 2 + 14;
class RuleItem extends StatelessWidget {
final bool isSelected;
final bool isEditing;
@@ -28,14 +31,19 @@ class RuleItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CommonSelectedListItem(
return SelectedDecorationListItem(
isSelected: isSelected,
isEditing: isEditing,
onSelected: () {
onSelected();
},
title: Text(
rule.value,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
title: TooltipText(
text: Text(
rule.value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
),
),
onPressed: () {
onEdit(rule);
@@ -58,30 +66,19 @@ class RuleStatusItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
type: CommonCardType.filled,
onPressed: () {
onChange(!status);
},
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
trailing: Switch(value: status, onChanged: onChange),
title: Text(rule.value),
),
return DecorationListItem(
title: TooltipText(
text: Text(
rule.value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium?.toJetBrainsMono,
),
),
trailing: Switch(value: status, onChanged: onChange),
onPressed: () {
onChange(!status);
},
);
}
}

View File

@@ -270,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("Default"),
"delay": MessageLookupByLibrary.simpleMessage("Delay"),
"delaySort": MessageLookupByLibrary.simpleMessage("Sort by delay"),
"delayTest": MessageLookupByLibrary.simpleMessage("Delay Test"),
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteMultipTip": m1,
"deleteTip": m2,

View File

@@ -205,6 +205,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("デフォルト"),
"delay": MessageLookupByLibrary.simpleMessage("遅延"),
"delaySort": MessageLookupByLibrary.simpleMessage("遅延順"),
"delayTest": MessageLookupByLibrary.simpleMessage("遅延テスト"),
"delete": MessageLookupByLibrary.simpleMessage("削除"),
"deleteMultipTip": m1,
"deleteTip": m2,

View File

@@ -277,6 +277,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("По умолчанию"),
"delay": MessageLookupByLibrary.simpleMessage("Задержка"),
"delaySort": MessageLookupByLibrary.simpleMessage("Сортировка по задержке"),
"delayTest": MessageLookupByLibrary.simpleMessage("Тест задержки"),
"delete": MessageLookupByLibrary.simpleMessage("Удалить"),
"deleteMultipTip": m1,
"deleteTip": m2,

View File

@@ -185,6 +185,7 @@ class MessageLookup extends MessageLookupByLibrary {
"defaultText": MessageLookupByLibrary.simpleMessage("默认"),
"delay": MessageLookupByLibrary.simpleMessage("延迟"),
"delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"),
"delayTest": MessageLookupByLibrary.simpleMessage("延迟测试"),
"delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteMultipTip": m1,
"deleteTip": m2,

View File

@@ -3743,6 +3743,11 @@ class AppLocalizations {
String get addProfile {
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> {

View File

@@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/clash_config.freezed.dart';
part 'generated/clash_config.g.dart';
const defaultClashConfig = ClashConfig();
const defaultClashConfig = PatchClashConfig();
const defaultTun = Tun();
const defaultDns = Dns();
@@ -108,21 +108,37 @@ abstract class ProxyGroup with _$ProxyGroup {
List<String>? use,
int? interval,
bool? lazy,
@JsonKey(name: 'disable-udp') bool? disableUDP,
String? url,
int? timeout,
@JsonKey(name: 'max-failed-times') int? maxFailedTimes,
String? filter,
@JsonKey(name: 'expected-filter') String? excludeFilter,
@JsonKey(name: 'exclude-filter') String? excludeFilter,
@JsonKey(name: 'exclude-type') String? excludeType,
@JsonKey(name: 'expected-status') dynamic expectedStatus,
@JsonKey(name: 'expected-status') String? expectedStatus,
@JsonKey(name: 'include-all') bool? includeAll,
@JsonKey(name: 'include-all-proxies') bool? includeAllProxies,
@JsonKey(name: 'include-all-providers') bool? includeAllProviders,
bool? hidden,
String? icon,
String? order,
}) = _ProxyGroup;
factory ProxyGroup.fromJson(Map<String, Object?> json) =>
_$ProxyGroupFromJson(json);
}
@freezed
abstract class Proxy with _$Proxy {
const factory Proxy({
required String name,
required String type,
String? now,
}) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
}
@freezed
abstract class RuleProvider with _$RuleProvider {
const factory RuleProvider({required String name}) = _RuleProvider;
@@ -211,7 +227,7 @@ abstract class FallbackFilter with _$FallbackFilter {
const factory FallbackFilter({
@Default(true) bool geoip,
@Default('CN') @JsonKey(name: 'geoip-code') String geoipCode,
@Default(['gfw']) List<String> geosite,
@Default(['']) List<String> geosite,
@Default(['240.0.0.0/4']) List<String> ipcidr,
@Default(['+.google.com', '+.facebook.com', '+.youtube.com'])
List<String> domain,
@@ -390,7 +406,7 @@ extension RulesExt on List<Rule> {
var newList = List<Rule>.from(this);
final index = newList.indexWhere((item) => item.id == rule.id);
if (index != -1) {
newList[index] = rule;
rule = newList[index] = rule;
} else {
newList.insert(0, rule);
}
@@ -413,34 +429,36 @@ List<Rule> _genRule(List<dynamic>? rules) {
return rules.map((item) => Rule.value(item)).toList();
}
List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
}
List<SubRule> _genSubRules(Map<String, dynamic> json) {
return json.entries.map((entry) => SubRule(name: entry.key)).toList();
}
@freezed
abstract class ClashConfigSnippet with _$ClashConfigSnippet {
const factory ClashConfigSnippet({
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRule, name: 'rules') @Default([]) List<Rule> rule,
@JsonKey(name: 'rule-providers', fromJson: _genRuleProviders)
@Default([])
List<RuleProvider> ruleProvider,
@JsonKey(name: 'sub-rules', fromJson: _genSubRules)
@Default([])
List<SubRule> subRules,
}) = _ClashConfigSnippet;
factory ClashConfigSnippet.fromJson(Map<String, Object?> json) =>
_$ClashConfigSnippetFromJson(json);
}
// List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
// return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
// }
//
// List<SubRule> _genSubRules(Map<String, dynamic> json) {
// return json.entries.map((entry) => SubRule(name: entry.key)).toList();
// }
@freezed
abstract class ClashConfig with _$ClashConfig {
const factory ClashConfig({
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRule) @Default([]) List<Rule> rules,
@Default([]) List<Proxy> proxies,
// @JsonKey(name: 'rule-providers', fromJson: _genRuleProviders)
// @Default([])
// List<RuleProvider> ruleProvider,
// @JsonKey(name: 'sub-rules', fromJson: _genSubRules)
// @Default([])
// List<SubRule> subRules,
@Default({}) Map<String, String> proxyTypeMap,
}) = _ClashConfig;
factory ClashConfig.fromJson(Map<String, Object?> json) =>
_$ClashConfigFromJson(json);
}
@freezed
abstract class PatchClashConfig with _$PatchClashConfig {
const factory PatchClashConfig({
@Default(defaultMixedPort) @JsonKey(name: 'mixed-port') int mixedPort,
@Default(0) @JsonKey(name: 'socks-port') int socksPort,
@Default(0) @JsonKey(name: 'port') int port,
@@ -469,24 +487,22 @@ abstract class ClashConfig with _$ClashConfig {
@Default(GeodataLoader.memconservative)
@JsonKey(name: 'geodata-loader')
GeodataLoader geodataLoader,
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@Default([]) List<String> rule,
@JsonKey(name: 'global-ua') String? globalUa,
@Default(ExternalControllerStatus.close)
@JsonKey(name: 'external-controller')
ExternalControllerStatus externalController,
@Default({}) Map<String, String> hosts,
}) = _ClashConfig;
}) = _PatchClashConfig;
factory ClashConfig.fromJson(Map<String, Object?> json) =>
_$ClashConfigFromJson(json);
factory PatchClashConfig.fromJson(Map<String, Object?> json) =>
_$PatchClashConfigFromJson(json);
factory ClashConfig.safeFormJson(Map<String, Object?>? json) {
factory PatchClashConfig.safeFormJson(Map<String, Object?>? json) {
if (json == null) {
return defaultClashConfig;
}
try {
return ClashConfig.fromJson(json);
return PatchClashConfig.fromJson(json);
} catch (_) {
return defaultClashConfig;
}

View File

@@ -6,6 +6,8 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'clash_config.dart';
part 'generated/common.freezed.dart';
part 'generated/common.g.dart';
@@ -285,17 +287,6 @@ extension TrafficShowExt on TrafficShow {
String get show => '$value$unit';
}
@freezed
abstract class Proxy with _$Proxy {
const factory Proxy({
required String name,
required String type,
String? now,
}) = _Proxy;
factory Proxy.fromJson(Map<String, Object?> json) => _$ProxyFromJson(json);
}
@freezed
abstract class Group with _$Group {
const factory Group({
@@ -596,3 +587,11 @@ abstract class UpdatingMessage with _$UpdatingMessage {
required String message,
}) = _UpdatingMessage;
}
@freezed
abstract class IconButtonData with _$IconButtonData {
const factory IconButtonData({
required IconData icon,
required VoidCallback onPressed,
}) = _IconButtonData;
}

View File

@@ -239,7 +239,7 @@ abstract class Config with _$Config {
@JsonKey(fromJson: ThemeProps.safeFromJson) required ThemeProps themeProps,
@Default(defaultProxiesStyleProps) ProxiesStyleProps proxiesStyleProps,
@Default(defaultWindowProps) WindowProps windowProps,
@Default(defaultClashConfig) ClashConfig patchClashConfig,
@Default(defaultClashConfig) PatchClashConfig patchClashConfig,
}) = _Config;
factory Config.fromJson(Map<String, Object?> json) => _$ConfigFromJson(json);

File diff suppressed because it is too large Load Diff

View File

@@ -15,15 +15,20 @@ _ProxyGroup _$ProxyGroupFromJson(Map<String, dynamic> json) => _ProxyGroup(
use: (json['use'] as List<dynamic>?)?.map((e) => e as String).toList(),
interval: (json['interval'] as num?)?.toInt(),
lazy: json['lazy'] as bool?,
disableUDP: json['disable-udp'] as bool?,
url: json['url'] as String?,
timeout: (json['timeout'] as num?)?.toInt(),
maxFailedTimes: (json['max-failed-times'] as num?)?.toInt(),
filter: json['filter'] as String?,
excludeFilter: json['expected-filter'] as String?,
excludeFilter: json['exclude-filter'] as String?,
excludeType: json['exclude-type'] as String?,
expectedStatus: json['expected-status'],
expectedStatus: json['expected-status'] as String?,
includeAll: json['include-all'] as bool?,
includeAllProxies: json['include-all-proxies'] as bool?,
includeAllProviders: json['include-all-providers'] as bool?,
hidden: json['hidden'] as bool?,
icon: json['icon'] as String?,
order: json['order'] as String?,
);
Map<String, dynamic> _$ProxyGroupToJson(_ProxyGroup instance) =>
@@ -34,15 +39,20 @@ Map<String, dynamic> _$ProxyGroupToJson(_ProxyGroup instance) =>
'use': instance.use,
'interval': instance.interval,
'lazy': instance.lazy,
'disable-udp': instance.disableUDP,
'url': instance.url,
'timeout': instance.timeout,
'max-failed-times': instance.maxFailedTimes,
'filter': instance.filter,
'expected-filter': instance.excludeFilter,
'exclude-filter': instance.excludeFilter,
'exclude-type': instance.excludeType,
'expected-status': instance.expectedStatus,
'include-all': instance.includeAll,
'include-all-proxies': instance.includeAllProxies,
'include-all-providers': instance.includeAllProviders,
'hidden': instance.hidden,
'icon': instance.icon,
'order': instance.order,
};
const _$GroupTypeEnumMap = {
@@ -53,6 +63,18 @@ const _$GroupTypeEnumMap = {
GroupType.Relay: 'Relay',
};
_Proxy _$ProxyFromJson(Map<String, dynamic> json) => _Proxy(
name: json['name'] as String,
type: json['type'] as String,
now: json['now'] as String?,
);
Map<String, dynamic> _$ProxyToJson(_Proxy instance) => <String, dynamic>{
'name': instance.name,
'type': instance.type,
'now': instance.now,
};
_RuleProvider _$RuleProviderFromJson(Map<String, dynamic> json) =>
_RuleProvider(name: json['name'] as String);
@@ -168,7 +190,7 @@ _FallbackFilter _$FallbackFilterFromJson(
geoipCode: json['geoip-code'] as String? ?? 'CN',
geosite:
(json['geosite'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const ['gfw'],
const [''],
ipcidr:
(json['ipcidr'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const ['240.0.0.0/4'],
@@ -304,89 +326,85 @@ Map<String, dynamic> _$SubRuleToJson(_SubRule instance) => <String, dynamic>{
'name': instance.name,
};
_ClashConfigSnippet _$ClashConfigSnippetFromJson(Map<String, dynamic> json) =>
_ClashConfigSnippet(
proxyGroups:
(json['proxy-groups'] as List<dynamic>?)
?.map((e) => ProxyGroup.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
rule: json['rules'] == null ? const [] : _genRule(json['rules'] as List?),
ruleProvider: json['rule-providers'] == null
? const []
: _genRuleProviders(json['rule-providers'] as Map<String, dynamic>),
subRules: json['sub-rules'] == null
? const []
: _genSubRules(json['sub-rules'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ClashConfigSnippetToJson(_ClashConfigSnippet instance) =>
<String, dynamic>{
'proxy-groups': instance.proxyGroups,
'rules': instance.rule,
'rule-providers': instance.ruleProvider,
'sub-rules': instance.subRules,
};
_ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => _ClashConfig(
mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort,
socksPort: (json['socks-port'] as num?)?.toInt() ?? 0,
port: (json['port'] as num?)?.toInt() ?? 0,
redirPort: (json['redir-port'] as num?)?.toInt() ?? 0,
tproxyPort: (json['tproxy-port'] as num?)?.toInt() ?? 0,
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false,
logLevel:
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
LogLevel.error,
ipv6: json['ipv6'] as bool? ?? false,
findProcessMode:
$enumDecodeNullable(
_$FindProcessModeEnumMap,
json['find-process-mode'],
unknownValue: FindProcessMode.always,
) ??
FindProcessMode.always,
keepAliveInterval:
(json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval,
unifiedDelay: json['unified-delay'] as bool? ?? true,
tcpConcurrent: json['tcp-concurrent'] as bool? ?? true,
tun: json['tun'] == null
? defaultTun
: Tun.safeFormJson(json['tun'] as Map<String, Object?>?),
dns: json['dns'] == null
? defaultDns
: Dns.safeDnsFromJson(json['dns'] as Map<String, Object?>),
geoXUrl: json['geox-url'] == null
? defaultGeoXUrl
: GeoXUrl.safeFormJson(json['geox-url'] as Map<String, Object?>?),
geodataLoader:
$enumDecodeNullable(_$GeodataLoaderEnumMap, json['geodata-loader']) ??
GeodataLoader.memconservative,
proxyGroups:
(json['proxy-groups'] as List<dynamic>?)
?.map((e) => ProxyGroup.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
rule:
(json['rule'] as List<dynamic>?)?.map((e) => e as String).toList() ??
rules: json['rules'] == null ? const [] : _genRule(json['rules'] as List?),
proxies:
(json['proxies'] as List<dynamic>?)
?.map((e) => Proxy.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
globalUa: json['global-ua'] as String?,
externalController:
$enumDecodeNullable(
_$ExternalControllerStatusEnumMap,
json['external-controller'],
) ??
ExternalControllerStatus.close,
hosts:
(json['hosts'] as Map<String, dynamic>?)?.map(
proxyTypeMap:
(json['proxyTypeMap'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
);
Map<String, dynamic> _$ClashConfigToJson(_ClashConfig instance) =>
<String, dynamic>{
'proxy-groups': instance.proxyGroups,
'rules': instance.rules,
'proxies': instance.proxies,
'proxyTypeMap': instance.proxyTypeMap,
};
_PatchClashConfig _$PatchClashConfigFromJson(Map<String, dynamic> json) =>
_PatchClashConfig(
mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort,
socksPort: (json['socks-port'] as num?)?.toInt() ?? 0,
port: (json['port'] as num?)?.toInt() ?? 0,
redirPort: (json['redir-port'] as num?)?.toInt() ?? 0,
tproxyPort: (json['tproxy-port'] as num?)?.toInt() ?? 0,
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false,
logLevel:
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
LogLevel.error,
ipv6: json['ipv6'] as bool? ?? false,
findProcessMode:
$enumDecodeNullable(
_$FindProcessModeEnumMap,
json['find-process-mode'],
unknownValue: FindProcessMode.always,
) ??
FindProcessMode.always,
keepAliveInterval:
(json['keep-alive-interval'] as num?)?.toInt() ??
defaultKeepAliveInterval,
unifiedDelay: json['unified-delay'] as bool? ?? true,
tcpConcurrent: json['tcp-concurrent'] as bool? ?? true,
tun: json['tun'] == null
? defaultTun
: Tun.safeFormJson(json['tun'] as Map<String, Object?>?),
dns: json['dns'] == null
? defaultDns
: Dns.safeDnsFromJson(json['dns'] as Map<String, Object?>),
geoXUrl: json['geox-url'] == null
? defaultGeoXUrl
: GeoXUrl.safeFormJson(json['geox-url'] as Map<String, Object?>?),
geodataLoader:
$enumDecodeNullable(_$GeodataLoaderEnumMap, json['geodata-loader']) ??
GeodataLoader.memconservative,
globalUa: json['global-ua'] as String?,
externalController:
$enumDecodeNullable(
_$ExternalControllerStatusEnumMap,
json['external-controller'],
) ??
ExternalControllerStatus.close,
hosts:
(json['hosts'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
) ??
const {},
);
Map<String, dynamic> _$PatchClashConfigToJson(_PatchClashConfig instance) =>
<String, dynamic>{
'mixed-port': instance.mixedPort,
'socks-port': instance.socksPort,
@@ -405,8 +423,6 @@ Map<String, dynamic> _$ClashConfigToJson(_ClashConfig instance) =>
'dns': instance.dns,
'geox-url': instance.geoXUrl,
'geodata-loader': _$GeodataLoaderEnumMap[instance.geodataLoader]!,
'proxy-groups': instance.proxyGroups,
'rule': instance.rule,
'global-ua': instance.globalUa,
'external-controller':
_$ExternalControllerStatusEnumMap[instance.externalController]!,

View File

@@ -3356,275 +3356,6 @@ as String,
}
/// @nodoc
mixin _$Proxy {
String get name; String get type; String? get now;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProxyCopyWith<Proxy> get copyWith => _$ProxyCopyWithImpl<Proxy>(this as Proxy, _$identity);
/// Serializes this Proxy to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Proxy&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.now, now) || other.now == now));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name,type,now);
@override
String toString() {
return 'Proxy(name: $name, type: $type, now: $now)';
}
}
/// @nodoc
abstract mixin class $ProxyCopyWith<$Res> {
factory $ProxyCopyWith(Proxy value, $Res Function(Proxy) _then) = _$ProxyCopyWithImpl;
@useResult
$Res call({
String name, String type, String? now
});
}
/// @nodoc
class _$ProxyCopyWithImpl<$Res>
implements $ProxyCopyWith<$Res> {
_$ProxyCopyWithImpl(this._self, this._then);
final Proxy _self;
final $Res Function(Proxy) _then;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? type = null,Object? now = freezed,}) {
return _then(_self.copyWith(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,now: freezed == now ? _self.now : now // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [Proxy].
extension ProxyPatterns on Proxy {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Proxy value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Proxy() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Proxy value) $default,){
final _that = this;
switch (_that) {
case _Proxy():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Proxy value)? $default,){
final _that = this;
switch (_that) {
case _Proxy() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String type, String? now)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Proxy() when $default != null:
return $default(_that.name,_that.type,_that.now);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String type, String? now) $default,) {final _that = this;
switch (_that) {
case _Proxy():
return $default(_that.name,_that.type,_that.now);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String type, String? now)? $default,) {final _that = this;
switch (_that) {
case _Proxy() when $default != null:
return $default(_that.name,_that.type,_that.now);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Proxy implements Proxy {
const _Proxy({required this.name, required this.type, this.now});
factory _Proxy.fromJson(Map<String, dynamic> json) => _$ProxyFromJson(json);
@override final String name;
@override final String type;
@override final String? now;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ProxyCopyWith<_Proxy> get copyWith => __$ProxyCopyWithImpl<_Proxy>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ProxyToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Proxy&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.now, now) || other.now == now));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name,type,now);
@override
String toString() {
return 'Proxy(name: $name, type: $type, now: $now)';
}
}
/// @nodoc
abstract mixin class _$ProxyCopyWith<$Res> implements $ProxyCopyWith<$Res> {
factory _$ProxyCopyWith(_Proxy value, $Res Function(_Proxy) _then) = __$ProxyCopyWithImpl;
@override @useResult
$Res call({
String name, String type, String? now
});
}
/// @nodoc
class __$ProxyCopyWithImpl<$Res>
implements _$ProxyCopyWith<$Res> {
__$ProxyCopyWithImpl(this._self, this._then);
final _Proxy _self;
final $Res Function(_Proxy) _then;
/// Create a copy of Proxy
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? type = null,Object? now = freezed,}) {
return _then(_Proxy(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,now: freezed == now ? _self.now : now // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$Group {
@@ -6019,6 +5750,266 @@ as String,
}
}
/// @nodoc
mixin _$IconButtonData {
IconData get icon; VoidCallback get onPressed;
/// Create a copy of IconButtonData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$IconButtonDataCopyWith<IconButtonData> get copyWith => _$IconButtonDataCopyWithImpl<IconButtonData>(this as IconButtonData, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is IconButtonData&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.onPressed, onPressed) || other.onPressed == onPressed));
}
@override
int get hashCode => Object.hash(runtimeType,icon,onPressed);
@override
String toString() {
return 'IconButtonData(icon: $icon, onPressed: $onPressed)';
}
}
/// @nodoc
abstract mixin class $IconButtonDataCopyWith<$Res> {
factory $IconButtonDataCopyWith(IconButtonData value, $Res Function(IconButtonData) _then) = _$IconButtonDataCopyWithImpl;
@useResult
$Res call({
IconData icon, VoidCallback onPressed
});
}
/// @nodoc
class _$IconButtonDataCopyWithImpl<$Res>
implements $IconButtonDataCopyWith<$Res> {
_$IconButtonDataCopyWithImpl(this._self, this._then);
final IconButtonData _self;
final $Res Function(IconButtonData) _then;
/// Create a copy of IconButtonData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? icon = null,Object? onPressed = null,}) {
return _then(_self.copyWith(
icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
as IconData,onPressed: null == onPressed ? _self.onPressed : onPressed // ignore: cast_nullable_to_non_nullable
as VoidCallback,
));
}
}
/// Adds pattern-matching-related methods to [IconButtonData].
extension IconButtonDataPatterns on IconButtonData {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _IconButtonData value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _IconButtonData() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _IconButtonData value) $default,){
final _that = this;
switch (_that) {
case _IconButtonData():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _IconButtonData value)? $default,){
final _that = this;
switch (_that) {
case _IconButtonData() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( IconData icon, VoidCallback onPressed)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _IconButtonData() when $default != null:
return $default(_that.icon,_that.onPressed);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( IconData icon, VoidCallback onPressed) $default,) {final _that = this;
switch (_that) {
case _IconButtonData():
return $default(_that.icon,_that.onPressed);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( IconData icon, VoidCallback onPressed)? $default,) {final _that = this;
switch (_that) {
case _IconButtonData() when $default != null:
return $default(_that.icon,_that.onPressed);case _:
return null;
}
}
}
/// @nodoc
class _IconButtonData implements IconButtonData {
const _IconButtonData({required this.icon, required this.onPressed});
@override final IconData icon;
@override final VoidCallback onPressed;
/// Create a copy of IconButtonData
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$IconButtonDataCopyWith<_IconButtonData> get copyWith => __$IconButtonDataCopyWithImpl<_IconButtonData>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _IconButtonData&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.onPressed, onPressed) || other.onPressed == onPressed));
}
@override
int get hashCode => Object.hash(runtimeType,icon,onPressed);
@override
String toString() {
return 'IconButtonData(icon: $icon, onPressed: $onPressed)';
}
}
/// @nodoc
abstract mixin class _$IconButtonDataCopyWith<$Res> implements $IconButtonDataCopyWith<$Res> {
factory _$IconButtonDataCopyWith(_IconButtonData value, $Res Function(_IconButtonData) _then) = __$IconButtonDataCopyWithImpl;
@override @useResult
$Res call({
IconData icon, VoidCallback onPressed
});
}
/// @nodoc
class __$IconButtonDataCopyWithImpl<$Res>
implements _$IconButtonDataCopyWith<$Res> {
__$IconButtonDataCopyWithImpl(this._self, this._then);
final _IconButtonData _self;
final $Res Function(_IconButtonData) _then;
/// Create a copy of IconButtonData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? icon = null,Object? onPressed = null,}) {
return _then(_IconButtonData(
icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
as IconData,onPressed: null == onPressed ? _self.onPressed : onPressed // ignore: cast_nullable_to_non_nullable
as VoidCallback,
));
}
}
// dart format on

View File

@@ -158,18 +158,6 @@ Map<String, dynamic> _$TrafficToJson(_Traffic instance) => <String, dynamic>{
'down': instance.down,
};
_Proxy _$ProxyFromJson(Map<String, dynamic> json) => _Proxy(
name: json['name'] as String,
type: json['type'] as String,
now: json['now'] as String?,
);
Map<String, dynamic> _$ProxyToJson(_Proxy instance) => <String, dynamic>{
'name': instance.name,
'type': instance.type,
'now': instance.now,
};
_Group _$GroupFromJson(Map<String, dynamic> json) => _Group(
type: $enumDecode(_$GroupTypeEnumMap, json['type']),
all:

View File

@@ -2326,7 +2326,7 @@ $TextScaleCopyWith<$Res> get textScale {
/// @nodoc
mixin _$Config {
int? get currentProfileId; bool get overrideDns; List<HotKeyAction> get hotKeyActions;@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps get appSettingProps; DAVProps? get davProps; NetworkProps get networkProps; VpnProps get vpnProps;@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps get themeProps; ProxiesStyleProps get proxiesStyleProps; WindowProps get windowProps; ClashConfig get patchClashConfig;
int? get currentProfileId; bool get overrideDns; List<HotKeyAction> get hotKeyActions;@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps get appSettingProps; DAVProps? get davProps; NetworkProps get networkProps; VpnProps get vpnProps;@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps get themeProps; ProxiesStyleProps get proxiesStyleProps; WindowProps get windowProps; PatchClashConfig get patchClashConfig;
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -2359,11 +2359,11 @@ abstract mixin class $ConfigCopyWith<$Res> {
factory $ConfigCopyWith(Config value, $Res Function(Config) _then) = _$ConfigCopyWithImpl;
@useResult
$Res call({
int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions,@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, ClashConfig patchClashConfig
int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions,@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, PatchClashConfig patchClashConfig
});
$AppSettingPropsCopyWith<$Res> get appSettingProps;$DAVPropsCopyWith<$Res>? get davProps;$NetworkPropsCopyWith<$Res> get networkProps;$VpnPropsCopyWith<$Res> get vpnProps;$ThemePropsCopyWith<$Res> get themeProps;$ProxiesStylePropsCopyWith<$Res> get proxiesStyleProps;$WindowPropsCopyWith<$Res> get windowProps;$ClashConfigCopyWith<$Res> get patchClashConfig;
$AppSettingPropsCopyWith<$Res> get appSettingProps;$DAVPropsCopyWith<$Res>? get davProps;$NetworkPropsCopyWith<$Res> get networkProps;$VpnPropsCopyWith<$Res> get vpnProps;$ThemePropsCopyWith<$Res> get themeProps;$ProxiesStylePropsCopyWith<$Res> get proxiesStyleProps;$WindowPropsCopyWith<$Res> get windowProps;$PatchClashConfigCopyWith<$Res> get patchClashConfig;
}
/// @nodoc
@@ -2389,7 +2389,7 @@ as VpnProps,themeProps: null == themeProps ? _self.themeProps : themeProps // ig
as ThemeProps,proxiesStyleProps: null == proxiesStyleProps ? _self.proxiesStyleProps : proxiesStyleProps // ignore: cast_nullable_to_non_nullable
as ProxiesStyleProps,windowProps: null == windowProps ? _self.windowProps : windowProps // ignore: cast_nullable_to_non_nullable
as WindowProps,patchClashConfig: null == patchClashConfig ? _self.patchClashConfig : patchClashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,
as PatchClashConfig,
));
}
/// Create a copy of Config
@@ -2462,9 +2462,9 @@ $WindowPropsCopyWith<$Res> get windowProps {
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get patchClashConfig {
$PatchClashConfigCopyWith<$Res> get patchClashConfig {
return $ClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return $PatchClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return _then(_self.copyWith(patchClashConfig: value));
});
}
@@ -2549,7 +2549,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, ClashConfig patchClashConfig)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, PatchClashConfig patchClashConfig)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Config() when $default != null:
return $default(_that.currentProfileId,_that.overrideDns,_that.hotKeyActions,_that.appSettingProps,_that.davProps,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyleProps,_that.windowProps,_that.patchClashConfig);case _:
@@ -2570,7 +2570,7 @@ return $default(_that.currentProfileId,_that.overrideDns,_that.hotKeyActions,_th
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, ClashConfig patchClashConfig) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, PatchClashConfig patchClashConfig) $default,) {final _that = this;
switch (_that) {
case _Config():
return $default(_that.currentProfileId,_that.overrideDns,_that.hotKeyActions,_that.appSettingProps,_that.davProps,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyleProps,_that.windowProps,_that.patchClashConfig);case _:
@@ -2590,7 +2590,7 @@ return $default(_that.currentProfileId,_that.overrideDns,_that.hotKeyActions,_th
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, ClashConfig patchClashConfig)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions, @JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps, @JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, PatchClashConfig patchClashConfig)? $default,) {final _that = this;
switch (_that) {
case _Config() when $default != null:
return $default(_that.currentProfileId,_that.overrideDns,_that.hotKeyActions,_that.appSettingProps,_that.davProps,_that.networkProps,_that.vpnProps,_that.themeProps,_that.proxiesStyleProps,_that.windowProps,_that.patchClashConfig);case _:
@@ -2624,7 +2624,7 @@ class _Config implements Config {
@override@JsonKey(fromJson: ThemeProps.safeFromJson) final ThemeProps themeProps;
@override@JsonKey() final ProxiesStyleProps proxiesStyleProps;
@override@JsonKey() final WindowProps windowProps;
@override@JsonKey() final ClashConfig patchClashConfig;
@override@JsonKey() final PatchClashConfig patchClashConfig;
/// Create a copy of Config
/// with the given fields replaced by the non-null parameter values.
@@ -2659,11 +2659,11 @@ abstract mixin class _$ConfigCopyWith<$Res> implements $ConfigCopyWith<$Res> {
factory _$ConfigCopyWith(_Config value, $Res Function(_Config) _then) = __$ConfigCopyWithImpl;
@override @useResult
$Res call({
int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions,@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, ClashConfig patchClashConfig
int? currentProfileId, bool overrideDns, List<HotKeyAction> hotKeyActions,@JsonKey(fromJson: AppSettingProps.safeFromJson) AppSettingProps appSettingProps, DAVProps? davProps, NetworkProps networkProps, VpnProps vpnProps,@JsonKey(fromJson: ThemeProps.safeFromJson) ThemeProps themeProps, ProxiesStyleProps proxiesStyleProps, WindowProps windowProps, PatchClashConfig patchClashConfig
});
@override $AppSettingPropsCopyWith<$Res> get appSettingProps;@override $DAVPropsCopyWith<$Res>? get davProps;@override $NetworkPropsCopyWith<$Res> get networkProps;@override $VpnPropsCopyWith<$Res> get vpnProps;@override $ThemePropsCopyWith<$Res> get themeProps;@override $ProxiesStylePropsCopyWith<$Res> get proxiesStyleProps;@override $WindowPropsCopyWith<$Res> get windowProps;@override $ClashConfigCopyWith<$Res> get patchClashConfig;
@override $AppSettingPropsCopyWith<$Res> get appSettingProps;@override $DAVPropsCopyWith<$Res>? get davProps;@override $NetworkPropsCopyWith<$Res> get networkProps;@override $VpnPropsCopyWith<$Res> get vpnProps;@override $ThemePropsCopyWith<$Res> get themeProps;@override $ProxiesStylePropsCopyWith<$Res> get proxiesStyleProps;@override $WindowPropsCopyWith<$Res> get windowProps;@override $PatchClashConfigCopyWith<$Res> get patchClashConfig;
}
/// @nodoc
@@ -2689,7 +2689,7 @@ as VpnProps,themeProps: null == themeProps ? _self.themeProps : themeProps // ig
as ThemeProps,proxiesStyleProps: null == proxiesStyleProps ? _self.proxiesStyleProps : proxiesStyleProps // ignore: cast_nullable_to_non_nullable
as ProxiesStyleProps,windowProps: null == windowProps ? _self.windowProps : windowProps // ignore: cast_nullable_to_non_nullable
as WindowProps,patchClashConfig: null == patchClashConfig ? _self.patchClashConfig : patchClashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,
as PatchClashConfig,
));
}
@@ -2763,9 +2763,9 @@ $WindowPropsCopyWith<$Res> get windowProps {
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get patchClashConfig {
$PatchClashConfigCopyWith<$Res> get patchClashConfig {
return $ClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return $PatchClashConfigCopyWith<$Res>(_self.patchClashConfig, (value) {
return _then(_self.copyWith(patchClashConfig: value));
});
}

View File

@@ -339,7 +339,9 @@ _Config _$ConfigFromJson(Map<String, dynamic> json) => _Config(
: WindowProps.fromJson(json['windowProps'] as Map<String, dynamic>?),
patchClashConfig: json['patchClashConfig'] == null
? defaultClashConfig
: ClashConfig.fromJson(json['patchClashConfig'] as Map<String, dynamic>),
: PatchClashConfig.fromJson(
json['patchClashConfig'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$ConfigToJson(_Config instance) => <String, dynamic>{

View File

@@ -73,6 +73,7 @@ Map<String, dynamic> _$ProfileToJson(_Profile instance) => <String, dynamic>{
const _$OverwriteTypeEnumMap = {
OverwriteType.standard: 'standard',
OverwriteType.script: 'script',
OverwriteType.custom: 'custom',
};
_StandardOverwrite _$StandardOverwriteFromJson(Map<String, dynamic> json) =>

View File

@@ -7364,287 +7364,6 @@ as int,
}
/// @nodoc
mixin _$ClashConfigState {
bool get overrideDns; ClashConfig get clashConfig; RouteMode get routeMode;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ClashConfigStateCopyWith<ClashConfigState> get copyWith => _$ClashConfigStateCopyWithImpl<ClashConfigState>(this as ClashConfigState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ClashConfigState&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.clashConfig, clashConfig) || other.clashConfig == clashConfig)&&(identical(other.routeMode, routeMode) || other.routeMode == routeMode));
}
@override
int get hashCode => Object.hash(runtimeType,overrideDns,clashConfig,routeMode);
@override
String toString() {
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, routeMode: $routeMode)';
}
}
/// @nodoc
abstract mixin class $ClashConfigStateCopyWith<$Res> {
factory $ClashConfigStateCopyWith(ClashConfigState value, $Res Function(ClashConfigState) _then) = _$ClashConfigStateCopyWithImpl;
@useResult
$Res call({
bool overrideDns, ClashConfig clashConfig, RouteMode routeMode
});
$ClashConfigCopyWith<$Res> get clashConfig;
}
/// @nodoc
class _$ClashConfigStateCopyWithImpl<$Res>
implements $ClashConfigStateCopyWith<$Res> {
_$ClashConfigStateCopyWithImpl(this._self, this._then);
final ClashConfigState _self;
final $Res Function(ClashConfigState) _then;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? overrideDns = null,Object? clashConfig = null,Object? routeMode = null,}) {
return _then(_self.copyWith(
overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,clashConfig: null == clashConfig ? _self.clashConfig : clashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,routeMode: null == routeMode ? _self.routeMode : routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
));
}
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get clashConfig {
return $ClashConfigCopyWith<$Res>(_self.clashConfig, (value) {
return _then(_self.copyWith(clashConfig: value));
});
}
}
/// Adds pattern-matching-related methods to [ClashConfigState].
extension ClashConfigStatePatterns on ClashConfigState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ClashConfigState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ClashConfigState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ClashConfigState value) $default,){
final _that = this;
switch (_that) {
case _ClashConfigState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ClashConfigState value)? $default,){
final _that = this;
switch (_that) {
case _ClashConfigState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool overrideDns, ClashConfig clashConfig, RouteMode routeMode)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ClashConfigState() when $default != null:
return $default(_that.overrideDns,_that.clashConfig,_that.routeMode);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool overrideDns, ClashConfig clashConfig, RouteMode routeMode) $default,) {final _that = this;
switch (_that) {
case _ClashConfigState():
return $default(_that.overrideDns,_that.clashConfig,_that.routeMode);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool overrideDns, ClashConfig clashConfig, RouteMode routeMode)? $default,) {final _that = this;
switch (_that) {
case _ClashConfigState() when $default != null:
return $default(_that.overrideDns,_that.clashConfig,_that.routeMode);case _:
return null;
}
}
}
/// @nodoc
class _ClashConfigState implements ClashConfigState {
const _ClashConfigState({required this.overrideDns, required this.clashConfig, required this.routeMode});
@override final bool overrideDns;
@override final ClashConfig clashConfig;
@override final RouteMode routeMode;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ClashConfigStateCopyWith<_ClashConfigState> get copyWith => __$ClashConfigStateCopyWithImpl<_ClashConfigState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ClashConfigState&&(identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns)&&(identical(other.clashConfig, clashConfig) || other.clashConfig == clashConfig)&&(identical(other.routeMode, routeMode) || other.routeMode == routeMode));
}
@override
int get hashCode => Object.hash(runtimeType,overrideDns,clashConfig,routeMode);
@override
String toString() {
return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, routeMode: $routeMode)';
}
}
/// @nodoc
abstract mixin class _$ClashConfigStateCopyWith<$Res> implements $ClashConfigStateCopyWith<$Res> {
factory _$ClashConfigStateCopyWith(_ClashConfigState value, $Res Function(_ClashConfigState) _then) = __$ClashConfigStateCopyWithImpl;
@override @useResult
$Res call({
bool overrideDns, ClashConfig clashConfig, RouteMode routeMode
});
@override $ClashConfigCopyWith<$Res> get clashConfig;
}
/// @nodoc
class __$ClashConfigStateCopyWithImpl<$Res>
implements _$ClashConfigStateCopyWith<$Res> {
__$ClashConfigStateCopyWithImpl(this._self, this._then);
final _ClashConfigState _self;
final $Res Function(_ClashConfigState) _then;
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? overrideDns = null,Object? clashConfig = null,Object? routeMode = null,}) {
return _then(_ClashConfigState(
overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,clashConfig: null == clashConfig ? _self.clashConfig : clashConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,routeMode: null == routeMode ? _self.routeMode : routeMode // ignore: cast_nullable_to_non_nullable
as RouteMode,
));
}
/// Create a copy of ClashConfigState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get clashConfig {
return $ClashConfigCopyWith<$Res>(_self.clashConfig, (value) {
return _then(_self.copyWith(clashConfig: value));
});
}
}
/// @nodoc
mixin _$DashboardState {
@@ -9086,7 +8805,7 @@ $ProxiesDataCopyWith<$Res> get proxiesData {
/// @nodoc
mixin _$MakeRealProfileState {
String get profilesPath; int get profileId; Map<String, dynamic> get rawConfig; ClashConfig get realPatchConfig; bool get overrideDns; bool get appendSystemDns; List<Rule> get addedRules; String get defaultUA;
String get profilesPath; int get profileId; Map<String, dynamic> get rawConfig; PatchClashConfig get realPatchConfig; bool get overrideDns; bool get appendSystemDns; List<Rule> get addedRules; String get defaultUA;
/// Create a copy of MakeRealProfileState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -9117,11 +8836,11 @@ abstract mixin class $MakeRealProfileStateCopyWith<$Res> {
factory $MakeRealProfileStateCopyWith(MakeRealProfileState value, $Res Function(MakeRealProfileState) _then) = _$MakeRealProfileStateCopyWithImpl;
@useResult
$Res call({
String profilesPath, int profileId, Map<String, dynamic> rawConfig, ClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA
String profilesPath, int profileId, Map<String, dynamic> rawConfig, PatchClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA
});
$ClashConfigCopyWith<$Res> get realPatchConfig;
$PatchClashConfigCopyWith<$Res> get realPatchConfig;
}
/// @nodoc
@@ -9140,7 +8859,7 @@ profilesPath: null == profilesPath ? _self.profilesPath : profilesPath // ignore
as String,profileId: null == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as int,rawConfig: null == rawConfig ? _self.rawConfig : rawConfig // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,realPatchConfig: null == realPatchConfig ? _self.realPatchConfig : realPatchConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as PatchClashConfig,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,appendSystemDns: null == appendSystemDns ? _self.appendSystemDns : appendSystemDns // ignore: cast_nullable_to_non_nullable
as bool,addedRules: null == addedRules ? _self.addedRules : addedRules // ignore: cast_nullable_to_non_nullable
as List<Rule>,defaultUA: null == defaultUA ? _self.defaultUA : defaultUA // ignore: cast_nullable_to_non_nullable
@@ -9151,9 +8870,9 @@ as String,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get realPatchConfig {
$PatchClashConfigCopyWith<$Res> get realPatchConfig {
return $ClashConfigCopyWith<$Res>(_self.realPatchConfig, (value) {
return $PatchClashConfigCopyWith<$Res>(_self.realPatchConfig, (value) {
return _then(_self.copyWith(realPatchConfig: value));
});
}
@@ -9238,7 +8957,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, ClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, PatchClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MakeRealProfileState() when $default != null:
return $default(_that.profilesPath,_that.profileId,_that.rawConfig,_that.realPatchConfig,_that.overrideDns,_that.appendSystemDns,_that.addedRules,_that.defaultUA);case _:
@@ -9259,7 +8978,7 @@ return $default(_that.profilesPath,_that.profileId,_that.rawConfig,_that.realPat
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, ClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, PatchClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA) $default,) {final _that = this;
switch (_that) {
case _MakeRealProfileState():
return $default(_that.profilesPath,_that.profileId,_that.rawConfig,_that.realPatchConfig,_that.overrideDns,_that.appendSystemDns,_that.addedRules,_that.defaultUA);case _:
@@ -9279,7 +8998,7 @@ return $default(_that.profilesPath,_that.profileId,_that.rawConfig,_that.realPat
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, ClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String profilesPath, int profileId, Map<String, dynamic> rawConfig, PatchClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA)? $default,) {final _that = this;
switch (_that) {
case _MakeRealProfileState() when $default != null:
return $default(_that.profilesPath,_that.profileId,_that.rawConfig,_that.realPatchConfig,_that.overrideDns,_that.appendSystemDns,_that.addedRules,_that.defaultUA);case _:
@@ -9306,7 +9025,7 @@ class _MakeRealProfileState implements MakeRealProfileState {
return EqualUnmodifiableMapView(_rawConfig);
}
@override final ClashConfig realPatchConfig;
@override final PatchClashConfig realPatchConfig;
@override final bool overrideDns;
@override final bool appendSystemDns;
final List<Rule> _addedRules;
@@ -9348,11 +9067,11 @@ abstract mixin class _$MakeRealProfileStateCopyWith<$Res> implements $MakeRealPr
factory _$MakeRealProfileStateCopyWith(_MakeRealProfileState value, $Res Function(_MakeRealProfileState) _then) = __$MakeRealProfileStateCopyWithImpl;
@override @useResult
$Res call({
String profilesPath, int profileId, Map<String, dynamic> rawConfig, ClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA
String profilesPath, int profileId, Map<String, dynamic> rawConfig, PatchClashConfig realPatchConfig, bool overrideDns, bool appendSystemDns, List<Rule> addedRules, String defaultUA
});
@override $ClashConfigCopyWith<$Res> get realPatchConfig;
@override $PatchClashConfigCopyWith<$Res> get realPatchConfig;
}
/// @nodoc
@@ -9371,7 +9090,7 @@ profilesPath: null == profilesPath ? _self.profilesPath : profilesPath // ignore
as String,profileId: null == profileId ? _self.profileId : profileId // ignore: cast_nullable_to_non_nullable
as int,rawConfig: null == rawConfig ? _self._rawConfig : rawConfig // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,realPatchConfig: null == realPatchConfig ? _self.realPatchConfig : realPatchConfig // ignore: cast_nullable_to_non_nullable
as ClashConfig,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as PatchClashConfig,overrideDns: null == overrideDns ? _self.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable
as bool,appendSystemDns: null == appendSystemDns ? _self.appendSystemDns : appendSystemDns // ignore: cast_nullable_to_non_nullable
as bool,addedRules: null == addedRules ? _self._addedRules : addedRules // ignore: cast_nullable_to_non_nullable
as List<Rule>,defaultUA: null == defaultUA ? _self.defaultUA : defaultUA // ignore: cast_nullable_to_non_nullable
@@ -9383,9 +9102,9 @@ as String,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ClashConfigCopyWith<$Res> get realPatchConfig {
$PatchClashConfigCopyWith<$Res> get realPatchConfig {
return $ClashConfigCopyWith<$Res>(_self.realPatchConfig, (value) {
return $PatchClashConfigCopyWith<$Res>(_self.realPatchConfig, (value) {
return _then(_self.copyWith(realPatchConfig: value));
});
}

View File

@@ -271,15 +271,6 @@ abstract class ProxyState with _$ProxyState {
}) = _ProxyState;
}
@freezed
abstract class ClashConfigState with _$ClashConfigState {
const factory ClashConfigState({
required bool overrideDns,
required ClashConfig clashConfig,
required RouteMode routeMode,
}) = _ClashConfigState;
}
@freezed
abstract class DashboardState with _$DashboardState {
const factory DashboardState({
@@ -343,7 +334,7 @@ abstract class MakeRealProfileState with _$MakeRealProfileState {
required String profilesPath,
required int profileId,
required Map<String, dynamic> rawConfig,
required ClashConfig realPatchConfig,
required PatchClashConfig realPatchConfig,
required bool overrideDns,
required bool appendSystemDns,
required List<Rule> addedRules,

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
@@ -18,7 +20,7 @@ typedef TextEditingValueChangeBuilder = Widget Function(TextEditingValue value);
class EditorPage extends ConsumerStatefulWidget {
final String title;
final String content;
final String? content;
final List<Language> languages;
final bool supportRemoteDownload;
final bool titleEditable;
@@ -58,7 +60,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
super.initState();
readOnly = widget.onSave == null;
_toolbarController = ContextMenuControllerImpl(readOnly);
_focusNode = FocusNode(canRequestFocus: !readOnly);
_focusNode = FocusNode();
_controller = CodeLineEditingController.fromText(widget.content);
_findController = CodeFindController(_controller);
_titleController = TextEditingController(text: widget.title);
@@ -89,6 +91,18 @@ class _EditorPageState extends ConsumerState<EditorPage> {
});
}
@override
void didUpdateWidget(covariant oldWidget) {
super.didUpdateWidget(oldWidget);
WidgetsBinding.instance.addPostFrameCallback((_) {
final content = widget.content;
if (content != null && oldWidget.content != content) {
_controller.text = content;
_controller.clearHistory();
}
});
}
@override
void dispose() {
_toolbarController.hide(context);
@@ -125,7 +139,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
if (file == null) {
return;
}
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
final res = utf8.decode(file.bytes?.toList() ?? []);
_controller.text = res;
}
@@ -178,7 +192,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
enabled: widget.titleEditable,
controller: _titleController,
decoration: InputDecoration(
border: _NoInputBorder(),
border: NoInputBorder(),
counter: SizedBox(),
hintText: appLocalizations.unnamed,
),
@@ -254,58 +268,78 @@ class _EditorPageState extends ConsumerState<EditorPage> {
),
]),
),
body: CodeEditor(
readOnly: readOnly,
autofocus: false,
findController: _findController,
findBuilder: (context, controller, readOnly) => FindPanel(
controller: controller,
readOnly: readOnly,
isMobileView: isMobileView,
),
padding: EdgeInsets.only(right: 16),
autocompleteSymbols: true,
focusNode: _focusNode,
scrollbarBuilder: (context, child, details) {
return CommonScrollBar(
controller: details.controller,
child: child,
);
},
toolbarController: _toolbarController,
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
children: [
DefaultCodeLineNumber(
controller: editingController,
notifier: notifier,
),
DefaultCodeChunkIndicator(
width: 20,
controller: chunkController,
notifier: notifier,
),
],
body: Stack(
children: [
CodeEditor(
readOnly: readOnly,
autofocus: false,
showCursorWhenReadOnly: false,
findController: _findController,
findBuilder: (context, controller, readOnly) => FindPanel(
controller: controller,
readOnly: readOnly,
isMobileView: isMobileView,
),
padding: EdgeInsets.only(right: 16),
autocompleteSymbols: true,
focusNode: _focusNode,
scrollbarBuilder: (context, child, details) {
return CommonScrollBar(
controller: details.controller,
child: child,
);
},
shortcutsActivatorsBuilder: DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: context.textTheme.bodyLarge?.fontSize?.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {
if (widget.languages.contains(Language.yaml))
'yaml': CodeHighlightThemeMode(mode: langYaml),
if (widget.languages.contains(Language.javaScript))
'javascript': CodeHighlightThemeMode(mode: langJavascript),
if (widget.languages.contains(Language.json))
'json': CodeHighlightThemeMode(mode: langJson),
},
theme: atomOneLightTheme,
toolbarController: _toolbarController,
indicatorBuilder:
(context, editingController, chunkController, notifier) {
return Row(
children: [
DefaultCodeLineNumber(
controller: editingController,
notifier: notifier,
),
DefaultCodeChunkIndicator(
width: 20,
controller: chunkController,
notifier: notifier,
),
],
);
},
shortcutsActivatorsBuilder:
DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: context.textTheme.bodyLarge?.fontSize?.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {
if (widget.languages.contains(Language.yaml))
'yaml': CodeHighlightThemeMode(mode: langYaml),
if (widget.languages.contains(Language.javaScript))
'javascript': CodeHighlightThemeMode(
mode: langJavascript,
),
if (widget.languages.contains(Language.json))
'json': CodeHighlightThemeMode(mode: langJson),
},
theme: atomOneLightTheme,
),
),
),
),
FadeBox(
child: widget.content == null
? Container(
color: context.colorScheme.surface,
alignment: Alignment.center,
child: SizedBox.square(
dimension: 200,
child: CommonCircleLoading(),
),
)
: SizedBox.shrink(),
),
],
),
),
);
@@ -406,7 +440,7 @@ class FindPanel extends StatelessWidget implements PreferredSizeWidget {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [bar, SizedBox(height: 4), _buildFindInput(context, value)],
children: [bar, SizedBox(height: 12), _buildFindInput(context, value)],
);
}
return bar;
@@ -605,55 +639,6 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
}
}
class _NoInputBorder extends InputBorder {
const _NoInputBorder() : super(borderSide: BorderSide.none);
@override
_NoInputBorder copyWith({BorderSide? borderSide}) => const _NoInputBorder();
@override
bool get isOutline => false;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
_NoInputBorder scale(double t) => const _NoInputBorder();
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
void paintInterior(
Canvas canvas,
Rect rect,
Paint paint, {
TextDirection? textDirection,
}) {
canvas.drawRect(rect, paint);
}
@override
bool get preferPaintInterior => true;
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {}
}
class _ImportOptionsDialog extends StatefulWidget {
const _ImportOptionsDialog();

View File

@@ -1,3 +1,4 @@
import 'package:fl_clash/common/color.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -49,9 +50,9 @@ class InitErrorScreen extends StatelessWidget {
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.errorContainer.withOpacity(0.5),
color: colorScheme.errorContainer.opacity50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: colorScheme.error.withOpacity(0.5)),
border: Border.all(color: colorScheme.error.opacity50),
),
child: SelectableText(
error.toString(),
@@ -71,7 +72,7 @@ class InitErrorScreen extends StatelessWidget {
? Colors.grey[900]
: Colors.grey[200],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.5)),
border: Border.all(color: Colors.grey.opacity50),
),
child: SelectableText(
stack.toString(),

View File

@@ -295,7 +295,7 @@ class Loading extends _$Loading with AutoDisposeNotifierMixin {
}
@riverpod
class SelectedItems extends _$SelectedItems with AutoDisposeNotifierMixin {
class Items extends _$Items with AutoDisposeNotifierMixin {
@override
Set<dynamic> build(String key) {
return {};
@@ -303,7 +303,7 @@ class SelectedItems extends _$SelectedItems with AutoDisposeNotifierMixin {
}
@riverpod
class SelectedItem extends _$SelectedItem with AutoDisposeNotifierMixin {
class Item extends _$Item with AutoDisposeNotifierMixin {
@override
dynamic build(String key) {
return null;

View File

@@ -88,12 +88,12 @@ class ProxiesStyleSetting extends _$ProxiesStyleSetting
}
}
@riverpod
class PatchClashConfig extends _$PatchClashConfig
@Riverpod(name: 'patchClashConfigProvider')
class _PatchClashConfig extends _$PatchClashConfig
with AutoDisposeNotifierMixin {
@override
ClashConfig build() {
return ClashConfig();
PatchClashConfig build() {
return PatchClashConfig();
}
}

View File

@@ -1,4 +1,7 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/database/database.dart';
import 'package:fl_clash/models/models.dart';
@@ -12,10 +15,25 @@ Stream<List<Profile>> profilesStream(Ref ref) {
}
@riverpod
Stream<List<Rule>> addedRuleStream(Ref ref, int profileId) {
Stream<List<Rule>> addedRulesStream(Ref ref, int profileId) {
return database.rulesDao.allAddedRules(profileId).watch();
}
@riverpod
Future<List<Rule>> addedRules(Ref ref, int profileId) {
return database.rulesDao.allAddedRules(profileId).get();
}
@riverpod
Stream<int> customRulesCount(Ref ref, int profileId) {
return database.rulesDao.profileCustomRulesCount(profileId).watchSingle();
}
@riverpod
Stream<int> proxyGroupsCount(Ref ref, int profileId) {
return database.proxyGroupsDao.count(profileId).watchSingle();
}
@Riverpod(keepAlive: true)
class Profiles extends _$Profiles {
@override
@@ -143,8 +161,16 @@ class GlobalRules extends _$GlobalRules with AsyncNotifierMixin {
}
void put(Rule rule) {
value = value.copyAndPut(rule);
database.rulesDao.putGlobalRule(rule);
final Rule newRule;
if (rule.order?.isNotEmpty != true) {
newRule = rule.copyWith(
order: indexing.generateKeyBetween(null, value.firstOrNull?.order),
);
} else {
newRule = rule;
}
value = value.copyAndPut(newRule);
database.rulesDao.putGlobalRule(newRule);
}
void order(int oldIndex, int newIndex) {
@@ -158,7 +184,7 @@ class GlobalRules extends _$GlobalRules with AsyncNotifierMixin {
value = nextItems;
final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(nextOrder, preOrder)!;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
database.rulesDao.orderGlobalRule(ruleId: item.id, order: newOrder);
}
}
@@ -182,8 +208,16 @@ class ProfileAddedRules extends _$ProfileAddedRules with AsyncNotifierMixin {
}
void put(Rule rule) {
value = value.copyAndPut(rule);
database.rulesDao.putProfileAddedRule(profileId, rule);
final Rule newRule;
if (rule.order?.isNotEmpty != true) {
newRule = rule.copyWith(
order: indexing.generateKeyBetween(null, value.firstOrNull?.order),
);
} else {
newRule = rule;
}
value = value.copyAndPut(newRule);
database.rulesDao.putProfileAddedRule(profileId, newRule);
}
void delAll(Iterable<int> ruleIds) {
@@ -202,7 +236,7 @@ class ProfileAddedRules extends _$ProfileAddedRules with AsyncNotifierMixin {
value = nextItems;
final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(nextOrder, preOrder)!;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
database.rulesDao.orderProfileAddedRule(
profileId,
ruleId: item.id,
@@ -211,6 +245,104 @@ class ProfileAddedRules extends _$ProfileAddedRules with AsyncNotifierMixin {
}
}
@riverpod
class ProfileCustomRules extends _$ProfileCustomRules with AsyncNotifierMixin {
@override
Stream<List<Rule>> build(int profileId) {
return database.rulesDao.allProfileCustomRules(profileId).watch();
}
@override
List<Rule> get value => state.value ?? [];
@override
bool updateShouldNotify(
AsyncValue<List<Rule>> previous,
AsyncValue<List<Rule>> next,
) {
return !ruleListEquality.equals(previous.value, next.value);
}
void put(Rule rule) {
final Rule newRule;
if (rule.order?.isNotEmpty != true) {
newRule = rule.copyWith(
order: indexing.generateKeyBetween(null, value.firstOrNull?.order),
);
} else {
newRule = rule;
}
value = value.copyAndPut(newRule);
database.rulesDao.putProfileCustomRule(profileId, newRule);
}
void delAll(Iterable<int> ruleIds) {
value = List<Rule>.from(value.where((item) => !ruleIds.contains(item.id)));
database.rulesDao.delRules(ruleIds);
}
void order(int oldIndex, int newIndex) {
int insertIndex = newIndex;
if (oldIndex < newIndex) {
insertIndex -= 1;
}
final nextItems = List<Rule>.from(value);
final item = nextItems.removeAt(oldIndex);
nextItems.insert(insertIndex, item);
value = nextItems;
final preOrder = nextItems.safeGet(insertIndex - 1)?.order;
final nextOrder = nextItems.safeGet(insertIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
database.rulesDao.orderProfileCustomRule(
profileId,
ruleId: item.id,
order: newOrder,
);
}
}
@riverpod
class ProxyGroups extends _$ProxyGroups with AsyncNotifierMixin {
@override
Stream<List<ProxyGroup>> build(int profileId) {
return database.proxyGroupsDao.all(profileId).watch();
}
@override
bool updateShouldNotify(
AsyncValue<List<ProxyGroup>> previous,
AsyncValue<List<ProxyGroup>> next,
) {
return !proxyGroupsEquality.equals(previous.value, next.value);
}
void del(String name) {
database.proxyGroups.remove(
(t) => t.profileId.equals(profileId) & t.name.equals(name),
);
List<ProxyGroup> newList = List.from(value);
newList = newList.where((item) => item.name != name).toList();
value = newList;
}
void order(int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final nextItems = List<ProxyGroup>.from(value);
final item = nextItems.removeAt(oldIndex);
nextItems.insert(newIndex, item);
value = nextItems;
final preOrder = nextItems.safeGet(newIndex - 1)?.order;
final nextOrder = nextItems.safeGet(newIndex + 1)?.order;
final newOrder = indexing.generateKeyBetween(preOrder, nextOrder)!;
database.proxyGroupsDao.order(profileId, proxyGroup: item, order: newOrder);
}
@override
List<ProxyGroup> get value => state.value ?? [];
}
@riverpod
class ProfileDisabledRuleIds extends _$ProfileDisabledRuleIds
with AsyncNotifierMixin {
@@ -225,6 +357,14 @@ class ProfileDisabledRuleIds extends _$ProfileDisabledRuleIds
.watch();
}
@override
bool updateShouldNotify(
AsyncValue<List<int>> previous,
AsyncValue<List<int>> next,
) {
return !intListEquality.equals(previous.value, next.value);
}
void _put(int ruleId) {
var newList = List<int>.from(value);
final index = newList.indexWhere((item) => item == ruleId);

View File

@@ -1511,35 +1511,34 @@ abstract class _$Loading extends $Notifier<bool> {
}
}
@ProviderFor(SelectedItems)
const selectedItemsProvider = SelectedItemsFamily._();
@ProviderFor(Items)
const itemsProvider = ItemsFamily._();
final class SelectedItemsProvider
extends $NotifierProvider<SelectedItems, Set<dynamic>> {
const SelectedItemsProvider._({
required SelectedItemsFamily super.from,
final class ItemsProvider extends $NotifierProvider<Items, Set<dynamic>> {
const ItemsProvider._({
required ItemsFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'selectedItemsProvider',
name: r'itemsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedItemsHash();
String debugGetCreateSourceHash() => _$itemsHash();
@override
String toString() {
return r'selectedItemsProvider'
return r'itemsProvider'
''
'($argument)';
}
@$internal
@override
SelectedItems create() => SelectedItems();
Items create() => Items();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Set<dynamic> value) {
@@ -1551,7 +1550,7 @@ final class SelectedItemsProvider
@override
bool operator ==(Object other) {
return other is SelectedItemsProvider && other.argument == argument;
return other is ItemsProvider && other.argument == argument;
}
@override
@@ -1560,34 +1559,33 @@ final class SelectedItemsProvider
}
}
String _$selectedItemsHash() => r'05ef5c5584cbac90d416e5c4fe53ec9e29604020';
String _$itemsHash() => r'e4d68c86d62dfa3ba7153954208891e4df4c4355';
final class SelectedItemsFamily extends $Family
final class ItemsFamily extends $Family
with
$ClassFamilyOverride<
SelectedItems,
Items,
Set<dynamic>,
Set<dynamic>,
Set<dynamic>,
String
> {
const SelectedItemsFamily._()
const ItemsFamily._()
: super(
retry: null,
name: r'selectedItemsProvider',
name: r'itemsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SelectedItemsProvider call(String key) =>
SelectedItemsProvider._(argument: key, from: this);
ItemsProvider call(String key) => ItemsProvider._(argument: key, from: this);
@override
String toString() => r'selectedItemsProvider';
String toString() => r'itemsProvider';
}
abstract class _$SelectedItems extends $Notifier<Set<dynamic>> {
abstract class _$Items extends $Notifier<Set<dynamic>> {
late final _$args = ref.$arg as String;
String get key => _$args;
@@ -1609,35 +1607,34 @@ abstract class _$SelectedItems extends $Notifier<Set<dynamic>> {
}
}
@ProviderFor(SelectedItem)
const selectedItemProvider = SelectedItemFamily._();
@ProviderFor(Item)
const itemProvider = ItemFamily._();
final class SelectedItemProvider
extends $NotifierProvider<SelectedItem, dynamic> {
const SelectedItemProvider._({
required SelectedItemFamily super.from,
final class ItemProvider extends $NotifierProvider<Item, dynamic> {
const ItemProvider._({
required ItemFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'selectedItemProvider',
name: r'itemProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedItemHash();
String debugGetCreateSourceHash() => _$itemHash();
@override
String toString() {
return r'selectedItemProvider'
return r'itemProvider'
''
'($argument)';
}
@$internal
@override
SelectedItem create() => SelectedItem();
Item create() => Item();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(dynamic value) {
@@ -1649,7 +1646,7 @@ final class SelectedItemProvider
@override
bool operator ==(Object other) {
return other is SelectedItemProvider && other.argument == argument;
return other is ItemProvider && other.argument == argument;
}
@override
@@ -1658,27 +1655,26 @@ final class SelectedItemProvider
}
}
String _$selectedItemHash() => r'b50be0386d53ee8441c37d1a2a4c25640ce10766';
String _$itemHash() => r'bd46bf2e285d7171173ed7c46455ff4c39e80a46';
final class SelectedItemFamily extends $Family
with $ClassFamilyOverride<SelectedItem, dynamic, dynamic, dynamic, String> {
const SelectedItemFamily._()
final class ItemFamily extends $Family
with $ClassFamilyOverride<Item, dynamic, dynamic, dynamic, String> {
const ItemFamily._()
: super(
retry: null,
name: r'selectedItemProvider',
name: r'itemProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SelectedItemProvider call(String key) =>
SelectedItemProvider._(argument: key, from: this);
ItemProvider call(String key) => ItemProvider._(argument: key, from: this);
@override
String toString() => r'selectedItemProvider';
String toString() => r'itemProvider';
}
abstract class _$SelectedItem extends $Notifier<dynamic> {
abstract class _$Item extends $Notifier<dynamic> {
late final _$args = ref.$arg as String;
String get key => _$args;

View File

@@ -538,12 +538,12 @@ abstract class _$ProxiesStyleSetting extends $Notifier<ProxiesStyleProps> {
}
}
@ProviderFor(PatchClashConfig)
const patchClashConfigProvider = PatchClashConfigProvider._();
@ProviderFor(_PatchClashConfig)
const patchClashConfigProvider = _PatchClashConfigProvider._();
final class PatchClashConfigProvider
extends $NotifierProvider<PatchClashConfig, ClashConfig> {
const PatchClashConfigProvider._()
final class _PatchClashConfigProvider
extends $NotifierProvider<_PatchClashConfig, PatchClashConfig> {
const _PatchClashConfigProvider._()
: super(
from: null,
argument: null,
@@ -555,35 +555,35 @@ final class PatchClashConfigProvider
);
@override
String debugGetCreateSourceHash() => _$patchClashConfigHash();
String debugGetCreateSourceHash() => _$_patchClashConfigHash();
@$internal
@override
PatchClashConfig create() => PatchClashConfig();
_PatchClashConfig create() => _PatchClashConfig();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ClashConfig value) {
Override overrideWithValue(PatchClashConfig value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ClashConfig>(value),
providerOverride: $SyncValueProvider<PatchClashConfig>(value),
);
}
}
String _$patchClashConfigHash() => r'ff92f991ccb3a3d13a938affc006d7e2cb85fecd';
String _$_patchClashConfigHash() => r'855a2162368773b90de48fd0139018ce73d209cc';
abstract class _$PatchClashConfig extends $Notifier<ClashConfig> {
ClashConfig build();
abstract class _$PatchClashConfig extends $Notifier<PatchClashConfig> {
PatchClashConfig build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ClashConfig, ClashConfig>;
final ref = this.ref as $Ref<PatchClashConfig, PatchClashConfig>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ClashConfig, ClashConfig>,
ClashConfig,
AnyNotifier<PatchClashConfig, PatchClashConfig>,
PatchClashConfig,
Object?,
Object?
>;

View File

@@ -48,10 +48,10 @@ final class ProfilesStreamProvider
String _$profilesStreamHash() => r'483907aa6c324209b5202369300a4a53230f83db';
@ProviderFor(addedRuleStream)
const addedRuleStreamProvider = AddedRuleStreamFamily._();
@ProviderFor(addedRulesStream)
const addedRulesStreamProvider = AddedRulesStreamFamily._();
final class AddedRuleStreamProvider
final class AddedRulesStreamProvider
extends
$FunctionalProvider<
AsyncValue<List<Rule>>,
@@ -59,23 +59,23 @@ final class AddedRuleStreamProvider
Stream<List<Rule>>
>
with $FutureModifier<List<Rule>>, $StreamProvider<List<Rule>> {
const AddedRuleStreamProvider._({
required AddedRuleStreamFamily super.from,
const AddedRulesStreamProvider._({
required AddedRulesStreamFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'addedRuleStreamProvider',
name: r'addedRulesStreamProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$addedRuleStreamHash();
String debugGetCreateSourceHash() => _$addedRulesStreamHash();
@override
String toString() {
return r'addedRuleStreamProvider'
return r'addedRulesStreamProvider'
''
'($argument)';
}
@@ -88,12 +88,12 @@ final class AddedRuleStreamProvider
@override
Stream<List<Rule>> create(Ref ref) {
final argument = this.argument as int;
return addedRuleStream(ref, argument);
return addedRulesStream(ref, argument);
}
@override
bool operator ==(Object other) {
return other is AddedRuleStreamProvider && other.argument == argument;
return other is AddedRulesStreamProvider && other.argument == argument;
}
@override
@@ -102,24 +102,236 @@ final class AddedRuleStreamProvider
}
}
String _$addedRuleStreamHash() => r'491968ce795e56d4516a95676fcf46d575b3495f';
String _$addedRulesStreamHash() => r'3147271d6149b9c3861e99671fe7ac1f8a8fa23b';
final class AddedRuleStreamFamily extends $Family
final class AddedRulesStreamFamily extends $Family
with $FunctionalFamilyOverride<Stream<List<Rule>>, int> {
const AddedRuleStreamFamily._()
const AddedRulesStreamFamily._()
: super(
retry: null,
name: r'addedRuleStreamProvider',
name: r'addedRulesStreamProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
AddedRuleStreamProvider call(int profileId) =>
AddedRuleStreamProvider._(argument: profileId, from: this);
AddedRulesStreamProvider call(int profileId) =>
AddedRulesStreamProvider._(argument: profileId, from: this);
@override
String toString() => r'addedRuleStreamProvider';
String toString() => r'addedRulesStreamProvider';
}
@ProviderFor(addedRules)
const addedRulesProvider = AddedRulesFamily._();
final class AddedRulesProvider
extends
$FunctionalProvider<
AsyncValue<List<Rule>>,
List<Rule>,
FutureOr<List<Rule>>
>
with $FutureModifier<List<Rule>>, $FutureProvider<List<Rule>> {
const AddedRulesProvider._({
required AddedRulesFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'addedRulesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$addedRulesHash();
@override
String toString() {
return r'addedRulesProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<Rule>> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<List<Rule>> create(Ref ref) {
final argument = this.argument as int;
return addedRules(ref, argument);
}
@override
bool operator ==(Object other) {
return other is AddedRulesProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$addedRulesHash() => r'fa2569f7781c93e00bd2017c956ff377e436667a';
final class AddedRulesFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<Rule>>, int> {
const AddedRulesFamily._()
: super(
retry: null,
name: r'addedRulesProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
AddedRulesProvider call(int profileId) =>
AddedRulesProvider._(argument: profileId, from: this);
@override
String toString() => r'addedRulesProvider';
}
@ProviderFor(customRulesCount)
const customRulesCountProvider = CustomRulesCountFamily._();
final class CustomRulesCountProvider
extends $FunctionalProvider<AsyncValue<int>, int, Stream<int>>
with $FutureModifier<int>, $StreamProvider<int> {
const CustomRulesCountProvider._({
required CustomRulesCountFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'customRulesCountProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$customRulesCountHash();
@override
String toString() {
return r'customRulesCountProvider'
''
'($argument)';
}
@$internal
@override
$StreamProviderElement<int> $createElement($ProviderPointer pointer) =>
$StreamProviderElement(pointer);
@override
Stream<int> create(Ref ref) {
final argument = this.argument as int;
return customRulesCount(ref, argument);
}
@override
bool operator ==(Object other) {
return other is CustomRulesCountProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$customRulesCountHash() => r'a3ff7941bcbb2696ba48c82b9310d81d7238536f';
final class CustomRulesCountFamily extends $Family
with $FunctionalFamilyOverride<Stream<int>, int> {
const CustomRulesCountFamily._()
: super(
retry: null,
name: r'customRulesCountProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
CustomRulesCountProvider call(int profileId) =>
CustomRulesCountProvider._(argument: profileId, from: this);
@override
String toString() => r'customRulesCountProvider';
}
@ProviderFor(proxyGroupsCount)
const proxyGroupsCountProvider = ProxyGroupsCountFamily._();
final class ProxyGroupsCountProvider
extends $FunctionalProvider<AsyncValue<int>, int, Stream<int>>
with $FutureModifier<int>, $StreamProvider<int> {
const ProxyGroupsCountProvider._({
required ProxyGroupsCountFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'proxyGroupsCountProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$proxyGroupsCountHash();
@override
String toString() {
return r'proxyGroupsCountProvider'
''
'($argument)';
}
@$internal
@override
$StreamProviderElement<int> $createElement($ProviderPointer pointer) =>
$StreamProviderElement(pointer);
@override
Stream<int> create(Ref ref) {
final argument = this.argument as int;
return proxyGroupsCount(ref, argument);
}
@override
bool operator ==(Object other) {
return other is ProxyGroupsCountProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$proxyGroupsCountHash() => r'9bf90fc25a9ae3b9ab7aa0784d4e47786f4c4d52';
final class ProxyGroupsCountFamily extends $Family
with $FunctionalFamilyOverride<Stream<int>, int> {
const ProxyGroupsCountFamily._()
: super(
retry: null,
name: r'proxyGroupsCountProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ProxyGroupsCountProvider call(int profileId) =>
ProxyGroupsCountProvider._(argument: profileId, from: this);
@override
String toString() => r'proxyGroupsCountProvider';
}
@ProviderFor(Profiles)
@@ -244,7 +456,7 @@ final class GlobalRulesProvider
GlobalRules create() => GlobalRules();
}
String _$globalRulesHash() => r'3ed947f389649a86d5c6d78d8c02ba5b8d0f7119';
String _$globalRulesHash() => r'39d27f04f14d4498dc9dd89cea8e9cc2cc9da548';
abstract class _$GlobalRules extends $StreamNotifier<List<Rule>> {
Stream<List<Rule>> build();
@@ -306,7 +518,7 @@ final class ProfileAddedRulesProvider
}
}
String _$profileAddedRulesHash() => r'4155448335cf14a8928db6adf68e59572aa4ce47';
String _$profileAddedRulesHash() => r'6909191ccf493d8b9dd657265f3da1ae27485d73';
final class ProfileAddedRulesFamily extends $Family
with
@@ -355,6 +567,188 @@ abstract class _$ProfileAddedRules extends $StreamNotifier<List<Rule>> {
}
}
@ProviderFor(ProfileCustomRules)
const profileCustomRulesProvider = ProfileCustomRulesFamily._();
final class ProfileCustomRulesProvider
extends $StreamNotifierProvider<ProfileCustomRules, List<Rule>> {
const ProfileCustomRulesProvider._({
required ProfileCustomRulesFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'profileCustomRulesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$profileCustomRulesHash();
@override
String toString() {
return r'profileCustomRulesProvider'
''
'($argument)';
}
@$internal
@override
ProfileCustomRules create() => ProfileCustomRules();
@override
bool operator ==(Object other) {
return other is ProfileCustomRulesProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$profileCustomRulesHash() =>
r'b267939b552c7967a85caff5a249c0534686753b';
final class ProfileCustomRulesFamily extends $Family
with
$ClassFamilyOverride<
ProfileCustomRules,
AsyncValue<List<Rule>>,
List<Rule>,
Stream<List<Rule>>,
int
> {
const ProfileCustomRulesFamily._()
: super(
retry: null,
name: r'profileCustomRulesProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ProfileCustomRulesProvider call(int profileId) =>
ProfileCustomRulesProvider._(argument: profileId, from: this);
@override
String toString() => r'profileCustomRulesProvider';
}
abstract class _$ProfileCustomRules extends $StreamNotifier<List<Rule>> {
late final _$args = ref.$arg as int;
int get profileId => _$args;
Stream<List<Rule>> build(int profileId);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref = this.ref as $Ref<AsyncValue<List<Rule>>, List<Rule>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<List<Rule>>, List<Rule>>,
AsyncValue<List<Rule>>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
@ProviderFor(ProxyGroups)
const proxyGroupsProvider = ProxyGroupsFamily._();
final class ProxyGroupsProvider
extends $StreamNotifierProvider<ProxyGroups, List<ProxyGroup>> {
const ProxyGroupsProvider._({
required ProxyGroupsFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'proxyGroupsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$proxyGroupsHash();
@override
String toString() {
return r'proxyGroupsProvider'
''
'($argument)';
}
@$internal
@override
ProxyGroups create() => ProxyGroups();
@override
bool operator ==(Object other) {
return other is ProxyGroupsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$proxyGroupsHash() => r'b747de5d114e8e6d764befca26e9a8dc81d9d127';
final class ProxyGroupsFamily extends $Family
with
$ClassFamilyOverride<
ProxyGroups,
AsyncValue<List<ProxyGroup>>,
List<ProxyGroup>,
Stream<List<ProxyGroup>>,
int
> {
const ProxyGroupsFamily._()
: super(
retry: null,
name: r'proxyGroupsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ProxyGroupsProvider call(int profileId) =>
ProxyGroupsProvider._(argument: profileId, from: this);
@override
String toString() => r'proxyGroupsProvider';
}
abstract class _$ProxyGroups extends $StreamNotifier<List<ProxyGroup>> {
late final _$args = ref.$arg as int;
int get profileId => _$args;
Stream<List<ProxyGroup>> build(int profileId);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref =
this.ref as $Ref<AsyncValue<List<ProxyGroup>>, List<ProxyGroup>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<List<ProxyGroup>>, List<ProxyGroup>>,
AsyncValue<List<ProxyGroup>>,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
@ProviderFor(ProfileDisabledRuleIds)
const profileDisabledRuleIdsProvider = ProfileDisabledRuleIdsFamily._();
@@ -398,7 +792,7 @@ final class ProfileDisabledRuleIdsProvider
}
String _$profileDisabledRuleIdsHash() =>
r'22d6e68bcee55b42fbb909e7f66e5c7095935224';
r'5093cc1d77ec69a2c1db6efa86a3f5916475d4f0';
final class ProfileDisabledRuleIdsFamily extends $Family
with

View File

@@ -2443,6 +2443,81 @@ final class ScriptFamily extends $Family
String toString() => r'scriptProvider';
}
@ProviderFor(clashConfig)
const clashConfigProvider = ClashConfigFamily._();
final class ClashConfigProvider
extends
$FunctionalProvider<
AsyncValue<ClashConfig>,
ClashConfig,
FutureOr<ClashConfig>
>
with $FutureModifier<ClashConfig>, $FutureProvider<ClashConfig> {
const ClashConfigProvider._({
required ClashConfigFamily super.from,
required int super.argument,
}) : super(
retry: null,
name: r'clashConfigProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$clashConfigHash();
@override
String toString() {
return r'clashConfigProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<ClashConfig> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<ClashConfig> create(Ref ref) {
final argument = this.argument as int;
return clashConfig(ref, argument);
}
@override
bool operator ==(Object other) {
return other is ClashConfigProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$clashConfigHash() => r'f7da0ec3a29379f6192c1206d2cd7535b45fab5e';
final class ClashConfigFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<ClashConfig>, int> {
const ClashConfigFamily._()
: super(
retry: null,
name: r'clashConfigProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ClashConfigProvider call(int profileId) =>
ClashConfigProvider._(argument: profileId, from: this);
@override
String toString() => r'clashConfigProvider';
}
@ProviderFor(setupState)
const setupStateProvider = SetupStateFamily._();
@@ -2497,7 +2572,7 @@ final class SetupStateProvider
}
}
String _$setupStateHash() => r'8e0c849fa1a51ee15f8b40be94e3094182325b58';
String _$setupStateHash() => r'3ded29d70c607f869b625dc808936887cbd6ecc1';
final class SetupStateFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<SetupState>, int?> {
@@ -2570,3 +2645,57 @@ abstract class _$AccessControlState extends $Notifier<AccessControlProps> {
element.handleValue(ref, created);
}
}
@ProviderFor(ProxyGroupProvider)
const proxyGroupProvider = ProxyGroupProviderProvider._();
final class ProxyGroupProviderProvider
extends $NotifierProvider<ProxyGroupProvider, ProxyGroup> {
const ProxyGroupProviderProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'proxyGroupProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$proxyGroupProviderHash();
@$internal
@override
ProxyGroupProvider create() => ProxyGroupProvider();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ProxyGroup value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ProxyGroup>(value),
);
}
}
String _$proxyGroupProviderHash() =>
r'3d14cffb0b0316646fa78b85083e74d573e55fe9';
abstract class _$ProxyGroupProvider extends $Notifier<ProxyGroup> {
ProxyGroup build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ProxyGroup, ProxyGroup>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ProxyGroup, ProxyGroup>,
ProxyGroup,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
@@ -677,6 +678,20 @@ Future<Script?> script(Ref ref, int? scriptId) async {
return script;
}
@riverpod
Future<ClashConfig> clashConfig(Ref ref, int profileId) async {
final configMap = await coreController.getConfig(profileId);
final clashConfig = ClashConfig.fromJson(configMap);
final Map<String, String> proxyTypeMap = {};
for (final proxy in clashConfig.proxies) {
proxyTypeMap[proxy.name] = proxy.type;
}
for (final proxyGroup in clashConfig.proxyGroups) {
proxyTypeMap[proxyGroup.name] = proxyGroup.type.value;
}
return clashConfig.copyWith(proxyTypeMap: proxyTypeMap);
}
@riverpod
Future<SetupState> setupState(Ref ref, int? profileId) async {
final profile = ref.watch(profileProvider(profileId));
@@ -686,9 +701,15 @@ Future<SetupState> setupState(Ref ref, int? profileId) async {
final dns = ref.watch(patchClashConfigProvider.select((state) => state.dns));
final script = await ref.watch(scriptProvider(scriptId).future);
final overrideDns = ref.watch(overrideDnsProvider);
final List<Rule> addedRules = profileId != null
? await ref.watch(addedRuleStreamProvider(profileId).future)
: [];
List<Rule> addedRules = [];
if (profileId != null) {
final currentProfileId = ref.read(currentProfileIdProvider);
if (currentProfileId == profileId) {
addedRules = await ref.watch(addedRulesStreamProvider(profileId).future);
} else {
addedRules = await ref.read(addedRulesProvider(profileId).future);
}
}
return SetupState(
profileId: profileId,
profileLastUpdateDate: profileLastUpdateDate,
@@ -706,3 +727,12 @@ class AccessControlState extends _$AccessControlState
@override
AccessControlProps build() => AccessControlProps();
}
@Riverpod(name: 'proxyGroupProvider')
class ProxyGroupProvider extends _$ProxyGroupProvider
with AutoDisposeNotifierMixin {
@override
ProxyGroup build() {
return throw 'Initialization proxyGroupProvider error';
}
}

View File

@@ -38,6 +38,7 @@ class GlobalState {
late Measure measure;
late CommonTheme theme;
late Color accentColor;
bool needInitStatus = true;
CorePalette? corePalette;
DateTime? startTime;
UpdateTasks tasks = [];

View File

@@ -114,9 +114,8 @@ class _AccessViewState extends ConsumerState<AccessView> {
await showSheet<int>(
context: context,
props: SheetProps(isScrollControlled: true),
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: AccessControlPanel(),
title: appLocalizations.accessControlSettings,
);

View File

@@ -1,4 +1,5 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/features/features.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/providers/database.dart';
@@ -29,7 +30,7 @@ class _AddedRulesViewState extends ConsumerState<AddedRulesView> {
}
void _handleSelected(int ruleId) {
ref.read(selectedItemsProvider(_key).notifier).update((selectedRules) {
ref.read(itemsProvider(_key).notifier).update((selectedRules) {
final newSelectedRules = Set<int>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
@@ -40,7 +41,7 @@ class _AddedRulesViewState extends ConsumerState<AddedRulesView> {
final ids =
ref.read(globalRulesProvider).value?.map((item) => item.id).toSet() ??
{};
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
ref.read(itemsProvider(_key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
@@ -55,19 +56,19 @@ class _AddedRulesViewState extends ConsumerState<AddedRulesView> {
if (res != true) {
return;
}
final selectedRules = ref.read(selectedItemsProvider(_key));
final selectedRules = ref.read(itemsProvider(_key));
ref.read(globalRulesProvider.notifier).delAll(selectedRules.cast<int>());
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
}
@override
Widget build(BuildContext context) {
final rules = ref.watch(globalRulesProvider).value ?? [];
final selectedRules = ref.watch(selectedItemsProvider(_key));
final selectedRules = ref.watch(itemsProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedRules.isNotEmpty) {
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
return false;
}
Navigator.of(context).pop();
@@ -106,25 +107,30 @@ class _AddedRulesViewState extends ConsumerState<AddedRulesView> {
illustration: RuleEmptyIllustration(),
)
: ReorderableList(
padding: EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
itemBuilder: (context, index) {
final rule = rules[index];
final position = ItemPosition.get(index, rules.length);
return ReorderableDelayedDragStartListener(
key: ObjectKey(rule),
index: index,
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
rule: rule,
isSelected: selectedRules.contains(rule.id),
onSelected: () {
_handleSelected(rule.id);
},
onEdit: (Rule rule) {
_handleAddOrUpdate(rule);
},
child: ItemPositionProvider(
position: position,
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
rule: rule,
isSelected: selectedRules.contains(rule.id),
onSelected: () {
_handleSelected(rule.id);
},
onEdit: (Rule rule) {
_handleAddOrUpdate(rule);
},
),
),
);
},
itemExtent: ruleItemHeight,
itemCount: rules.length,
onReorder: ref.read(globalRulesProvider.notifier).order,
),

View File

@@ -37,7 +37,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
return;
}
ref.read(scriptsProvider.notifier).del(id);
ref.read(selectedItemProvider(_key).notifier).value = null;
ref.read(itemProvider(_key).notifier).value = null;
_clearEffect(id);
}
@@ -47,7 +47,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
}
void _handleSelected(int id) {
ref.read(selectedItemProvider(_key).notifier).update((value) {
ref.read(itemProvider(_key).notifier).update((value) {
if (value == id) {
return null;
}
@@ -187,11 +187,11 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
@override
Widget build(BuildContext context) {
final scripts = ref.watch(scriptsProvider).value ?? [];
final selectedScriptId = ref.watch(selectedItemProvider(_key));
final selectedScriptId = ref.watch(itemProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedScriptId != null) {
ref.read(selectedItemProvider(_key).notifier).value = null;
ref.read(itemProvider(_key).notifier).value = null;
return false;
}
Navigator.of(context).pop();

View File

@@ -144,9 +144,8 @@ class TrackerInfoItem extends ConsumerWidget {
onTap: () {
showExtend(
context,
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: TrackerInfoDetailView(trackerInfo: trackerInfo),
title: detailTitle,
);

View File

@@ -178,12 +178,11 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
void _showAddWidgetsModal() {
showSheet(
builder: (_, type) {
builder: (_) {
return ValueListenableBuilder(
valueListenable: _addedWidgetsNotifier,
builder: (_, value, _) {
return AdaptiveSheetScaffold(
type: type,
body: _AddDashboardWidgetModal(
items: value,
onAdd: (gridItem) {
@@ -211,7 +210,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
if (currentState == null) {
return;
}
if (mounted) {
if (mounted && currentState.children.isNotEmpty) {
await currentState.isTransformCompleter;
final dashboardWidgets = currentState.children
.map((item) => DashboardWidget.getDashboardWidget(item))
@@ -228,7 +227,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
Widget build(BuildContext context) {
final dashboardState = ref.watch(dashboardStateProvider);
final columns = max(4 * ((dashboardState.contentWidth / 280).ceil()), 8);
final spacing = 14.ap;
final spacing = 14.mAp;
final children = [
...dashboardState.dashboardWidgets
.where(
@@ -263,15 +262,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: [
...dashboardState.dashboardWidgets
.where(
(item) => item.platforms.contains(
SupportPlatform.currentPlatform,
),
)
.map((item) => item.widget),
],
children: children,
onUpdate: () {
_handleSave();
},

View File

@@ -49,44 +49,49 @@ class _MemoryInfoState extends State<MemoryInfo> {
Widget build(BuildContext context) {
return SizedBox(
height: getWidgetHeight(1),
child: CommonCard(
info: Info(iconData: Icons.memory, label: appLocalizations.memoryInfo),
onPressed: () {
coreController.requestGc();
},
child: Container(
padding: baseInfoEdgeInsets.copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: globalState.measure.bodyMediumHeight + 2,
child: ValueListenableBuilder(
valueListenable: _memoryStateNotifier,
builder: (_, memory, _) {
final traffic = memory.traffic;
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
traffic.value,
style: context.textTheme.bodyMedium?.toLight
.adjustSize(1),
),
SizedBox(width: 8),
Text(
traffic.unit,
style: context.textTheme.bodyMedium?.toLight
.adjustSize(1),
),
],
);
},
child: RepaintBoundary(
child: CommonCard(
info: Info(
iconData: Icons.memory,
label: appLocalizations.memoryInfo,
),
onPressed: () {
coreController.requestGc();
},
child: Container(
padding: baseInfoEdgeInsets.copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: globalState.measure.bodyMediumHeight + 2,
child: ValueListenableBuilder(
valueListenable: _memoryStateNotifier,
builder: (_, memory, _) {
final traffic = memory.traffic;
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
traffic.value,
style: context.textTheme.bodyMedium?.toLight
.adjustSize(1),
),
SizedBox(width: 8),
Text(
traffic.unit,
style: context.textTheme.bodyMedium?.toLight
.adjustSize(1),
),
],
);
},
),
),
),
],
],
),
),
),
),

View File

@@ -41,45 +41,54 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
final color = context.colorScheme.onSurfaceVariant.opacity80;
return SizedBox(
height: getWidgetHeight(2),
child: CommonCard(
onPressed: () {},
info: Info(
label: appLocalizations.networkSpeed,
iconData: Icons.speed_sharp,
),
child: Consumer(
builder: (_, ref, _) {
final traffics = ref.watch(trafficsProvider).list;
return Stack(
children: [
Positioned.fill(
child: Padding(
padding: EdgeInsets.all(
16,
).copyWith(bottom: 0, left: 0, right: 0),
child: LineChart(
gradient: true,
color: Theme.of(context).colorScheme.primary,
points: _getPoints(traffics),
child: RepaintBoundary(
child: CommonCard(
onPressed: () {},
child: Consumer(
builder: (_, ref, _) {
final traffics = ref.watch(trafficsProvider).list;
return Column(
children: [
Padding(
padding: baseInfoEdgeInsets.copyWith(bottom: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: InfoHeader(
padding: EdgeInsets.zero,
info: Info(
label: appLocalizations.networkSpeed,
iconData: Icons.speed_sharp,
),
),
),
SizedBox(width: 8),
Text(
_getLastTraffic(traffics).speedText,
style: context.textTheme.bodySmall?.copyWith(
color: color,
),
),
],
),
),
),
Positioned(
top: 0,
right: 0,
child: Transform.translate(
offset: Offset(-16, -20),
child: Text(
_getLastTraffic(traffics).speedText,
style: context.textTheme.bodySmall?.copyWith(
color: color,
Flexible(
child: Padding(
padding: EdgeInsets.all(
16,
).copyWith(bottom: 0, left: 0, right: 0),
child: LineChart(
gradient: true,
color: Theme.of(context).colorScheme.primary,
points: _getPoints(traffics),
),
),
),
),
],
);
},
],
);
},
),
),
),
);

View File

@@ -110,7 +110,6 @@ class OutboundModeV2 extends StatelessWidget {
return SizedBox(
height: height,
child: CommonCard(
padding: EdgeInsets.zero,
child: Consumer(
builder: (_, ref, _) {
final mode = ref.watch(

View File

@@ -16,19 +16,22 @@ class TUNButton extends StatelessWidget {
onPressed: () {
showSheet(
context: context,
builder: (_, type) {
return AdaptiveSheetScaffold(
type: type,
body: generateListView(
generateSection(
items: [
if (system.isDesktop) const TUNItem(),
if (system.isMacOS) const AutoSetSystemDnsItem(),
const TunStackItem(),
],
),
),
title: appLocalizations.tun,
builder: (_) {
return Builder(
builder: (context) {
return AdaptiveSheetScaffold(
body: generateListView(
generateSection(
items: [
if (system.isDesktop) const TUNItem(),
if (system.isMacOS) const AutoSetSystemDnsItem(),
const TunStackItem(),
],
),
),
title: appLocalizations.tun,
);
},
);
},
);
@@ -92,9 +95,8 @@ class SystemProxyButton extends StatelessWidget {
onPressed: () {
showSheet(
context: context,
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: generateListView(
generateSection(
items: [SystemProxyItem(), BypassDomainItem()],
@@ -165,9 +167,8 @@ class VpnButton extends StatelessWidget {
onPressed: () {
showSheet(
context: context,
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: generateListView(
generateSection(
items: [

View File

@@ -33,10 +33,7 @@ class _StartButtonState extends ConsumerState<StartButton>
parent: _controller!,
curve: Curves.easeOutBack,
);
ref.listenManual(runTimeProvider.select((state) => state != null), (
prev,
next,
) {
ref.listenManual(isStartProvider, (prev, next) {
if (next != isStart) {
isStart = next;
updateController();
@@ -55,7 +52,7 @@ class _StartButtonState extends ConsumerState<StartButton>
isStart = !isStart;
updateController();
debouncer.call(FunctionTag.updateStatus, () {
appController.updateStatus(isStart);
appController.updateStatus(isStart, isInit: !ref.read(initProvider));
}, duration: commonDuration);
}
@@ -77,62 +74,64 @@ class _StartButtonState extends ConsumerState<StartButton>
if (!hasProfile) {
return Container();
}
return Theme(
data: Theme.of(context).copyWith(
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
.copyWith(
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
),
),
child: AnimatedBuilder(
animation: _controller!.view,
builder: (_, child) {
final textWidth =
globalState.measure
.computeTextSize(
Text(
utils.getTimeDifference(DateTime.now()),
style: context.textTheme.titleMedium?.toSoftBold,
return RepaintBoundary(
child: Theme(
data: Theme.of(context).copyWith(
floatingActionButtonTheme: Theme.of(context).floatingActionButtonTheme
.copyWith(
sizeConstraints: BoxConstraints(minWidth: 56, maxWidth: 200),
),
),
child: AnimatedBuilder(
animation: _controller!.view,
builder: (_, child) {
final textWidth =
globalState.measure
.computeTextSize(
Text(
utils.getTimeDifference(DateTime.now()),
style: context.textTheme.titleMedium?.toSoftBold,
),
)
.width +
16;
return FloatingActionButton(
clipBehavior: Clip.antiAlias,
materialTapTargetSize: MaterialTapTargetSize.padded,
heroTag: null,
onPressed: () {
handleSwitchStart();
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 56,
width: 56,
alignment: Alignment.center,
child: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _animation,
),
)
.width +
16;
return FloatingActionButton(
clipBehavior: Clip.antiAlias,
materialTapTargetSize: MaterialTapTargetSize.padded,
heroTag: null,
onPressed: () {
handleSwitchStart();
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 56,
width: 56,
alignment: Alignment.center,
child: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _animation,
),
),
SizedBox(width: textWidth * _animation.value, child: child!),
],
),
);
},
child: Consumer(
builder: (_, ref, _) {
final runTime = ref.watch(runTimeProvider);
final text = utils.getTimeText(runTime);
return Text(
text,
maxLines: 1,
overflow: TextOverflow.visible,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold
.copyWith(color: context.colorScheme.onPrimaryContainer),
SizedBox(width: textWidth * _animation.value, child: child!),
],
),
);
},
child: Consumer(
builder: (_, ref, _) {
final runTime = ref.watch(runTimeProvider);
final text = utils.getTimeText(runTime);
return Text(
text,
maxLines: 1,
overflow: TextOverflow.visible,
style: Theme.of(context).textTheme.titleMedium?.toSoftBold
.copyWith(color: context.colorScheme.onPrimaryContainer),
);
},
),
),
),
);

View File

@@ -52,147 +52,154 @@ class TrafficUsage extends StatelessWidget {
final secondaryColor = globalState.theme.darken2SecondaryContainer;
return SizedBox(
height: getWidgetHeight(2),
child: CommonCard(
info: Info(
label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off,
),
onPressed: () {},
child: Consumer(
builder: (_, ref, _) {
final totalTraffic = ref.watch(totalTrafficProvider);
final upTotalTrafficValue = totalTraffic.up;
final downTotalTrafficValue = totalTraffic.down;
return Padding(
padding: baseInfoEdgeInsets.copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AspectRatio(
aspectRatio: 1,
child: DonutChart(
data: [
DonutChartData(
value: upTotalTrafficValue.toDouble(),
color: primaryColor,
),
DonutChartData(
value: downTotalTrafficValue.toDouble(),
color: secondaryColor,
),
],
child: RepaintBoundary(
child: CommonCard(
info: Info(
label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off,
),
onPressed: () {},
child: Consumer(
builder: (_, ref, _) {
final totalTraffic = ref.watch(totalTrafficProvider);
final upTotalTrafficValue = totalTraffic.up;
final downTotalTrafficValue = totalTraffic.down;
return Padding(
padding: baseInfoEdgeInsets.copyWith(top: 0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AspectRatio(
aspectRatio: 1,
child: DonutChart(
data: [
DonutChartData(
value: upTotalTrafficValue.toDouble(),
color: primaryColor,
),
DonutChartData(
value: downTotalTrafficValue.toDouble(),
color: secondaryColor,
),
],
),
),
),
SizedBox(width: 8),
Flexible(
child: LayoutBuilder(
builder: (_, container) {
final uploadText = Text(
maxLines: 1,
appLocalizations.upload,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
);
final downloadText = Text(
maxLines: 1,
appLocalizations.download,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
);
final uploadTextSize = globalState.measure
.computeTextSize(uploadText);
final downloadTextSize = globalState.measure
.computeTextSize(downloadText);
final maxTextWidth = max(
uploadTextSize.width,
downloadTextSize.width,
);
if (maxTextWidth + 24 > container.maxWidth) {
return Container();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
color: primaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
SizedBox(width: 8),
Flexible(
child: LayoutBuilder(
builder: (_, container) {
final uploadText = Text(
maxLines: 1,
appLocalizations.upload,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
);
final downloadText = Text(
maxLines: 1,
appLocalizations.download,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
);
final uploadTextSize = globalState.measure
.computeTextSize(uploadText);
final downloadTextSize = globalState.measure
.computeTextSize(downloadText);
final maxTextWidth = max(
uploadTextSize.width,
downloadTextSize.width,
);
if (maxTextWidth + 24 > container.maxWidth) {
return Container();
}
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
color: primaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
),
),
),
),
SizedBox(width: 4),
Text(
maxLines: 1,
appLocalizations.upload,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
),
],
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
color: secondaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
SizedBox(width: 4),
Text(
maxLines: 1,
appLocalizations.upload,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
),
],
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20,
height: 8,
decoration: ShapeDecoration(
color: secondaryColor,
shape: RoundedSuperellipseBorder(
borderRadius:
BorderRadius.circular(3),
),
),
),
),
SizedBox(width: 4),
Text(
maxLines: 1,
appLocalizations.download,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
),
],
),
],
);
},
SizedBox(width: 4),
Text(
maxLines: 1,
appLocalizations.download,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall,
),
],
),
],
);
},
),
),
),
],
],
),
),
),
),
_buildTrafficDataItem(
context,
Icon(Icons.arrow_upward, color: primaryColor, size: 14),
upTotalTrafficValue,
),
const SizedBox(height: 8),
_buildTrafficDataItem(
context,
Icon(Icons.arrow_downward, color: secondaryColor, size: 14),
downTotalTrafficValue,
),
],
),
);
},
_buildTrafficDataItem(
context,
Icon(Icons.arrow_upward, color: primaryColor, size: 14),
upTotalTrafficValue,
),
const SizedBox(height: 8),
_buildTrafficDataItem(
context,
Icon(
Icons.arrow_downward,
color: secondaryColor,
size: 14,
),
downTotalTrafficValue,
),
],
),
);
},
),
),
),
);

View File

@@ -1,498 +0,0 @@
// ignore_for_file: deprecated_member_use
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/features/overwrite/rule.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/database.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/config/scripts.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class OverwriteView extends ConsumerStatefulWidget {
final int profileId;
const OverwriteView({super.key, required this.profileId});
@override
ConsumerState<OverwriteView> createState() => _OverwriteViewState();
}
class _OverwriteViewState extends ConsumerState<OverwriteView> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return CommonScaffold(
title: appLocalizations.override,
body: CustomScrollView(
slivers: [_Title(widget.profileId), _Content(widget.profileId)],
),
);
}
@override
void dispose() {
super.dispose();
appController.autoApplyProfile();
}
}
class _Title extends ConsumerWidget {
final int profileId;
const _Title(this.profileId);
String _getTitle(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standard,
OverwriteType.script => appLocalizations.script,
// OverwriteType.custom => appLocalizations.overwriteTypeCustom,
};
}
IconData _getIcon(OverwriteType type) {
return switch (type) {
OverwriteType.standard => Icons.stars,
OverwriteType.script => Icons.rocket,
// OverwriteType.custom => Icons.dashboard_customize,
};
}
String _getDesc(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standardModeDesc,
OverwriteType.script => appLocalizations.scriptModeDesc,
// OverwriteType.custom => appLocalizations.overwriteTypeCustomDesc,
};
}
void _handleChangeType(WidgetRef ref, OverwriteType type) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
return state.copyWith(overwriteType: type);
});
}
@override
Widget build(context, ref) {
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader(info: Info(label: appLocalizations.overrideMode)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
spacing: 16,
children: [
for (final type in OverwriteType.values)
CommonCard(
isSelected: overwriteType == type,
onPressed: () {
_handleChangeType(ref, type);
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(_getIcon(type)),
const SizedBox(width: 8),
Flexible(child: Text(_getTitle(type))),
],
),
),
),
],
),
),
SizedBox(height: 12),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
_getDesc(overwriteType),
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurfaceVariant.opacity80,
),
),
),
],
),
);
}
}
class _Content extends ConsumerWidget {
final int profileId;
const _Content(this.profileId);
@override
Widget build(BuildContext context, ref) {
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
return switch (overwriteType) {
OverwriteType.standard => _StandardContent(profileId),
OverwriteType.script => _ScriptContent(profileId),
// OverwriteType.custom => SliverToBoxAdapter(),
};
}
}
class _StandardContent extends ConsumerStatefulWidget {
final int profileId;
const _StandardContent(this.profileId);
@override
ConsumerState createState() => __StandardContentState();
}
class __StandardContentState extends ConsumerState<_StandardContent> {
final _key = utils.id;
Future<void> _handleAddOrUpdate([Rule? rule]) async {
final res = await globalState.showCommonDialog<Rule>(
child: AddOrEditRuleDialog(rule: rule),
);
if (res == null) {
return;
}
ref.read(profileAddedRulesProvider(widget.profileId).notifier).put(res);
}
void _handleSelected(int ruleId) {
ref.read(selectedItemsProvider(_key).notifier).update((selectedRules) {
final newSelectedRules = Set<int>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
});
}
void _handleSelectAll() {
final ids =
ref
.read(profileAddedRulesProvider(widget.profileId))
.value
?.map((item) => item.id)
.toSet() ??
{};
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
Future<void> _handleDelete() async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
),
);
if (res != true) {
return;
}
final selectedRules = ref.read(selectedItemsProvider(_key));
ref
.read(profileAddedRulesProvider(widget.profileId).notifier)
.delAll(selectedRules.cast<int>());
ref.read(selectedItemsProvider(_key).notifier).value = {};
}
@override
Widget build(BuildContext context) {
final addedRules =
ref.watch(profileAddedRulesProvider(widget.profileId)).value ?? [];
final selectedRules = ref.watch(selectedItemsProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedRules.isNotEmpty) {
ref.read(selectedItemsProvider(_key).notifier).value = {};
return false;
}
Navigator.of(context).pop();
return false;
},
child: SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(
info: Info(label: appLocalizations.addedRules),
actions: [
if (selectedRules.isNotEmpty) ...[
CommonMinIconButtonTheme(
child: IconButton.filledTonal(
onPressed: () {
_handleDelete();
},
icon: Icon(Icons.delete),
),
),
SizedBox(width: 8),
],
CommonMinFilledButtonTheme(
child: selectedRules.isNotEmpty
? FilledButton(
onPressed: () {
_handleSelectAll();
},
child: Text(appLocalizations.selectAll),
)
: FilledButton.tonal(
onPressed: () {
_handleAddOrUpdate();
},
child: Text(appLocalizations.add),
),
),
],
),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverReorderableList(
itemCount: addedRules.length,
itemBuilder: (_, index) {
final rule = addedRules[index];
return ReorderableDelayedDragStartListener(
key: ObjectKey(rule),
index: index,
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
isSelected: selectedRules.contains(rule.id),
rule: rule,
onSelected: () {
_handleSelected(rule.id);
},
onEdit: (rule) {
_handleAddOrUpdate(rule);
},
),
);
},
onReorder: ref
.read(profileAddedRulesProvider(widget.profileId).notifier)
.order,
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
appLocalizations.controlGlobalAddedRules,
style: context.textTheme.bodyLarge,
),
),
SizedBox(width: 4),
Icon(Icons.arrow_forward, size: 18),
],
),
),
onPressed: () {
BaseNavigator.push(
context,
_EditGlobalAddedRules(profileId: widget.profileId),
);
},
),
),
),
],
),
);
}
}
class _ScriptContent extends ConsumerWidget {
final int profileId;
const _ScriptContent(this.profileId);
void _handleChange(WidgetRef ref, int scriptId) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
return state.copyWith(scriptId: scriptId);
});
}
@override
Widget build(BuildContext context, ref) {
final scriptId = ref.watch(
profileProvider(profileId).select((state) => state?.scriptId),
);
final scripts = ref.watch(scriptsProvider).value ?? [];
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(info: Info(label: appLocalizations.overrideScript)),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList.builder(
itemCount: scripts.length,
itemBuilder: (_, index) {
final script = scripts[index];
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
type: CommonCardType.filled,
radius: 18,
child: ListTile(
minLeadingWidth: 0,
minTileHeight: 0,
minVerticalPadding: 16,
contentPadding: const EdgeInsets.symmetric(
horizontal: 14,
),
title: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Radio(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
toggleable: true,
value: script.id,
groupValue: scriptId,
onChanged: (_) {
_handleChange(ref, script.id);
},
),
),
SizedBox(width: 8),
Flexible(child: Text(script.label)),
],
),
onTap: () {
_handleChange(ref, script.id);
},
),
),
);
},
),
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
appLocalizations.goToConfigureScript,
style: context.textTheme.bodyLarge,
),
),
SizedBox(width: 4),
Icon(Icons.arrow_forward, size: 18),
],
),
),
onPressed: () {
BaseNavigator.push(context, const ScriptsView());
},
),
),
),
],
);
}
}
class _EditGlobalAddedRules extends ConsumerWidget {
final int profileId;
const _EditGlobalAddedRules({required this.profileId});
void _handleChange(WidgetRef ref, bool status, int ruleId) {
if (status) {
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).put(ruleId);
} else {
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).del(ruleId);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final disabledRuleIds =
ref.watch(profileDisabledRuleIdsProvider(profileId)).value ?? [];
final rules = ref.watch(globalRulesProvider).value ?? [];
return BaseScaffold(
title: appLocalizations.editGlobalRules,
body: rules.isEmpty
? NullStatus(
label: appLocalizations.nullTip(appLocalizations.rule),
illustration: RuleEmptyIllustration(),
)
: ListView.builder(
padding: EdgeInsets.all(16),
itemBuilder: (context, index) {
final rule = rules[index];
return RuleStatusItem(
status: !disabledRuleIds.contains(rule.id),
rule: rule,
onChange: (status) {
_handleChange(ref, !status, rule.id);
},
);
},
itemCount: rules.length,
),
);
}
}

View File

@@ -0,0 +1,119 @@
part of 'overwrite.dart';
class _CustomContent extends ConsumerWidget {
const _CustomContent();
void _handleUseDefault(WidgetRef ref, int profileId) async {
final clashConfig = await ref.read(clashConfigProvider(profileId).future);
await database.setProfileCustomData(
profileId,
clashConfig.proxyGroups,
clashConfig.rules,
);
}
void _handleToProxyGroupsView(BuildContext context, int profileId) {
BaseNavigator.push(context, _CustomProxyGroupsView(profileId));
}
void _handleToRulesView(BuildContext context, int profileId) {
BaseNavigator.push(context, _CustomRulesView(profileId));
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final profileId = ProfileIdProvider.of(context)!.profileId;
final proxyGroupNum =
ref.watch(proxyGroupsCountProvider(profileId)).value ?? -1;
final ruleNum = ref.watch(customRulesCountProvider(profileId)).value ?? -1;
final hasDefault = ref.watch(
clashConfigProvider(profileId).select((state) {
final clashConfig = state.value;
return ((clashConfig?.proxyGroups.length ?? 0) +
(clashConfig?.rules.length ?? 0)) >
0;
}),
);
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [InfoHeader(info: Info(label: '自定义'))],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
SliverToBoxAdapter(
child: _MoreActionButton(
label: '策略组',
onPressed: () {
_handleToProxyGroupsView(context, profileId);
},
trailing: Card.filled(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
constraints: BoxConstraints(minWidth: 44),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Text(
textAlign: TextAlign.center,
'$proxyGroupNum',
style: context.textTheme.bodySmall,
),
),
),
),
),
),
SliverToBoxAdapter(child: SizedBox(height: 4)),
SliverToBoxAdapter(
child: _MoreActionButton(
label: '规则',
onPressed: () {
_handleToRulesView(context, profileId);
},
trailing: Card.filled(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
constraints: BoxConstraints(minWidth: 44),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Text(
'$ruleNum',
style: context.textTheme.bodySmall,
textAlign: TextAlign.center,
),
),
),
),
),
SliverToBoxAdapter(child: SizedBox(height: 32)),
if (proxyGroupNum == 0 && ruleNum == 0 && hasDefault)
SliverFillRemaining(
hasScrollBody: false,
child: Align(
alignment: Alignment.bottomCenter,
child: MaterialBanner(
elevation: 0,
dividerColor: Colors.transparent,
content: Text('检测到没有数据'),
actions: [
CommonMinFilledButtonTheme(
child: FilledButton.tonal(
onPressed: () {
_handleUseDefault(ref, profileId);
},
child: Text('一键填入'),
),
),
],
),
),
),
],
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
part of 'overwrite.dart';
class _CustomRulesView extends ConsumerStatefulWidget {
final int profileId;
const _CustomRulesView(this.profileId);
@override
ConsumerState createState() => _CustomRulesViewState();
}
class _CustomRulesViewState extends ConsumerState<_CustomRulesView>
with UniqueKeyStateMixin {
int get _profileId => widget.profileId;
@override
void initState() {
super.initState();
}
void _handleReorder(int oldIndex, int newIndex) {
ref
.read(profileCustomRulesProvider(_profileId).notifier)
.order(oldIndex, newIndex);
}
void _handleSelected(int ruleId) {
ref.read(itemsProvider(key).notifier).update((selectedRules) {
final newSelectedRules = Set<int>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
});
}
void _handleSelectAll() {
final ids =
ref
.read(profileCustomRulesProvider(_profileId))
.value
?.map((item) => item.id)
.toSet() ??
{};
ref.read(itemsProvider(key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
Future<void> _handleDelete() async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
),
);
if (res != true) {
return;
}
final selectedRules = ref.read(itemsProvider(key));
ref
.read(profileCustomRulesProvider(_profileId).notifier)
.delAll(selectedRules.cast<int>());
ref.read(itemsProvider(key).notifier).value = {};
}
@override
Widget build(context) {
final rules = ref.watch(profileCustomRulesProvider(_profileId)).value ?? [];
final selectedRules = ref.watch(itemsProvider(key));
return CommonScaffold(
title: appLocalizations.rule,
actions: [
if (selectedRules.isNotEmpty) ...[
CommonMinIconButtonTheme(
child: IconButton.filledTonal(
onPressed: _handleDelete,
icon: Icon(Icons.delete),
),
),
SizedBox(width: 2),
],
CommonMinFilledButtonTheme(
child: selectedRules.isNotEmpty
? FilledButton(
onPressed: _handleSelectAll,
child: Text(appLocalizations.selectAll),
)
: FilledButton.tonal(
onPressed: () {
// _handleAddOrUpdate();
},
child: Text(appLocalizations.add),
),
),
SizedBox(width: 8),
],
body: ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
itemBuilder: (_, index) {
final rule = rules[index];
final position = ItemPosition.get(index, rules.length);
return ReorderableDelayedDragStartListener(
key: ValueKey(rule),
index: index,
child: ItemPositionProvider(
position: position,
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
isSelected: selectedRules.contains(rule.id),
rule: rule,
onSelected: () {
_handleSelected(rule.id);
},
onEdit: (rule) {
// _handleAddOrUpdate(rule);
},
),
),
);
},
itemExtent: ruleItemHeight,
itemCount: rules.length,
onReorder: _handleReorder,
),
);
}
}

View File

@@ -0,0 +1,178 @@
// ignore_for_file: deprecated_member_use
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/database/database.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/features/overwrite/rule.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/database.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/config/scripts.dart';
import 'package:fl_clash/views/profiles/preview.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:smooth_sheets/smooth_sheets.dart';
part 'custom.dart';
part 'custom_groups.dart';
part 'custom_rules.dart';
part 'script.dart';
part 'standard.dart';
part 'widgets.dart';
class OverwriteView extends ConsumerStatefulWidget {
final int profileId;
const OverwriteView({super.key, required this.profileId});
@override
ConsumerState<OverwriteView> createState() => _OverwriteViewState();
}
class _OverwriteViewState extends ConsumerState<OverwriteView> {
@override
void initState() {
super.initState();
}
Future<void> _handlePreview() async {
final profile = ref.read(profileProvider(widget.profileId));
if (profile == null) {
return;
}
BaseNavigator.push<String>(context, PreviewProfileView(profile: profile));
}
@override
Widget build(BuildContext context) {
return ProfileIdProvider(
profileId: widget.profileId,
child: CommonScaffold(
title: appLocalizations.override,
actions: [
CommonMinFilledButtonTheme(
child: FilledButton(
onPressed: _handlePreview,
child: Text(appLocalizations.preview),
),
),
SizedBox(width: 8),
],
body: CustomScrollView(slivers: [_Title(), _Content()]),
),
);
}
@override
void dispose() {
super.dispose();
appController.autoApplyProfile();
}
}
class _Title extends ConsumerWidget {
const _Title();
String _getTitle(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standard,
OverwriteType.script => appLocalizations.script,
OverwriteType.custom => appLocalizations.overwriteTypeCustom,
};
}
IconData _getIcon(OverwriteType type) {
return switch (type) {
OverwriteType.standard => Icons.stars,
OverwriteType.script => Icons.rocket,
OverwriteType.custom => Icons.dashboard_customize,
};
}
String _getDesc(OverwriteType type) {
return switch (type) {
OverwriteType.standard => appLocalizations.standardModeDesc,
OverwriteType.script => appLocalizations.scriptModeDesc,
OverwriteType.custom => appLocalizations.overwriteTypeCustomDesc,
};
}
void _handleChangeType(WidgetRef ref, int profileId, OverwriteType type) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
return state.copyWith(overwriteType: type);
});
}
@override
Widget build(context, ref) {
final profileId = ProfileIdProvider.of(context)!.profileId;
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader(info: Info(label: appLocalizations.overrideMode)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Wrap(
spacing: 16,
children: [
for (final type in OverwriteType.values)
CommonCard(
isSelected: overwriteType == type,
onPressed: () {
_handleChangeType(ref, profileId, type);
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(_getIcon(type)),
const SizedBox(width: 8),
Flexible(child: Text(_getTitle(type))),
],
),
),
),
],
),
),
SizedBox(height: 12),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
_getDesc(overwriteType),
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.onSurfaceVariant.opacity80,
),
),
),
],
),
);
}
}
class _Content extends ConsumerWidget {
const _Content();
@override
Widget build(BuildContext context, ref) {
final profileId = ProfileIdProvider.of(context)!.profileId;
final overwriteType = ref.watch(overwriteTypeProvider(profileId));
ref.listen(clashConfigProvider(profileId), (_, _) {});
return switch (overwriteType) {
OverwriteType.standard => _StandardContent(),
OverwriteType.script => _ScriptContent(),
OverwriteType.custom => _CustomContent(),
};
}
}

View File

@@ -0,0 +1,122 @@
// ignore_for_file: deprecated_member_use
part of 'overwrite.dart';
class _ScriptContent extends ConsumerWidget {
const _ScriptContent();
void _handleChange(WidgetRef ref, int profileId, int scriptId) {
ref.read(profilesProvider.notifier).updateProfile(profileId, (state) {
return state.copyWith(
scriptId: state.scriptId == scriptId ? null : scriptId,
);
});
}
@override
Widget build(BuildContext context, ref) {
final profileId = ProfileIdProvider.of(context)!.profileId;
final scriptId = ref.watch(
profileProvider(profileId).select((state) => state?.scriptId),
);
final scripts = ref.watch(scriptsProvider).value ?? [];
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(info: Info(label: appLocalizations.overrideScript)),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList.builder(
itemCount: scripts.length,
itemBuilder: (_, index) {
final script = scripts[index];
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: CommonCard(
type: CommonCardType.filled,
radius: 18,
child: ListTile(
minLeadingWidth: 0,
minTileHeight: 0,
minVerticalPadding: 16,
contentPadding: const EdgeInsets.symmetric(
horizontal: 14,
),
title: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Radio(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
toggleable: true,
value: script.id,
groupValue: scriptId,
onChanged: (_) {
_handleChange(ref, profileId, script.id);
},
),
),
SizedBox(width: 8),
Flexible(child: Text(script.label)),
],
),
onTap: () {
_handleChange(ref, profileId, script.id);
},
),
),
);
},
),
);
},
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
radius: 18,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
appLocalizations.goToConfigureScript,
style: context.textTheme.bodyLarge,
),
),
Icon(Icons.arrow_forward_ios, size: 18),
],
),
),
onPressed: () {
BaseNavigator.push(context, const ScriptsView());
},
),
),
),
],
);
}
}

View File

@@ -0,0 +1,223 @@
part of 'overwrite.dart';
class _StandardContent extends ConsumerStatefulWidget {
const _StandardContent();
@override
ConsumerState createState() => _StandardContentState();
}
class _StandardContentState extends ConsumerState<_StandardContent> {
final _key = utils.id;
late int _profileId;
Future<void> _handleAddOrUpdate([Rule? rule]) async {
final res = await globalState.showCommonDialog<Rule>(
child: AddOrEditRuleDialog(rule: rule),
);
if (res == null) {
return;
}
ref.read(profileAddedRulesProvider(_profileId).notifier).put(res);
}
void _handleSelected(int ruleId) {
ref.read(itemsProvider(_key).notifier).update((selectedRules) {
final newSelectedRules = Set<int>.from(selectedRules)
..addOrRemove(ruleId);
return newSelectedRules;
});
}
void _handleSelectAll() {
final ids =
ref
.read(profileAddedRulesProvider(_profileId))
.value
?.map((item) => item.id)
.toSet() ??
{};
ref.read(itemsProvider(_key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
Future<void> _handleDelete() async {
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(
text: appLocalizations.deleteMultipTip(appLocalizations.rule),
),
);
if (res != true) {
return;
}
final selectedRules = ref.read(itemsProvider(_key));
ref
.read(profileAddedRulesProvider(_profileId).notifier)
.delAll(selectedRules.cast<int>());
ref.read(itemsProvider(_key).notifier).value = {};
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_profileId = ProfileIdProvider.of(context)!.profileId;
}
void _handleToEditGlobalAddedRules() {
BaseNavigator.push(context, _EditGlobalAddedRules(_profileId));
}
@override
Widget build(BuildContext context) {
_profileId = ProfileIdProvider.of(context)!.profileId;
final addedRules =
ref.watch(profileAddedRulesProvider(_profileId)).value ?? [];
final selectedRules = ref.watch(itemsProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedRules.isNotEmpty) {
ref.read(itemsProvider(_key).notifier).value = {};
return false;
}
Navigator.of(context).pop();
return false;
},
child: SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: SizedBox(height: 24)),
SliverToBoxAdapter(
child: Column(
children: [
InfoHeader(
info: Info(label: appLocalizations.addedRules),
actions: [
if (selectedRules.isNotEmpty) ...[
CommonMinIconButtonTheme(
child: IconButton.filledTonal(
onPressed: () {
_handleDelete();
},
icon: Icon(Icons.delete),
),
),
SizedBox(width: 8),
],
CommonMinFilledButtonTheme(
child: selectedRules.isNotEmpty
? FilledButton(
onPressed: () {
_handleSelectAll();
},
child: Text(appLocalizations.selectAll),
)
: FilledButton.tonal(
onPressed: () {
_handleAddOrUpdate();
},
child: Text(appLocalizations.add),
),
),
],
),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 8)),
Consumer(
builder: (_, ref, _) {
return SliverReorderableList(
itemCount: addedRules.length,
itemBuilder: (_, index) {
final rule = addedRules[index];
final position = ItemPosition.get(index, addedRules.length);
return ReorderableDelayedDragStartListener(
key: ObjectKey(rule),
index: index,
child: ItemPositionProvider(
position: position,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 16),
child: RuleItem(
isEditing: selectedRules.isNotEmpty,
isSelected: selectedRules.contains(rule.id),
rule: rule,
onSelected: () {
_handleSelected(rule.id);
},
onEdit: (rule) {
_handleAddOrUpdate(rule);
},
),
),
),
);
},
itemExtent: ruleItemHeight,
onReorder: ref
.read(profileAddedRulesProvider(_profileId).notifier)
.order,
);
},
),
SliverToBoxAdapter(child: SizedBox(height: 16)),
SliverToBoxAdapter(
child: _MoreActionButton(
label: appLocalizations.controlGlobalAddedRules,
onPressed: _handleToEditGlobalAddedRules,
),
),
],
),
);
}
}
class _EditGlobalAddedRules extends ConsumerWidget {
final int profileId;
const _EditGlobalAddedRules(this.profileId);
void _handleChange(WidgetRef ref, int profileId, bool status, int ruleId) {
if (status) {
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).put(ruleId);
} else {
ref.read(profileDisabledRuleIdsProvider(profileId).notifier).del(ruleId);
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final disabledRuleIds =
ref.watch(profileDisabledRuleIdsProvider(profileId)).value ?? [];
final rules = ref.watch(globalRulesProvider).value ?? [];
return BaseScaffold(
title: appLocalizations.editGlobalRules,
body: rules.isEmpty
? NullStatus(
label: appLocalizations.nullTip(appLocalizations.rule),
illustration: RuleEmptyIllustration(),
)
: ListView.builder(
padding: EdgeInsets.all(16),
itemExtent: ruleItemHeight,
itemBuilder: (context, index) {
final rule = rules[index];
final position = ItemPosition.get(index, rules.length);
return ItemPositionProvider(
position: position,
child: RuleStatusItem(
status: !disabledRuleIds.contains(rule.id),
rule: rule,
onChange: (status) {
_handleChange(ref, profileId, !status, rule.id);
},
),
);
},
itemCount: rules.length,
),
);
}
}

View File

@@ -0,0 +1,49 @@
part of 'overwrite.dart';
class _MoreActionButton extends StatelessWidget {
final VoidCallback? onPressed;
final String label;
final Widget? trailing;
const _MoreActionButton({this.onPressed, required this.label, this.trailing});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: CommonCard(
radius: 18,
onPressed: onPressed,
child: ListTile(
minTileHeight: 0,
minVerticalPadding: 0,
titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
title: Text(label, style: context.textTheme.bodyLarge),
trailing: trailing ?? Icon(Icons.arrow_forward_ios, size: 18),
),
),
);
}
}
class ProfileIdProvider extends InheritedWidget {
final int profileId;
const ProfileIdProvider({
super.key,
required this.profileId,
required super.child,
});
static ProfileIdProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ProfileIdProvider>();
}
@override
bool updateShouldNotify(ProfileIdProvider oldWidget) =>
profileId != oldWidget.profileId;
}

View File

@@ -0,0 +1,49 @@
import 'package:fl_clash/common/task.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/models/profile.dart';
import 'package:fl_clash/pages/editor.dart';
import 'package:flutter/material.dart';
class PreviewProfileView extends StatefulWidget {
final Profile profile;
const PreviewProfileView({super.key, required this.profile});
@override
State<PreviewProfileView> createState() => _PreviewProfileViewState();
}
class _PreviewProfileViewState extends State<PreviewProfileView> {
final contentNotifier = ValueNotifier<String?>(null);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final configMap = await appController.getProfileWithId(widget.profile.id);
final content = await encodeYamlTask(configMap);
if (!mounted) {
return;
}
contentNotifier.value = content;
});
}
@override
void dispose() {
contentNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: contentNotifier,
builder: (_, content, _) {
final title = widget.profile.realLabel;
return EditorPage(key: Key('content'), title: title, content: content);
},
);
}
}

View File

@@ -2,10 +2,9 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/pages/editor.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/profiles/overwrite.dart';
import 'package:fl_clash/views/profiles/overwrite/overwrite.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -13,6 +12,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'add.dart';
import 'edit.dart';
import 'preview.dart';
class ProfilesView extends StatefulWidget {
const ProfilesView({super.key});
@@ -23,13 +23,31 @@ class ProfilesView extends StatefulWidget {
class _ProfilesViewState extends State<ProfilesView> {
Function? applyConfigDebounce;
bool _isUpdating = false;
// final GlobalKey _targetKey = GlobalKey();
@override
void initState() {
super.initState();
// WidgetsBinding.instance.addPostFrameCallback((_) {
// final context = _targetKey.currentContext;
// if (context == null) {
// return;
// }
// Scrollable.ensureVisible(
// context,
// duration: commonDuration,
// alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
// );
// });
}
void _handleShowAddExtendPage() {
showExtend(
globalState.navigatorKey.currentState!.context,
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: AddProfileView(
context: globalState.navigatorKey.currentState!.context,
),
@@ -40,6 +58,10 @@ class _ProfilesViewState extends State<ProfilesView> {
}
Future<void> _updateProfiles(List<Profile> profiles) async {
if (_isUpdating == true) {
return;
}
_isUpdating = true;
final List<UpdatingMessage> messages = [];
final updateProfiles = profiles.map<Future>((profile) async {
if (profile.type == ProfileType.file) return;
@@ -55,6 +77,7 @@ class _ProfilesViewState extends State<ProfilesView> {
if (messages.isNotEmpty) {
globalState.showAllUpdatingMessagesDialog(messages);
}
_isUpdating = false;
}
List<Widget> _buildActions(List<Profile> profiles) {
@@ -70,11 +93,8 @@ class _ProfilesViewState extends State<ProfilesView> {
onPressed: () {
showSheet(
context: context,
builder: (_, type) {
return ReorderableProfilesSheet(
type: type,
profiles: profiles,
);
builder: (_) {
return ReorderableProfilesSheet(profiles: profiles);
},
);
},
@@ -86,10 +106,10 @@ class _ProfilesViewState extends State<ProfilesView> {
}
Widget _buildFAB() {
return FloatingActionButton(
heroTag: null,
return CommonFloatingActionButton(
onPressed: _handleShowAddExtendPage,
child: const Icon(Icons.add),
icon: const Icon(Icons.add),
label: context.appLocalizations.addProfile,
);
}
@@ -99,7 +119,7 @@ class _ProfilesViewState extends State<ProfilesView> {
builder: (_, ref, _) {
final isLoading = ref.watch(loadingProvider(LoadingTag.profiles));
final state = ref.watch(profilesStateProvider);
final spacing = 14.ap;
final spacing = 14.mAp;
return CommonScaffold(
isLoading: isLoading,
title: appLocalizations.profiles,
@@ -128,7 +148,6 @@ class _ProfilesViewState extends State<ProfilesView> {
for (int i = 0; i < state.profiles.length; i++)
GridItem(
child: ProfileItem(
key: Key(state.profiles[i].id.toString()),
profile: state.profiles[i],
groupValue: state.currentProfileId,
onChanged: (profileId) {
@@ -175,14 +194,7 @@ class ProfileItem extends StatelessWidget {
}
Future<void> _handlePreview(BuildContext context) async {
final configMap = await appController.getProfileWithId(profile.id);
final content = await encodeYamlTask(configMap);
if (!context.mounted) {
return;
}
final previewPage = EditorPage(title: profile.realLabel, content: content);
BaseNavigator.push<String>(context, previewPage);
BaseNavigator.push<String>(context, PreviewProfileView(profile: profile));
}
Future updateProfile() async {
@@ -196,9 +208,8 @@ class ProfileItem extends StatelessWidget {
void _handleShowEditExtendPage(BuildContext context) {
showExtend(
context,
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: EditProfileView(profile: profile, context: context),
title: '${appLocalizations.edit}${appLocalizations.profile}',
);
@@ -410,13 +421,8 @@ class ProfileItem extends StatelessWidget {
class ReorderableProfilesSheet extends StatefulWidget {
final List<Profile> profiles;
final SheetType type;
const ReorderableProfilesSheet({
super.key,
required this.profiles,
required this.type,
});
const ReorderableProfilesSheet({super.key, required this.profiles});
@override
State<ReorderableProfilesSheet> createState() =>
@@ -433,19 +439,19 @@ class _ReorderableProfilesSheetState extends State<ReorderableProfilesSheet> {
}
Widget _buildItem(int index, [bool isDecorator = false]) {
final isLast = index == profiles.length - 1;
final isFirst = index == 0;
final position = ItemPosition.get(index, profiles.length);
final profile = profiles[index];
return CommonInputListItem(
return ItemPositionProvider(
key: Key(profile.id.toString()),
trailing: ReorderableDelayedDragStartListener(
index: index,
child: const Icon(Icons.drag_handle),
position: position,
child: DecorationListItem(
trailing: ReorderableDelayedDragStartListener(
index: index,
child: const Icon(Icons.drag_handle),
),
title: Text(profile.realLabel),
isDecorator: isDecorator,
),
title: Text(profile.realLabel),
isFirst: isFirst,
isLast: isLast,
isDecorator: isDecorator,
);
}
@@ -457,30 +463,15 @@ class _ReorderableProfilesSheetState extends State<ReorderableProfilesSheet> {
@override
Widget build(BuildContext context) {
return AdaptiveSheetScaffold(
type: widget.type,
actions: [
if (widget.type == SheetType.bottomSheet)
IconButton.filledTonal(
onPressed: _handleSave,
style: IconButton.styleFrom(
visualDensity: VisualDensity.comfortable,
tapTargetSize: MaterialTapTargetSize.padded,
padding: EdgeInsets.all(8),
iconSize: 20,
),
icon: Icon(Icons.check),
)
else
IconButton.filledTonal(
icon: Icon(Icons.check),
onPressed: _handleSave,
),
],
sheetTransparentToolBar: true,
actions: [IconButtonData(icon: Icons.check, onPressed: _handleSave)],
body: Padding(
padding: EdgeInsets.only(bottom: 32, top: 12),
padding: EdgeInsets.only(bottom: 32),
child: ReorderableListView.builder(
buildDefaultDragHandles: false,
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(
horizontal: 16,
).copyWith(top: context.sheetTopPadding),
proxyDecorator: (child, index, animation) {
return commonProxyDecorator(
_buildItem(index, true),

View File

@@ -15,9 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
typedef UpdatingMap = Map<String, bool>;
class ProvidersView extends ConsumerStatefulWidget {
final SheetType type;
const ProvidersView({super.key, required this.type});
const ProvidersView({super.key});
@override
ConsumerState<ProvidersView> createState() => _ProvidersViewState();
@@ -58,15 +56,7 @@ class _ProvidersViewState extends ConsumerState<ProvidersView> {
items: ruleProviders,
);
return AdaptiveSheetScaffold(
actions: [
IconButton(
onPressed: () {
_updateProviders();
},
icon: const Icon(Icons.sync),
),
],
type: widget.type,
actions: [IconButtonData(icon: Icons.sync, onPressed: _updateProviders)],
body: generateListView([...proxySection, ...ruleSection]),
title: appLocalizations.providers,
);

View File

@@ -53,9 +53,8 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
showSheet(
context: context,
props: SheetProps(isScrollControlled: true),
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: const ProxiesSetting(),
title: appLocalizations.settings,
);
@@ -70,8 +69,8 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
onPressed: () {
showExtend(
context,
builder: (_, type) {
return ProvidersView(type: type);
builder: (_) {
return ProvidersView();
},
);
},

View File

@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/widgets/widgets.dart';
@@ -82,9 +83,8 @@ class ProxiesTabViewState extends ConsumerState<ProxiesTabView>
showSheet(
context: context,
props: SheetProps(isScrollControlled: false),
builder: (_, type) {
builder: (_) {
return AdaptiveSheetScaffold(
type: type,
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Consumer(
@@ -410,19 +410,15 @@ class _DelayTestButtonState extends State<DelayTestButton>
return AnimatedBuilder(
animation: _controller.view,
builder: (_, child) {
return SizedBox(
width: 56,
height: 56,
child: FadeTransition(
opacity: _animation,
child: ScaleTransition(scale: _animation, child: child),
),
return FadeTransition(
opacity: _animation,
child: ScaleTransition(scale: _animation, child: child),
);
},
child: FloatingActionButton(
heroTag: null,
child: CommonFloatingActionButton(
onPressed: _healthcheck,
child: const Icon(Icons.network_ping),
label: appLocalizations.delayTest,
icon: const Icon(Icons.network_ping),
),
);
}

View File

@@ -1,3 +1,4 @@
import 'package:fl_clash/widgets/inherited.dart';
import 'package:flutter/material.dart';
class ScrollOverBuilder extends StatefulWidget {
@@ -35,58 +36,18 @@ class _ScrollOverBuilderState extends State<ScrollOverBuilder> {
}
}
// class ProxiesActionsBuilder extends StatelessWidget {
// final Widget? child;
// 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 FloatingActionButtonExtendedBuilder extends StatelessWidget {
final Widget Function(bool isExtend) builder;
// class ActiveBuilder extends StatelessWidget {
// final String label;
// final StateAndChildWidgetBuilder<bool> builder;
// final Widget? child;
//
// const ActiveBuilder({
// super.key,
// 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,
// );
// }
// }
const FloatingActionButtonExtendedBuilder({super.key, required this.builder});
@override
Widget build(BuildContext context) {
final isExtended =
CommonScaffoldFabExtendedProvider.of(context)?.isExtended ?? true;
return builder(isExtended);
}
}
typedef StateWidgetBuilder<T> = Widget Function(T state);

56
lib/widgets/button.dart Normal file
View 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(),
),
),
);
},
),
);
}
}

View File

@@ -29,7 +29,7 @@ class InfoHeader extends StatelessWidget {
Widget build(BuildContext context) {
EdgeInsetsGeometry nextPadding = (padding ?? baseInfoEdgeInsets);
if (actions.isNotEmpty) {
nextPadding = nextPadding.subtract(EdgeInsets.symmetric(vertical: 8.ap));
nextPadding = nextPadding.subtract(EdgeInsets.symmetric(vertical: 8.mAp));
}
return Padding(
padding: nextPadding,
@@ -186,7 +186,7 @@ class CommonCard extends StatelessWidget {
onLongPress: onLongPress,
clipBehavior: Clip.antiAlias,
style: ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
padding: WidgetStatePropertyAll(padding ?? EdgeInsets.zero),
shape: WidgetStatePropertyAll(
RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(radius ?? 14),

View File

@@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
enum ExternalDismissibleEffect { normal, resize }
class ExternalDismissible extends StatefulWidget {
final Widget child;
final VoidCallback? onDismissed;
final bool dismiss;
final ExternalDismissibleEffect effect;
const ExternalDismissible({
super.key,
required this.child,
required this.dismiss,
this.onDismissed,
this.effect = ExternalDismissibleEffect.normal,
});
@override
State<ExternalDismissible> createState() => _ExternalDismissibleState();
}
class _ExternalDismissibleState extends State<ExternalDismissible>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late final AnimationController _controller;
Animation<Offset>? _slideAnimation;
Animation<double>? _fadeAnimation;
late Animation<double> _resizeAnimation;
bool _isDismissing = false;
bool get _isNormal => widget.effect == ExternalDismissibleEffect.normal;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
);
_initAnimations();
if (widget.dismiss) {
_dismiss();
}
}
void _initAnimations() {
const curve = Curves.fastOutSlowIn;
if (_isNormal) {
_slideAnimation =
Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 1.0, curve: curve),
),
);
_resizeAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.3, 1.0, curve: curve),
),
);
} else {
_fadeAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.6, curve: curve),
),
);
_resizeAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.2, 1.0, curve: curve),
),
);
}
}
@override
bool get wantKeepAlive => _isDismissing;
@override
void didUpdateWidget(covariant ExternalDismissible oldWidget) {
super.didUpdateWidget(oldWidget);
if (!oldWidget.dismiss && widget.dismiss) {
_dismiss();
}
}
Future<void> _dismiss() async {
if (_isDismissing) return;
if (!mounted) return;
_isDismissing = true;
updateKeepAlive();
await _controller.forward();
if (mounted && widget.onDismissed != null) {
widget.onDismissed!();
}
_isDismissing = false;
updateKeepAlive();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
Widget content = widget.child;
if (_slideAnimation != null) {
content = SlideTransition(position: _slideAnimation!, child: content);
}
if (_fadeAnimation != null) {
content = FadeTransition(opacity: _fadeAnimation!, child: content);
}
return SizeTransition(
axisAlignment: 0.5,
sizeFactor: _resizeAnimation,
axis: Axis.vertical,
child: content,
);
}
}

View File

@@ -1,7 +1,8 @@
import 'dart:math' as math;
import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
typedef WrapBuilder = Widget Function(Widget child);
@@ -27,10 +28,10 @@ class Grid extends MultiChildRenderObjectWidget {
TextDirection? textDirection,
this.mainAxisExtent,
List<Widget>? children,
}) : crossAxisCount = crossAxisCount ?? 1,
axisDirection = axisDirection ?? AxisDirection.down,
textDirection = textDirection ?? TextDirection.ltr,
super(children: children ?? const []);
}) : crossAxisCount = crossAxisCount ?? 1,
axisDirection = axisDirection ?? AxisDirection.down,
textDirection = textDirection ?? TextDirection.ltr,
super(children: children ?? const []);
const Grid.baseGap({
Key? key,
@@ -42,15 +43,15 @@ class Grid extends MultiChildRenderObjectWidget {
double? mainAxisExtent,
List<Widget>? children,
}) : this(
key: key,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
crossAxisCount: crossAxisCount,
axisDirection: axisDirection,
textDirection: textDirection,
mainAxisExtent: mainAxisExtent,
children: children,
);
key: key,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
crossAxisCount: crossAxisCount,
axisDirection: axisDirection,
textDirection: textDirection,
mainAxisExtent: mainAxisExtent,
children: children,
);
@override
RenderObject createRenderObject(BuildContext context) {
@@ -65,10 +66,7 @@ class Grid extends MultiChildRenderObjectWidget {
}
@override
void updateRenderObject(
BuildContext context,
RenderGrid renderObject,
) {
void updateRenderObject(BuildContext context, RenderGrid renderObject) {
renderObject
..mainAxisSpacing = mainAxisSpacing
..mainAxisExtent = mainAxisExtent
@@ -90,12 +88,12 @@ class RenderGrid extends RenderBox
required AxisDirection axisDirection,
required TextDirection textDirection,
double? mainAxisExtent,
}) : _crossAxisCount = crossAxisCount,
_crossAxisSpacing = crossAxisSpacing,
_mainAxisSpacing = mainAxisSpacing,
_axisDirection = axisDirection,
_textDirection = textDirection,
_mainAxisExtent = mainAxisExtent;
}) : _crossAxisCount = crossAxisCount,
_crossAxisSpacing = crossAxisSpacing,
_mainAxisSpacing = mainAxisSpacing,
_axisDirection = axisDirection,
_textDirection = textDirection,
_mainAxisExtent = mainAxisExtent;
int _crossAxisCount;
@@ -214,15 +212,10 @@ class RenderGrid extends RenderBox
GridParentData childParentData,
int crossAxisCount,
) {
return math.min(
childParentData.crossAxisCellCount ?? 1,
crossAxisCount,
);
return math.min(childParentData.crossAxisCellCount ?? 1, crossAxisCount);
}
Size _computeSize({
required BoxConstraints constraints,
}) {
Size _computeSize({required BoxConstraints constraints}) {
final crossAxisExtent = mainAxis == Axis.vertical
? constraints.maxWidth
: constraints.maxHeight;
@@ -245,11 +238,13 @@ class RenderGrid extends RenderBox
? BoxConstraints.tightFor(width: crossAxisExtent)
: BoxConstraints.tightFor(height: crossAxisExtent);
_layoutChild(child, childConstraints, parentUsesSize: true);
mainAxisExtent =
mainAxis == Axis.vertical ? child.size.height : child.size.width;
mainAxisExtent = mainAxis == Axis.vertical
? child.size.height
: child.size.width;
} else {
final mainAxisCellCount = childParentData.mainAxisCellCount ?? 1;
mainAxisExtent = (this.mainAxisExtent ?? stride) * mainAxisCellCount -
mainAxisExtent =
(this.mainAxisExtent ?? stride) * mainAxisCellCount -
mainAxisSpacing;
childParentData.realMainAxisExtent = mainAxisExtent;
final childSize = mainAxis == Axis.vertical
@@ -265,9 +260,7 @@ class RenderGrid extends RenderBox
? Offset(crossAxisOffset, mainAxisOffset)
: Offset(mainAxisOffset, crossAxisOffset);
childParentData.offset = offset;
final nextOffset = mainAxisOffset + mainAxisExtent + mainAxisSpacing;
for (int i = 0; i < crossAxisCellCount; i++) {
offsets[origin.crossAxisIndex + i] = nextOffset;
}
@@ -281,7 +274,8 @@ class RenderGrid extends RenderBox
final childParentData = _getParentData(child);
final offset = childParentData.offset;
final crossAxisOffset = offset.getCrossAxisOffset(mainAxis);
final mainAxisOffset = mainAxisExtent -
final mainAxisOffset =
mainAxisExtent -
offset.getMainAxisOffset(mainAxis) -
childParentData.realMainAxisExtent!;
final newOffset = mainAxis == Axis.vertical
@@ -365,15 +359,11 @@ class GridItem extends ParentDataWidget<GridParentData> {
@override
Type get debugTypicalAncestorWidgetClass => GridItem;
GridItem wrap({
required WrapBuilder builder,
}) {
GridItem wrap({required WrapBuilder builder}) {
return GridItem(
mainAxisCellCount: mainAxisCellCount,
crossAxisCellCount: crossAxisCellCount,
child: builder(
child,
),
child: builder(child),
);
}
}
@@ -395,11 +385,13 @@ _Origin _getOrigin(List<double> offsets, int crossAxisCount) {
}
int start = 0;
int span = 0;
for (int j = 0;
span < crossAxisCount &&
j < length &&
length - j >= crossAxisCount - span;
j++) {
for (
int j = 0;
span < crossAxisCount &&
j < length &&
length - j >= crossAxisCount - span;
j++
) {
if (offset.moreOrEqual(offsets[j])) {
span++;
if (span == crossAxisCount) {

View File

@@ -0,0 +1,95 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/widgets/sheet.dart';
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;
}
class ItemPositionProvider extends InheritedWidget {
final ItemPosition position;
const ItemPositionProvider({
super.key,
required this.position,
required super.child,
});
static ItemPositionProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ItemPositionProvider>();
}
@override
bool updateShouldNotify(ItemPositionProvider oldWidget) =>
position != oldWidget.position;
}
class SheetProvider extends InheritedWidget {
final SheetType type;
final VoidCallback? nestedNavigatorPopCallback;
const SheetProvider({
super.key,
required super.child,
required this.type,
this.nestedNavigatorPopCallback,
});
SheetProvider copyWith({
SheetType? type,
VoidCallback? nestedNavigatorPopCallback,
required Widget child,
}) {
return SheetProvider(
type: type ?? this.type,
nestedNavigatorPopCallback:
nestedNavigatorPopCallback ?? this.nestedNavigatorPopCallback,
child: child,
);
}
static SheetProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<SheetProvider>();
}
@override
bool updateShouldNotify(SheetProvider oldWidget) =>
type != oldWidget.type &&
nestedNavigatorPopCallback != oldWidget.nestedNavigatorPopCallback;
}

View File

@@ -1,8 +1,10 @@
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/inherited.dart';
import 'package:fl_clash/widgets/null_status.dart';
import 'package:fl_clash/widgets/pop_scope.dart';
import 'package:fl_clash/widgets/scaffold.dart';
@@ -245,7 +247,7 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
}
void _handleSelected(String value) {
ref.read(selectedItemsProvider(_key).notifier).update((state) {
ref.read(itemsProvider(_key).notifier).update((state) {
final newState = Set<String>.from(state)..addOrRemove(value);
return newState;
});
@@ -253,7 +255,7 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
void _handleSelectAll() {
final ids = _items.toSet();
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
ref.read(itemsProvider(_key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
@@ -296,12 +298,12 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
}
void _handleDelete() {
final selectedItems = ref.read(selectedItemsProvider(_key));
final selectedItems = ref.read(itemsProvider(_key));
final newItems = _items
.where((item) => !selectedItems.contains(item))
.toList();
_items = newItems;
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
setState(() {});
}
@@ -319,46 +321,46 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
Widget _buildItem({
required String value,
required int index,
required int totalLength,
required int length,
required bool isSelected,
required bool isEditing,
isDecorator = false,
}) {
final isFirst = index == 0;
final isLast = index == totalLength - 1;
final position = ItemPosition.get(index, length);
return ReorderableDelayedDragStartListener(
key: ValueKey(value),
index: index,
child: CommonSelectedInputListItem(
isDecorator: isDecorator,
isLast: isLast,
isFirst: isFirst,
title: widget.titleBuilder(value),
isSelected: isSelected,
isEditing: isEditing,
onSelected: () {
_handleSelected(value);
},
onPressed: () {
_handleAddOrEdit(value);
},
leading: widget.leadingBuilder != null
? widget.leadingBuilder!(value)
: null,
subtitle: widget.subtitleBuilder != null
? widget.subtitleBuilder!(value)
: null,
child: ItemPositionProvider(
position: position,
child: SelectedDecorationListItem(
isDecorator: isDecorator,
title: widget.titleBuilder(value),
isSelected: isSelected,
isEditing: isEditing,
onSelected: () {
_handleSelected(value);
},
onPressed: () {
_handleAddOrEdit(value);
},
leading: widget.leadingBuilder != null
? widget.leadingBuilder!(value)
: null,
subtitle: widget.subtitleBuilder != null
? widget.subtitleBuilder!(value)
: null,
),
),
);
}
@override
Widget build(BuildContext context) {
final selectedItems = ref.watch(selectedItemsProvider(_key));
final selectedItems = ref.watch(itemsProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedItems.isNotEmpty) {
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
return false;
}
Navigator.of(context).pop(_items);
@@ -415,7 +417,7 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
return _buildItem(
value: value,
index: index,
totalLength: _items.length,
length: _items.length,
isSelected: selectedItems.contains(value),
isEditing: selectedItems.isNotEmpty,
);
@@ -426,7 +428,7 @@ class _ListInputPageState extends ConsumerState<ListInputPage> {
_buildItem(
value: value,
index: index,
totalLength: _items.length,
length: _items.length,
isDecorator: true,
isSelected: selectedItems.contains(value),
isEditing: selectedItems.isNotEmpty,
@@ -490,7 +492,7 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
}
void _handleSelected(MapEntry<String, String> value) {
ref.read(selectedItemsProvider(_key).notifier).update((state) {
ref.read(itemsProvider(_key).notifier).update((state) {
final newState = Set<String>.from(state)..addOrRemove(value.key);
return newState;
});
@@ -498,7 +500,7 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
void _handleSelectAll() {
final ids = _items.map((item) => item.key).toSet();
ref.read(selectedItemsProvider(_key).notifier).update((selected) {
ref.read(itemsProvider(_key).notifier).update((selected) {
return selected.containsAll(ids) ? {} : ids;
});
}
@@ -549,12 +551,12 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
}
void _handleDelete() {
final selectedItems = ref.read(selectedItemsProvider(_key));
final selectedItems = ref.read(itemsProvider(_key));
final newItems = _items
.where((item) => !selectedItems.contains(item.key))
.toList();
_items = newItems;
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
setState(() {});
}
@@ -572,46 +574,46 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
Widget _buildItem({
required MapEntry<String, String> value,
required int index,
required int totalLength,
required int length,
required bool isSelected,
required bool isEditing,
isDecorator = false,
}) {
final isFirst = index == 0;
final isLast = index == totalLength - 1;
final position = ItemPosition.get(index, length);
return ReorderableDelayedDragStartListener(
key: ValueKey(value),
index: index,
child: CommonSelectedInputListItem(
isDecorator: isDecorator,
isLast: isLast,
isFirst: isFirst,
title: widget.titleBuilder(value),
leading: widget.leadingBuilder != null
? widget.leadingBuilder!(value)
: null,
subtitle: widget.subtitleBuilder != null
? widget.subtitleBuilder!(value)
: null,
isSelected: isSelected,
isEditing: isEditing,
onSelected: () {
_handleSelected(value);
},
onPressed: () {
_handleAddOrEdit(value);
},
child: ItemPositionProvider(
position: position,
child: SelectedDecorationListItem(
isDecorator: isDecorator,
title: widget.titleBuilder(value),
leading: widget.leadingBuilder != null
? widget.leadingBuilder!(value)
: null,
subtitle: widget.subtitleBuilder != null
? widget.subtitleBuilder!(value)
: null,
isSelected: isSelected,
isEditing: isEditing,
onSelected: () {
_handleSelected(value);
},
onPressed: () {
_handleAddOrEdit(value);
},
),
),
);
}
@override
Widget build(BuildContext context) {
final selectedItems = ref.watch(selectedItemsProvider(_key));
final selectedItems = ref.watch(itemsProvider(_key));
return CommonPopScope(
onPop: (_) {
if (selectedItems.isNotEmpty) {
ref.read(selectedItemsProvider(_key).notifier).value = {};
ref.read(itemsProvider(_key).notifier).value = {};
return false;
}
Navigator.of(context).pop(Map<String, String>.fromEntries(_items));
@@ -671,7 +673,7 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
return _buildItem(
value: value,
index: index,
totalLength: _items.length,
length: _items.length,
isSelected: selectedItems.contains(value.key),
isEditing: selectedItems.isNotEmpty,
);
@@ -682,7 +684,7 @@ class _MapInputPageState extends ConsumerState<MapInputPage> {
_buildItem(
value: value,
index: index,
totalLength: _items.length,
length: _items.length,
isDecorator: true,
isSelected: selectedItems.contains(value.key),
isEditing: selectedItems.isNotEmpty,
@@ -818,3 +820,52 @@ class _AddDialogState extends State<AddDialog> {
);
}
}
class NoInputBorder extends InputBorder {
const NoInputBorder() : super(borderSide: BorderSide.none);
@override
NoInputBorder copyWith({BorderSide? borderSide}) => const NoInputBorder();
@override
bool get isOutline => false;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
NoInputBorder scale(double t) => const NoInputBorder();
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
void paintInterior(
Canvas canvas,
Rect rect,
Paint paint, {
TextDirection? textDirection,
}) {
canvas.drawRect(rect, paint);
}
@override
bool get preferPaintInterior => true;
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {}
}

View File

@@ -1,7 +1,9 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/inherited.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -312,7 +314,7 @@ class ListItem<T> extends StatelessWidget {
maxWidth: openDelegate.maxWidth,
forceFull: openDelegate.forceFull,
),
builder: (_, type) {
builder: (_) {
return child;
},
);
@@ -344,7 +346,7 @@ class ListItem<T> extends StatelessWidget {
blur: nextDelegate.blur,
maxWidth: nextDelegate.maxWidth,
),
builder: (_, type) {
builder: (_) {
return child;
},
);
@@ -540,6 +542,27 @@ Widget generateSectionV2({
);
}
Widget generateSectionV3({
String? title,
required Iterable<Widget> items,
List<Widget>? actions,
}) {
final genItems = items.mapIndexed<Widget>((index, item) {
final position = ItemPosition.get(index, items.length);
if (position != ItemPosition.middle) {
return ItemPositionProvider(position: position, child: item);
}
return item;
});
return Column(
children: [
if (items.isNotEmpty && title != null)
ListHeader(title: title, actions: actions),
Column(children: [...genItems]),
],
);
}
List<Widget> generateInfoSection({
required Info info,
required Iterable<Widget> items,
@@ -587,7 +610,6 @@ class CommonSelectedListItem extends StatelessWidget {
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 16),
color: Colors.transparent,
child: CommonCard(
padding: EdgeInsets.zero,
radius: 18,
type: CommonCardType.filled,
isSelected: isSelected,
@@ -621,92 +643,105 @@ class CommonSelectedListItem extends StatelessWidget {
}
}
class CommonInputListItem extends StatelessWidget {
class DecorationListItem extends StatelessWidget {
final bool isDecorator;
final bool isFirst;
final bool isLast;
final Widget? title;
final Widget title;
final Widget? subtitle;
final Widget? leading;
final Widget? trailing;
final bool? isSelected;
final VoidCallback? onPressed;
final double minVerticalPadding;
const CommonInputListItem({
const DecorationListItem({
super.key,
this.isDecorator = false,
this.isFirst = false,
this.isLast = false,
this.title,
required this.title,
this.leading,
this.trailing,
this.subtitle,
this.isSelected,
this.onPressed,
this.minVerticalPadding = 6,
});
@override
Widget build(BuildContext context) {
final position = ItemPositionProvider.of(context)?.position;
final isStart = [
ItemPosition.start,
ItemPosition.startAndEnd,
].contains(position);
final isEnd = [
ItemPosition.end,
ItemPosition.startAndEnd,
].contains(position);
return Container(
clipBehavior: Clip.hardEdge,
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
shape: isDecorator == true
? LinearBorder.none
: RoundedSuperellipseBorder(
borderRadius: BorderRadius.vertical(
top: isFirst ? Radius.circular(24) : Radius.zero,
bottom: isLast ? Radius.circular(24) : Radius.zero,
top: isStart ? Radius.circular(24) : Radius.zero,
bottom: isEnd ? Radius.circular(24) : Radius.zero,
),
),
),
child: CommonCard(
radius: 0,
isSelected: isSelected,
padding: EdgeInsets.zero,
type: CommonCardType.filled,
onPressed: onPressed,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: ListTile(
leading: leading,
contentPadding: const EdgeInsets.only(right: 16, left: 16),
title: title,
subtitle: subtitle,
minVerticalPadding: 14,
trailing: trailing,
),
),
if (isDecorator != true && !isLast)
Divider(height: 0, indent: 14, endIndent: 14),
],
child: LayoutBuilder(
builder: (_, constraints) {
final isInfinite = constraints.maxHeight >= double.infinity;
final tile = ListTile(
leading: leading,
contentPadding: const EdgeInsets.only(right: 16, left: 16),
title: title,
subtitle: subtitle,
minVerticalPadding: minVerticalPadding,
minTileHeight: 54,
trailing: trailing,
);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
fit: isInfinite ? FlexFit.loose : FlexFit.tight,
child: tile,
),
if (isDecorator != true && !isEnd)
Divider(height: 0, indent: 14, endIndent: 14),
],
);
},
),
),
);
}
}
class CommonSelectedInputListItem extends StatelessWidget {
class SelectedDecorationListItem extends StatelessWidget {
final bool isSelected;
final bool isEditing;
final Widget title;
final Widget? subtitle;
final VoidCallback onSelected;
final VoidCallback onPressed;
final bool isFirst;
final bool isLast;
final bool isDecorator;
final Widget? leading;
const CommonSelectedInputListItem({
const SelectedDecorationListItem({
super.key,
required this.isSelected,
required this.onSelected,
this.isEditing = false,
required this.title,
required this.onPressed,
this.isFirst = false,
this.isLast = false,
this.isDecorator = false,
this.subtitle,
this.leading,
@@ -714,12 +749,10 @@ class CommonSelectedInputListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CommonInputListItem(
return DecorationListItem(
title: title,
isDecorator: isDecorator,
isSelected: isSelected,
isFirst: isFirst,
isLast: isLast,
leading: leading,
onPressed: isDecorator
? null

View File

@@ -3,8 +3,10 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/widgets/pop_scope.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'chip.dart';
import 'inherited.dart';
typedef OnKeywordsUpdateCallback = void Function(List<String> keywords);
@@ -47,8 +49,8 @@ class CommonScaffold extends StatefulWidget {
class CommonScaffoldState extends State<CommonScaffold> {
late final ValueNotifier<AppBarState> _appBarState;
final ValueNotifier<Widget?> _floatingActionButton = ValueNotifier(null);
final ValueNotifier<bool> _loadingNotifier = ValueNotifier(false);
final ValueNotifier<bool> _isFabExtendedNotifier = ValueNotifier(true);
final ValueNotifier<List<String>> _keywordsNotifier = ValueNotifier([]);
final _textController = TextEditingController();
@@ -83,12 +85,6 @@ class CommonScaffoldState extends State<CommonScaffold> {
_updateSearchState((state) => state?.copyWith(query: ''));
}
set floatingActionButton(Widget? floatingActionButton) {
if (_floatingActionButton.value != floatingActionButton) {
_floatingActionButton.value = floatingActionButton;
}
}
Widget _buildSearchingAppBarTheme(Widget child) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
@@ -156,7 +152,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
void dispose() {
_appBarState.dispose();
_textController.dispose();
_floatingActionButton.dispose();
_isFabExtendedNotifier.dispose();
_loadingNotifier.dispose();
super.dispose();
}
@@ -350,17 +346,31 @@ class CommonScaffoldState extends State<CommonScaffold> {
);
return Scaffold(
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,
backgroundColor: widget.backgroundColor,
floatingActionButton:
widget.floatingActionButton ??
ValueListenableBuilder<Widget?>(
valueListenable: _floatingActionButton,
builder: (_, value, _) {
return value ?? SizedBox();
},
),
floatingActionButton: widget.floatingActionButton != null
? ValueListenableBuilder<bool>(
valueListenable: _isFabExtendedNotifier,
builder: (_, isExtended, child) {
return CommonScaffoldFabExtendedProvider(
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 {
final String title;
final List<Widget> actions;

View File

@@ -1,5 +1,10 @@
import 'dart:ui';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/controller.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/widgets/inherited.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'scaffold.dart';
@@ -11,11 +16,13 @@ class SheetProps {
final double? maxHeight;
final bool isScrollControlled;
final bool useSafeArea;
final Color? backgroundColor;
final bool blur;
const SheetProps({
this.maxWidth,
this.maxHeight,
this.backgroundColor,
this.useSafeArea = true,
this.isScrollControlled = false,
this.blur = true,
@@ -39,11 +46,9 @@ class ExtendProps {
enum SheetType { page, bottomSheet, sideSheet }
typedef SheetBuilder = Widget Function(BuildContext context, SheetType type);
Future<T?> showSheet<T>({
required BuildContext context,
required SheetBuilder builder,
required WidgetBuilder builder,
SheetProps props = const SheetProps(),
}) {
final isMobile = appController.isMobile;
@@ -52,8 +57,12 @@ Future<T?> showSheet<T>({
context: context,
isScrollControlled: props.isScrollControlled,
builder: (_) {
return builder(context, SheetType.bottomSheet);
return SheetProvider(
type: SheetType.bottomSheet,
child: builder(context),
);
},
backgroundColor: props.backgroundColor,
showDragHandle: false,
useSafeArea: props.useSafeArea,
),
@@ -61,10 +70,14 @@ Future<T?> showSheet<T>({
useSafeArea: props.useSafeArea,
isScrollControlled: props.isScrollControlled,
context: context,
backgroundColor: props.backgroundColor,
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
filter: props.blur ? commonFilter : null,
builder: (_) {
return builder(context, SheetType.sideSheet);
return SheetProvider(
type: SheetType.sideSheet,
child: builder(context),
);
},
),
};
@@ -72,37 +85,41 @@ Future<T?> showSheet<T>({
Future<T?> showExtend<T>(
BuildContext context, {
required SheetBuilder builder,
required WidgetBuilder builder,
ExtendProps props = const ExtendProps(),
}) {
final isMobile = appController.isMobile;
return switch (isMobile || props.forceFull) {
true => BaseNavigator.push(context, builder(context, SheetType.page)),
true => BaseNavigator.push(
context,
SheetProvider(type: SheetType.page, child: builder(context)),
),
false => showModalSideSheet<T>(
useSafeArea: props.useSafeArea,
context: context,
constraints: BoxConstraints(maxWidth: props.maxWidth ?? 360),
filter: props.blur ? commonFilter : null,
builder: (context) {
return builder(context, SheetType.sideSheet);
return SheetProvider(
type: SheetType.sideSheet,
child: builder(context),
);
},
),
};
}
class AdaptiveSheetScaffold extends StatefulWidget {
final SheetType type;
final Widget body;
final String title;
final bool? centerTitle;
final List<Widget> actions;
final bool sheetTransparentToolBar;
final List<IconButtonData> actions;
const AdaptiveSheetScaffold({
super.key,
required this.type,
required this.body,
required this.title,
this.centerTitle,
this.sheetTransparentToolBar = false,
this.actions = const [],
});
@@ -111,34 +128,113 @@ class AdaptiveSheetScaffold extends StatefulWidget {
}
class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
final _isScrolledController = ValueNotifier<bool>(false);
IconData get backIconData {
if (kIsWeb) {
return Icons.arrow_back;
}
switch (Theme.of(context).platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return Icons.arrow_back;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return Icons.arrow_back_ios_new_rounded;
}
}
@override
void dispose() {
_isScrolledController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final backgroundColor = context.colorScheme.surface;
final bottomSheet = widget.type == SheetType.bottomSheet;
final sideSheet = widget.type == SheetType.sideSheet;
final sheetProvider = SheetProvider.of(context);
final nestedNavigatorPopCallback =
sheetProvider?.nestedNavigatorPopCallback;
final ModalRoute<dynamic>? route = ModalRoute.of(context);
final type = sheetProvider?.type ?? SheetType.page;
final backgroundColor = type == SheetType.bottomSheet
? context.colorScheme.surfaceContainerLow
: context.colorScheme.surface;
final useCloseIcon =
type != SheetType.page &&
(nestedNavigatorPopCallback != null &&
route?.impliesAppBarDismissal == false ||
nestedNavigatorPopCallback == null);
Widget buildIconButton(IconButtonData data) {
if (type == SheetType.bottomSheet) {
return IconButton.filledTonal(
onPressed: data.onPressed,
style: IconButton.styleFrom(
visualDensity: VisualDensity.standard,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
icon: Icon(data.icon),
);
}
return IconButton(
onPressed: data.onPressed,
style: IconButton.styleFrom(
visualDensity: VisualDensity.standard,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
icon: Icon(data.icon),
);
}
final actions = widget.actions.map(buildIconButton).toList();
final popButton = type != SheetType.page
? (useCloseIcon
? buildIconButton(
IconButtonData(
icon: Icons.close,
onPressed: () {
if (nestedNavigatorPopCallback != null) {
nestedNavigatorPopCallback();
} else {
Navigator.of(context).pop();
}
},
),
)
: buildIconButton(
IconButtonData(
icon: backIconData,
onPressed: () {
Navigator.of(context).pop();
},
),
))
: null;
final suffixPop = type != SheetType.page && actions.isEmpty && useCloseIcon;
final appBar = AppBar(
forceMaterialTransparency: bottomSheet ? true : false,
automaticallyImplyLeading: bottomSheet
? false
: widget.actions.isEmpty && sideSheet
? false
: true,
centerTitle:
widget.centerTitle ?? (bottomSheet && widget.actions.isEmpty),
backgroundColor: backgroundColor,
forceMaterialTransparency: type == SheetType.bottomSheet ? true : false,
leading: suffixPop ? null : popButton,
automaticallyImplyLeading: type == SheetType.page ? true : false,
centerTitle: true,
toolbarHeight: type == SheetType.bottomSheet ? 48 : null,
title: Text(widget.title),
actions: genActions([
if (widget.actions.isEmpty && sideSheet) CloseButton(),
...widget.actions,
]),
titleTextStyle: type == SheetType.bottomSheet
? context.textTheme.titleLarge?.adjustSize(-4)
: null,
actions: !suffixPop ? genActions(actions) : genActions([?popButton]),
);
if (bottomSheet) {
final handleSize = Size(32, 4);
return Column(
if (type == SheetType.bottomSheet) {
final handleSize = Size(28, 4);
final sheetAppBar = Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(top: 16),
padding: EdgeInsets.only(top: 6),
child: Container(
alignment: Alignment.center,
height: handleSize.height,
@@ -151,11 +247,71 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
),
),
),
Padding(padding: EdgeInsets.symmetric(horizontal: 8), child: appBar),
Flexible(flex: 1, child: widget.body),
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: appBar),
SizedBox(height: 6),
],
);
return ScrollConfiguration(
behavior: HiddenBarScrollBehavior(),
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!widget.sheetTransparentToolBar) ...[
sheetAppBar,
Flexible(child: widget.body),
] else ...[
Flexible(
child: Stack(
children: [
NotificationListener<ScrollNotification>(
child: widget.body,
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
final pixels = notification.metrics.pixels;
_isScrolledController.value = pixels > 6;
}
return false;
},
),
Positioned(
top: 0,
left: 0,
right: 0,
child: ValueListenableBuilder(
valueListenable: _isScrolledController,
builder: (_, isScrolled, child) {
return ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(28),
),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 12.0,
sigmaY: 12.0,
),
child: ColoredBox(
color: isScrolled
? backgroundColor.opacity60
: backgroundColor,
child: child!,
),
),
);
},
child: sheetAppBar,
),
),
],
),
),
],
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
],
),
),
);
}
return CommonScaffold(
appBar: appBar,

View File

@@ -113,6 +113,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
children = widget.children;
_childrenNotifier.addListener(() {
children = _childrenNotifier.value;
if (widget.onUpdate != null) {
@@ -230,7 +231,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
Tween<double>(
begin: 0.0,
end: 1,
).chain(CurveTween(curve: Easing.emphasizedAccelerate)),
).chain(CurveTween(curve: Curves.fastOutSlowIn)),
),
),
);
@@ -504,7 +505,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
Widget _builderItem(int index) {
final girdItem = _childrenNotifier.value[index];
final child = RepaintBoundary(child: girdItem.child);
final child = girdItem.child;
return GridItem(
mainAxisCellCount: girdItem.mainAxisCellCount,
crossAxisCellCount: girdItem.crossAxisCellCount,

View File

@@ -7,20 +7,18 @@ import '../state.dart';
class TooltipText extends StatelessWidget {
final Text text;
const TooltipText({
super.key,
required this.text,
});
const TooltipText({super.key, required this.text});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, container) {
final maxWidth = container.maxWidth;
final size = globalState.measure.computeTextSize(
builder: (context, constraints) {
final maxWidth = constraints.maxWidth;
final isOverflow = globalState.measure.computeTextIsOverflow(
text,
maxWidth: maxWidth,
);
if (maxWidth < size.width) {
if (isOverflow) {
return Tooltip(
triggerMode: TooltipTriggerMode.longPress,
preferBelow: false,
@@ -57,26 +55,21 @@ class EmojiText extends StatelessWidget {
if (match.start > lastMatchEnd) {
spans.add(
TextSpan(
text: text.substring(lastMatchEnd, match.start), style: style),
text: text.substring(lastMatchEnd, match.start),
style: style,
),
);
}
spans.add(
TextSpan(
text: match.group(0),
style: style?.copyWith(
fontFamily: FontFamily.twEmoji.value,
),
style: style?.copyWith(fontFamily: FontFamily.twEmoji.value),
),
);
lastMatchEnd = match.end;
}
if (lastMatchEnd < text.length) {
spans.add(
TextSpan(
text: text.substring(lastMatchEnd),
style: style,
),
);
spans.add(TextSpan(text: text.substring(lastMatchEnd), style: style));
}
return spans;
@@ -88,9 +81,7 @@ class EmojiText extends StatelessWidget {
textScaler: MediaQuery.of(context).textScaler,
maxLines: maxLines,
overflow: overflow ?? TextOverflow.clip,
text: TextSpan(
children: _buildTextSpans(text),
),
text: TextSpan(children: _buildTextSpans(text)),
);
}
}

View File

@@ -1,18 +1,21 @@
export 'activate_box.dart';
export 'animate_grid.dart';
export 'builder.dart';
export 'button.dart';
export 'card.dart';
export 'chip.dart';
export 'color_scheme_box.dart';
export 'container.dart';
export 'dialog.dart';
export 'disabled_mask.dart';
export 'dismissible.dart';
export 'donut_chart.dart';
export 'effect.dart';
export 'fade_box.dart';
export 'float_layout.dart';
export 'grid.dart';
export 'icon.dart';
export 'inherited.dart';
export 'input.dart';
export 'keep_scope.dart';
export 'line_chart.dart';

View File

@@ -918,6 +918,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.5.0"
navigator_resizable:
dependency: transitive
description:
name: navigator_resizable
sha256: "6df8fcf5ef4267dd8b1cda35b220427a4f4864c659519125a40365c8d3027697"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
nm:
dependency: transitive
description:
@@ -1109,6 +1117,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
reorderable_grid:
dependency: "direct main"
description:
name: reorderable_grid
sha256: "15873d8afa6c0a106f0e165f4fbb2bb92dbfe18837e0c9f5d62ac4e26448863d"
url: "https://pub.dev"
source: hosted
version: "1.0.13"
riverpod:
dependency: "direct main"
description:
@@ -1298,6 +1314,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
smooth_sheets:
dependency: "direct main"
description:
name: smooth_sheets
sha256: fe1c9e4f1cd20c841cc2155cb125da74c004baf7bcfdf0bef4f87a7183d0907b
url: "https://pub.dev"
source: hosted
version: "0.17.0"
source_gen:
dependency: transitive
description:
@@ -1755,4 +1779,4 @@ packages:
version: "2.1.0"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"
flutter: ">=3.35.1"

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.92+2026012502
version: 0.8.92+2026020201
environment:
sdk: '>=3.8.0 <4.0.0'
@@ -69,6 +69,8 @@ dependencies:
drift: ^2.29.0
drift_flutter: ^0.2.7
fractional_indexing: ^1.0.0
reorderable_grid: ^1.0.13
smooth_sheets: ^0.17.0
dev_dependencies:
flutter_test:
sdk: flutter