Merge branch 'main' of hiddify-github:hiddify/hiddify-next
This commit is contained in:
26
changelog.md
26
changelog.md
@@ -1,7 +1,31 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
## 0.7.2 (2023-10-04)
|
## 0.8.0 (2023-10-05)
|
||||||
|
|
||||||
|
#### New
|
||||||
|
|
||||||
|
* Add russian lang.
|
||||||
|
|
||||||
|
#### Other
|
||||||
|
|
||||||
|
* Add proxy tag sanitization.
|
||||||
|
|
||||||
|
* Fix bugs.
|
||||||
|
|
||||||
|
* Add ignore app update version.
|
||||||
|
|
||||||
|
* Add new protocols to link parser.
|
||||||
|
|
||||||
|
* Auto update translations on release.
|
||||||
|
|
||||||
|
* Update translation.
|
||||||
|
|
||||||
|
* Remove param in ru translation.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## v0.7.2 (2023-10-04)
|
||||||
|
|
||||||
#### Other
|
#### Other
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
core.version=0.4.1
|
core.version=0.5.0
|
||||||
@@ -137,7 +137,7 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}).handleExceptions(
|
}).handleExceptions(
|
||||||
(error, stackTrace) {
|
(error, stackTrace) {
|
||||||
loggy.warning("error watching outbounds", error, stackTrace);
|
loggy.error("error watching outbounds", error, stackTrace);
|
||||||
return CoreServiceFailure.unexpected(error, stackTrace);
|
return CoreServiceFailure.unexpected(error, stackTrace);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ProfilesRepositoryImpl
|
|||||||
Stream<Either<ProfileFailure, Profile?>> watchActiveProfile() {
|
Stream<Either<ProfileFailure, Profile?>> watchActiveProfile() {
|
||||||
return profilesDao.watchActiveProfile().handleExceptions(
|
return profilesDao.watchActiveProfile().handleExceptions(
|
||||||
(error, stackTrace) {
|
(error, stackTrace) {
|
||||||
loggy.warning("error watching active profile", error, stackTrace);
|
loggy.error("error watching active profile", error, stackTrace);
|
||||||
return ProfileUnexpectedFailure(error, stackTrace);
|
return ProfileUnexpectedFailure(error, stackTrace);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class OutboundGroup with _$OutboundGroup {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class OutboundGroupItem with _$OutboundGroupItem {
|
class OutboundGroupItem with _$OutboundGroupItem {
|
||||||
|
const OutboundGroupItem._();
|
||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||||
const factory OutboundGroupItem({
|
const factory OutboundGroupItem({
|
||||||
required String tag,
|
required String tag,
|
||||||
@@ -28,6 +30,9 @@ class OutboundGroupItem with _$OutboundGroupItem {
|
|||||||
required int urlTestDelay,
|
required int urlTestDelay,
|
||||||
}) = _OutboundGroupItem;
|
}) = _OutboundGroupItem;
|
||||||
|
|
||||||
|
String get sanitizedTag =>
|
||||||
|
tag.replaceFirst(RegExp(r"\§[^]*"), "").trimRight();
|
||||||
|
|
||||||
factory OutboundGroupItem.fromJson(Map<String, dynamic> json) =>
|
factory OutboundGroupItem.fromJson(Map<String, dynamic> json) =>
|
||||||
_$OutboundGroupItemFromJson(json);
|
_$OutboundGroupItemFromJson(json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ enum ProxyType {
|
|||||||
dns("DNS"),
|
dns("DNS"),
|
||||||
socks("SOCKS"),
|
socks("SOCKS"),
|
||||||
http("HTTP"),
|
http("HTTP"),
|
||||||
|
shadowsocks("Shadowsocks"),
|
||||||
vmess("VMess"),
|
vmess("VMess"),
|
||||||
trojan("Trojan"),
|
trojan("Trojan"),
|
||||||
naive("Naive"),
|
naive("Naive"),
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class AboutPage extends HookConsumerWidget {
|
|||||||
(_, next) async {
|
(_, next) async {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
switch (next) {
|
switch (next) {
|
||||||
case AppUpdateStateAvailable(:final versionInfo):
|
case AppUpdateStateAvailable(:final versionInfo) ||
|
||||||
|
AppUpdateStateIgnored(:final versionInfo):
|
||||||
return NewVersionDialog(
|
return NewVersionDialog(
|
||||||
appInfo.presentVersion,
|
appInfo.presentVersion,
|
||||||
versionInfo,
|
versionInfo,
|
||||||
@@ -108,15 +109,16 @@ class AboutPage extends HookConsumerWidget {
|
|||||||
.check();
|
.check();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
if (PlatformUtils.isDesktop)
|
||||||
title: Text(t.settings.general.openWorkingDir),
|
ListTile(
|
||||||
trailing: const Icon(Icons.arrow_outward_outlined),
|
title: Text(t.settings.general.openWorkingDir),
|
||||||
onTap: () async {
|
trailing: const Icon(Icons.arrow_outward_outlined),
|
||||||
final path =
|
onTap: () async {
|
||||||
ref.read(filesEditorServiceProvider).workingDir.uri;
|
final path =
|
||||||
await UriUtils.tryLaunch(path);
|
ref.read(filesEditorServiceProvider).workingDir.uri;
|
||||||
},
|
await UriUtils.tryLaunch(path);
|
||||||
),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:hiddify/data/data_providers.dart';
|
|||||||
import 'package:hiddify/domain/app/app.dart';
|
import 'package:hiddify/domain/app/app.dart';
|
||||||
import 'package:hiddify/features/common/new_version_dialog.dart';
|
import 'package:hiddify/features/common/new_version_dialog.dart';
|
||||||
import 'package:hiddify/services/service_providers.dart';
|
import 'package:hiddify/services/service_providers.dart';
|
||||||
|
import 'package:hiddify/utils/pref_notifier.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ class AppUpdateState with _$AppUpdateState {
|
|||||||
const factory AppUpdateState.error(AppFailure error) = AppUpdateStateError;
|
const factory AppUpdateState.error(AppFailure error) = AppUpdateStateError;
|
||||||
const factory AppUpdateState.available(RemoteVersionInfo versionInfo) =
|
const factory AppUpdateState.available(RemoteVersionInfo versionInfo) =
|
||||||
AppUpdateStateAvailable;
|
AppUpdateStateAvailable;
|
||||||
|
const factory AppUpdateState.ignored(RemoteVersionInfo versionInfo) =
|
||||||
|
AppUpdateStateIgnored;
|
||||||
const factory AppUpdateState.notAvailable() = AppUpdateStateNotAvailable;
|
const factory AppUpdateState.notAvailable() = AppUpdateStateNotAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +34,12 @@ class AppUpdateNotifier extends _$AppUpdateNotifier with AppLogger {
|
|||||||
return const AppUpdateState.initial();
|
return const AppUpdateState.initial();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pref<String?, dynamic> get _ignoreReleasePref => Pref(
|
||||||
|
ref.read(sharedPreferencesProvider),
|
||||||
|
'ignored_release_version',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
Future<AppUpdateState> check() async {
|
Future<AppUpdateState> check() async {
|
||||||
loggy.debug("checking for update");
|
loggy.debug("checking for update");
|
||||||
state = const AppUpdateState.checking();
|
state = const AppUpdateState.checking();
|
||||||
@@ -54,7 +63,10 @@ class AppUpdateNotifier extends _$AppUpdateNotifier with AppLogger {
|
|||||||
return state = AppUpdateState.error(err);
|
return state = AppUpdateState.error(err);
|
||||||
},
|
},
|
||||||
(remote) {
|
(remote) {
|
||||||
if (remote.version.compareTo(currentVersion) > 0) {
|
if (remote.version == _ignoreReleasePref.getValue()) {
|
||||||
|
loggy.debug("ignored release [${remote.version}]");
|
||||||
|
return state = AppUpdateStateIgnored(remote);
|
||||||
|
} else if (remote.version.compareTo(currentVersion) > 0) {
|
||||||
loggy.debug("new version available: $remote");
|
loggy.debug("new version available: $remote");
|
||||||
return state = AppUpdateState.available(remote);
|
return state = AppUpdateState.available(remote);
|
||||||
}
|
}
|
||||||
@@ -66,6 +78,12 @@ class AppUpdateNotifier extends _$AppUpdateNotifier with AppLogger {
|
|||||||
).run();
|
).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> ignoreRelease(RemoteVersionInfo versionInfo) async {
|
||||||
|
loggy.debug("ignoring release [${versionInfo.version}]");
|
||||||
|
await _ignoreReleasePref.update(versionInfo.version);
|
||||||
|
state = AppUpdateStateIgnored(versionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _schedule() async {
|
Future<void> _schedule() async {
|
||||||
loggy.debug("scheduling app update checker");
|
loggy.debug("scheduling app update checker");
|
||||||
return ref.read(cronServiceProvider).schedule(
|
return ref.read(cronServiceProvider).schedule(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hiddify/core/core_providers.dart';
|
import 'package:hiddify/core/core_providers.dart';
|
||||||
import 'package:hiddify/domain/app/app.dart';
|
import 'package:hiddify/domain/app/app.dart';
|
||||||
|
import 'package:hiddify/features/common/app_update_notifier.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
|||||||
NewVersionDialog(
|
NewVersionDialog(
|
||||||
this.currentVersion,
|
this.currentVersion,
|
||||||
this.newVersion, {
|
this.newVersion, {
|
||||||
// super.key,
|
|
||||||
this.canIgnore = true,
|
this.canIgnore = true,
|
||||||
}) : super(key: _dialogKey);
|
}) : super(key: _dialogKey);
|
||||||
|
|
||||||
@@ -79,9 +79,11 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
|||||||
actions: [
|
actions: [
|
||||||
if (canIgnore)
|
if (canIgnore)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO add prefs for ignoring version
|
await ref
|
||||||
context.pop();
|
.read(appUpdateNotifierProvider.notifier)
|
||||||
|
.ignoreRelease(newVersion);
|
||||||
|
if (context.mounted) context.pop();
|
||||||
},
|
},
|
||||||
child: Text(t.appUpdate.ignoreBtnTxt),
|
child: Text(t.appUpdate.ignoreBtnTxt),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class IntroPage extends HookConsumerWidget with PresLogger {
|
|||||||
try {
|
try {
|
||||||
await Sentry.close();
|
await Sentry.close();
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
loggy.warning(
|
loggy.error(
|
||||||
"could not disable analytics",
|
"could not disable analytics",
|
||||||
error,
|
error,
|
||||||
stackTrace,
|
stackTrace,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class ProxiesNotifier extends _$ProxiesNotifier with AppLogger {
|
|||||||
loggy.debug("testing group: [$groupTag]");
|
loggy.debug("testing group: [$groupTag]");
|
||||||
if (state case AsyncData()) {
|
if (state case AsyncData()) {
|
||||||
await ref.read(coreFacadeProvider).urlTest(groupTag).getOrElse((err) {
|
await ref.read(coreFacadeProvider).urlTest(groupTag).getOrElse((err) {
|
||||||
loggy.warning("error testing group", err);
|
loggy.error("error testing group", err);
|
||||||
throw err;
|
throw err;
|
||||||
}).run();
|
}).run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ProxyTile extends HookConsumerWidget {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
title: Text(
|
title: Text(
|
||||||
proxy.tag,
|
proxy.sanitizedTag,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Future<List<InstalledPackageInfo>> installedPackagesInfo(
|
|||||||
.watch(platformSettingsProvider)
|
.watch(platformSettingsProvider)
|
||||||
.getInstalledPackages()
|
.getInstalledPackages()
|
||||||
.getOrElse((err) {
|
.getOrElse((err) {
|
||||||
_logger.warning("error getting installed packages", err);
|
_logger.error("error getting installed packages", err);
|
||||||
throw err;
|
throw err;
|
||||||
}).run();
|
}).run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class FFISingboxService
|
|||||||
_logger.debug("stopping status command client");
|
_logger.debug("stopping status command client");
|
||||||
final err = _box.stopCommandClient(1).cast<Utf8>().toDartString();
|
final err = _box.stopCommandClient(1).cast<Utf8>().toDartString();
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
_logger.warning("error stopping status client");
|
_logger.error("error stopping status client");
|
||||||
}
|
}
|
||||||
receiver.close();
|
receiver.close();
|
||||||
_statusStream = null;
|
_statusStream = null;
|
||||||
@@ -200,12 +200,12 @@ class FFISingboxService
|
|||||||
(event) {
|
(event) {
|
||||||
if (event case String _) {
|
if (event case String _) {
|
||||||
if (event.startsWith('error:')) {
|
if (event.startsWith('error:')) {
|
||||||
loggy.warning("[status client] error received: $event");
|
loggy.error("[status client] error received: $event");
|
||||||
throw event.replaceFirst('error:', "");
|
throw event.replaceFirst('error:', "");
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
loggy.warning("[status client] unexpected type, msg: $event");
|
loggy.error("[status client] unexpected type, msg: $event");
|
||||||
throw "invalid type";
|
throw "invalid type";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -215,7 +215,7 @@ class FFISingboxService
|
|||||||
.cast<Utf8>()
|
.cast<Utf8>()
|
||||||
.toDartString();
|
.toDartString();
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
loggy.warning("error starting status command: $err");
|
loggy.error("error starting status command: $err");
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ class FFISingboxService
|
|||||||
_logger.debug("stopping group command client");
|
_logger.debug("stopping group command client");
|
||||||
final err = _box.stopCommandClient(4).cast<Utf8>().toDartString();
|
final err = _box.stopCommandClient(4).cast<Utf8>().toDartString();
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
_logger.warning("error stopping group client");
|
_logger.error("error stopping group client");
|
||||||
}
|
}
|
||||||
receiver.close();
|
receiver.close();
|
||||||
_groupsStream = null;
|
_groupsStream = null;
|
||||||
@@ -240,12 +240,12 @@ class FFISingboxService
|
|||||||
(event) {
|
(event) {
|
||||||
if (event case String _) {
|
if (event case String _) {
|
||||||
if (event.startsWith('error:')) {
|
if (event.startsWith('error:')) {
|
||||||
loggy.warning("[group client] error received: $event");
|
loggy.error("[group client] error received: $event");
|
||||||
throw event.replaceFirst('error:', "");
|
throw event.replaceFirst('error:', "");
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
loggy.warning("[group client] unexpected type, msg: $event");
|
loggy.error("[group client] unexpected type, msg: $event");
|
||||||
throw "invalid type";
|
throw "invalid type";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -255,7 +255,7 @@ class FFISingboxService
|
|||||||
.cast<Utf8>()
|
.cast<Utf8>()
|
||||||
.toDartString();
|
.toDartString();
|
||||||
if (err.isNotEmpty) {
|
if (err.isNotEmpty) {
|
||||||
loggy.warning("error starting group command: $err");
|
loggy.error("error starting group command: $err");
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class MobileSingboxService
|
|||||||
if (event case String _) {
|
if (event case String _) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
loggy.warning("[group client] unexpected type, msg: $event");
|
loggy.error("[group client] unexpected type, msg: $event");
|
||||||
throw "invalid type";
|
throw "invalid type";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:hiddify/domain/clash/clash.dart';
|
import 'package:hiddify/domain/singbox/singbox.dart';
|
||||||
import 'package:hiddify/utils/validators.dart';
|
import 'package:hiddify/utils/validators.dart';
|
||||||
|
|
||||||
typedef ProfileLink = ({String url, String name});
|
typedef ProfileLink = ({String url, String name});
|
||||||
@@ -9,7 +9,15 @@ typedef ProfileLink = ({String url, String name});
|
|||||||
abstract class LinkParser {
|
abstract class LinkParser {
|
||||||
// protocols schemas
|
// protocols schemas
|
||||||
static const protocols = {'clash', 'clashmeta', 'sing-box', 'hiddify'};
|
static const protocols = {'clash', 'clashmeta', 'sing-box', 'hiddify'};
|
||||||
static const rawProtocols = {'vmess', 'vless', 'trojan', 'ss', 'tuic'};
|
static const rawProtocols = {
|
||||||
|
'ss',
|
||||||
|
'vmess',
|
||||||
|
'vless',
|
||||||
|
'trojan',
|
||||||
|
'tuic',
|
||||||
|
'hysteria2',
|
||||||
|
'ssh',
|
||||||
|
};
|
||||||
|
|
||||||
static ProfileLink? parse(String link) {
|
static ProfileLink? parse(String link) {
|
||||||
return simple(link) ?? deep(link);
|
return simple(link) ?? deep(link);
|
||||||
@@ -32,10 +40,13 @@ abstract class LinkParser {
|
|||||||
final fragment =
|
final fragment =
|
||||||
uri.hasFragment ? Uri.decodeComponent(uri.fragment) : null;
|
uri.hasFragment ? Uri.decodeComponent(uri.fragment) : null;
|
||||||
final name = switch (uri.scheme) {
|
final name = switch (uri.scheme) {
|
||||||
'ss' => fragment ?? ProxyType.shadowSocks.label,
|
'ss' => fragment ?? ProxyType.shadowsocks.label,
|
||||||
'vless' => fragment ?? ProxyType.vless.label,
|
|
||||||
'tuic' => fragment ?? ProxyType.tuic.label,
|
|
||||||
'vmess' => ProxyType.vmess.label,
|
'vmess' => ProxyType.vmess.label,
|
||||||
|
'vless' => fragment ?? ProxyType.vless.label,
|
||||||
|
'trojan' => fragment ?? ProxyType.trojan.label,
|
||||||
|
'tuic' => fragment ?? ProxyType.tuic.label,
|
||||||
|
'hy2' || 'hysteria2' => fragment ?? ProxyType.hysteria.label,
|
||||||
|
'ssh' => fragment ?? ProxyType.ssh.label,
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class SentryLoggyIntegration extends LoggyPrinter
|
|||||||
implements Integration<SentryOptions> {
|
implements Integration<SentryOptions> {
|
||||||
SentryLoggyIntegration({
|
SentryLoggyIntegration({
|
||||||
LogLevel minBreadcrumbLevel = LogLevel.info,
|
LogLevel minBreadcrumbLevel = LogLevel.info,
|
||||||
LogLevel minEventLevel = LogLevel.warning,
|
LogLevel minEventLevel = LogLevel.error,
|
||||||
}) : _minBreadcrumbLevel = minBreadcrumbLevel,
|
}) : _minBreadcrumbLevel = minBreadcrumbLevel,
|
||||||
_minEventLevel = minEventLevel;
|
_minEventLevel = minEventLevel;
|
||||||
|
|
||||||
|
|||||||
2
libcore
2
libcore
Submodule libcore updated: 75e342b6ba...dced5a30d0
@@ -1,7 +1,7 @@
|
|||||||
name: hiddify
|
name: hiddify
|
||||||
description: A Proxy Frontend.
|
description: A Proxy Frontend.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 0.7.2+702
|
version: 0.8.0+800
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.5 <4.0.0"
|
sdk: ">=3.0.5 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user