Support profiles sort
Support windows country flags display Optimize proxies page and profiles page columns
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.x'
|
||||
flutter-version-file: pubspec.yaml
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
|
||||
|
||||
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
BIN
assets/fonts/Twemoji.Mozilla.ttf
Normal file
Binary file not shown.
@@ -135,7 +135,6 @@ class ApplicationState extends State<Application> {
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: globalState.navigatorKey,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
|
||||
@@ -29,6 +29,7 @@ extension ColorSchemeExtension on ColorScheme {
|
||||
ColorScheme toPrueBlack(bool isPrueBlack) => isPrueBlack
|
||||
? copyWith(
|
||||
surface: Colors.black,
|
||||
background: Colors.black,
|
||||
surfaceContainer: surfaceContainer.darken(0.05),
|
||||
)
|
||||
: this;
|
||||
|
||||
@@ -47,6 +47,7 @@ class DAVClient {
|
||||
}
|
||||
|
||||
Future<List<int>> recovery() async {
|
||||
await client.mkdir("$root");
|
||||
final data = await client.read(backupFile);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -191,22 +192,18 @@ class Other {
|
||||
return ViewMode.desktop;
|
||||
}
|
||||
|
||||
int getColumns(ViewMode viewMode, int currentColumns) {
|
||||
final targetColumnsArray = viewModeColumnsMap[viewMode]!;
|
||||
if (targetColumnsArray.contains(currentColumns)) {
|
||||
return currentColumns;
|
||||
}
|
||||
return targetColumnsArray.first;
|
||||
|
||||
int getProxiesColumns(double viewWidth, ProxiesLayout proxiesLayout) {
|
||||
final columns = max((viewWidth / 300).ceil(), 2);
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => columns - 1,
|
||||
ProxiesLayout.standard => columns,
|
||||
ProxiesLayout.loose => columns + 1,
|
||||
};
|
||||
}
|
||||
|
||||
String getColumnsTextForInt(int number) {
|
||||
return switch (number) {
|
||||
1 => appLocalizations.oneColumn,
|
||||
2 => appLocalizations.twoColumns,
|
||||
3 => appLocalizations.threeColumns,
|
||||
4 => appLocalizations.fourColumns,
|
||||
int() => throw UnimplementedError(),
|
||||
};
|
||||
int getProfilesColumns(double viewWidth) {
|
||||
return max((viewWidth / 400).floor(), 1);
|
||||
}
|
||||
|
||||
String getBackupFileName() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
@@ -49,7 +50,9 @@ class Request {
|
||||
.get(
|
||||
url,
|
||||
options: Options(
|
||||
headers: {"User-Agent": globalState.appController.clashConfig.globalUa},
|
||||
headers: {
|
||||
"User-Agent": globalState.appController.clashConfig.globalUa
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
)
|
||||
@@ -84,10 +87,13 @@ class Request {
|
||||
};
|
||||
|
||||
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
|
||||
for (final source in _ipInfoSources.entries) {
|
||||
for (final source in _ipInfoSources.entries.toList()..shuffle(Random())) {
|
||||
try {
|
||||
final response = await _dio
|
||||
.get<Map<String, dynamic>>(source.key, cancelToken: cancelToken)
|
||||
.get<Map<String, dynamic>>(
|
||||
source.key,
|
||||
cancelToken: cancelToken,
|
||||
)
|
||||
.timeout(
|
||||
httpTimeoutDuration,
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:fl_clash/common/archive.dart';
|
||||
@@ -414,7 +415,6 @@ class AppController {
|
||||
addProfileFormURL(url);
|
||||
}
|
||||
|
||||
int get columns => other.getColumns(appState.viewMode, config.proxiesColumns);
|
||||
|
||||
updateViewWidth(double width) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
||||
@@ -86,4 +86,6 @@ enum CommonCardType { plain, filled }
|
||||
|
||||
enum ProxiesType { tab, list }
|
||||
|
||||
enum ProxiesLayout{ loose, standard, tight }
|
||||
|
||||
enum ProxyCardType { expand, shrink, min }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -18,6 +18,7 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
final timeoutNotifier = ValueNotifier<bool>(false);
|
||||
bool? _preIsStart;
|
||||
Function? _checkIpDebounce;
|
||||
CancelToken? cancelToken;
|
||||
|
||||
_checkIp() async {
|
||||
final appState = globalState.appController.appState;
|
||||
@@ -28,13 +29,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
if (_preIsStart == false && _preIsStart == isStart) return;
|
||||
_preIsStart = isStart;
|
||||
ipInfoNotifier.value = null;
|
||||
final ipInfo = await request.checkIp();
|
||||
if (cancelToken != null) {
|
||||
cancelToken!.cancel();
|
||||
_preIsStart = null;
|
||||
timeoutNotifier.value == false;
|
||||
cancelToken = null;
|
||||
}
|
||||
cancelToken = CancelToken();
|
||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
||||
if (ipInfo == null) {
|
||||
timeoutNotifier.value = true;
|
||||
return;
|
||||
} else {
|
||||
timeoutNotifier.value = false;
|
||||
}
|
||||
timeoutNotifier.value = false;
|
||||
ipInfoNotifier.value = ipInfo;
|
||||
}
|
||||
|
||||
@@ -60,9 +67,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
timeoutNotifier.dispose();
|
||||
}
|
||||
|
||||
String countryCodeToEmoji(String countryCode) {
|
||||
final String code = countryCode.toUpperCase();
|
||||
if (code.length != 2) {
|
||||
return countryCode;
|
||||
}
|
||||
final int firstLetter = code.codeUnitAt(0) - 0x41 + 0x1F1E6;
|
||||
final int secondLetter = code.codeUnitAt(1) - 0x41 + 0x1F1E6;
|
||||
return String.fromCharCode(firstLetter) + String.fromCharCode(secondLetter);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_checkIpDebounce = debounce(_checkIp);
|
||||
_checkIpDebounce ??= debounce(_checkIp);
|
||||
return _checkIpContainer(
|
||||
ValueListenableBuilder<IpInfo?>(
|
||||
valueListenable: ipInfoNotifier,
|
||||
@@ -88,10 +105,19 @@ class _NetworkDetectionState extends State<NetworkDetection> {
|
||||
flex: 1,
|
||||
child: FadeBox(
|
||||
child: ipInfo != null
|
||||
? CountryFlag.fromCountryCode(
|
||||
ipInfo.countryCode,
|
||||
width: 24,
|
||||
height: 24,
|
||||
? Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: globalState.appController.measure
|
||||
.titleMediumHeight,
|
||||
child: Text(
|
||||
countryCodeToEmoji(ipInfo.countryCode),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontFamily: "Twemoji",
|
||||
),
|
||||
),
|
||||
)
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: timeoutNotifier,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/fragments/profiles/edit_profile.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -37,21 +39,11 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
);
|
||||
}
|
||||
|
||||
_getColumns(ViewMode viewMode) {
|
||||
switch (viewMode) {
|
||||
case ViewMode.mobile:
|
||||
return 1;
|
||||
case ViewMode.laptop:
|
||||
return 1;
|
||||
case ViewMode.desktop:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
_updateProfiles() async {
|
||||
final appController = globalState.appController;
|
||||
final config = appController.config;
|
||||
final profiles = appController.config.profiles;
|
||||
final messages = [];
|
||||
final updateProfiles = profiles.map<Future>(
|
||||
(profile) async {
|
||||
config.setProfile(
|
||||
@@ -62,7 +54,8 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
if (profile.id == appController.config.currentProfile?.id) {
|
||||
appController.applyProfile(isPrue: true);
|
||||
}
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
messages.add("${profile.label ?? profile.id}: $e \n");
|
||||
config.setProfile(
|
||||
profile.copyWith(
|
||||
isUpdating: false,
|
||||
@@ -71,13 +64,25 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
}
|
||||
},
|
||||
);
|
||||
final titleMedium = context.textTheme.titleMedium;
|
||||
await Future.wait(updateProfiles);
|
||||
if (messages.isNotEmpty) {
|
||||
globalState.showMessage(
|
||||
title: appLocalizations.tip,
|
||||
message: TextSpan(
|
||||
children: [
|
||||
for (final message in messages)
|
||||
TextSpan(text: message, style: titleMedium)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_initScaffoldState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
if (!context.mounted) return;
|
||||
if (!mounted) return;
|
||||
final commonScaffoldState =
|
||||
context.findAncestorStateOfType<CommonScaffoldState>();
|
||||
commonScaffoldState?.actions = [
|
||||
@@ -87,6 +92,24 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
},
|
||||
icon: const Icon(Icons.sync),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final profiles = globalState.appController.config.profiles;
|
||||
showSheet(
|
||||
title: appLocalizations.profilesSort,
|
||||
context: context,
|
||||
builder: (_) => SizedBox(
|
||||
height: 400,
|
||||
child: ReorderableProfiles(profiles: profiles),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
iconSize: 26,
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
@@ -116,7 +139,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
selector: (_, appState, config) => ProfilesSelectorState(
|
||||
profiles: config.profiles,
|
||||
currentProfileId: config.currentProfileId,
|
||||
viewMode: appState.viewMode,
|
||||
columns: other.getProfilesColumns(appState.viewWidth),
|
||||
),
|
||||
builder: (context, state, child) {
|
||||
if (state.profiles.isEmpty) {
|
||||
@@ -124,7 +147,6 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
label: appLocalizations.nullProfileDesc,
|
||||
);
|
||||
}
|
||||
final columns = _getColumns(state.viewMode);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
@@ -137,7 +159,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
child: Grid(
|
||||
mainAxisSpacing: 16,
|
||||
crossAxisSpacing: 16,
|
||||
crossAxisCount: columns,
|
||||
crossAxisCount: state.columns,
|
||||
children: [
|
||||
for (int i = 0; i < state.profiles.length; i++)
|
||||
GridItem(
|
||||
@@ -145,8 +167,7 @@ class _ProfilesFragmentState extends State<ProfilesFragment> {
|
||||
key: Key(state.profiles[i].id),
|
||||
profile: state.profiles[i],
|
||||
groupValue: state.currentProfileId,
|
||||
onChanged:
|
||||
globalState.appController.changeProfile,
|
||||
onChanged: globalState.appController.changeProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -244,6 +265,7 @@ class ProfileItem extends StatelessWidget {
|
||||
LinearProgressIndicator(
|
||||
minHeight: 6,
|
||||
value: progress,
|
||||
backgroundColor: context.colorScheme.primary.toSoft(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
@@ -372,3 +394,129 @@ class ProfileItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderableProfiles extends StatefulWidget {
|
||||
final List<Profile> profiles;
|
||||
|
||||
const ReorderableProfiles({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ReorderableProfiles> createState() => _ReorderableProfilesState();
|
||||
}
|
||||
|
||||
class _ReorderableProfilesState extends State<ReorderableProfiles> {
|
||||
late List<Profile> profiles;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
profiles = List.from(widget.profiles);
|
||||
}
|
||||
|
||||
Widget proxyDecorator(
|
||||
Widget child,
|
||||
int index,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final profile = profiles[index];
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 44,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ReorderableListView.builder(
|
||||
buildDefaultDragHandles: false,
|
||||
padding: const EdgeInsets.all(12),
|
||||
proxyDecorator: proxyDecorator,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex == newIndex) return;
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final profile = profiles.removeAt(oldIndex);
|
||||
profiles.insert(newIndex, profile);
|
||||
});
|
||||
},
|
||||
itemBuilder: (_, index) {
|
||||
final profile = profiles[index];
|
||||
return Container(
|
||||
key: Key(profile.id),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: CommonCard(
|
||||
type: CommonCardType.filled,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
title: Text(profile.label ?? profile.id),
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: const Icon(Icons.drag_handle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
globalState.appController.config.profiles = profiles;
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.check,
|
||||
),
|
||||
iconSize: 32,
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,23 +69,21 @@ class ProxyCard extends StatelessWidget {
|
||||
if (type == ProxyCardType.min) {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 1,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: measure.bodyMediumHeight * 2,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
proxy.name,
|
||||
maxLines: 2,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -155,14 +153,12 @@ class ProxyCard extends StatelessWidget {
|
||||
proxy.name,
|
||||
),
|
||||
builder: (_, desc, __) {
|
||||
return TooltipText(
|
||||
text: Text(
|
||||
desc,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
return EmojiText(
|
||||
desc,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.textTheme.bodySmall?.color
|
||||
?.toLight(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/clash/clash.dart';
|
||||
import 'package:fl_clash/common/other.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -60,7 +61,10 @@ double getScrollToSelectedOffset({
|
||||
required List<Proxy> proxies,
|
||||
}) {
|
||||
final appController = globalState.appController;
|
||||
final columns = appController.columns;
|
||||
final columns = other.getProxiesColumns(
|
||||
appController.appState.viewWidth,
|
||||
appController.config.proxiesLayout,
|
||||
);
|
||||
final proxyCardType = appController.config.proxyCardType;
|
||||
final selectedName = appController.getCurrentSelectedName(groupName);
|
||||
final findSelectedIndex = proxies.indexWhere(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/card.dart';
|
||||
import 'package:fl_clash/widgets/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -237,7 +238,10 @@ class _ProxiesListFragmentState extends State<ProxiesListFragment> {
|
||||
currentUnfoldSet: config.currentUnfoldSet,
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
);
|
||||
},
|
||||
@@ -450,7 +454,7 @@ class _ListHeaderState extends State<ListHeader>
|
||||
if (currentGroupName.isNotEmpty) ...[
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
child: EmojiText(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
" · $currentGroupName",
|
||||
style: context
|
||||
|
||||
@@ -33,6 +33,14 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
|
||||
String getTextForProxiesLayout(ProxiesLayout proxiesLayout) {
|
||||
return switch (proxiesLayout) {
|
||||
ProxiesLayout.tight => appLocalizations.tight,
|
||||
ProxiesLayout.standard => appLocalizations.standard,
|
||||
ProxiesLayout.loose => appLocalizations.loose,
|
||||
};
|
||||
}
|
||||
|
||||
List<Widget> _buildStyleSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.style,
|
||||
@@ -132,36 +140,28 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildColumnsSetting() {
|
||||
List<Widget> _buildLayoutSetting() {
|
||||
return generateSection(
|
||||
title: appLocalizations.columns,
|
||||
title: appLocalizations.layout,
|
||||
items: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Selector2<AppState, Config, ColumnsSelectorState>(
|
||||
selector: (_, appState, config) => ColumnsSelectorState(
|
||||
columns: config.proxiesColumns,
|
||||
viewMode: appState.viewMode,
|
||||
),
|
||||
builder: (_, state, __) {
|
||||
child: Selector< Config, ProxiesLayout>(
|
||||
selector: (_, config) => config.proxiesLayout,
|
||||
builder: (_, proxiesLayout, __) {
|
||||
final config = globalState.appController.config;
|
||||
final targetColumnsArray = viewModeColumnsMap[state.viewMode]!;
|
||||
final currentColumns = other.getColumns(
|
||||
state.viewMode,
|
||||
state.columns,
|
||||
);
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: [
|
||||
for (final item in targetColumnsArray)
|
||||
for (final item in ProxiesLayout.values)
|
||||
SettingTextCard(
|
||||
other.getColumnsTextForInt(item),
|
||||
isSelected: item == currentColumns,
|
||||
getTextForProxiesLayout(item),
|
||||
isSelected: item == proxiesLayout,
|
||||
onPressed: () {
|
||||
config.proxiesColumns = item;
|
||||
config.proxiesLayout = item;
|
||||
},
|
||||
)
|
||||
],
|
||||
@@ -183,10 +183,10 @@ class ProxiesSettingWidget extends StatelessWidget {
|
||||
children: [
|
||||
..._buildStyleSetting(),
|
||||
..._buildSortSetting(),
|
||||
..._buildColumnsSetting(),
|
||||
..._buildLayoutSetting(),
|
||||
..._buildSizeSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,10 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
|
||||
return ProxyGroupSelectorState(
|
||||
proxyCardType: config.proxyCardType,
|
||||
proxiesSortType: config.proxiesSortType,
|
||||
columns: globalState.appController.columns,
|
||||
columns: other.getProxiesColumns(
|
||||
appState.viewWidth,
|
||||
config.proxiesLayout,
|
||||
),
|
||||
sortNum: appState.sortNum,
|
||||
proxies: group.all,
|
||||
groupType: group.type,
|
||||
|
||||
@@ -236,5 +236,10 @@
|
||||
"action": "Action",
|
||||
"intelligentSelected": "Intelligent selection",
|
||||
"clipboardImport": "Clipboard import",
|
||||
"clipboardExport": "Export clipboard"
|
||||
"clipboardExport": "Export clipboard",
|
||||
"layout": "Layout",
|
||||
"tight": "Tight",
|
||||
"standard": "Standard",
|
||||
"loose": "Loose",
|
||||
"profilesSort": "Profiles sort"
|
||||
}
|
||||
@@ -236,5 +236,10 @@
|
||||
"action": "操作",
|
||||
"intelligentSelected": "智能选择",
|
||||
"clipboardImport": "剪贴板导入",
|
||||
"clipboardExport": "导出剪贴板"
|
||||
"clipboardExport": "导出剪贴板",
|
||||
"layout": "布局",
|
||||
"tight": "宽松",
|
||||
"standard": "标准",
|
||||
"loose": "紧凑",
|
||||
"profilesSort": "配置排序"
|
||||
}
|
||||
@@ -182,6 +182,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Tcp keep alive interval"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Language"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("List"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("Local"),
|
||||
@@ -195,6 +196,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Disabling will hide the log entry"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("Logs"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("Log capture records"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("Loose"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("Min"),
|
||||
"minimizeOnExit":
|
||||
MessageLookupByLibrary.simpleMessage("Minimize on exit"),
|
||||
@@ -266,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Please input the profile URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Project"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
"proxiesSetting":
|
||||
@@ -312,6 +315,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"size": MessageLookupByLibrary.simpleMessage("Size"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("Source"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Standard"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("Staring VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("Style"),
|
||||
@@ -334,6 +338,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Set dark mode,adjust the color"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("Three columns"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("Tight"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("Time"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("tip"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
|
||||
|
||||
@@ -147,6 +147,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"keepAliveIntervalDesc":
|
||||
MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("语言"),
|
||||
"layout": MessageLookupByLibrary.simpleMessage("布局"),
|
||||
"light": MessageLookupByLibrary.simpleMessage("浅色"),
|
||||
"list": MessageLookupByLibrary.simpleMessage("列表"),
|
||||
"local": MessageLookupByLibrary.simpleMessage("本地"),
|
||||
@@ -157,6 +158,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"logcatDesc": MessageLookupByLibrary.simpleMessage("禁用将会隐藏日志入口"),
|
||||
"logs": MessageLookupByLibrary.simpleMessage("日志"),
|
||||
"logsDesc": MessageLookupByLibrary.simpleMessage("日志捕获记录"),
|
||||
"loose": MessageLookupByLibrary.simpleMessage("紧凑"),
|
||||
"min": MessageLookupByLibrary.simpleMessage("最小"),
|
||||
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
|
||||
"minimizeOnExitDesc":
|
||||
@@ -214,6 +216,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"profileUrlNullValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("请输入配置URL"),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
"proxiesSetting": MessageLookupByLibrary.simpleMessage("代理设置"),
|
||||
@@ -249,6 +252,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
|
||||
"sort": MessageLookupByLibrary.simpleMessage("排序"),
|
||||
"source": MessageLookupByLibrary.simpleMessage("来源"),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("标准"),
|
||||
"startVpn": MessageLookupByLibrary.simpleMessage("正在启动VPN..."),
|
||||
"stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."),
|
||||
"style": MessageLookupByLibrary.simpleMessage("风格"),
|
||||
@@ -269,6 +273,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"),
|
||||
"themeMode": MessageLookupByLibrary.simpleMessage("主题模式"),
|
||||
"threeColumns": MessageLookupByLibrary.simpleMessage("三列"),
|
||||
"tight": MessageLookupByLibrary.simpleMessage("宽松"),
|
||||
"time": MessageLookupByLibrary.simpleMessage("时间"),
|
||||
"tip": MessageLookupByLibrary.simpleMessage("提示"),
|
||||
"tools": MessageLookupByLibrary.simpleMessage("工具"),
|
||||
|
||||
@@ -2429,6 +2429,56 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Layout`
|
||||
String get layout {
|
||||
return Intl.message(
|
||||
'Layout',
|
||||
name: 'layout',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Tight`
|
||||
String get tight {
|
||||
return Intl.message(
|
||||
'Tight',
|
||||
name: 'tight',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Standard`
|
||||
String get standard {
|
||||
return Intl.message(
|
||||
'Standard',
|
||||
name: 'standard',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Loose`
|
||||
String get loose {
|
||||
return Intl.message(
|
||||
'Loose',
|
||||
name: 'loose',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Profiles sort`
|
||||
String get profilesSort {
|
||||
return Intl.message(
|
||||
'Profiles sort',
|
||||
name: 'profilesSort',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -28,9 +28,9 @@ class AccessControl with _$AccessControl {
|
||||
|
||||
extension AccessControlExt on AccessControl {
|
||||
List<String> get currentList => switch (mode) {
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
AccessControlMode.acceptSelected => acceptList,
|
||||
AccessControlMode.rejectSelected => rejectList,
|
||||
};
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -44,7 +44,8 @@ class CoreState with _$CoreState {
|
||||
required bool onlyProxy,
|
||||
}) = _CoreState;
|
||||
|
||||
factory CoreState.fromJson(Map<String, Object?> json) => _$CoreStateFromJson(json);
|
||||
factory CoreState.fromJson(Map<String, Object?> json) =>
|
||||
_$CoreStateFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -87,7 +88,7 @@ class Config extends ChangeNotifier {
|
||||
bool _isCloseConnections;
|
||||
ProxiesType _proxiesType;
|
||||
ProxyCardType _proxyCardType;
|
||||
int _proxiesColumns;
|
||||
ProxiesLayout _proxiesLayout;
|
||||
String _testUrl;
|
||||
WindowProps _windowProps;
|
||||
bool _onlyProxy;
|
||||
@@ -116,9 +117,9 @@ class Config extends ChangeNotifier {
|
||||
_proxyCardType = ProxyCardType.expand,
|
||||
_windowProps = defaultWindowProps,
|
||||
_proxiesType = ProxiesType.tab,
|
||||
_proxiesColumns = 2,
|
||||
_prueBlack = false,
|
||||
_onlyProxy = false;
|
||||
_onlyProxy = false,
|
||||
_proxiesLayout = ProxiesLayout.standard;
|
||||
|
||||
deleteProfileById(String id) {
|
||||
_profiles = profiles.where((element) => element.id != id).toList();
|
||||
@@ -320,6 +321,16 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: ProxiesLayout.standard)
|
||||
ProxiesLayout get proxiesLayout => _proxiesLayout;
|
||||
|
||||
set proxiesLayout(ProxiesLayout value) {
|
||||
if (_proxiesLayout != value) {
|
||||
_proxiesLayout = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool get isMinimizeOnExit => _isMinimizeOnExit;
|
||||
|
||||
@@ -481,16 +492,6 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: 2)
|
||||
int get proxiesColumns => _proxiesColumns;
|
||||
|
||||
set proxiesColumns(int value) {
|
||||
if (_proxiesColumns != value) {
|
||||
_proxiesColumns = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(name: "test-url", defaultValue: defaultTestUrl)
|
||||
String get testUrl => _testUrl;
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxiesSortType =
|
||||
$enumDecodeNullable(_$ProxiesSortTypeEnumMap, json['proxiesSortType']) ??
|
||||
ProxiesSortType.none
|
||||
..proxiesLayout =
|
||||
$enumDecodeNullable(_$ProxiesLayoutEnumMap, json['proxiesLayout']) ??
|
||||
ProxiesLayout.standard
|
||||
..isMinimizeOnExit = json['isMinimizeOnExit'] as bool? ?? true
|
||||
..isAccessControl = json['isAccessControl'] as bool? ?? false
|
||||
..accessControl =
|
||||
@@ -44,7 +47,6 @@ Config _$ConfigFromJson(Map<String, dynamic> json) => Config()
|
||||
..proxyCardType =
|
||||
$enumDecodeNullable(_$ProxyCardTypeEnumMap, json['proxyCardType']) ??
|
||||
ProxyCardType.expand
|
||||
..proxiesColumns = (json['proxiesColumns'] as num?)?.toInt() ?? 2
|
||||
..testUrl =
|
||||
json['test-url'] as String? ?? 'https://www.gstatic.com/generate_204'
|
||||
..isExclude = json['isExclude'] as bool? ?? false
|
||||
@@ -62,6 +64,7 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'locale': instance.locale,
|
||||
'primaryColor': instance.primaryColor,
|
||||
'proxiesSortType': _$ProxiesSortTypeEnumMap[instance.proxiesSortType]!,
|
||||
'proxiesLayout': _$ProxiesLayoutEnumMap[instance.proxiesLayout]!,
|
||||
'isMinimizeOnExit': instance.isMinimizeOnExit,
|
||||
'isAccessControl': instance.isAccessControl,
|
||||
'accessControl': instance.accessControl,
|
||||
@@ -76,7 +79,6 @@ Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||
'isCloseConnections': instance.isCloseConnections,
|
||||
'proxiesType': _$ProxiesTypeEnumMap[instance.proxiesType]!,
|
||||
'proxyCardType': _$ProxyCardTypeEnumMap[instance.proxyCardType]!,
|
||||
'proxiesColumns': instance.proxiesColumns,
|
||||
'test-url': instance.testUrl,
|
||||
'isExclude': instance.isExclude,
|
||||
'windowProps': instance.windowProps,
|
||||
@@ -94,6 +96,12 @@ const _$ProxiesSortTypeEnumMap = {
|
||||
ProxiesSortType.name: 'name',
|
||||
};
|
||||
|
||||
const _$ProxiesLayoutEnumMap = {
|
||||
ProxiesLayout.loose: 'loose',
|
||||
ProxiesLayout.standard: 'standard',
|
||||
ProxiesLayout.tight: 'tight',
|
||||
};
|
||||
|
||||
const _$ProxiesTypeEnumMap = {
|
||||
ProxiesType.tab: 'tab',
|
||||
ProxiesType.list: 'list',
|
||||
|
||||
@@ -458,7 +458,7 @@ abstract class _NetworkDetectionSelectorState
|
||||
mixin _$ProfilesSelectorState {
|
||||
List<Profile> get profiles => throw _privateConstructorUsedError;
|
||||
String? get currentProfileId => throw _privateConstructorUsedError;
|
||||
ViewMode get viewMode => throw _privateConstructorUsedError;
|
||||
int get columns => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ProfilesSelectorStateCopyWith<ProfilesSelectorState> get copyWith =>
|
||||
@@ -471,8 +471,7 @@ abstract class $ProfilesSelectorStateCopyWith<$Res> {
|
||||
$Res Function(ProfilesSelectorState) then) =
|
||||
_$ProfilesSelectorStateCopyWithImpl<$Res, ProfilesSelectorState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
|
||||
$Res call({List<Profile> profiles, String? currentProfileId, int columns});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -491,7 +490,7 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
|
||||
$Res call({
|
||||
Object? profiles = null,
|
||||
Object? currentProfileId = freezed,
|
||||
Object? viewMode = null,
|
||||
Object? columns = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
profiles: null == profiles
|
||||
@@ -502,10 +501,10 @@ class _$ProfilesSelectorStateCopyWithImpl<$Res,
|
||||
? _value.currentProfileId
|
||||
: currentProfileId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -519,8 +518,7 @@ abstract class _$$ProfilesSelectorStateImplCopyWith<$Res>
|
||||
__$$ProfilesSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Profile> profiles, String? currentProfileId, ViewMode viewMode});
|
||||
$Res call({List<Profile> profiles, String? currentProfileId, int columns});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -537,7 +535,7 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
|
||||
$Res call({
|
||||
Object? profiles = null,
|
||||
Object? currentProfileId = freezed,
|
||||
Object? viewMode = null,
|
||||
Object? columns = null,
|
||||
}) {
|
||||
return _then(_$ProfilesSelectorStateImpl(
|
||||
profiles: null == profiles
|
||||
@@ -548,10 +546,10 @@ class __$$ProfilesSelectorStateImplCopyWithImpl<$Res>
|
||||
? _value.currentProfileId
|
||||
: currentProfileId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -562,7 +560,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
const _$ProfilesSelectorStateImpl(
|
||||
{required final List<Profile> profiles,
|
||||
required this.currentProfileId,
|
||||
required this.viewMode})
|
||||
required this.columns})
|
||||
: _profiles = profiles;
|
||||
|
||||
final List<Profile> _profiles;
|
||||
@@ -576,11 +574,11 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
@override
|
||||
final String? currentProfileId;
|
||||
@override
|
||||
final ViewMode viewMode;
|
||||
final int columns;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, viewMode: $viewMode)';
|
||||
return 'ProfilesSelectorState(profiles: $profiles, currentProfileId: $currentProfileId, columns: $columns)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -591,8 +589,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
const DeepCollectionEquality().equals(other._profiles, _profiles) &&
|
||||
(identical(other.currentProfileId, currentProfileId) ||
|
||||
other.currentProfileId == currentProfileId) &&
|
||||
(identical(other.viewMode, viewMode) ||
|
||||
other.viewMode == viewMode));
|
||||
(identical(other.columns, columns) || other.columns == columns));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -600,7 +597,7 @@ class _$ProfilesSelectorStateImpl implements _ProfilesSelectorState {
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_profiles),
|
||||
currentProfileId,
|
||||
viewMode);
|
||||
columns);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -614,14 +611,14 @@ abstract class _ProfilesSelectorState implements ProfilesSelectorState {
|
||||
const factory _ProfilesSelectorState(
|
||||
{required final List<Profile> profiles,
|
||||
required final String? currentProfileId,
|
||||
required final ViewMode viewMode}) = _$ProfilesSelectorStateImpl;
|
||||
required final int columns}) = _$ProfilesSelectorStateImpl;
|
||||
|
||||
@override
|
||||
List<Profile> get profiles;
|
||||
@override
|
||||
String? get currentProfileId;
|
||||
@override
|
||||
ViewMode get viewMode;
|
||||
int get columns;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ProfilesSelectorStateImplCopyWith<_$ProfilesSelectorStateImpl>
|
||||
@@ -2562,146 +2559,6 @@ abstract class _PackageListSelectorState implements PackageListSelectorState {
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ColumnsSelectorState {
|
||||
int get columns => throw _privateConstructorUsedError;
|
||||
ViewMode get viewMode => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ColumnsSelectorStateCopyWith<ColumnsSelectorState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ColumnsSelectorStateCopyWith<$Res> {
|
||||
factory $ColumnsSelectorStateCopyWith(ColumnsSelectorState value,
|
||||
$Res Function(ColumnsSelectorState) then) =
|
||||
_$ColumnsSelectorStateCopyWithImpl<$Res, ColumnsSelectorState>;
|
||||
@useResult
|
||||
$Res call({int columns, ViewMode viewMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ColumnsSelectorStateCopyWithImpl<$Res,
|
||||
$Val extends ColumnsSelectorState>
|
||||
implements $ColumnsSelectorStateCopyWith<$Res> {
|
||||
_$ColumnsSelectorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? columns = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ColumnsSelectorStateImplCopyWith<$Res>
|
||||
implements $ColumnsSelectorStateCopyWith<$Res> {
|
||||
factory _$$ColumnsSelectorStateImplCopyWith(_$ColumnsSelectorStateImpl value,
|
||||
$Res Function(_$ColumnsSelectorStateImpl) then) =
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int columns, ViewMode viewMode});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ColumnsSelectorStateImplCopyWithImpl<$Res>
|
||||
extends _$ColumnsSelectorStateCopyWithImpl<$Res, _$ColumnsSelectorStateImpl>
|
||||
implements _$$ColumnsSelectorStateImplCopyWith<$Res> {
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl(_$ColumnsSelectorStateImpl _value,
|
||||
$Res Function(_$ColumnsSelectorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? columns = null,
|
||||
Object? viewMode = null,
|
||||
}) {
|
||||
return _then(_$ColumnsSelectorStateImpl(
|
||||
columns: null == columns
|
||||
? _value.columns
|
||||
: columns // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
viewMode: null == viewMode
|
||||
? _value.viewMode
|
||||
: viewMode // ignore: cast_nullable_to_non_nullable
|
||||
as ViewMode,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ColumnsSelectorStateImpl implements _ColumnsSelectorState {
|
||||
const _$ColumnsSelectorStateImpl(
|
||||
{required this.columns, required this.viewMode});
|
||||
|
||||
@override
|
||||
final int columns;
|
||||
@override
|
||||
final ViewMode viewMode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ColumnsSelectorState(columns: $columns, viewMode: $viewMode)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ColumnsSelectorStateImpl &&
|
||||
(identical(other.columns, columns) || other.columns == columns) &&
|
||||
(identical(other.viewMode, viewMode) ||
|
||||
other.viewMode == viewMode));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, columns, viewMode);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
||||
get copyWith =>
|
||||
__$$ColumnsSelectorStateImplCopyWithImpl<_$ColumnsSelectorStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ColumnsSelectorState implements ColumnsSelectorState {
|
||||
const factory _ColumnsSelectorState(
|
||||
{required final int columns,
|
||||
required final ViewMode viewMode}) = _$ColumnsSelectorStateImpl;
|
||||
|
||||
@override
|
||||
int get columns;
|
||||
@override
|
||||
ViewMode get viewMode;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ColumnsSelectorStateImplCopyWith<_$ColumnsSelectorStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProxiesListHeaderSelectorState {
|
||||
double get offset => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProfilesSelectorState with _$ProfilesSelectorState {
|
||||
const factory ProfilesSelectorState({
|
||||
required List<Profile> profiles,
|
||||
required String? currentProfileId,
|
||||
required ViewMode viewMode,
|
||||
required int columns,
|
||||
}) = _ProfilesSelectorState;
|
||||
}
|
||||
|
||||
@@ -172,14 +172,6 @@ extension PackageListSelectorStateExt on PackageListSelectorState {
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ColumnsSelectorState with _$ColumnsSelectorState {
|
||||
const factory ColumnsSelectorState({
|
||||
required int columns,
|
||||
required ViewMode viewMode,
|
||||
}) = _ColumnsSelectorState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProxiesListHeaderSelectorState with _$ProxiesListHeaderSelectorState {
|
||||
const factory ProxiesListHeaderSelectorState({
|
||||
|
||||
@@ -60,7 +60,6 @@ class HomePage extends StatelessWidget {
|
||||
),
|
||||
selectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelTextStyle: context.textTheme.labelLarge!.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:emoji_regex/emoji_regex.dart';
|
||||
|
||||
import '../state.dart';
|
||||
|
||||
@@ -30,3 +31,63 @@ class TooltipText extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiText extends StatelessWidget {
|
||||
final String text;
|
||||
final TextStyle? style;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
|
||||
const EmojiText(
|
||||
this.text, {
|
||||
super.key,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
List<TextSpan> _buildTextSpans(String emojis) {
|
||||
final List<TextSpan> spans = [];
|
||||
final matches = emojiRegex().allMatches(text);
|
||||
|
||||
int lastMatchEnd = 0;
|
||||
for (final match in matches) {
|
||||
if (match.start > lastMatchEnd) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: text.substring(lastMatchEnd, match.start), style: style),
|
||||
);
|
||||
}
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text:match.group(0),
|
||||
style: style?.copyWith(
|
||||
fontFamily: "Twemoji",
|
||||
),
|
||||
),
|
||||
);
|
||||
lastMatchEnd = match.end;
|
||||
}
|
||||
if (lastMatchEnd < text.length) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: text.substring(lastMatchEnd),
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return spans;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
maxLines: maxLines,
|
||||
overflow: overflow ?? TextOverflow.clip,
|
||||
text: TextSpan(
|
||||
children: _buildTextSpans(text),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
66
pubspec.lock
66
pubspec.lock
@@ -193,14 +193,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
country_flags:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: country_flags
|
||||
sha256: dbc4f76e7c801619b2d841023e0327191ba00663f1f1b4770394e9bc6632c444
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -249,6 +241,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
emoji_regex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: emoji_regex
|
||||
sha256: "3a25dd4d16f98b6f76dc37cc9ae49b8511891ac4b87beac9443a1e9f4634b6c7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -541,22 +541,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.1"
|
||||
jovial_misc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_misc
|
||||
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.5"
|
||||
jovial_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_svg
|
||||
sha256: "000cafe183bdeba28f76d65bf93fc9b636d6f7eaa7e2a33e80c4345a28866ea8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.21"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -593,18 +577,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "10.0.4"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.3"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -649,10 +633,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.8.0"
|
||||
menu_base:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -665,10 +649,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
version: "1.12.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -797,14 +781,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1029,10 +1005,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1133,10 +1109,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1251,4 +1227,4 @@ packages:
|
||||
version: "0.2.3"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
flutter: ">=3.22.3"
|
||||
|
||||
10
pubspec.yaml
10
pubspec.yaml
@@ -1,9 +1,10 @@
|
||||
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.52+202408111
|
||||
version: 0.8.53+202408151
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
flutter: 3.22.3
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -37,13 +38,13 @@ dependencies:
|
||||
image: ^4.1.7
|
||||
webdav_client: ^1.2.2
|
||||
dio: ^5.4.3+1
|
||||
country_flags: ^2.2.0
|
||||
win32: ^5.5.1
|
||||
ffi: ^2.1.2
|
||||
re_editor: ^0.3.1
|
||||
re_highlight: ^0.0.3
|
||||
archive: ^3.6.1
|
||||
lpinyin: ^2.0.3
|
||||
emoji_regex: ^0.0.5
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -58,8 +59,13 @@ flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/data/
|
||||
- assets/fonts/
|
||||
- assets/images/
|
||||
- assets/images/avatars/
|
||||
fonts:
|
||||
- family: Twemoji
|
||||
fonts:
|
||||
- asset: assets/fonts/Twemoji.Mozilla.ttf
|
||||
ffigen:
|
||||
name: "ClashFFI"
|
||||
output: 'lib/clash/generated/clash_ffi.dart'
|
||||
|
||||
Reference in New Issue
Block a user