Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
ed7868282a Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Update go version

Optimize more details
2025-09-23 15:23:58 +08:00
13 changed files with 226 additions and 173 deletions

View File

@@ -103,6 +103,9 @@ object State {
suspend fun startServiceWithEngine() {
runLock.withLock {
if (serviceFlutterEngine != null || runStateFlow.value == RunState.PENDING || runStateFlow.value == RunState.START) {
return
}
withContext(Dispatchers.Main) {
serviceFlutterEngine = FlutterEngine(GlobalState.application)
serviceFlutterEngine?.plugins?.add(ServicePlugin())

View File

@@ -25,7 +25,7 @@
android:process=":remote">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="service" />
android:value="proxy" />
</service>
<service

View File

@@ -1,5 +1,6 @@
package com.follow.clash.service.models
import com.follow.clash.common.GlobalState
import com.follow.clash.common.formatBytes
import com.follow.clash.core.Core
import com.google.gson.Gson
@@ -17,7 +18,8 @@ fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
val res = getTraffic(onlyStatisticsProxy)
val traffic = Gson().fromJson(res, Traffic::class.java)
return traffic.speedText
} catch (_: Exception) {
} catch (e: Exception) {
GlobalState.log(e.message + "")
return ""
}
}

View File

@@ -47,9 +47,6 @@ class NotificationModule(private val service: Service) : Module() {
private val scope = CoroutineScope(Dispatchers.Default)
override fun onInstall() {
State.notificationParamsFlow.value?.let {
update(it.extended)
}
scope.launch {
val screenFlow = service.receiveBroadcastFlow {
addAction(Intent.ACTION_SCREEN_ON)
@@ -69,6 +66,12 @@ class NotificationModule(private val service: Service) : Module() {
.collect { (params, _) ->
update(params!!)
}
State.notificationParamsFlow.value?.let {
update(it.extended)
} ?: run {
update(NotificationParams().extended)
}
}
}

View File

@@ -36,16 +36,25 @@ class Throttler {
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
bool fire = false,
}) {
final timer = _operations[tag];
if (timer != null) {
return true;
}
_operations[tag] = Timer(duration, () {
_operations[tag]?.cancel();
_operations.remove(tag);
if (fire) {
Function.apply(func, args);
});
_operations[tag] = Timer(duration, () {
_operations[tag]?.cancel();
_operations.remove(tag);
});
} else {
_operations[tag] = Timer(duration, () {
Function.apply(func, args);
_operations[tag]?.cancel();
_operations.remove(tag);
});
}
return false;
}

View File

@@ -134,11 +134,15 @@ class CommonPageTransition extends StatefulWidget {
bool allowSnapshotting,
Widget? child,
) {
final Animation<Offset> delegatedPositionAnimation = CurvedAnimation(
final CurvedAnimation animation = CurvedAnimation(
parent: secondaryAnimation,
curve: Curves.linearToEaseOut,
reverseCurve: Curves.easeInToLinear,
).drive(_kMiddleLeftTween);
);
final Animation<Offset> delegatedPositionAnimation = animation.drive(
_kMiddleLeftTween,
);
animation.dispose();
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);

View File

@@ -552,7 +552,12 @@ class AppController {
Future<void> _connectCore() async {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connecting;
final message = await coreController.preload();
final result = await Future.wait([
coreController.preload(),
if (!globalState.isService) Future.delayed(Duration(milliseconds: 300)),
]);
final String message = result[0];
await Future.delayed(commonDuration);
if (message.isNotEmpty) {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
if (context.mounted) {

View File

@@ -24,11 +24,16 @@ class _VpnContainerState extends ConsumerState<VpnManager> {
}
void showTip() {
debouncer.call(FunctionTag.vpnTip, () {
if (ref.read(isStartProvider)) {
globalState.showNotifier(appLocalizations.vpnTip);
}
});
throttler.call(
FunctionTag.vpnTip,
() {
if (ref.read(isStartProvider)) {
globalState.showNotifier(appLocalizations.vpnTip);
}
},
duration: const Duration(seconds: 6),
fire: true,
);
}
@override

View File

@@ -14,7 +14,7 @@ class StartButton extends ConsumerStatefulWidget {
class _StartButtonState extends ConsumerState<StartButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
AnimationController? _controller;
late Animation<double> _animation;
bool isStart = false;
@@ -28,7 +28,7 @@ class _StartButtonState extends ConsumerState<StartButton>
duration: const Duration(milliseconds: 200),
);
_animation = CurvedAnimation(
parent: _controller,
parent: _controller!,
curve: Curves.easeOutBack,
);
ref.listenManual(runTimeProvider.select((state) => state != null), (
@@ -44,7 +44,8 @@ class _StartButtonState extends ConsumerState<StartButton>
@override
void dispose() {
_controller.dispose();
_controller?.dispose();
_controller = null;
super.dispose();
}
@@ -59,9 +60,9 @@ class _StartButtonState extends ConsumerState<StartButton>
void updateController() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (isStart && mounted) {
_controller.forward();
_controller?.forward();
} else {
_controller.reverse();
_controller?.reverse();
}
});
}
@@ -80,7 +81,7 @@ class _StartButtonState extends ConsumerState<StartButton>
),
),
child: AnimatedBuilder(
animation: _controller.view,
animation: _controller!.view,
builder: (_, child) {
final textWidth =
globalState.measure

View File

@@ -282,7 +282,7 @@ class ListItem<T> extends StatelessWidget {
closedBuilder: (_, action) {
openAction() {
final isMobile = globalState.appState.viewMode == ViewMode.mobile;
if (!isMobile || system.isDesktop) {
if (!isMobile) {
showExtend(
context,
props: ExtendProps(

View File

@@ -2,19 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
typedef CloseContainerActionCallback<S> = void Function({S? returnValue});
typedef OpenContainerBuilder<S> = Widget Function(
BuildContext context,
CloseContainerActionCallback<S> action,
);
typedef CloseContainerBuilder = Widget Function(
BuildContext context,
VoidCallback action,
);
typedef OpenContainerBuilder<S> =
Widget Function(
BuildContext context,
CloseContainerActionCallback<S> action,
);
typedef CloseContainerBuilder =
Widget Function(BuildContext context, VoidCallback action);
enum ContainerTransitionType {
fade,
fadeThrough,
}
enum ContainerTransitionType { fade, fadeThrough }
typedef ClosedCallback<S> = void Function(S data);
@@ -56,20 +52,23 @@ class _OpenContainerState<T> extends State<OpenContainer<T?>> {
Future<void> openContainer() async {
final Color middleColor =
widget.middleColor ?? Theme.of(context).canvasColor;
final T? data = await Navigator.of(
context,
rootNavigator: widget.useRootNavigator,
).push(_OpenContainerRoute<T>(
middleColor: middleColor,
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
transitionDuration: widget.transitionDuration,
transitionType: widget.transitionType,
useRootNavigator: widget.useRootNavigator,
routeSettings: widget.routeSettings,
));
final T? data =
await Navigator.of(
context,
rootNavigator: widget.useRootNavigator,
).push(
_OpenContainerRoute<T>(
middleColor: middleColor,
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
transitionDuration: widget.transitionDuration,
transitionType: widget.transitionType,
useRootNavigator: widget.useRootNavigator,
routeSettings: widget.routeSettings,
),
);
if (widget.onClosed != null) {
widget.onClosed!(data);
}
@@ -97,10 +96,7 @@ class _OpenContainerState<T> extends State<OpenContainer<T?>> {
}
class _Hideable extends StatefulWidget {
const _Hideable({
super.key,
required this.child,
});
const _Hideable({super.key, required this.child});
final Widget child;
@@ -161,9 +157,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
required this.transitionType,
required this.useRootNavigator,
required RouteSettings? routeSettings,
}) : _closedOpacityTween = _getClosedOpacityTween(transitionType),
_openOpacityTween = _getOpenOpacityTween(transitionType),
super(settings: routeSettings);
}) : _closedOpacityTween = _getClosedOpacityTween(transitionType),
_openOpacityTween = _getOpenOpacityTween(transitionType),
super(settings: routeSettings);
static _FlippableTweenSequence<Color?> _getColorTween({
required ContainerTransitionType transitionType,
@@ -173,99 +169,89 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
}) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(closedColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: openColor),
weight: 1 / 5,
),
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(openColor),
weight: 3 / 5,
),
],
);
return _FlippableTweenSequence<Color?>(<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(closedColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: openColor),
weight: 1 / 5,
),
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(openColor),
weight: 3 / 5,
),
]);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: middleColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: middleColor, end: openColor),
weight: 4 / 5,
),
],
);
return _FlippableTweenSequence<Color?>(<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: middleColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: middleColor, end: openColor),
weight: 4 / 5,
),
]);
}
}
static _FlippableTweenSequence<double> _getClosedOpacityTween(
ContainerTransitionType transitionType) {
ContainerTransitionType transitionType,
) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 1,
),
],
);
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 1,
),
]);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 4 / 5,
),
],
);
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 4 / 5,
),
]);
}
}
static _FlippableTweenSequence<double> _getOpenOpacityTween(
ContainerTransitionType transitionType) {
ContainerTransitionType transitionType,
) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 3 / 5,
),
],
);
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 3 / 5,
),
]);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 4 / 5,
),
],
);
return _FlippableTweenSequence<double>(<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 4 / 5,
),
]);
}
}
@@ -325,8 +311,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
@override
void dispose() {
if (hideableKey.currentState?.isVisible == false) {
SchedulerBinding.instance
.addPostFrameCallback((Duration d) => _toggleHideable(hide: false));
SchedulerBinding.instance.addPostFrameCallback(
(Duration d) => _toggleHideable(hide: false),
);
}
super.dispose();
}
@@ -343,10 +330,12 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
required BuildContext navigatorContext,
bool delayForSourceRoute = false,
}) {
final RenderBox navigator = Navigator.of(
navigatorContext,
rootNavigator: useRootNavigator,
).context.findRenderObject()! as RenderBox;
final RenderBox navigator =
Navigator.of(
navigatorContext,
rootNavigator: useRootNavigator,
).context.findRenderObject()!
as RenderBox;
final Size navSize = _getSize(navigator);
_rectTween.end = Offset.zero & navSize;
@@ -359,8 +348,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
}
if (delayForSourceRoute) {
SchedulerBinding.instance
.addPostFrameCallback(takeMeasurementsInSourceRoute);
SchedulerBinding.instance.addPostFrameCallback(
takeMeasurementsInSourceRoute,
);
} else {
takeMeasurementsInSourceRoute();
}
@@ -451,8 +441,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
final Animation<double> curvedAnimation = CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
reverseCurve:
_transitionWasInterrupted ? null : Curves.fastOutSlowIn.flipped,
reverseCurve: _transitionWasInterrupted
? null
: Curves.fastOutSlowIn.flipped,
);
TweenSequence<Color?>? colorTween;
TweenSequence<double>? closedOpacityTween, openOpacityTween;
@@ -508,8 +499,9 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
child: (hideableKey.currentState?.isInTree ?? false)
? null
: FadeTransition(
opacity:
closedOpacityTween!.animate(animation),
opacity: closedOpacityTween!.animate(
animation,
),
child: Builder(
key: closedBuilderKey,
builder: (BuildContext context) {
@@ -521,22 +513,18 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
),
),
),
// Open child fading in.
FittedBox(
fit: BoxFit.fitWidth,
OverflowBox(
maxWidth: _rectTween.end!.width,
maxHeight: _rectTween.end!.height,
alignment: Alignment.topLeft,
child: SizedBox(
width: _rectTween.end!.width,
height: _rectTween.end!.height,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
),
@@ -578,10 +566,12 @@ class _FlippableTweenSequence<T> extends TweenSequence<T> {
if (_flipped == null) {
final List<TweenSequenceItem<T>> newItems = <TweenSequenceItem<T>>[];
for (int i = 0; i < _items.length; i++) {
newItems.add(TweenSequenceItem<T>(
tween: _items[i].tween,
weight: _items[_items.length - 1 - i].weight,
));
newItems.add(
TweenSequenceItem<T>(
tween: _items[i].tween,
weight: _items[_items.length - 1 - i].weight,
),
);
}
_flipped = _FlippableTweenSequence<T>(newItems);
}

View File

@@ -18,7 +18,6 @@ class CommonScaffold extends StatefulWidget {
final Widget body;
final Color? backgroundColor;
final String? title;
final Widget? leading;
final List<Widget>? actions;
final bool? centerTitle;
final Widget? floatingActionButton;
@@ -31,7 +30,6 @@ class CommonScaffold extends StatefulWidget {
this.appBar,
required this.body,
this.backgroundColor,
this.leading,
this.title,
this.actions,
this.centerTitle,
@@ -163,7 +161,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
_keywordsNotifier.value = keywords;
}
Widget? _buildLeading() {
Widget? _buildLeading(VoidCallback? backAction) {
if (_isEdit) {
return IconButton(
onPressed: _appBarState.value.editState?.onExit,
@@ -176,7 +174,16 @@ class CommonScaffoldState extends State<CommonScaffold> {
icon: Icon(Icons.arrow_back),
);
}
return widget.leading;
return backAction != null
? BackButton(
onPressed: () {
if (!mounted) {
return;
}
backAction();
},
)
: null;
}
Widget _buildTitle(AppBarSearchState? startState) {
@@ -251,7 +258,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
);
}
PreferredSizeWidget _buildAppBar() {
PreferredSizeWidget _buildAppBar(VoidCallback? backAction) {
return PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack(
@@ -263,8 +270,11 @@ class CommonScaffoldState extends State<CommonScaffold> {
builder: (_, state, _) {
return _buildAppBarWrap(
AppBar(
automaticallyImplyLeading: backAction != null
? false
: true,
centerTitle: widget.centerTitle ?? false,
leading: _buildLeading(),
leading: _buildLeading(backAction),
title: _buildTitle(state.searchState),
actions: _buildActions(
state.searchState != null,
@@ -285,6 +295,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
@override
Widget build(BuildContext context) {
assert(widget.appBar != null || widget.title != null);
final backActionProvider = CommonScaffoldBackActionProvider.of(context);
final body = SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -327,7 +338,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
),
);
return Scaffold(
appBar: _buildAppBar(),
appBar: _buildAppBar(backActionProvider?.backAction),
body: body,
resizeToAvoidBottomInset: true,
backgroundColor: widget.backgroundColor,
@@ -349,3 +360,23 @@ List<Widget> genActions(List<Widget> actions, {double? space}) {
SizedBox(width: 8),
];
}
class CommonScaffoldBackActionProvider extends InheritedWidget {
final VoidCallback? backAction;
const CommonScaffoldBackActionProvider({
super.key,
required this.backAction,
required super.child,
});
static CommonScaffoldBackActionProvider? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<CommonScaffoldBackActionProvider>();
}
@override
bool updateShouldNotify(CommonScaffoldBackActionProvider oldWidget) {
return false;
}
}

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.88+2025092101
version: 0.8.88+2025092301
environment:
sdk: '>=3.8.0 <4.0.0'