feat(singbox): add foundational models and constants for chain functionality

This commit is contained in:
veto9292 2026-05-08 05:32:53 +03:30
parent 0a6d4ad9f0
commit 3383d51318
4 changed files with 274 additions and 53 deletions

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hiddify/utils/utils.dart';
abstract class Constants { abstract class Constants {
static const appName = "Hiddify"; static const appName = "Hiddify";
@ -56,21 +57,49 @@ abstract class IntroConst {
static const termsAndConditionsKey = 'terms-and-conditions'; static const termsAndConditionsKey = 'terms-and-conditions';
static const githubKey = 'github'; static const githubKey = 'github';
static const licenseKey = 'license'; static const licenseKey = 'license';
static const url = <String, String>{IntroConst.termsAndConditionsKey: Constants.termsAndConditionsUrl, IntroConst.githubKey: Constants.githubUrl, IntroConst.licenseKey: Constants.licenseUrl}; static const url = <String, String>{
IntroConst.termsAndConditionsKey: Constants.termsAndConditionsUrl,
IntroConst.githubKey: Constants.githubUrl,
IntroConst.licenseKey: Constants.licenseUrl,
};
} }
abstract class WarpConst { abstract class WarpConst {
static const warpAccountId = 'warp-account-id';
static const warpAccessToken = "warp-access-token";
static const warpConsentGiven = "warp-consent-given"; static const warpConsentGiven = "warp-consent-given";
static const warpTermsOfServiceKey = 'warp-terms-of-service'; static const warpTermsOfServiceKey = 'warp-terms-of-service';
static const warpPrivacyPolicyKey = 'warp-privacy-policy'; static const warpPrivacyPolicyKey = 'warp-privacy-policy';
static const url = <String, String>{WarpConst.warpTermsOfServiceKey: Constants.cfWarpTermsOfService, WarpConst.warpPrivacyPolicyKey: Constants.cfWarpPrivacyPolicy}; static const url = <String, String>{
WarpConst.warpTermsOfServiceKey: Constants.cfWarpTermsOfService,
WarpConst.warpPrivacyPolicyKey: Constants.cfWarpPrivacyPolicy,
};
} }
abstract class KeyboardConst { abstract class KeyboardConst {
static final allArrows = {LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowRight}; static final allArrows = {
LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.arrowRight,
};
static final horizontalArrows = {LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowRight}; static final horizontalArrows = {LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowRight};
static final verticalArrows = {LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowDown}; static final verticalArrows = {LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowDown};
static final select = {LogicalKeyboardKey.select, LogicalKeyboardKey.enter, LogicalKeyboardKey.tab}; static final select = {LogicalKeyboardKey.select, LogicalKeyboardKey.enter, LogicalKeyboardKey.tab};
} }
abstract class ChainConst {
static IconData iconByPlatform() {
if (PlatformUtils.isAndroid) return Icons.phone_android;
if (PlatformUtils.isIOS) return Icons.phone_iphone;
if (PlatformUtils.isWeb) return Icons.web;
// Desktops
return Icons.laptop;
}
static Color finalIpColor(ThemeData theme) =>
theme.brightness == Brightness.dark ? const Color(0xFF99AD7A) : const Color.fromARGB(255, 87, 136, 13);
static const warpColor = Color(0xFFF6821F);
static const psiphonColor = Color(0xFFD52027);
static const profileColor = Color(0xFF3282B8);
static const finalIpDuration = Duration(milliseconds: 500);
}

View File

@ -407,30 +407,30 @@ class HiddifyCoreService with InfraLogger {
}); });
} }
TaskEither<String, WarpResponse> generateWarpConfig({ // TaskEither<String, WarpResponse> generateWarpConfig({
required String licenseKey, // required String licenseKey,
required String previousAccountId, // required String previousAccountId,
required String previousAccessToken, // required String previousAccessToken,
}) { // }) {
return TaskEither(() async { // return TaskEither(() async {
loggy.debug("generating warp config"); // loggy.debug("generating warp config");
final warpConfig = await core.fgClient.generateWarpConfig( // final warpConfig = await core.fgClient.generateWarpConfig(
GenerateWarpConfigRequest( // GenerateWarpConfigRequest(
licenseKey: licenseKey, // licenseKey: licenseKey,
accountId: previousAccountId, // accountId: previousAccountId,
accessToken: previousAccessToken, // accessToken: previousAccessToken,
), // ),
); // );
// if (warpConfig.code != ResponseCode.OK) return left("${warpConfig.code} ${warpConfig.message}"); // // if (warpConfig.code != ResponseCode.OK) return left("${warpConfig.code} ${warpConfig.message}");
final WarpResponse warp = ( // final WarpResponse warp = (
log: warpConfig.log, // log: warpConfig.log,
accountId: warpConfig.account.accountId, // accountId: warpConfig.account.accountId,
accessToken: warpConfig.account.accessToken, // accessToken: warpConfig.account.accessToken,
wireguardConfig: jsonEncode(warpConfig.config.toProto3Json()), // wireguardConfig: jsonEncode(warpConfig.config.toProto3Json()),
); // );
return right(warp); // return right(warp);
}); // });
} // }
Stream<CoreStatus> watchStatus() async* { Stream<CoreStatus> watchStatus() async* {
await startListeningStatus("bg", core.bgClient); await startListeningStatus("bg", core.bgClient);

View File

@ -1,7 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/model/constants.dart';
import 'package:hiddify/utils/platform_utils.dart'; import 'package:hiddify/utils/platform_utils.dart';
@JsonEnum(valueField: 'key') @JsonEnum(valueField: 'key')
@ -119,24 +121,147 @@ enum TunImplementation {
}; };
} }
enum MuxProtocol { h2mux, smux, yamux }
@JsonEnum(valueField: 'key') @JsonEnum(valueField: 'key')
enum WarpDetourMode { enum ChainStatus {
proxyOverWarp("proxy_over_warp"), off('off'),
warpOverProxy("warp_over_proxy"); extraSecurity('extra_security'),
unblocker('unblocker');
const WarpDetourMode(this.key); const ChainStatus(this.key);
final String key; final String key;
String present(TranslationsEn t) => switch (this) { bool isOff() => this == off;
proxyOverWarp => t.pages.settings.warp.detourModes.proxyOverWarp, bool isUnblocker() => this == unblocker;
warpOverProxy => t.pages.settings.warp.detourModes.warpOverProxy, bool isExtraSecurity() => this == extraSecurity;
}
@JsonEnum(valueField: 'key')
enum ChainMode {
psiphon('psiphon'),
warp('warp'),
profile('profile');
const ChainMode(this.key);
final String key;
String present(Translations t) => switch (this) {
psiphon => t.common.psiphon,
warp => t.common.warp,
profile => t.common.profile,
}; };
String presentExplain(TranslationsEn t) => switch (this) { IconData icon() => switch (this) {
proxyOverWarp => t.pages.settings.warp.detourModes.proxyOverWarpExplain, psiphon => Icons.local_parking,
warpOverProxy => t.pages.settings.warp.detourModes.warpOverProxyExplain, warp => Icons.cloud,
profile => Icons.link,
};
Color color() => switch (this) {
psiphon => ChainConst.psiphonColor,
warp => ChainConst.warpColor,
profile => ChainConst.profileColor,
};
bool isPsiphon() => this == psiphon;
bool isWarp() => this == warp;
bool isProfile() => this == profile;
}
@JsonEnum(valueField: 'key')
enum PsiphonRegion {
auto('AUTO'),
austria('AT'),
australia('AU'),
belgium('BE'),
bulgaria('BG'),
canada('CA'),
switzerland('CH'),
czechRepublic('CZ'),
germany('DE'),
denmark('DK'),
estonia('EE'),
spain('ES'),
finland('FI'),
france('FR'),
unitedKingdom('GB'),
croatia('HR'),
hungary('HU'),
ireland('IE'),
india('IN'),
italy('IT'),
japan('JP'),
latvia('LV'),
netherlands('NL'),
norway('NO'),
poland('PL'),
portugal('PT'),
romania('RO'),
serbia('RS'),
sweden('SE'),
singapore('SG'),
slovakia('SK'),
unitedStates('US');
const PsiphonRegion(this.key);
final String key;
String present(Translations t) => switch (this) {
auto => t.pages.settings.chain.psiphon.regions.auto,
austria => t.pages.settings.chain.psiphon.regions.at,
australia => t.pages.settings.chain.psiphon.regions.au,
belgium => t.pages.settings.chain.psiphon.regions.be,
bulgaria => t.pages.settings.chain.psiphon.regions.bg,
canada => t.pages.settings.chain.psiphon.regions.ca,
switzerland => t.pages.settings.chain.psiphon.regions.ch,
czechRepublic => t.pages.settings.chain.psiphon.regions.cz,
germany => t.pages.settings.chain.psiphon.regions.de,
denmark => t.pages.settings.chain.psiphon.regions.dk,
estonia => t.pages.settings.chain.psiphon.regions.ee,
spain => t.pages.settings.chain.psiphon.regions.es,
finland => t.pages.settings.chain.psiphon.regions.fi,
france => t.pages.settings.chain.psiphon.regions.fr,
unitedKingdom => t.pages.settings.chain.psiphon.regions.gb,
croatia => t.pages.settings.chain.psiphon.regions.hr,
hungary => t.pages.settings.chain.psiphon.regions.hu,
ireland => t.pages.settings.chain.psiphon.regions.ie,
india => t.pages.settings.chain.psiphon.regions.kIn,
italy => t.pages.settings.chain.psiphon.regions.it,
japan => t.pages.settings.chain.psiphon.regions.jp,
latvia => t.pages.settings.chain.psiphon.regions.lv,
netherlands => t.pages.settings.chain.psiphon.regions.nl,
norway => t.pages.settings.chain.psiphon.regions.no,
poland => t.pages.settings.chain.psiphon.regions.pl,
portugal => t.pages.settings.chain.psiphon.regions.pt,
romania => t.pages.settings.chain.psiphon.regions.ro,
serbia => t.pages.settings.chain.psiphon.regions.rs,
sweden => t.pages.settings.chain.psiphon.regions.se,
singapore => t.pages.settings.chain.psiphon.regions.sg,
slovakia => t.pages.settings.chain.psiphon.regions.sk,
unitedStates => t.pages.settings.chain.psiphon.regions.us,
}; };
} }
enum MuxProtocol { h2mux, smux, yamux }
// @JsonEnum(valueField: 'key')
// enum WarpDetourMode {
// proxyOverWarp("proxy_over_warp"),
// warpOverProxy("warp_over_proxy");
// const WarpDetourMode(this.key);
// final String key;
// String present(TranslationsEn t) => switch (this) {
// proxyOverWarp => t.pages.settings.warp.detourModes.proxyOverWarp,
// warpOverProxy => t.pages.settings.warp.detourModes.warpOverProxy,
// };
// String presentExplain(TranslationsEn t) => switch (this) {
// proxyOverWarp => t.pages.settings.warp.detourModes.proxyOverWarpExplain,
// warpOverProxy => t.pages.settings.warp.detourModes.warpOverProxyExplain,
// };
// }

View File

@ -50,8 +50,9 @@ class SingboxConfigOption with _$SingboxConfigOption {
required List<SingboxRule> rules, required List<SingboxRule> rules,
// required SingboxMuxOption mux, // required SingboxMuxOption mux,
required SingboxTlsTricks tlsTricks, required SingboxTlsTricks tlsTricks,
required SingboxWarpOption warp, required ChainStatus chainStatus,
required SingboxWarpOption warp2, required SingboxExtraSecurityOption extraSecurity,
required SingboxUnblockerOption unblocker,
}) = _SingboxConfigOption; }) = _SingboxConfigOption;
String format() { String format() {
@ -63,24 +64,90 @@ class SingboxConfigOption with _$SingboxConfigOption {
} }
@freezed @freezed
class SingboxWarpOption with _$SingboxWarpOption { class SingboxExtraSecurityOption with _$SingboxExtraSecurityOption {
@JsonSerializable(fieldRename: FieldRename.kebab) @JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxWarpOption({ const factory SingboxExtraSecurityOption({
required bool enable, required ChainMode mode,
required WarpDetourMode mode, required SingboxExtraSecurityWarpOption warp,
required String wireguardConfig, required SingboxExtraSecurityPsiphonOption psiphon,
required SingboxExtraSecurityProfileOption profile,
}) = _SingboxExtraSecurityOption;
factory SingboxExtraSecurityOption.fromJson(Map<String, dynamic> json) => _$SingboxExtraSecurityOptionFromJson(json);
}
@freezed
class SingboxUnblockerOption with _$SingboxUnblockerOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxUnblockerOption({
required ChainMode mode,
required SingboxUnblockerWarpOption warp,
required SingboxUnblockerPsiphonOption psiphon,
required SingboxUnblockerProfileOption profile,
}) = _SingboxUnblockerOption;
factory SingboxUnblockerOption.fromJson(Map<String, dynamic> json) => _$SingboxUnblockerOptionFromJson(json);
}
@freezed
class SingboxExtraSecurityWarpOption with _$SingboxExtraSecurityWarpOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxExtraSecurityWarpOption({required String licenseKey}) = _SingboxExtraSecurityWarpOption;
factory SingboxExtraSecurityWarpOption.fromJson(Map<String, dynamic> json) =>
_$SingboxExtraSecurityWarpOptionFromJson(json);
}
@freezed
class SingboxUnblockerWarpOption with _$SingboxUnblockerWarpOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxUnblockerWarpOption({
required String licenseKey, required String licenseKey,
required String accountId,
required String accessToken,
required String cleanIp, required String cleanIp,
required int cleanPort, required int cleanPort,
@OptionalRangeJsonConverter() required OptionalRange noise, @OptionalRangeJsonConverter() required OptionalRange noise,
@OptionalRangeJsonConverter() required OptionalRange noiseSize, @OptionalRangeJsonConverter() required OptionalRange noiseSize,
@OptionalRangeJsonConverter() required OptionalRange noiseDelay, @OptionalRangeJsonConverter() required OptionalRange noiseDelay,
@OptionalRangeJsonConverter() required String noiseMode, required String noiseMode,
}) = _SingboxWarpOption; }) = _SingboxUnblockerWarpOption;
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxWarpOptionFromJson(json); factory SingboxUnblockerWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxUnblockerWarpOptionFromJson(json);
}
@freezed
class SingboxExtraSecurityPsiphonOption with _$SingboxExtraSecurityPsiphonOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxExtraSecurityPsiphonOption({required PsiphonRegion region}) = _SingboxExtraSecurityPsiphonOption;
factory SingboxExtraSecurityPsiphonOption.fromJson(Map<String, dynamic> json) =>
_$SingboxExtraSecurityPsiphonOptionFromJson(json);
}
@freezed
class SingboxUnblockerPsiphonOption with _$SingboxUnblockerPsiphonOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxUnblockerPsiphonOption({required PsiphonRegion region}) = _SingboxUnblockerPsiphonOption;
factory SingboxUnblockerPsiphonOption.fromJson(Map<String, dynamic> json) =>
_$SingboxUnblockerPsiphonOptionFromJson(json);
}
@freezed
class SingboxExtraSecurityProfileOption with _$SingboxExtraSecurityProfileOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxExtraSecurityProfileOption({required String? id}) = _SingboxExtraSecurityProfileOption;
factory SingboxExtraSecurityProfileOption.fromJson(Map<String, dynamic> json) =>
_$SingboxExtraSecurityProfileOptionFromJson(json);
}
@freezed
class SingboxUnblockerProfileOption with _$SingboxUnblockerProfileOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxUnblockerProfileOption({required String? id}) = _SingboxUnblockerProfileOption;
factory SingboxUnblockerProfileOption.fromJson(Map<String, dynamic> json) =>
_$SingboxUnblockerProfileOptionFromJson(json);
} }
// @freezed // @freezed