Compare commits

..

4 Commits

Author SHA1 Message Date
chen08209
094857e0d6 Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select
2025-04-28 15:27:47 +08:00
chen08209
0a98515457 Optimize startTun performance 2025-04-26 21:29:56 +08:00
chen08209
be8cf039ee Support custom text scaling
Optimize the display of different text scale

Optimize windows setup experience
2025-04-25 17:38:26 +08:00
chen08209
30ee6889ab Optimize android tv experience
Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-04-22 16:05:24 +08:00
39 changed files with 366 additions and 874 deletions

View File

@@ -1,43 +1,3 @@
## v0.8.84
- Fix windows service verify issues
- Update changelog
## v0.8.83
- Add windows server mode start process verify
- Add linux deb dependencies
- Add backup recovery strategy select
- Support custom text scaling
- Optimize the display of different text scale
- Optimize windows setup experience
- Optimize startTun performance
- Optimize android tv experience
- Optimize default option
- Optimize computed text size
- Optimize hyperOS freeform window
- Add developer mode
- Update core
- Optimize more details
- Add issues template
- Update changelog
## v0.8.82
- Optimize android vpn performance

View File

@@ -12,7 +12,7 @@ export 'iterable.dart';
export 'keyboard.dart';
export 'launch.dart';
export 'link.dart';
export 'fixed.dart';
export 'list.dart';
export 'lock.dart';
export 'measure.dart';
export 'navigation.dart';

View File

@@ -76,24 +76,22 @@ const viewModeColumnsMap = {
ViewMode.desktop: [4, 3],
};
const defaultPrimaryColor = 0XFFD8C0C3;
const defaultPrimaryColor = 0xFF795548;
double getWidgetHeight(num lines) {
return max(lines * 84 + (lines - 1) * 16, 0).ap;
}
const maxLength = 150;
final mainIsolate = "FlClashMainIsolate";
final serviceIsolate = "FlClashServiceIsolate";
const defaultPrimaryColors = [
0xFF795548,
defaultPrimaryColor,
0xFF03A9F4,
0xFFFFFF00,
0XFFBBC9CC,
0XFFABD397,
defaultPrimaryColor,
0XFFD8C0C3,
0XFF665390,
];

View File

@@ -1,79 +0,0 @@
import 'iterable.dart';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list})
: _list = (list ?? [])..truncate(maxLength);
add(T item) {
_list.add(item);
_list.truncate(maxLength);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxLength;
late Map<K, V> _map;
FixedMap(this.maxLength, {Map<K, V>? map}) {
_map = map ?? {};
}
updateCacheValue(K key, V Function() callback) {
final realValue = _map.updateCacheValue(
key,
callback,
);
_adjustMap();
return realValue;
}
clear() {
_map.clear();
}
updateMaxLength(int size) {
maxLength = size;
_adjustMap();
}
updateMap(Map<K, V> map) {
_map = map;
_adjustMap();
}
_adjustMap() {
if (_map.length > maxLength) {
_map = Map.fromEntries(
map.entries.toList()..truncate(maxLength),
);
}
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}

View File

@@ -38,43 +38,6 @@ extension IterableExt<T> on Iterable<T> {
count++;
}
}
Iterable<T> takeLast({int count = 50}) {
if (count <= 0) return Iterable.empty();
return count >= length ? this : toList().skip(length - count);
}
}
extension ListExt<T> on List<T> {
void truncate(int maxLength) {
assert(maxLength > 0);
if (length > maxLength) {
removeRange(0, length - maxLength);
}
}
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}
extension DoubleListExt on List<double> {
@@ -104,9 +67,9 @@ extension DoubleListExt on List<double> {
}
extension MapExt<K, V> on Map<K, V> {
updateCacheValue(K key, V Function() callback) {
getCacheValue(K key, V defaultValue) {
if (this[key] == null) {
this[key] = callback();
this[key] = defaultValue;
}
return this[key];
}

93
lib/common/list.dart Normal file
View File

@@ -0,0 +1,93 @@
import 'dart:collection';
class FixedList<T> {
final int maxLength;
final List<T> _list;
FixedList(this.maxLength, {List<T>? list}) : _list = list ?? [];
add(T item) {
if (_list.length == maxLength) {
_list.removeAt(0);
}
_list.add(item);
}
clear() {
_list.clear();
}
List<T> get list => List.unmodifiable(_list);
int get length => _list.length;
T operator [](int index) => _list[index];
FixedList<T> copyWith() {
return FixedList(
maxLength,
list: _list,
);
}
}
class FixedMap<K, V> {
int maxSize;
final Map<K, V> _map = {};
final Queue<K> _queue = Queue<K>();
FixedMap(this.maxSize);
put(K key, V value) {
if (_map.length == maxSize) {
final oldestKey = _queue.removeFirst();
_map.remove(oldestKey);
}
_map[key] = value;
_queue.add(key);
return value;
}
clear() {
_map.clear();
_queue.clear();
}
updateMaxSize(int size){
maxSize = size;
}
V? get(K key) => _map[key];
bool containsKey(K key) => _map.containsKey(key);
int get length => _map.length;
Map<K, V> get map => Map.unmodifiable(_map);
}
extension ListExtension<T> on List<T> {
List<T> intersection(List<T> list) {
return where((item) => list.contains(item)).toList();
}
List<List<T>> batch(int maxConcurrent) {
final batches = (length / maxConcurrent).ceil();
final List<List<T>> res = [];
for (int i = 0; i < batches; i++) {
if (i != batches - 1) {
res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
} else {
res.add(sublist(i * maxConcurrent, length));
}
}
return res;
}
List<T> safeSublist(int start) {
if (start <= 0) return this;
if (start > length) return [];
return sublist(start);
}
}

View File

@@ -32,9 +32,9 @@ class Measure {
}
double get bodyMediumHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"bodyMediumHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.bodyMedium,
@@ -44,9 +44,9 @@ class Measure {
}
double get bodyLargeHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"bodyLargeHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.bodyLarge,
@@ -56,9 +56,9 @@ class Measure {
}
double get bodySmallHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"bodySmallHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.bodySmall,
@@ -68,9 +68,9 @@ class Measure {
}
double get labelSmallHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"labelSmallHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.labelSmall,
@@ -80,9 +80,9 @@ class Measure {
}
double get labelMediumHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"labelMediumHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.labelMedium,
@@ -92,9 +92,9 @@ class Measure {
}
double get titleLargeHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"titleLargeHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.titleLarge,
@@ -104,9 +104,9 @@ class Measure {
}
double get titleMediumHeight {
return _measureMap.updateCacheValue(
return _measureMap.getCacheValue(
"titleMediumHeight",
() => computeTextSize(
computeTextSize(
Text(
"X",
style: context.textTheme.titleMedium,

View File

@@ -14,6 +14,7 @@ class Navigation {
const NavigationItem(
icon: Icon(Icons.space_dashboard),
label: PageLabel.dashboard,
keep: false,
fragment: DashboardFragment(
key: GlobalObjectKey(PageLabel.dashboard),
),

View File

@@ -12,35 +12,32 @@ class CommonTheme {
) : _colorMap = {};
Color get darkenSecondaryContainer {
return _colorMap.updateCacheValue(
return _colorMap.getCacheValue(
"darkenSecondaryContainer",
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1),
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1),
);
}
Color get darkenSecondaryContainerLighter {
return _colorMap.updateCacheValue(
return _colorMap.getCacheValue(
"darkenSecondaryContainerLighter",
() => context.colorScheme.secondaryContainer
context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.1)
.opacity60,
);
}
Color get darken2SecondaryContainer {
return _colorMap.updateCacheValue(
return _colorMap.getCacheValue(
"darken2SecondaryContainer",
() => context.colorScheme.secondaryContainer
.blendDarken(context, factor: 0.2),
context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2),
);
}
Color get darken3PrimaryContainer {
return _colorMap.updateCacheValue(
return _colorMap.getCacheValue(
"darken3PrimaryContainer",
() => context.colorScheme.primaryContainer
.blendDarken(context, factor: 0.3),
context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3),
);
}
}

View File

@@ -312,10 +312,6 @@ class AppController {
handleChangeProfile() {
_ref.read(delayDataSourceProvider.notifier).value = {};
applyProfile();
_ref.read(logsProvider.notifier).value = FixedList(500);
_ref.read(requestsProvider.notifier).value = FixedList(500);
globalState.cacheHeightMap = {};
globalState.cacheScrollPosition = {};
}
updateBrightness(Brightness brightness) {

View File

@@ -472,13 +472,8 @@ enum RuleTarget {
REJECT,
}
enum RecoveryStrategy {
compatible,
override,
}
enum CacheTag {
logs,
rules,
requests,
}

View File

@@ -51,7 +51,6 @@ class AboutFragment extends StatelessWidget {
title: Text(appLocalizations.contactMe),
onTap: () {
globalState.showMessage(
title: appLocalizations.contactMe,
message: TextSpan(text: "chen08209@gmail.com"),
);
},

View File

@@ -20,13 +20,12 @@ class RequestsFragment extends ConsumerStatefulWidget {
class _RequestsFragmentState extends ConsumerState<RequestsFragment>
with PageMixin {
final _requestsStateNotifier = ValueNotifier<ConnectionsState>(
const ConnectionsState(loading: true),
);
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
final _requestsStateNotifier =
ValueNotifier<ConnectionsState>(const ConnectionsState());
List<Connection> _requests = [];
final _tag = CacheTag.requests;
final _cacheKey = ValueKey("requests_list");
late ScrollController _scrollController;
bool _isLoad = false;
double _currentMaxWidth = 0;
@@ -46,13 +45,12 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
@override
void initState() {
super.initState();
final preOffset = globalState.cacheScrollPosition[_tag] ?? -1;
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: preOffset > 0 ? preOffset : double.maxFinite,
);
_requests = globalState.appState.requests.list;
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
connections: _requests,
connections: globalState.appState.requests.list,
);
ref.listenManual(
isCurrentPageProvider(
@@ -99,7 +97,14 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
final lines = (chainSize.height / baseHeight).round();
final computerHeight =
size.height + chainSize.height + 24 + 24 * (lines - 1);
return computerHeight + 8 + 32 + globalState.measure.bodyMediumHeight;
return computerHeight;
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_key.currentState?.clearCache();
}
}
@override
@@ -127,42 +132,6 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
}, duration: commonDuration);
}
_preLoad() {
if (_isLoad == true) {
return;
}
_isLoad = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final isMobileView = ref.read(isMobileViewProvider);
if (isMobileView) {
await Future.delayed(Duration(milliseconds: 300));
}
final parts = _requests.batch(10);
globalState.cacheHeightMap[_tag] ??= FixedMap(
_requests.length,
);
for (int i = 0; i < parts.length; i++) {
final part = parts[i];
await Future(
() {
for (final request in part) {
globalState.cacheHeightMap[_tag]?.updateCacheValue(
request.id,
() => _calcCacheHeight(request),
);
}
},
);
}
_requestsStateNotifier.value = _requestsStateNotifier.value.copyWith(
loading: false,
);
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
@@ -176,14 +145,13 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
Platform.isAndroid,
),
);
_currentMaxWidth = constraints.maxWidth - 40 - (value ? 60 : 0);
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
return child!;
},
child: TextScaleNotification(
child: ValueListenableBuilder<ConnectionsState>(
valueListenable: _requestsStateNotifier,
builder: (_, state, __) {
_preLoad();
final connections = state.list;
if (connections.isEmpty) {
return NullStatus(
@@ -206,56 +174,51 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
),
)
.toList();
final content = connections.isEmpty
? NullStatus(
label: appLocalizations.nullRequestsDesc,
)
: Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox(
controller: _scrollController,
tag: _tag,
dataSource: connections,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
tag: _tag,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemExtentBuilder: (index) {
if (index.isOdd) {
return 0;
}
return _calcCacheHeight(
connections[index ~/ 2]);
},
itemBuilder: (_, index) {
return items[index];
},
itemCount: items.length,
keyBuilder: (int index) {
if (index.isOdd) {
return "divider";
}
return connections[index ~/ 2].id;
},
),
),
),
);
return FadeBox(
child: state.loading
? Center(
child: CircularProgressIndicator(),
)
: content,
return Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox(
controller: _scrollController,
cacheKey: _cacheKey,
dataSource: connections,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
key: _key,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemExtentBuilder: (index) {
final widget = items[index];
if (widget.runtimeType == Divider) {
return 0;
}
final measure = globalState.measure;
final bodyMediumHeight = measure.bodyMediumHeight;
final connection = connections[(index / 2).floor()];
final height = _calcCacheHeight(connection);
return height + bodyMediumHeight + 32;
},
itemBuilder: (_, index) {
return items[index];
},
itemCount: items.length,
keyBuilder: (int index) {
final widget = items[index];
if (widget.runtimeType == Divider) {
return "divider";
}
final connection = connections[(index / 2).floor()];
return connection.id;
},
),
),
),
);
},
),
onNotification: (_) {
globalState.cacheHeightMap[_tag]?.clear();
_key.currentState?.clearCache();
},
),
);

View File

@@ -8,12 +8,10 @@ import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/app.dart';
class DeveloperView extends ConsumerWidget {
const DeveloperView({super.key});
Widget _getDeveloperList(BuildContext context, WidgetRef ref) {
Widget _getDeveloperList(BuildContext context) {
return generateSectionV2(
title: appLocalizations.options,
items: [
@@ -29,29 +27,10 @@ class DeveloperView extends ConsumerWidget {
title: Text(appLocalizations.logsTest),
onTap: () {
for (int i = 0; i < 1000; i++) {
ref.read(requestsProvider.notifier).addRequest(Connection(
id: utils.id,
start: DateTime.now(),
metadata: Metadata(
uid: i * i,
network: utils.generateRandomString(
maxLength: 1000,
minLength: 20,
),
sourceIP: '',
sourcePort: '',
destinationIP: '',
destinationPort: '',
host: '',
process: '',
remoteDestination: "",
),
chains: ["chains"],
));
globalState.appController.addLog(
Log.app(
utils.generateRandomString(
maxLength: 200,
maxLength: 1000,
minLength: 20,
),
),
@@ -112,7 +91,7 @@ class DeveloperView extends ConsumerWidget {
SizedBox(
height: 16,
),
_getDeveloperList(context, ref)
_getDeveloperList(context)
],
),
);

View File

@@ -1,5 +1,3 @@
import 'dart:math';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
@@ -18,27 +16,25 @@ class LogsFragment extends ConsumerStatefulWidget {
}
class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
final _logsStateNotifier = ValueNotifier<LogsState>(
LogsState(loading: true),
);
final _logsStateNotifier = ValueNotifier<LogsState>(LogsState());
final _cacheKey = ValueKey("logs_list");
late ScrollController _scrollController;
double _currentMaxWidth = 0;
final _tag = CacheTag.rules;
bool _isLoad = false;
final GlobalKey<CacheItemExtentListViewState> _key = GlobalKey();
List<Log> _logs = [];
@override
void initState() {
super.initState();
final position = globalState.cacheScrollPosition[_tag] ?? -1;
final preOffset = globalState.cacheScrollPosition[_cacheKey] ?? -1;
_scrollController = ScrollController(
initialScrollOffset: position > 0 ? position : double.maxFinite,
initialScrollOffset: preOffset > 0
? preOffset
: double.maxFinite,
);
_logs = globalState.appState.logs.list;
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
logs: globalState.appState.logs.list,
);
ref.listenManual(
isCurrentPageProvider(
@@ -99,6 +95,13 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
super.dispose();
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_key.currentState?.clearCache();
}
}
_handleExport() async {
final commonScaffoldState = context.commonScaffoldState;
final res = await commonScaffoldState?.loadingRun<bool>(
@@ -122,12 +125,12 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
.computeTextSize(
Text(
log.payload,
style: context.textTheme.bodyLarge,
style: globalState.appController.context.textTheme.bodyLarge,
),
maxWidth: _currentMaxWidth,
)
.height;
return height + bodySmallHeight + 8 + bodyMediumHeight + 40 + 8;
return height + bodySmallHeight + 8 + bodyMediumHeight + 40;
}
updateLogsThrottler() {
@@ -140,122 +143,86 @@ class _LogsFragmentState extends ConsumerState<LogsFragment> with PageMixin {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
);
}
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
logs: _logs,
);
});
}, duration: commonDuration);
}
_preLoad() {
if (_isLoad == true) {
return;
}
_isLoad = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) {
return;
}
final isMobileView = ref.read(isMobileViewProvider);
if (isMobileView) {
await Future.delayed(Duration(milliseconds: 300));
}
final parts = _logs.batch(10);
globalState.cacheHeightMap[_tag] ??= FixedMap(
_logs.length,
);
for (int i = 0; i < parts.length; i++) {
final part = parts[i];
await Future(
() {
for (final log in part) {
globalState.cacheHeightMap[_tag]?.updateCacheValue(
log.payload,
() => _getItemHeight(log),
);
}
},
);
}
_logsStateNotifier.value = _logsStateNotifier.value.copyWith(
loading: false,
);
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
_currentMaxWidth = constraints.maxWidth - 40;
return ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier,
builder: (_, state, __) {
_preLoad();
final logs = state.list;
final items = logs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime),
log: log,
onClick: (value) {
context.commonScaffoldState?.addKeyword(value);
},
),
)
.separated(
const Divider(
height: 0,
),
)
.toList();
final content = logs.isEmpty
? NullStatus(
label: appLocalizations.nullLogsDesc,
)
: Align(
alignment: Alignment.topCenter,
child: CommonScrollBar(
controller: _scrollController,
child: ScrollToEndBox(
controller: _scrollController,
tag: _tag,
dataSource: logs,
child: CacheItemExtentListView(
tag: _tag,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemBuilder: (_, index) {
return items[index];
},
itemExtentBuilder: (index) {
if (index.isOdd) {
return 0;
}
return _getItemHeight(logs[index ~/ 2]);
},
itemCount: items.length,
keyBuilder: (int index) {
if (index.isOdd) {
return "divider";
}
return logs[index ~/ 2].payload;
},
),
),
_handleTryClearCache(constraints.maxWidth - 40);
return TextScaleNotification(
child: ValueListenableBuilder<LogsState>(
valueListenable: _logsStateNotifier,
builder: (_, state, __) {
final logs = state.list;
if (logs.isEmpty) {
return NullStatus(
label: appLocalizations.nullLogsDesc,
);
}
final items = logs
.map<Widget>(
(log) => LogItem(
key: Key(log.dateTime),
log: log,
onClick: (value) {
context.commonScaffoldState?.addKeyword(value);
},
),
);
return FadeBox(
child: state.loading
? Center(
child: CircularProgressIndicator(),
)
: content,
);
)
.separated(
const Divider(
height: 0,
),
)
.toList();
return Align(
alignment: Alignment.topCenter,
child: ScrollToEndBox<Log>(
controller: _scrollController,
cacheKey: _cacheKey,
dataSource: logs,
child: CommonScrollBar(
controller: _scrollController,
child: CacheItemExtentListView(
key: _key,
reverse: true,
shrinkWrap: true,
physics: NextClampingScrollPhysics(),
controller: _scrollController,
itemBuilder: (_, index) {
return items[index];
},
itemExtentBuilder: (index) {
final item = items[index];
if (item.runtimeType == Divider) {
return 0;
}
final log = logs[(index / 2).floor()];
return _getItemHeight(log);
},
itemCount: items.length,
keyBuilder: (int index) {
final item = items[index];
if (item.runtimeType == Divider) {
return "divider";
}
final log = logs[(index / 2).floor()];
return log.payload;
},
),
),
),
);
},
),
onNotification: (_) {
_key.currentState?.clearCache();
},
);
},

View File

@@ -23,6 +23,7 @@ class OverrideProfile extends StatefulWidget {
}
class _OverrideProfileState extends State<OverrideProfile> {
final GlobalKey<CacheItemExtentListViewState> _ruleListKey = GlobalKey();
final _controller = ScrollController();
double _currentMaxWidth = 0;
@@ -85,6 +86,13 @@ class _OverrideProfileState extends State<OverrideProfile> {
);
}
_handleTryClearCache(double maxWidth) {
if (_currentMaxWidth != maxWidth) {
_currentMaxWidth = maxWidth;
_ruleListKey.currentState?.clearCache();
}
}
_buildContent() {
return Consumer(
builder: (_, ref, child) {
@@ -108,7 +116,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
},
child: LayoutBuilder(
builder: (_, constraints) {
_currentMaxWidth = constraints.maxWidth - 104;
_handleTryClearCache(constraints.maxWidth - 104);
return CommonAutoHiddenScrollBar(
controller: _controller,
child: CustomScrollView(
@@ -140,6 +148,7 @@ class _OverrideProfileState extends State<OverrideProfile> {
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0),
sliver: RuleContent(
maxWidth: _currentMaxWidth,
ruleListKey: _ruleListKey,
),
),
SliverToBoxAdapter(
@@ -440,10 +449,12 @@ class RuleTitle extends ConsumerWidget {
}
class RuleContent extends ConsumerWidget {
final Key ruleListKey;
final double maxWidth;
const RuleContent({
super.key,
required this.ruleListKey,
required this.maxWidth,
});
@@ -591,7 +602,7 @@ class RuleContent extends ConsumerWidget {
);
}
return CacheItemExtentSliverReorderableList(
tag: CacheTag.rules,
key: ruleListKey,
itemBuilder: (context, index) {
final rule = rules[index];
return GestureDetector(

View File

@@ -370,7 +370,7 @@ class ProfileItem extends StatelessWidget {
),
),
title: Container(
padding: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,

View File

@@ -21,14 +21,13 @@ import 'common/common.dart';
Future<void> main() async {
globalState.isService = false;
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
commonPrint.log(details.stack.toString());
};
final version = await system.version;
await clashCore.preload();
await globalState.initApp(version);
await android?.init();
await window?.init(version);
globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable';
globalState.coreSHA256 = const String.fromEnvironment("CORE_SHA256");
HttpOverrides.global = FlClashHttpOverrides();
runApp(ProviderScope(
child: const Application(),

View File

@@ -1,13 +1,11 @@
import 'dart:async';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AppStateManager extends ConsumerStatefulWidget {
class AppStateManager extends StatefulWidget {
final Widget child;
const AppStateManager({
@@ -16,22 +14,15 @@ class AppStateManager extends ConsumerStatefulWidget {
});
@override
ConsumerState<AppStateManager> createState() => _AppStateManagerState();
State<AppStateManager> createState() => _AppStateManagerState();
}
class _AppStateManagerState extends ConsumerState<AppStateManager>
class _AppStateManagerState extends State<AppStateManager>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
ref.listenManual(layoutChangeProvider, (prev, next) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (prev != next) {
globalState.cacheHeightMap = {};
}
});
});
}
@override

View File

@@ -71,7 +71,7 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onLog(Log log) {
ref.read(logsProvider.notifier).addLog(log);
ref.watch(logsProvider.notifier).addLog(log);
if (log.logLevel == LogLevel.error) {
globalState.showNotifier(log.payload);
}
@@ -80,13 +80,13 @@ class _ClashContainerState extends ConsumerState<ClashManager>
@override
void onRequest(Connection connection) async {
ref.read(requestsProvider.notifier).addRequest(connection);
ref.watch(requestsProvider.notifier).addRequest(connection);
super.onRequest(connection);
}
@override
Future<void> onLoaded(String providerName) async {
ref.read(providersProvider.notifier).setProvider(
ref.watch(providersProvider.notifier).setProvider(
await clashCore.getExternalProvider(
providerName,
),

View File

@@ -148,7 +148,7 @@ class Tun with _$Tun {
@Default(false) bool enable,
@Default(appName) String device,
@JsonKey(name: "auto-route") @Default(false) bool autoRoute,
@Default(TunStack.mixed) TunStack stack,
@Default(TunStack.gvisor) TunStack stack,
@JsonKey(name: "dns-hijack") @Default(["any:53"]) List<String> dnsHijack,
@JsonKey(name: "route-address") @Default([]) List<String> routeAddress,
}) = _Tun;

View File

@@ -120,7 +120,6 @@ class LogsState with _$LogsState {
@Default([]) List<Log> logs,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _LogsState;
}
@@ -144,7 +143,6 @@ class ConnectionsState with _$ConnectionsState {
@Default([]) List<Connection> connections,
@Default([]) List<String> keywords,
@Default("") String query,
@Default(false) bool loading,
}) = _ConnectionsState;
}
@@ -514,17 +512,3 @@ class PopupMenuItemData {
final IconData? icon;
final PopupMenuItemType? type;
}
@freezed
class TextPainterParams with _$TextPainterParams {
const factory TextPainterParams({
required String? text,
required double? fontSize,
required double textScaleFactor,
@Default(double.infinity) double maxWidth,
int? maxLines,
}) = _TextPainterParams;
factory TextPainterParams.fromJson(Map<String, Object?> json) =>
_$TextPainterParamsFromJson(json);
}

View File

@@ -75,7 +75,7 @@ class AppSettingProps with _$AppSettingProps {
@Default(false) bool autoLaunch,
@Default(false) bool silentLaunch,
@Default(false) bool autoRun,
@Default(false) bool openLogs,
@Default(true) bool openLogs,
@Default(true) bool closeConnections,
@Default(defaultTestUrl) String testUrl,
@Default(true) bool isAnimateToPage,
@@ -192,7 +192,7 @@ class ThemeProps with _$ThemeProps {
int? primaryColor,
@Default(defaultPrimaryColors) List<int> primaryColors,
@Default(ThemeMode.dark) ThemeMode themeMode,
@Default(DynamicSchemeVariant.content) DynamicSchemeVariant schemeVariant,
@Default(DynamicSchemeVariant.tonalSpot) DynamicSchemeVariant schemeVariant,
@Default(false) bool pureBlack,
@Default(TextScale()) TextScale textScale,
}) = _ThemeProps;

View File

@@ -810,7 +810,7 @@ class _$TunImpl implements _Tun {
{this.enable = false,
this.device = appName,
@JsonKey(name: "auto-route") this.autoRoute = false,
this.stack = TunStack.mixed,
this.stack = TunStack.gvisor,
@JsonKey(name: "dns-hijack")
final List<String> dnsHijack = const ["any:53"],
@JsonKey(name: "route-address")

View File

@@ -68,7 +68,7 @@ _$TunImpl _$$TunImplFromJson(Map<String, dynamic> json) => _$TunImpl(
device: json['device'] as String? ?? appName,
autoRoute: json['auto-route'] as bool? ?? false,
stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ??
TunStack.mixed,
TunStack.gvisor,
dnsHijack: (json['dns-hijack'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??

View File

@@ -1320,7 +1320,6 @@ mixin _$LogsState {
List<Log> get logs => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
String get query => throw _privateConstructorUsedError;
bool get loading => throw _privateConstructorUsedError;
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1334,8 +1333,7 @@ abstract class $LogsStateCopyWith<$Res> {
factory $LogsStateCopyWith(LogsState value, $Res Function(LogsState) then) =
_$LogsStateCopyWithImpl<$Res, LogsState>;
@useResult
$Res call(
{List<Log> logs, List<String> keywords, String query, bool loading});
$Res call({List<Log> logs, List<String> keywords, String query});
}
/// @nodoc
@@ -1356,7 +1354,6 @@ class _$LogsStateCopyWithImpl<$Res, $Val extends LogsState>
Object? logs = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_value.copyWith(
logs: null == logs
@@ -1371,10 +1368,6 @@ class _$LogsStateCopyWithImpl<$Res, $Val extends LogsState>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -1387,8 +1380,7 @@ abstract class _$$LogsStateImplCopyWith<$Res>
__$$LogsStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{List<Log> logs, List<String> keywords, String query, bool loading});
$Res call({List<Log> logs, List<String> keywords, String query});
}
/// @nodoc
@@ -1407,7 +1399,6 @@ class __$$LogsStateImplCopyWithImpl<$Res>
Object? logs = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_$LogsStateImpl(
logs: null == logs
@@ -1422,10 +1413,6 @@ class __$$LogsStateImplCopyWithImpl<$Res>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -1436,8 +1423,7 @@ class _$LogsStateImpl implements _LogsState {
const _$LogsStateImpl(
{final List<Log> logs = const [],
final List<String> keywords = const [],
this.query = "",
this.loading = false})
this.query = ""})
: _logs = logs,
_keywords = keywords;
@@ -1462,13 +1448,10 @@ class _$LogsStateImpl implements _LogsState {
@override
@JsonKey()
final String query;
@override
@JsonKey()
final bool loading;
@override
String toString() {
return 'LogsState(logs: $logs, keywords: $keywords, query: $query, loading: $loading)';
return 'LogsState(logs: $logs, keywords: $keywords, query: $query)';
}
@override
@@ -1478,8 +1461,7 @@ class _$LogsStateImpl implements _LogsState {
other is _$LogsStateImpl &&
const DeepCollectionEquality().equals(other._logs, _logs) &&
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
(identical(other.query, query) || other.query == query) &&
(identical(other.loading, loading) || other.loading == loading));
(identical(other.query, query) || other.query == query));
}
@override
@@ -1487,8 +1469,7 @@ class _$LogsStateImpl implements _LogsState {
runtimeType,
const DeepCollectionEquality().hash(_logs),
const DeepCollectionEquality().hash(_keywords),
query,
loading);
query);
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1503,8 +1484,7 @@ abstract class _LogsState implements LogsState {
const factory _LogsState(
{final List<Log> logs,
final List<String> keywords,
final String query,
final bool loading}) = _$LogsStateImpl;
final String query}) = _$LogsStateImpl;
@override
List<Log> get logs;
@@ -1512,8 +1492,6 @@ abstract class _LogsState implements LogsState {
List<String> get keywords;
@override
String get query;
@override
bool get loading;
/// Create a copy of LogsState
/// with the given fields replaced by the non-null parameter values.
@@ -1528,7 +1506,6 @@ mixin _$ConnectionsState {
List<Connection> get connections => throw _privateConstructorUsedError;
List<String> get keywords => throw _privateConstructorUsedError;
String get query => throw _privateConstructorUsedError;
bool get loading => throw _privateConstructorUsedError;
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -1544,10 +1521,7 @@ abstract class $ConnectionsStateCopyWith<$Res> {
_$ConnectionsStateCopyWithImpl<$Res, ConnectionsState>;
@useResult
$Res call(
{List<Connection> connections,
List<String> keywords,
String query,
bool loading});
{List<Connection> connections, List<String> keywords, String query});
}
/// @nodoc
@@ -1568,7 +1542,6 @@ class _$ConnectionsStateCopyWithImpl<$Res, $Val extends ConnectionsState>
Object? connections = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_value.copyWith(
connections: null == connections
@@ -1583,10 +1556,6 @@ class _$ConnectionsStateCopyWithImpl<$Res, $Val extends ConnectionsState>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -1600,10 +1569,7 @@ abstract class _$$ConnectionsStateImplCopyWith<$Res>
@override
@useResult
$Res call(
{List<Connection> connections,
List<String> keywords,
String query,
bool loading});
{List<Connection> connections, List<String> keywords, String query});
}
/// @nodoc
@@ -1622,7 +1588,6 @@ class __$$ConnectionsStateImplCopyWithImpl<$Res>
Object? connections = null,
Object? keywords = null,
Object? query = null,
Object? loading = null,
}) {
return _then(_$ConnectionsStateImpl(
connections: null == connections
@@ -1637,10 +1602,6 @@ class __$$ConnectionsStateImplCopyWithImpl<$Res>
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String,
loading: null == loading
? _value.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -1651,8 +1612,7 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
const _$ConnectionsStateImpl(
{final List<Connection> connections = const [],
final List<String> keywords = const [],
this.query = "",
this.loading = false})
this.query = ""})
: _connections = connections,
_keywords = keywords;
@@ -1677,13 +1637,10 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
@override
@JsonKey()
final String query;
@override
@JsonKey()
final bool loading;
@override
String toString() {
return 'ConnectionsState(connections: $connections, keywords: $keywords, query: $query, loading: $loading)';
return 'ConnectionsState(connections: $connections, keywords: $keywords, query: $query)';
}
@override
@@ -1694,8 +1651,7 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
const DeepCollectionEquality()
.equals(other._connections, _connections) &&
const DeepCollectionEquality().equals(other._keywords, _keywords) &&
(identical(other.query, query) || other.query == query) &&
(identical(other.loading, loading) || other.loading == loading));
(identical(other.query, query) || other.query == query));
}
@override
@@ -1703,8 +1659,7 @@ class _$ConnectionsStateImpl implements _ConnectionsState {
runtimeType,
const DeepCollectionEquality().hash(_connections),
const DeepCollectionEquality().hash(_keywords),
query,
loading);
query);
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -1720,8 +1675,7 @@ abstract class _ConnectionsState implements ConnectionsState {
const factory _ConnectionsState(
{final List<Connection> connections,
final List<String> keywords,
final String query,
final bool loading}) = _$ConnectionsStateImpl;
final String query}) = _$ConnectionsStateImpl;
@override
List<Connection> get connections;
@@ -1729,8 +1683,6 @@ abstract class _ConnectionsState implements ConnectionsState {
List<String> get keywords;
@override
String get query;
@override
bool get loading;
/// Create a copy of ConnectionsState
/// with the given fields replaced by the non-null parameter values.
@@ -3226,243 +3178,3 @@ abstract class _Field implements Field {
_$$FieldImplCopyWith<_$FieldImpl> get copyWith =>
throw _privateConstructorUsedError;
}
TextPainterParams _$TextPainterParamsFromJson(Map<String, dynamic> json) {
return _TextPainterParams.fromJson(json);
}
/// @nodoc
mixin _$TextPainterParams {
String? get text => throw _privateConstructorUsedError;
double? get fontSize => throw _privateConstructorUsedError;
double get textScaleFactor => throw _privateConstructorUsedError;
double get maxWidth => throw _privateConstructorUsedError;
int? get maxLines => throw _privateConstructorUsedError;
/// Serializes this TextPainterParams to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TextPainterParamsCopyWith<TextPainterParams> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TextPainterParamsCopyWith<$Res> {
factory $TextPainterParamsCopyWith(
TextPainterParams value, $Res Function(TextPainterParams) then) =
_$TextPainterParamsCopyWithImpl<$Res, TextPainterParams>;
@useResult
$Res call(
{String? text,
double? fontSize,
double textScaleFactor,
double maxWidth,
int? maxLines});
}
/// @nodoc
class _$TextPainterParamsCopyWithImpl<$Res, $Val extends TextPainterParams>
implements $TextPainterParamsCopyWith<$Res> {
_$TextPainterParamsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = freezed,
Object? fontSize = freezed,
Object? textScaleFactor = null,
Object? maxWidth = null,
Object? maxLines = freezed,
}) {
return _then(_value.copyWith(
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
fontSize: freezed == fontSize
? _value.fontSize
: fontSize // ignore: cast_nullable_to_non_nullable
as double?,
textScaleFactor: null == textScaleFactor
? _value.textScaleFactor
: textScaleFactor // ignore: cast_nullable_to_non_nullable
as double,
maxWidth: null == maxWidth
? _value.maxWidth
: maxWidth // ignore: cast_nullable_to_non_nullable
as double,
maxLines: freezed == maxLines
? _value.maxLines
: maxLines // ignore: cast_nullable_to_non_nullable
as int?,
) as $Val);
}
}
/// @nodoc
abstract class _$$TextPainterParamsImplCopyWith<$Res>
implements $TextPainterParamsCopyWith<$Res> {
factory _$$TextPainterParamsImplCopyWith(_$TextPainterParamsImpl value,
$Res Function(_$TextPainterParamsImpl) then) =
__$$TextPainterParamsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? text,
double? fontSize,
double textScaleFactor,
double maxWidth,
int? maxLines});
}
/// @nodoc
class __$$TextPainterParamsImplCopyWithImpl<$Res>
extends _$TextPainterParamsCopyWithImpl<$Res, _$TextPainterParamsImpl>
implements _$$TextPainterParamsImplCopyWith<$Res> {
__$$TextPainterParamsImplCopyWithImpl(_$TextPainterParamsImpl _value,
$Res Function(_$TextPainterParamsImpl) _then)
: super(_value, _then);
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = freezed,
Object? fontSize = freezed,
Object? textScaleFactor = null,
Object? maxWidth = null,
Object? maxLines = freezed,
}) {
return _then(_$TextPainterParamsImpl(
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
fontSize: freezed == fontSize
? _value.fontSize
: fontSize // ignore: cast_nullable_to_non_nullable
as double?,
textScaleFactor: null == textScaleFactor
? _value.textScaleFactor
: textScaleFactor // ignore: cast_nullable_to_non_nullable
as double,
maxWidth: null == maxWidth
? _value.maxWidth
: maxWidth // ignore: cast_nullable_to_non_nullable
as double,
maxLines: freezed == maxLines
? _value.maxLines
: maxLines // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TextPainterParamsImpl implements _TextPainterParams {
const _$TextPainterParamsImpl(
{required this.text,
required this.fontSize,
required this.textScaleFactor,
this.maxWidth = double.infinity,
this.maxLines});
factory _$TextPainterParamsImpl.fromJson(Map<String, dynamic> json) =>
_$$TextPainterParamsImplFromJson(json);
@override
final String? text;
@override
final double? fontSize;
@override
final double textScaleFactor;
@override
@JsonKey()
final double maxWidth;
@override
final int? maxLines;
@override
String toString() {
return 'TextPainterParams(text: $text, fontSize: $fontSize, textScaleFactor: $textScaleFactor, maxWidth: $maxWidth, maxLines: $maxLines)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TextPainterParamsImpl &&
(identical(other.text, text) || other.text == text) &&
(identical(other.fontSize, fontSize) ||
other.fontSize == fontSize) &&
(identical(other.textScaleFactor, textScaleFactor) ||
other.textScaleFactor == textScaleFactor) &&
(identical(other.maxWidth, maxWidth) ||
other.maxWidth == maxWidth) &&
(identical(other.maxLines, maxLines) ||
other.maxLines == maxLines));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, text, fontSize, textScaleFactor, maxWidth, maxLines);
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TextPainterParamsImplCopyWith<_$TextPainterParamsImpl> get copyWith =>
__$$TextPainterParamsImplCopyWithImpl<_$TextPainterParamsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TextPainterParamsImplToJson(
this,
);
}
}
abstract class _TextPainterParams implements TextPainterParams {
const factory _TextPainterParams(
{required final String? text,
required final double? fontSize,
required final double textScaleFactor,
final double maxWidth,
final int? maxLines}) = _$TextPainterParamsImpl;
factory _TextPainterParams.fromJson(Map<String, dynamic> json) =
_$TextPainterParamsImpl.fromJson;
@override
String? get text;
@override
double? get fontSize;
@override
double get textScaleFactor;
@override
double get maxWidth;
@override
int? get maxLines;
/// Create a copy of TextPainterParams
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TextPainterParamsImplCopyWith<_$TextPainterParamsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -198,23 +198,3 @@ const _$KeyboardModifierEnumMap = {
KeyboardModifier.meta: 'meta',
KeyboardModifier.shift: 'shift',
};
_$TextPainterParamsImpl _$$TextPainterParamsImplFromJson(
Map<String, dynamic> json) =>
_$TextPainterParamsImpl(
text: json['text'] as String?,
fontSize: (json['fontSize'] as num?)?.toDouble(),
textScaleFactor: (json['textScaleFactor'] as num).toDouble(),
maxWidth: (json['maxWidth'] as num?)?.toDouble() ?? double.infinity,
maxLines: (json['maxLines'] as num?)?.toInt(),
);
Map<String, dynamic> _$$TextPainterParamsImplToJson(
_$TextPainterParamsImpl instance) =>
<String, dynamic>{
'text': instance.text,
'fontSize': instance.fontSize,
'textScaleFactor': instance.textScaleFactor,
'maxWidth': instance.maxWidth,
'maxLines': instance.maxLines,
};

View File

@@ -327,7 +327,7 @@ class _$AppSettingPropsImpl implements _AppSettingProps {
this.autoLaunch = false,
this.silentLaunch = false,
this.autoRun = false,
this.openLogs = false,
this.openLogs = true,
this.closeConnections = true,
this.testUrl = defaultTestUrl,
this.isAnimateToPage = true,
@@ -2127,7 +2127,7 @@ class _$ThemePropsImpl implements _ThemeProps {
{this.primaryColor,
final List<int> primaryColors = defaultPrimaryColors,
this.themeMode = ThemeMode.dark,
this.schemeVariant = DynamicSchemeVariant.content,
this.schemeVariant = DynamicSchemeVariant.tonalSpot,
this.pureBlack = false,
this.textScale = const TextScale()})
: _primaryColors = primaryColors;

View File

@@ -17,7 +17,7 @@ _$AppSettingPropsImpl _$$AppSettingPropsImplFromJson(
autoLaunch: json['autoLaunch'] as bool? ?? false,
silentLaunch: json['silentLaunch'] as bool? ?? false,
autoRun: json['autoRun'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? false,
openLogs: json['openLogs'] as bool? ?? true,
closeConnections: json['closeConnections'] as bool? ?? true,
testUrl: json['testUrl'] as String? ?? defaultTestUrl,
isAnimateToPage: json['isAnimateToPage'] as bool? ?? true,
@@ -257,7 +257,7 @@ _$ThemePropsImpl _$$ThemePropsImplFromJson(Map<String, dynamic> json) =>
ThemeMode.dark,
schemeVariant: $enumDecodeNullable(
_$DynamicSchemeVariantEnumMap, json['schemeVariant']) ??
DynamicSchemeVariant.content,
DynamicSchemeVariant.tonalSpot,
pureBlack: json['pureBlack'] as bool? ?? false,
textScale: json['textScale'] == null
? const TextScale()

View File

@@ -1765,22 +1765,6 @@ class _GetProfileOverrideDataProviderElement
String get profileId => (origin as GetProfileOverrideDataProvider).profileId;
}
String _$layoutChangeHash() => r'f25182e1dfaf3c70000404d7635bb1e1db09efbb';
/// See also [layoutChange].
@ProviderFor(layoutChange)
final layoutChangeProvider = AutoDisposeProvider<VM2?>.internal(
layoutChange,
name: r'layoutChangeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$layoutChangeHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef LayoutChangeRef = AutoDisposeProviderRef<VM2?>;
String _$genColorSchemeHash() => r'b18f15c938a8132ee4ed02cdfc02f3b9f01724e2';
/// See also [genColorScheme].

View File

@@ -510,17 +510,6 @@ OverrideData? getProfileOverrideData(Ref ref, String profileId) {
);
}
@riverpod
VM2? layoutChange(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final textScale =
ref.watch(themeSettingProvider.select((state) => state.textScale));
return VM2(
a: viewWidth,
b: textScale,
);
}
@riverpod
ColorScheme genColorScheme(
Ref ref,

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:animations/animations.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart';
@@ -21,8 +22,8 @@ typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState {
static GlobalState? _instance;
Map<CacheTag, double> cacheScrollPosition = {};
Map<CacheTag, FixedMap<String, double>> cacheHeightMap = {};
Map<Key, double> cacheScrollPosition = {};
Map<Key, FixedMap<String, double>> cacheHeightMap = {};
bool isService = false;
Timer? timer;
Timer? groupsUpdateTimer;
@@ -52,13 +53,11 @@ class GlobalState {
}
initApp(int version) async {
coreSHA256 = const String.fromEnvironment("CORE_SHA256");
isPre = const String.fromEnvironment("APP_ENV") != 'stable';
appState = AppState(
version: version,
viewSize: Size.zero,
requests: FixedList(maxLength),
logs: FixedList(maxLength),
requests: FixedList(500),
logs: FixedList(500),
traffics: FixedList(30),
totalTraffic: Traffic(),
);

View File

@@ -257,7 +257,7 @@ class ListItem<T> extends StatelessWidget {
leading: leading ?? this.leading,
horizontalTitleGap: horizontalTitleGap,
title: title,
minVerticalPadding: 12,
minTileHeight: 52,
subtitle: subtitle,
titleAlignment: tileTitleAlignment,
onTap: onTap,

View File

@@ -16,7 +16,7 @@ class TextScaleNotification extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder: (_, ref, child) {
builder: (_, ref, __) {
ref.listen(
themeSettingProvider.select((state) => state.textScale),
(prev, next) {
@@ -25,7 +25,7 @@ class TextScaleNotification extends StatelessWidget {
}
},
);
return child!;
return child;
},
child: child,
);

View File

@@ -1,6 +1,5 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -54,13 +53,13 @@ class ScrollToEndBox<T> extends StatefulWidget {
final ScrollController controller;
final List<T> dataSource;
final Widget child;
final CacheTag tag;
final Key cacheKey;
const ScrollToEndBox({
super.key,
required this.child,
required this.controller,
required this.tag,
required this.cacheKey,
required this.dataSource,
});
@@ -73,7 +72,8 @@ class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
_handleTryToEnd() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final double offset = globalState.cacheScrollPosition[widget.tag] ?? -1;
final double offset =
globalState.cacheScrollPosition[widget.cacheKey] ?? -1;
if (offset < 0) {
widget.controller.animateTo(
duration: kThemeAnimationDuration,
@@ -94,12 +94,13 @@ class _ScrollToEndBoxState<T> extends State<ScrollToEndBox<T>> {
@override
Widget build(BuildContext context) {
return NotificationListener<UserScrollNotification>(
return NotificationListener<ScrollNotification>(
onNotification: (details) {
globalState.cacheScrollPosition[widget.tag] =
double offset =
details.metrics.pixels == details.metrics.maxScrollExtent
? -1
: details.metrics.pixels;
globalState.cacheScrollPosition[widget.cacheKey] = offset;
return false;
},
child: widget.child,
@@ -116,7 +117,6 @@ class CacheItemExtentListView extends StatefulWidget {
final bool shrinkWrap;
final bool reverse;
final ScrollController controller;
final CacheTag tag;
const CacheItemExtentListView({
super.key,
@@ -128,7 +128,6 @@ class CacheItemExtentListView extends StatefulWidget {
required this.keyBuilder,
required this.itemCount,
required this.itemExtentBuilder,
required this.tag,
});
@override
@@ -137,19 +136,21 @@ class CacheItemExtentListView extends StatefulWidget {
}
class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
_updateCacheHeightMap();
_cacheHeightMap = FixedMap(widget.itemCount);
}
_updateCacheHeightMap() {
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(widget.itemCount);
globalState.cacheHeightMap[widget.tag] ??= FixedMap(widget.itemCount);
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
_cacheHeightMap.updateMaxSize(widget.itemCount);
return ListView.builder(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
@@ -158,14 +159,20 @@ class CacheItemExtentListViewState extends State<CacheItemExtentListView> {
shrinkWrap: widget.shrinkWrap,
controller: widget.controller,
itemExtentBuilder: (index, __) {
_updateCacheHeightMap();
return globalState.cacheHeightMap[widget.tag]?.updateCacheValue(
widget.keyBuilder(index),
() => widget.itemExtentBuilder(index),
);
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
);
}
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}
class CacheItemExtentSliverReorderableList extends StatefulWidget {
@@ -175,7 +182,6 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
final double Function(int index) itemExtentBuilder;
final ReorderCallback onReorder;
final ReorderItemProxyDecorator? proxyDecorator;
final CacheTag tag;
const CacheItemExtentSliverReorderableList({
super.key,
@@ -185,7 +191,6 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
required this.itemExtentBuilder,
required this.onReorder,
this.proxyDecorator,
required this.tag,
});
@override
@@ -195,24 +200,30 @@ class CacheItemExtentSliverReorderableList extends StatefulWidget {
class CacheItemExtentSliverReorderableListState
extends State<CacheItemExtentSliverReorderableList> {
late final FixedMap<String, double> _cacheHeightMap;
@override
void initState() {
super.initState();
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(widget.itemCount);
globalState.cacheHeightMap[widget.tag] ??= FixedMap(widget.itemCount);
_cacheHeightMap = FixedMap(widget.itemCount);
}
clearCache() {
_cacheHeightMap.clear();
}
@override
Widget build(BuildContext context) {
globalState.cacheHeightMap[widget.tag]?.updateMaxLength(widget.itemCount);
_cacheHeightMap.updateMaxSize(widget.itemCount);
return SliverReorderableList(
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
itemExtentBuilder: (index, __) {
return globalState.cacheHeightMap[widget.tag]?.updateCacheValue(
widget.keyBuilder(index),
() => widget.itemExtentBuilder(index),
);
final key = widget.keyBuilder(index);
if (_cacheHeightMap.containsKey(key)) {
return _cacheHeightMap.get(key);
}
return _cacheHeightMap.put(key, widget.itemExtentBuilder(index));
},
onReorder: widget.onReorder,
proxyDecorator: widget.proxyDecorator,
@@ -221,6 +232,7 @@ class CacheItemExtentSliverReorderableListState
@override
void dispose() {
_cacheHeightMap.clear();
super.dispose();
}
}

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.84+202505013
version: 0.8.83+202504281
environment:
sdk: '>=3.1.0 <4.0.0'

View File

@@ -39,9 +39,8 @@ static PROCESS: Lazy<Arc<Mutex<Option<std::process::Child>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
fn start(start_params: StartParams) -> impl Reply {
let sha256 = sha256_file(start_params.path.as_str()).unwrap_or("".to_string());
if sha256 != env!("TOKEN") {
return format!("The SHA256 hash of the program requesting execution is: {}. The helper program only allows execution of applications with the SHA256 hash: {}.", sha256, env!("TOKEN"),);
if sha256_file(start_params.path.as_str()).unwrap_or("".to_string()) != env!("TOKEN") {
return "Only FlClashCore is allowed to run.".to_string();
}
stop();
let mut process = PROCESS.lock().unwrap();