Compare commits
1 Commits
v0.8.81
...
v0.8.81-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f10a8a189d |
14
.github/workflows/build.yaml
vendored
14
.github/workflows/build.yaml
vendored
@@ -76,7 +76,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Setup
|
||||
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }}
|
||||
run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && format('--env stable') }}
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -91,12 +91,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/heads/main
|
||||
- name: Generate
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
run: |
|
||||
tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate))
|
||||
preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1)
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
cat NEW_CHANGELOG.md > CHANGELOG.md
|
||||
|
||||
- name: Commit
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
run: |
|
||||
git add CHANGELOG.md
|
||||
if ! git diff --cached --quiet; then
|
||||
@@ -215,21 +215,21 @@ jobs:
|
||||
sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md
|
||||
|
||||
- name: Release
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./dist/*
|
||||
body_path: './release.md'
|
||||
|
||||
- name: Create Fdroid Source Dir
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
run: |
|
||||
mkdir -p ./tmp
|
||||
cp ./dist/*android-arm64-v8a* ./tmp/ || true
|
||||
echo "Files copied successfully"
|
||||
|
||||
- name: Push to fdroid repo
|
||||
if: ${{ env.IS_STABLE == 'true' }}
|
||||
if: ${{ env.IS_STABLE }}
|
||||
uses: cpina/github-action-push-to-another-repository@v1.7.2
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,7 +1,7 @@
|
||||
[submodule "core/Clash.Meta"]
|
||||
path = core/Clash.Meta
|
||||
url = git@github.com:chen08209/Clash.Meta.git
|
||||
branch = FlClash
|
||||
branch = FlClash-Alpha
|
||||
[submodule "plugins/flutter_distributor"]
|
||||
path = plugins/flutter_distributor
|
||||
url = git@github.com:chen08209/flutter_distributor.git
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,15 +1,3 @@
|
||||
## v0.8.80
|
||||
|
||||
- Optimize dashboard performance
|
||||
|
||||
- Fix some issues
|
||||
|
||||
- Fix unselected proxy group delay issues
|
||||
|
||||
- Fix asn url issues
|
||||
|
||||
- Update changelog
|
||||
|
||||
## v0.8.79
|
||||
|
||||
- Fix tab delay view issues
|
||||
|
||||
@@ -45,16 +45,9 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
addAddress(cidr.address, cidr.prefixLength)
|
||||
val routeAddress = options.getIpv4RouteAddress()
|
||||
if (routeAddress.isNotEmpty()) {
|
||||
try {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d(
|
||||
"addRoute4",
|
||||
"address: ${i.address} prefixLength:${i.prefixLength}"
|
||||
)
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
addRoute("0.0.0.0", 0)
|
||||
routeAddress.forEach { i ->
|
||||
Log.d("addRoute4", "address: ${i.address} prefixLength:${i.prefixLength}")
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} else {
|
||||
addRoute("0.0.0.0", 0)
|
||||
@@ -65,16 +58,9 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
|
||||
addAddress(cidr.address, cidr.prefixLength)
|
||||
val routeAddress = options.getIpv6RouteAddress()
|
||||
if (routeAddress.isNotEmpty()) {
|
||||
try {
|
||||
routeAddress.forEach { i ->
|
||||
Log.d(
|
||||
"addRoute6",
|
||||
"address: ${i.address} prefixLength:${i.prefixLength}"
|
||||
)
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
addRoute("::", 0)
|
||||
routeAddress.forEach { i ->
|
||||
Log.d("addRoute6", "address: ${i.address} prefixLength:${i.prefixLength}")
|
||||
addRoute(i.address, i.prefixLength)
|
||||
}
|
||||
} else {
|
||||
addRoute("::", 0)
|
||||
|
||||
Submodule core/Clash.Meta updated: f19dad529f...50dc973bab
@@ -14,13 +14,15 @@ class Protocol {
|
||||
|
||||
void register(String scheme) {
|
||||
String protocolRegKey = 'Software\\Classes\\$scheme';
|
||||
RegistryValue protocolRegValue = RegistryValue.string(
|
||||
RegistryValue protocolRegValue = const RegistryValue(
|
||||
'URL Protocol',
|
||||
RegistryValueType.string,
|
||||
'',
|
||||
);
|
||||
String protocolCmdRegKey = 'shell\\open\\command';
|
||||
RegistryValue protocolCmdRegValue = RegistryValue.string(
|
||||
RegistryValue protocolCmdRegValue = RegistryValue(
|
||||
'',
|
||||
RegistryValueType.string,
|
||||
'"${Platform.resolvedExecutable}" "%1"',
|
||||
);
|
||||
final regKey = Registry.currentUser.createKey(protocolRegKey);
|
||||
@@ -29,4 +31,4 @@ class Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
final protocol = Protocol();
|
||||
final protocol = Protocol();
|
||||
@@ -125,7 +125,7 @@ class _ConnectionsFragmentState extends ConsumerState<ConnectionsFragment>
|
||||
return ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClickKeyword: (value) {
|
||||
onClick: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
trailing: IconButton(
|
||||
|
||||
@@ -9,15 +9,40 @@ import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ConnectionItem extends ConsumerWidget {
|
||||
class FindProcessBuilder extends StatelessWidget {
|
||||
final Widget Function(bool value) builder;
|
||||
|
||||
const FindProcessBuilder({
|
||||
super.key,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
return builder(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionItem extends StatelessWidget {
|
||||
final Connection connection;
|
||||
final Function(String)? onClickKeyword;
|
||||
final Function(String)? onClick;
|
||||
final Widget? trailing;
|
||||
|
||||
const ConnectionItem({
|
||||
super.key,
|
||||
required this.connection,
|
||||
this.onClickKeyword,
|
||||
this.onClick,
|
||||
this.trailing,
|
||||
});
|
||||
|
||||
@@ -34,14 +59,7 @@ class ConnectionItem extends ConsumerWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
final title = Text(
|
||||
connection.desc,
|
||||
style: context.textTheme.bodyLarge,
|
||||
@@ -68,143 +86,70 @@ class ConnectionItem extends ConsumerWidget {
|
||||
CommonChip(
|
||||
label: chain,
|
||||
onPressed: () {
|
||||
if (onClickKeyword == null) return;
|
||||
onClickKeyword!(chain);
|
||||
if (onClick == null) return;
|
||||
onClick!(chain);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
return CommonPopupBox(
|
||||
targetBuilder: (open) {
|
||||
if (!Platform.isAndroid) {
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
title: title,
|
||||
subtitle: subTitle,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
return FindProcessBuilder(
|
||||
builder: (bool value) {
|
||||
final leading = value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
if (onClick == null) return;
|
||||
final process = connection.metadata.process;
|
||||
if (process.isEmpty) return;
|
||||
onClick!(process);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: FutureBuilder<ImageProvider?>(
|
||||
future: _getPackageIcon(connection),
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData && snapshot.data == null) {
|
||||
return Container();
|
||||
} else {
|
||||
return Image(
|
||||
image: snapshot.data!,
|
||||
gaplessPlayback: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: null;
|
||||
return ListItem(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
leading: value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
if (onClickKeyword == null) return;
|
||||
final process = connection.metadata.process;
|
||||
if (process.isEmpty) return;
|
||||
onClickKeyword!(process);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: FutureBuilder<ImageProvider?>(
|
||||
future: _getPackageIcon(connection),
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData && snapshot.data == null) {
|
||||
return Container();
|
||||
} else {
|
||||
return Image(
|
||||
image: snapshot.data!,
|
||||
gaplessPlayback: true,
|
||||
width: 48,
|
||||
height: 48,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
leading: leading,
|
||||
title: title,
|
||||
subtitle: subTitle,
|
||||
trailing: trailing,
|
||||
);
|
||||
// return InkWell(
|
||||
// child: GestureDetector(
|
||||
// onLongPressStart: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// onSecondaryTapDown: (details) {
|
||||
// if (!system.isDesktop) {
|
||||
// return;
|
||||
// }
|
||||
// open(
|
||||
// offset: details.localPosition.translate(
|
||||
// 0,
|
||||
// -12,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// child: ListItem(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 16,
|
||||
// vertical: 4,
|
||||
// ),
|
||||
// tileTitleAlignment: ListTileTitleAlignment.titleHeight,
|
||||
// leading: value
|
||||
// ? GestureDetector(
|
||||
// onTap: () {
|
||||
// if (onClickKeyword == null) return;
|
||||
// final process = connection.metadata.process;
|
||||
// if (process.isEmpty) return;
|
||||
// onClickKeyword!(process);
|
||||
// },
|
||||
// child: Container(
|
||||
// margin: const EdgeInsets.only(top: 4),
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// child: FutureBuilder<ImageProvider?>(
|
||||
// future: _getPackageIcon(connection),
|
||||
// builder: (_, snapshot) {
|
||||
// if (!snapshot.hasData && snapshot.data == null) {
|
||||
// return Container();
|
||||
// } else {
|
||||
// return Image(
|
||||
// image: snapshot.data!,
|
||||
// gaplessPlayback: true,
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// title: title,
|
||||
// subtitle: subTitle,
|
||||
// trailing: trailing,
|
||||
// ),
|
||||
// ),
|
||||
// onTap: () {},
|
||||
// );
|
||||
},
|
||||
popup: CommonPopupMenu(
|
||||
minWidth: 160,
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
label: "编辑规则",
|
||||
onPressed: () {
|
||||
// _handleShowEditExtendPage(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "设置直连",
|
||||
onPressed: () {},
|
||||
),
|
||||
PopupMenuItemData(
|
||||
label: "一键屏蔽",
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
@@ -138,19 +136,9 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return Consumer(
|
||||
builder: (_, ref, child) {
|
||||
final value = ref.watch(
|
||||
patchClashConfigProvider.select(
|
||||
(state) =>
|
||||
state.findProcessMode == FindProcessMode.always &&
|
||||
Platform.isAndroid,
|
||||
),
|
||||
);
|
||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||
return child!;
|
||||
},
|
||||
child: ValueListenableBuilder<ConnectionsState>(
|
||||
return FindProcessBuilder(builder: (value) {
|
||||
_handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0));
|
||||
return ValueListenableBuilder<ConnectionsState>(
|
||||
valueListenable: _requestsStateNotifier,
|
||||
builder: (_, state, __) {
|
||||
final connections = state.list;
|
||||
@@ -164,7 +152,7 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
(connection) => ConnectionItem(
|
||||
key: Key(connection.id),
|
||||
connection: connection,
|
||||
onClickKeyword: (value) {
|
||||
onClick: (value) {
|
||||
context.commonScaffoldState?.addKeyword(value);
|
||||
},
|
||||
),
|
||||
@@ -218,8 +206,8 @@ class _RequestsFragmentState extends ConsumerState<RequestsFragment>
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@ class ProfileItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = PopupController();
|
||||
return CommonCard(
|
||||
isSelected: profile.id == groupValue,
|
||||
onPressed: () {
|
||||
@@ -315,6 +316,7 @@ class ProfileItem extends StatelessWidget {
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: CommonPopupBox(
|
||||
controller: controller,
|
||||
popup: CommonPopupMenu(
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
@@ -358,14 +360,12 @@ class ProfileItem extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
targetBuilder: (open) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
open();
|
||||
},
|
||||
icon: Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
target: IconButton(
|
||||
onPressed: () {
|
||||
controller.open();
|
||||
},
|
||||
icon: Icon(Icons.more_vert),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,11 +13,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
typedef UpdatingMap = Map<String, bool>;
|
||||
|
||||
class ProvidersView extends ConsumerStatefulWidget {
|
||||
final SheetType type;
|
||||
|
||||
const ProvidersView({
|
||||
super.key,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -25,6 +22,25 @@ class ProvidersView extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _ProvidersViewState extends ConsumerState<ProvidersView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
globalState.appController.updateProviders();
|
||||
context.commonScaffoldState?.actions = [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_updateProviders() async {
|
||||
final providers = ref.read(providersProvider);
|
||||
@@ -86,24 +102,10 @@ class _ProvidersViewState extends ConsumerState<ProvidersView> {
|
||||
title: appLocalizations.ruleProviders,
|
||||
items: ruleProviders,
|
||||
);
|
||||
return AdaptiveSheetScaffold(
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_updateProviders();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.sync,
|
||||
),
|
||||
)
|
||||
],
|
||||
type: widget.type,
|
||||
body: generateListView([
|
||||
...proxySection,
|
||||
...ruleSection,
|
||||
]),
|
||||
title: appLocalizations.providers,
|
||||
);
|
||||
return generateListView([
|
||||
...proxySection,
|
||||
...ruleSection,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ class _ProxiesFragmentState extends ConsumerState<ProxiesFragment>
|
||||
showExtend(
|
||||
context,
|
||||
builder: (_, type) {
|
||||
return ProvidersView(
|
||||
return AdaptiveSheetScaffold(
|
||||
type: type,
|
||||
body: const ProvidersView(),
|
||||
title: appLocalizations.providers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/widgets/fade_box.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageManager extends StatefulWidget {
|
||||
@@ -18,49 +17,41 @@ class MessageManager extends StatefulWidget {
|
||||
State<MessageManager> createState() => MessageManagerState();
|
||||
}
|
||||
|
||||
class MessageManagerState extends State<MessageManager> {
|
||||
class MessageManagerState extends State<MessageManager>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
|
||||
final List<CommonMessage> _bufferMessages = [];
|
||||
Completer<bool>? _messageIngCompleter;
|
||||
double maxWidth = 0;
|
||||
Offset offset = Offset.zero;
|
||||
|
||||
late AnimationController _animationController;
|
||||
|
||||
final animationDuration = commonDuration * 2;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: 400),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messagesNotifier.dispose();
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> message(String text) async {
|
||||
message(String text) async {
|
||||
final commonMessage = CommonMessage(
|
||||
id: other.uuidV4,
|
||||
text: text,
|
||||
);
|
||||
_bufferMessages.add(commonMessage);
|
||||
_showMessage();
|
||||
}
|
||||
|
||||
_showMessage() async {
|
||||
if (_messageIngCompleter?.isCompleted == false) {
|
||||
return;
|
||||
}
|
||||
while (_bufferMessages.isNotEmpty) {
|
||||
final commonMessage = _bufferMessages.removeAt(0);
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)
|
||||
..add(
|
||||
commonMessage,
|
||||
);
|
||||
|
||||
_messageIngCompleter = Completer();
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
Future.delayed(commonMessage.duration, () {
|
||||
_handleRemove(commonMessage);
|
||||
});
|
||||
_messageIngCompleter?.complete(true);
|
||||
}
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)
|
||||
..add(
|
||||
commonMessage,
|
||||
);
|
||||
}
|
||||
|
||||
_handleRemove(CommonMessage commonMessage) async {
|
||||
@@ -73,49 +64,171 @@ class MessageManagerState extends State<MessageManager> {
|
||||
return Stack(
|
||||
children: [
|
||||
widget.child,
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _messagesNotifier,
|
||||
builder: (_, messages, __) {
|
||||
return FadeThroughBox(
|
||||
alignment: Alignment.topRight,
|
||||
child: messages.isEmpty
|
||||
? SizedBox()
|
||||
: LayoutBuilder(
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
elevation: 10,
|
||||
margin: EdgeInsets.only(
|
||||
top: kToolbarHeight,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(
|
||||
constraints.maxWidth,
|
||||
400,
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Text(
|
||||
messages.last.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, container) {
|
||||
maxWidth = container.maxWidth / 2 + 16;
|
||||
return SizedBox(
|
||||
width: maxWidth,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: globalState.safeMessageOffsetNotifier,
|
||||
builder: (_, offset, child) {
|
||||
this.offset = offset;
|
||||
if (offset == Offset.zero) {
|
||||
return SizedBox();
|
||||
}
|
||||
return Transform.translate(
|
||||
offset: offset,
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
right: 0,
|
||||
left: 8,
|
||||
top: 0,
|
||||
bottom: 16,
|
||||
),
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomLeft,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
reverse: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _messagesNotifier,
|
||||
builder: (_, messages, ___) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final message in messages) ...[
|
||||
if (message != messages.first)
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_MessageItem(
|
||||
key: GlobalObjectKey(message.id),
|
||||
message: message,
|
||||
onRemove: _handleRemove,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageItem extends StatefulWidget {
|
||||
final CommonMessage message;
|
||||
final Function(CommonMessage message) onRemove;
|
||||
|
||||
const _MessageItem({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.onRemove,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_MessageItem> createState() => _MessageItemState();
|
||||
}
|
||||
|
||||
class _MessageItemState extends State<_MessageItem>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<Offset> _offsetAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: commonDuration * 1.5,
|
||||
);
|
||||
_offsetAnimation = Tween<Offset>(
|
||||
begin: Offset(-1.0, 0.0),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(
|
||||
0.0,
|
||||
1,
|
||||
curve: Curves.easeOut,
|
||||
),
|
||||
));
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(
|
||||
0.0,
|
||||
0.2,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
));
|
||||
|
||||
_controller.forward();
|
||||
|
||||
Future.delayed(
|
||||
widget.message.duration,
|
||||
() async {
|
||||
await _controller.reverse();
|
||||
widget.onRemove(
|
||||
widget.message,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller.view,
|
||||
builder: (_, child) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _offsetAnimation,
|
||||
child: Material(
|
||||
elevation: _controller.value * 12,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
clipBehavior: Clip.none,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||
child: Text(
|
||||
widget.message.text,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 5,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ProxyGroup with _$ProxyGroup {
|
||||
String? filter,
|
||||
@JsonKey(name: "expected-filter") String? excludeFilter,
|
||||
@JsonKey(name: "exclude-type") String? excludeType,
|
||||
@JsonKey(name: "expected-status") dynamic expectedStatus,
|
||||
@JsonKey(name: "expected-status") int? expectedStatus,
|
||||
bool? hidden,
|
||||
String? icon,
|
||||
}) = _ProxyGroup;
|
||||
|
||||
@@ -37,7 +37,7 @@ mixin _$ProxyGroup {
|
||||
@JsonKey(name: "exclude-type")
|
||||
String? get excludeType => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "expected-status")
|
||||
dynamic get expectedStatus => throw _privateConstructorUsedError;
|
||||
int? get expectedStatus => throw _privateConstructorUsedError;
|
||||
bool? get hidden => throw _privateConstructorUsedError;
|
||||
String? get icon => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -70,7 +70,7 @@ abstract class $ProxyGroupCopyWith<$Res> {
|
||||
String? filter,
|
||||
@JsonKey(name: "expected-filter") String? excludeFilter,
|
||||
@JsonKey(name: "exclude-type") String? excludeType,
|
||||
@JsonKey(name: "expected-status") dynamic expectedStatus,
|
||||
@JsonKey(name: "expected-status") int? expectedStatus,
|
||||
bool? hidden,
|
||||
String? icon});
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class _$ProxyGroupCopyWithImpl<$Res, $Val extends ProxyGroup>
|
||||
expectedStatus: freezed == expectedStatus
|
||||
? _value.expectedStatus
|
||||
: expectedStatus // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as int?,
|
||||
hidden: freezed == hidden
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
@@ -192,7 +192,7 @@ abstract class _$$ProxyGroupImplCopyWith<$Res>
|
||||
String? filter,
|
||||
@JsonKey(name: "expected-filter") String? excludeFilter,
|
||||
@JsonKey(name: "exclude-type") String? excludeType,
|
||||
@JsonKey(name: "expected-status") dynamic expectedStatus,
|
||||
@JsonKey(name: "expected-status") int? expectedStatus,
|
||||
bool? hidden,
|
||||
String? icon});
|
||||
}
|
||||
@@ -278,7 +278,7 @@ class __$$ProxyGroupImplCopyWithImpl<$Res>
|
||||
expectedStatus: freezed == expectedStatus
|
||||
? _value.expectedStatus
|
||||
: expectedStatus // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as int?,
|
||||
hidden: freezed == hidden
|
||||
? _value.hidden
|
||||
: hidden // ignore: cast_nullable_to_non_nullable
|
||||
@@ -362,7 +362,7 @@ class _$ProxyGroupImpl implements _ProxyGroup {
|
||||
final String? excludeType;
|
||||
@override
|
||||
@JsonKey(name: "expected-status")
|
||||
final dynamic expectedStatus;
|
||||
final int? expectedStatus;
|
||||
@override
|
||||
final bool? hidden;
|
||||
@override
|
||||
@@ -394,8 +394,8 @@ class _$ProxyGroupImpl implements _ProxyGroup {
|
||||
other.excludeFilter == excludeFilter) &&
|
||||
(identical(other.excludeType, excludeType) ||
|
||||
other.excludeType == excludeType) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.expectedStatus, expectedStatus) &&
|
||||
(identical(other.expectedStatus, expectedStatus) ||
|
||||
other.expectedStatus == expectedStatus) &&
|
||||
(identical(other.hidden, hidden) || other.hidden == hidden) &&
|
||||
(identical(other.icon, icon) || other.icon == icon));
|
||||
}
|
||||
@@ -416,7 +416,7 @@ class _$ProxyGroupImpl implements _ProxyGroup {
|
||||
filter,
|
||||
excludeFilter,
|
||||
excludeType,
|
||||
const DeepCollectionEquality().hash(expectedStatus),
|
||||
expectedStatus,
|
||||
hidden,
|
||||
icon);
|
||||
|
||||
@@ -451,7 +451,7 @@ abstract class _ProxyGroup implements ProxyGroup {
|
||||
final String? filter,
|
||||
@JsonKey(name: "expected-filter") final String? excludeFilter,
|
||||
@JsonKey(name: "exclude-type") final String? excludeType,
|
||||
@JsonKey(name: "expected-status") final dynamic expectedStatus,
|
||||
@JsonKey(name: "expected-status") final int? expectedStatus,
|
||||
final bool? hidden,
|
||||
final String? icon}) = _$ProxyGroupImpl;
|
||||
|
||||
@@ -488,7 +488,7 @@ abstract class _ProxyGroup implements ProxyGroup {
|
||||
String? get excludeType;
|
||||
@override
|
||||
@JsonKey(name: "expected-status")
|
||||
dynamic get expectedStatus;
|
||||
int? get expectedStatus;
|
||||
@override
|
||||
bool? get hidden;
|
||||
@override
|
||||
|
||||
@@ -21,7 +21,7 @@ _$ProxyGroupImpl _$$ProxyGroupImplFromJson(Map<String, dynamic> json) =>
|
||||
filter: json['filter'] as String?,
|
||||
excludeFilter: json['expected-filter'] as String?,
|
||||
excludeType: json['exclude-type'] as String?,
|
||||
expectedStatus: json['expected-status'],
|
||||
expectedStatus: (json['expected-status'] as num?)?.toInt(),
|
||||
hidden: json['hidden'] as bool?,
|
||||
icon: json['icon'] as String?,
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ class EditorPage extends ConsumerStatefulWidget {
|
||||
class _EditorPageState extends ConsumerState<EditorPage> {
|
||||
late CodeLineEditingController _controller;
|
||||
late CodeFindController _findController;
|
||||
final _popupController = PopupController();
|
||||
final _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
@@ -120,12 +121,11 @@ class _EditorPageState extends ConsumerState<EditorPage> {
|
||||
),
|
||||
_wrapController(
|
||||
(value) => CommonPopupBox(
|
||||
targetBuilder: (open) {
|
||||
return IconButton(
|
||||
onPressed: open,
|
||||
icon: const Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
controller: _popupController,
|
||||
target: IconButton(
|
||||
onPressed: _popupController.open,
|
||||
icon: const Icon(Icons.more_vert),
|
||||
),
|
||||
popup: CommonPopupMenu(
|
||||
items: [
|
||||
PopupMenuItemData(
|
||||
|
||||
@@ -153,8 +153,26 @@ class CommonNavigationBar extends ConsumerWidget {
|
||||
required this.currentIndex,
|
||||
});
|
||||
|
||||
_updateSafeMessageOffset(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final size = context.size;
|
||||
if (viewMode == ViewMode.mobile) {
|
||||
globalState.safeMessageOffsetNotifier.value = Offset(
|
||||
0,
|
||||
-(size?.height ?? 0),
|
||||
);
|
||||
} else {
|
||||
globalState.safeMessageOffsetNotifier.value = Offset(
|
||||
size?.width ?? 0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
_updateSafeMessageOffset(context);
|
||||
if (viewMode == ViewMode.mobile) {
|
||||
return NavigationBarTheme(
|
||||
data: _NavigationBarDefaultsM3(context),
|
||||
|
||||
@@ -32,6 +32,7 @@ class GlobalState {
|
||||
late CommonTheme theme;
|
||||
DateTime? startTime;
|
||||
UpdateTasks tasks = [];
|
||||
final safeMessageOffsetNotifier = ValueNotifier(Offset.zero);
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
late AppController appController;
|
||||
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
|
||||
|
||||
@@ -4,12 +4,10 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class FadeBox extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Alignment? alignment;
|
||||
|
||||
const FadeBox({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.alignment,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -21,7 +19,7 @@ class FadeBox extends StatelessWidget {
|
||||
secondaryAnimation,
|
||||
) {
|
||||
return Container(
|
||||
alignment: alignment ?? Alignment.centerLeft,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
@@ -35,12 +33,10 @@ class FadeBox extends StatelessWidget {
|
||||
|
||||
class FadeThroughBox extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Alignment? alignment;
|
||||
|
||||
const FadeThroughBox({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.alignment,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -52,7 +48,7 @@ class FadeThroughBox extends StatelessWidget {
|
||||
secondaryAnimation,
|
||||
) {
|
||||
return Container(
|
||||
alignment: alignment ?? Alignment.centerLeft,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FadeThroughTransition(
|
||||
animation: animation,
|
||||
fillColor: Colors.transparent,
|
||||
|
||||
@@ -40,41 +40,39 @@ class CommonPopupRoute<T> extends PopupRoute<T> {
|
||||
parent: animation,
|
||||
curve: Curves.easeIn,
|
||||
).value;
|
||||
return SafeArea(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: offsetNotifier,
|
||||
builder: (_, value, child) {
|
||||
return Align(
|
||||
alignment: align,
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: OverflowAwareLayoutDelegate(
|
||||
offset: value.translate(
|
||||
48,
|
||||
-8,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: offsetNotifier,
|
||||
builder: (_, value, child) {
|
||||
return Align(
|
||||
alignment: align,
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: OverflowAwareLayoutDelegate(
|
||||
offset: value.translate(
|
||||
48,
|
||||
12,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
return Opacity(
|
||||
opacity: 0.1 + 0.9 * animationValue,
|
||||
child: Transform.scale(
|
||||
alignment: align,
|
||||
scale: 0.8 + 0.2 * animationValue,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -10) * (1 - animationValue),
|
||||
child: child!,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, Widget? child) {
|
||||
return Opacity(
|
||||
opacity: 0.1 + 0.9 * animationValue,
|
||||
child: Transform.scale(
|
||||
alignment: align,
|
||||
scale: 0.8 + 0.2 * animationValue,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -10) * (1 - animationValue),
|
||||
child: child!,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: builder(
|
||||
context,
|
||||
),
|
||||
child: builder(
|
||||
context,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -96,18 +94,16 @@ class PopupController extends ValueNotifier<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
typedef PopupOpen = Function({
|
||||
Offset offset,
|
||||
});
|
||||
|
||||
class CommonPopupBox extends StatefulWidget {
|
||||
final Widget Function(PopupOpen open) targetBuilder;
|
||||
final Widget target;
|
||||
final Widget popup;
|
||||
final PopupController? controller;
|
||||
|
||||
const CommonPopupBox({
|
||||
super.key,
|
||||
required this.targetBuilder,
|
||||
required this.target,
|
||||
required this.popup,
|
||||
this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -115,14 +111,38 @@ class CommonPopupBox extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CommonPopupBoxState extends State<CommonPopupBox> {
|
||||
bool _isOpen = false;
|
||||
final _targetOffsetValueNotifier = ValueNotifier<Offset>(Offset.zero);
|
||||
Offset _offset = Offset.zero;
|
||||
final _targetOffsetValueNotifier = ValueNotifier(Offset.zero);
|
||||
|
||||
_open({Offset offset = Offset.zero}) {
|
||||
_offset = offset;
|
||||
_updateOffset();
|
||||
_isOpen = true;
|
||||
@override
|
||||
void initState() {
|
||||
widget.controller?.addListener(_handleChange);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CommonPopupBox oldWidget) {
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller?.removeListener(_handleChange);
|
||||
oldWidget.controller?.dispose();
|
||||
widget.controller?.addListener(_handleChange);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller?.removeListener(_handleChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_handleChange() {
|
||||
if (widget.controller?.value == true) {
|
||||
_open();
|
||||
}
|
||||
}
|
||||
|
||||
_open() {
|
||||
_handleTargetOffset();
|
||||
Navigator.of(context)
|
||||
.push(
|
||||
CommonPopupRoute(
|
||||
@@ -133,37 +153,24 @@ class _CommonPopupBoxState extends State<CommonPopupBox> {
|
||||
offsetNotifier: _targetOffsetValueNotifier,
|
||||
),
|
||||
)
|
||||
.then((_) {
|
||||
_isOpen = false;
|
||||
.then((res) {
|
||||
widget.controller?.close();
|
||||
});
|
||||
}
|
||||
|
||||
_updateOffset() {
|
||||
_handleTargetOffset() {
|
||||
final renderBox = context.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) {
|
||||
return;
|
||||
}
|
||||
final viewPadding = MediaQuery.of(context).viewPadding;
|
||||
_targetOffsetValueNotifier.value = renderBox
|
||||
.localToGlobal(
|
||||
Offset.zero.translate(viewPadding.right, viewPadding.top),
|
||||
)
|
||||
.translate(
|
||||
_offset.dx,
|
||||
_offset.dy,
|
||||
);
|
||||
_targetOffsetValueNotifier.value = renderBox.localToGlobal(
|
||||
Offset.zero,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (_, __) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_isOpen) {
|
||||
_updateOffset();
|
||||
}
|
||||
});
|
||||
return widget.targetBuilder(_open);
|
||||
});
|
||||
return widget.target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,14 +188,14 @@ class OverflowAwareLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
final safeOffset = Offset(16, 16);
|
||||
final saveOffset = Offset(16, 16);
|
||||
double x = (offset.dx - childSize.width).clamp(
|
||||
0,
|
||||
size.width - safeOffset.dx - childSize.width,
|
||||
size.width - saveOffset.dx - childSize.width,
|
||||
);
|
||||
double y = (offset.dy).clamp(
|
||||
0,
|
||||
size.height - safeOffset.dy - childSize.height,
|
||||
size.height - saveOffset.dy - childSize.height,
|
||||
);
|
||||
return Offset(x, y);
|
||||
}
|
||||
@@ -201,12 +208,10 @@ class OverflowAwareLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
|
||||
class CommonPopupMenu extends StatelessWidget {
|
||||
final List<PopupMenuItemData> items;
|
||||
final double? minWidth;
|
||||
|
||||
const CommonPopupMenu({
|
||||
super.key,
|
||||
required this.items,
|
||||
this.minWidth,
|
||||
});
|
||||
|
||||
Widget _popupMenuItem(
|
||||
@@ -231,10 +236,7 @@ class CommonPopupMenu extends StatelessWidget {
|
||||
onPressed();
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: minWidth ?? 120,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 64,
|
||||
|
||||
@@ -136,6 +136,11 @@ class _AdaptiveSheetScaffoldState extends State<AdaptiveSheetScaffold> {
|
||||
: true,
|
||||
centerTitle: bottomSheet,
|
||||
backgroundColor: backgroundColor,
|
||||
// titleTextStyle: bottomSheet
|
||||
// ? context.textTheme.labelMedium?.copyWith(
|
||||
// fontSize: 20,
|
||||
// )
|
||||
// : null,
|
||||
title: Text(
|
||||
widget.title,
|
||||
),
|
||||
|
||||
@@ -99,9 +99,9 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d
|
||||
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
|
||||
connectivity_plus: 2256d3e20624a7749ed21653aafe291a46446fee
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
device_info_plus: a56e6e74dbbd2bb92f2da12c64ddd4f67a749041
|
||||
dynamic_color: b820c000cc68df65e7ba7ff177cb98404ce56651
|
||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */; };
|
||||
F50091052CF74B7700D43AEA /* FlClashCore in CopyFiles */ = {isa = PBXBuildFile; fileRef = F50091042CF74B7700D43AEA /* FlClashCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
F5AA39AF2DA1D9FB00F5C816 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = F5AA39AE2DA1D9FB00F5C816 /* LaunchAtLogin */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -136,7 +135,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5AA39AF2DA1D9FB00F5C816 /* LaunchAtLogin in Frameworks */,
|
||||
CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -328,9 +326,6 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
packageReferences = (
|
||||
F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
|
||||
);
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -832,25 +827,6 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
F5AA39AE2DA1D9FB00F5C816 /* LaunchAtLogin */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */;
|
||||
productName = LaunchAtLogin;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import window_manager
|
||||
import LaunchAtLogin
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
FlutterMethodChannel(
|
||||
name: "launch_at_startup", binaryMessenger: flutterViewController.engine.binaryMessenger
|
||||
)
|
||||
.setMethodCallHandler { (_ call: FlutterMethodCall, result: @escaping FlutterResult) in
|
||||
switch call.method {
|
||||
case "launchAtStartupIsEnabled":
|
||||
result(LaunchAtLogin.isEnabled)
|
||||
case "launchAtStartupSetEnabled":
|
||||
if let arguments = call.arguments as? [String: Any] {
|
||||
LaunchAtLogin.isEnabled = arguments["setEnabledValue"] as! Bool
|
||||
}
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
super.awakeFromNib()
|
||||
}
|
||||
override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
|
||||
super.order(place, relativeTo: otherWin)
|
||||
hiddenWindowAtLaunch()
|
||||
}
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
super.awakeFromNib()
|
||||
}
|
||||
override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
|
||||
super.order(place, relativeTo: otherWin)
|
||||
hiddenWindowAtLaunch()
|
||||
}
|
||||
}
|
||||
|
||||
54
pubspec.lock
54
pubspec.lock
@@ -42,34 +42,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
|
||||
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_linux
|
||||
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
app_links_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_platform_interface
|
||||
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
app_links_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_web
|
||||
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "3.5.1"
|
||||
archive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -346,10 +322,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513"
|
||||
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.3"
|
||||
version: "10.1.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -410,10 +386,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -774,10 +750,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: launch_at_startup
|
||||
sha256: "7db33398b76ec0ed9e27f9f4640553e239977437564046625e215be89c91f084"
|
||||
sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.2.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1378,10 +1354,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: tray_manager
|
||||
sha256: c2da0f0f1ddb455e721cf68d05d1281fec75cf5df0a1d3cb67b6ca0bdfd5709d
|
||||
sha256: "80be6c508159a6f3c57983de795209ac13453e9832fd574143b06dceee188ed2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.3.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1530,18 +1506,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: win32
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.12.0"
|
||||
version: "5.9.0"
|
||||
win32_registry:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
|
||||
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "1.1.5"
|
||||
window_ext:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1598,5 +1574,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
12
pubspec.yaml
12
pubspec.yaml
@@ -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.81+202504081
|
||||
version: 0.8.81+202504151
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -20,14 +20,14 @@ dependencies:
|
||||
path: plugins/proxy
|
||||
window_ext:
|
||||
path: plugins/window_ext
|
||||
launch_at_startup: ^0.5.1
|
||||
launch_at_startup: ^0.2.2
|
||||
windows_single_instance: ^1.0.1
|
||||
json_annotation: ^4.9.0
|
||||
file_picker: ^8.0.3
|
||||
mobile_scanner: ^6.0.2
|
||||
app_links: ^6.4.0
|
||||
win32_registry: ^2.0.0
|
||||
tray_manager: ^0.4.0
|
||||
app_links: ^3.5.0
|
||||
win32_registry: ^1.1.5
|
||||
tray_manager: ^0.3.2
|
||||
collection: ^1.18.0
|
||||
animations: ^2.0.11
|
||||
package_info_plus: ^8.0.0
|
||||
@@ -46,7 +46,7 @@ dependencies:
|
||||
cached_network_image: ^3.4.0
|
||||
hotkey_manager: ^0.2.3
|
||||
uni_platform: ^0.1.3
|
||||
device_info_plus: ^11.3.3
|
||||
device_info_plus: ^10.1.2
|
||||
connectivity_plus: ^6.1.0
|
||||
screen_retriever: ^0.2.0
|
||||
defer_pointer: ^0.0.2
|
||||
|
||||
Reference in New Issue
Block a user