update ios project

update ios project
This commit is contained in:
Zeus
2024-07-24 11:51:39 +08:00
parent 876d355b18
commit 1bf69048f3
275 changed files with 3372 additions and 1015 deletions
+438
View File
@@ -0,0 +1,438 @@
import 'dart:collection';
class Config {
General? general;
List<Proxy>? proxies;
List<ProxyGroup>? proxyGroups;
List<Rule>? rules;
HashMap<String, List<String>>? hosts;
@override
String toString() {
String result = "";
if (general != null) {
result += "$general\n";
}
if (proxies != null && proxies!.isNotEmpty) {
result += "[Proxy]\n";
result += proxies!.map((e) => e.toString()).join("\n");
result += "\n\n";
}
if (proxyGroups != null && proxyGroups!.isNotEmpty) {
result += "[Proxy Group]\n";
result += proxyGroups!.map((e) => e.toString()).join("\n");
result += "\n\n";
}
if (rules != null && rules!.isNotEmpty) {
result += "[Rule]\n";
result += rules!.map((e) => e.toString()).join("\n");
result += "\n\n";
}
if (hosts != null && hosts!.isNotEmpty) {
result += "[Host]\n";
result += hosts!.entries
.map((e) => "${e.key} = ${e.value.join(", ")}")
.join("\n");
result += "\n\n";
}
return result;
}
}
class Rule {
Rule({required this.typeField, this.filter, required this.target});
String typeField;
String? filter;
String target;
@override
String toString() {
String result = typeField;
if (filter != null) {
result += ', $filter';
}
result += ', $target';
return result;
}
}
class ProxyGroup {
ProxyGroup(
{required this.tag,
required this.protocol,
this.actors,
this.healthCheck,
this.checkInterval,
this.failTimeout,
this.failover,
this.fallbackCache,
this.cacheSize,
this.cacheTimeout,
this.lastResort,
this.healthCheckTimeout,
this.healthCheckDelay,
this.healthCheckActive,
this.delayBase,
this.method});
String tag;
String protocol;
List<String>? actors;
/// failover
bool? healthCheck;
int? checkInterval;
int? failTimeout;
bool? failover;
bool? fallbackCache;
int? cacheSize;
int? cacheTimeout;
String? lastResort;
int? healthCheckTimeout;
int? healthCheckDelay;
int? healthCheckActive;
/// tryall
int? delayBase;
/// static
String? method;
@override
String toString() {
String result = tag;
result += " = $protocol";
if (actors != null) {
result += ", ${actors!.join(", ")}";
}
if (healthCheck != null) {
result += ", health-check = $healthCheck";
}
if (checkInterval != null) {
result += ", check-interval = $checkInterval";
}
if (failTimeout != null) {
result += ", fail-timeout = $failTimeout";
}
if (failover != null) {
result += ", failover = $failover";
}
if (fallbackCache != null) {
result += ", fallback-cache = $fallbackCache";
}
if (cacheSize != null) {
result += ", cache-size = $cacheSize";
}
if (cacheTimeout != null) {
result += ", cache-timeout = $cacheTimeout";
}
if (lastResort != null) {
result += ", last-resort = $lastResort";
}
if (healthCheckTimeout != null) {
result += ", health-check-timeout = $healthCheckTimeout";
}
if (healthCheckDelay != null) {
result += ", health-check-delay = $healthCheckDelay";
}
if (healthCheckActive != null) {
result += ", health-check-active = $healthCheckActive";
}
if (delayBase != null) {
result += ", delay-base = $delayBase";
}
if (method != null) {
result += ", method = $method";
}
return result;
}
}
class Proxy {
Proxy(
{required this.tag,
required this.protocol,
this.interface,
this.address,
this.port,
this.encryptMethod,
this.password,
this.ws,
this.tls,
this.tlsCert,
this.wsPath,
this.wsHost,
this.sni,
this.username,
this.amux,
this.amuxMax,
this.amuxCon,
this.quic});
String tag;
String protocol;
String? interface;
/// address
String? address;
int? port;
/// shadowsocks
String? encryptMethod;
/// shadowsocks, trojan
String? password;
bool? ws;
bool? tls;
String? tlsCert;
String? wsPath;
String? wsHost;
/// trojan
String? sni;
/// vmess
String? username;
bool? amux;
int? amuxMax;
int? amuxCon;
bool? quic;
@override
String toString() {
String result = tag;
result += " = $protocol";
switch (protocol) {
case 'direct':
case 'drop':
case 'reject':
return result;
}
if (address != null && port != null) {
result += ", $address, $port";
}
if (encryptMethod != null) {
result += ", encrypt-method = $encryptMethod";
}
if (password != null) {
result += ", password = $password";
}
if (ws != null) {
result += ", ws = $ws";
}
if (tls != null) {
result += ", tls = $tls";
}
if (tlsCert != null) {
result += ", tls-cert = $tlsCert";
}
if (wsPath != null) {
result += ", ws-path = $wsPath";
}
if (wsHost != null) {
result += ", ws-host = $wsHost";
}
if (sni != null) {
result += ", sni = $sni";
}
if (username != null) {
result += ", username = $username";
}
if (amux != null) {
result += ", amux = $amux";
}
if (amuxMax != null) {
result += ", amux-max = $amuxMax";
}
if (amuxCon != null) {
result += ", amux-con = $amuxCon";
}
if (quic != null) {
result += ", quic = $quic";
}
if (interface != null) {
result += ", interface = $interface";
}
return result;
}
}
class General {
General(
{this.tun,
this.tunFd,
this.loglevel,
this.logoutput,
this.dnsServer,
this.dnsInterface,
this.alwaysRealIp,
this.alwaysFakeIp,
this.httpInterface,
this.httpPort,
this.socksInterface,
this.socksPort,
this.apiInterface,
this.apiPort,
this.routingDomainResolve});
Tun? tun;
dynamic tunFd;
String? loglevel;
String? logoutput;
List<String>? dnsServer;
String? dnsInterface;
List<String>? alwaysRealIp;
List<String>? alwaysFakeIp;
String? httpInterface;
int? httpPort;
String? socksInterface;
int? socksPort;
String? apiInterface;
int? apiPort;
bool? routingDomainResolve;
@override
String toString() {
String result = "\n[General]\n";
if (tun != null) {
result += "tun = $tun\n";
}
if (tunFd != null) {
result += "tun-fd = $tunFd\n";
}
if (loglevel != null) {
result += "loglevel = $loglevel\n";
}
if (logoutput != null) {
result += "logoutput = $logoutput\n";
}
if (dnsServer != null) {
result += "dns-server = ${dnsServer!.join(", ")}\n";
}
if (dnsInterface != null) {
result += "dns-interface = $dnsInterface\n";
}
if (alwaysRealIp != null) {
result += "always-real-ip = ${alwaysRealIp!.join(", ")}\n";
}
if (alwaysFakeIp != null) {
result += "always-fake-ip = ${alwaysFakeIp!.join(", ")}\n";
}
if (routingDomainResolve != null) {
result += "routing-domain-resolve = $routingDomainResolve\n";
}
if (httpInterface != null) {
result += "http-interface = $httpInterface\n";
}
if (httpPort != null) {
result += "http-port = $httpPort\n";
}
if (socksInterface != null) {
result += "socks-interface = $socksInterface\n";
}
if (socksInterface != null) {
result += "socks-port = $socksPort\n";
}
if (apiInterface != null) {
result += "api-interface = $apiInterface\n";
}
if (apiPort != null) {
result += "api-port = $apiPort\n";
}
return result;
}
}
class Tun {
Tun({this.name, this.address, this.netmask, this.gateway, this.mtu});
String? name;
String? address;
String? netmask;
String? gateway;
int? mtu;
@override
String toString() {
String result = "";
if (name == 'auto') {
result += "auto";
return result;
}
if (name != null &&
address != null &&
netmask != null &&
gateway != null &&
mtu != null) {
result += ", $name, $address, $netmask, $gateway, $mtu";
}
return result;
}
}
+67
View File
@@ -0,0 +1,67 @@
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
enum Os {
web,
android,
ios,
macOS,
linux,
windows,
fuchsia,
}
class Platform {
const Platform();
/// Platform is Web.
static bool get isWeb => os == Os.web;
/// Platform is Android.
static bool get isAndroid => os == Os.android;
/// Platform is IOS.
static bool get isIOS => os == Os.ios;
/// Platform is Fuchsia.
static bool get isFuchsia => os == Os.fuchsia;
/// Platform is Linux.
static bool get isLinux => os == Os.linux;
/// Platform is MacOS.
static bool get isMacOS => os == Os.macOS;
/// Platform is Windows.
static bool get isWindows => os == Os.windows;
/// Platform is Android or IOS.
static bool get isMobile => isAndroid || isIOS;
/// Platform is Android or IOS or Fuchsia.
static bool get isFullMobile => isMobile || isFuchsia;
/// Platform is Linux or Windows or MacOS.
static bool get isDesktop => isLinux || isWindows || isMacOS;
/// Getting the os name.
static Os get os {
if (kIsWeb) {
return Os.web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return Os.android;
case TargetPlatform.iOS:
return Os.ios;
case TargetPlatform.macOS:
return Os.macOS;
case TargetPlatform.windows:
return Os.windows;
case TargetPlatform.fuchsia:
return Os.fuchsia;
case TargetPlatform.linux:
return Os.linux;
}
}
}
+114
View File
@@ -0,0 +1,114 @@
import 'package:flutter/services.dart';
import 'package:uuvpn/channels/Platform.dart';
import 'package:uuvpn/utils/common_util.dart';
enum VpnStatus {
connected(code: 2),
connecting(code: 1),
reasserting(code: 4),
disconnecting(code: 5),
disconnected(code: 0),
invalid(code: 3);
const VpnStatus({required this.code});
final int code;
}
class VpnManager {
Future<VpnStatus> getStatus() async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
if (Platform.isAndroid || Platform.isMacOS) {
bool? result = await platform.invokeMethod("getStatus");
// print("${result}");
return (result ?? false) ? VpnStatus.connected : VpnStatus.disconnected;
}
int result;
try {
// bool xxx = await platform.invokeMethod("getStatus");
// result = xxx ? 1 : 0;
result = await platform.invokeMethod("getStatus");
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return VpnStatus.values.firstWhere((e) => e.code == result);
}
Future<DateTime> getConnectedDate() async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
double result;
try {
if (Platform.isAndroid || Platform.isMacOS) {
// bool? result = await platform.invokeMethod("getConnectedDate");
return DateTime.fromMillisecondsSinceEpoch((1 * 1000).toInt());
}
result = await platform.invokeMethod("getConnectedDate");
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return DateTime.fromMillisecondsSinceEpoch((result * 1000).toInt());
}
Future<bool> toggle() async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
bool result = false;
try {
result = await platform.invokeMethod("toggle");
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return result;
}
Future<String> getTunnelLog() async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
String result;
try {
result = await platform.invokeMethod("getTunnelLog");
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return result;
}
Future<String> getTunnelConfiguration() async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
String result;
try {
result = await platform.invokeMethod("getTunnelConfiguration");
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return result;
}
Future<String> setTunnelConfiguration(String conf) async {
// Native channel
const platform = MethodChannel("com.vpntunnel.vpnzeus/vpn_manager");
String result;
try {
result = await platform.invokeMethod("setTunnelConfiguration", conf);
} on PlatformException catch (e) {
print(e.toString());
rethrow;
}
return result;
}
}
+5
View File
@@ -0,0 +1,5 @@
class StringUtils {
static bool isNullOrEmpty(String? str) {
return str == null || str.isEmpty;
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class Colours {
static const Color text_dark = Color(0xFF333333);
static const Color text_normal = Color(0xFF666666);
static const Color text_gray = Color(0xFF888888);
static const Color gray_33 = Color(0xFF333333);
static const Color gray_66 = Color(0xFF666666);
static const Color gray_88 = Color(0xFF888888);
static const Color gray_99 = Color(0xFF999999);
static const Color gray_5A = Color(0xFF5A5A5A);
static const Color blue_main = Color(0xFF1890FF);
static const Color indexlabel_main = Color(0xFF757575);
static const Color divider = Color(0xFFF6F6F6);
static const Color divider2 = Color(0xFFD1D1D1);
}
+8
View File
@@ -0,0 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class Global {
static const baseUrl = 'https://api.dcgvc.com/';
static GlobalKey<NavigatorState> navigatorState = GlobalKey<NavigatorState>();
}
+13
View File
@@ -0,0 +1,13 @@
export 'dart:async';
// export 'dart:convert';
// export 'dart:io';
export '../utils/message_util.dart';
export 'package:flutter_screenutil/flutter_screenutil.dart';
export '../utils/sp_util.dart';
export '../http/api/apis.dart';
export '../http/http_utils.dart';
export './colours.dart';
export './styles.dart';
export 'global.dart';
export 'StringUtils.dart';
+52
View File
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui show window;
class Screen {
static double get width {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.size.width;
}
static double get height {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.size.height;
}
static double get scale {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.devicePixelRatio;
}
static double get textScaleFactor {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.textScaleFactor;
}
static double get navigationBarHeight {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.padding.top + kToolbarHeight;
}
static double get topSafeHeight {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
if (mediaQuery.padding.top <= 0) {
return 24.0;
}
return mediaQuery.padding.top;
}
static double get bottomSafeHeight {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.padding.bottom;
}
static bool get isHorizontal {
MediaQueryData mediaQuery = MediaQueryData.fromWindow(ui.window);
return mediaQuery.size.width > mediaQuery.size.height;
}
static updateStatusBarStyle(SystemUiOverlayStyle style) {
SystemChrome.setSystemUIOverlayStyle(style);
}
}
+92
View File
@@ -0,0 +1,92 @@
import 'package:flutter/widgets.dart';
import './colours.dart';
class TextStyles {
static TextStyle bigTitle = TextStyle(
fontSize: Dimens.font_sp24,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
static TextStyle listTitle = TextStyle(
fontSize: Dimens.font_sp16,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
static TextStyle labelTitle = TextStyle(
fontSize: Dimens.font_sp18,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
static TextStyle listContent = TextStyle(
fontSize: Dimens.font_sp14,
color: Colours.text_normal,
);
static TextStyle listContentBlack = TextStyle(
fontSize: Dimens.font_sp14,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
static TextStyle listExtra = TextStyle(
fontSize: Dimens.font_sp12,
color: Colours.text_gray,
);
static TextStyle listSmallExtra = TextStyle(
fontSize: Dimens.font_sp10,
color: Colours.gray_66,
);
static TextStyle hugeTitle = TextStyle(
fontSize: Dimens.font_sp30,
color: Colours.text_dark,
fontWeight: FontWeight.bold,
);
}
class Decorations {
static Decoration bottom = BoxDecoration(
border: Border(bottom: BorderSide(width: 0.33, color: Colours.divider)));
}
/// 间隔
class Gaps {
/// 水平间隔
static Widget hGap5 = new SizedBox(width: Dimens.gap_dp5);
static Widget hGap15 = new SizedBox(width: Dimens.gap_dp15);
static Widget hGap10 = new SizedBox(width: Dimens.gap_dp10);
static Widget hGap8 = new SizedBox(width: Dimens.gap_dp8);
static Widget hGap20 = new SizedBox(width: Dimens.gap_dp20);
/// 垂直间隔
static Widget vGap5 = new SizedBox(height: Dimens.gap_dp5);
static Widget vGap10 = new SizedBox(height: Dimens.gap_dp10);
static Widget vGap15 = new SizedBox(height: Dimens.gap_dp15);
static Widget vGap20 = new SizedBox(height: Dimens.gap_dp20);
}
class Dimens {
static const double font_sp8 = 8;
static const double font_sp10 = 10;
static const double font_sp12 = 12;
static const double font_sp14 = 14;
static const double font_sp16 = 16;
static const double font_sp18 = 18;
static const double font_sp20 = 20;
static const double font_sp24 = 24;
static const double font_sp26 = 26;
static const double font_sp30 = 30;
static const double gap_dp3 = 3;
static const double gap_dp5 = 5;
static const double gap_dp8 = 8;
static const double gap_dp10 = 10;
static const double gap_dp12 = 12;
static const double gap_dp15 = 15;
static const double gap_dp16 = 16;
static const double gap_dp20 = 20;
static const double gap_dp25 = 25;
static const double gap_dp30 = 30;
static const double btn_h_48 = 48;
static const double item_h_42 = 42;
static const double border_width = 0.33;
}
+11
View File
@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class AppColors {
static const MaterialColor themeColor = Colors.green; // Colors.amber;
static const Color yellowColor = Colors.green; // Color(0xFFfbd033);
static const Color grayColor = Color(0xFF2d2d2d);
static const Color darkSurfaceColor = Color(0xff373737);
static const Color whiteColor = Colors.white;
static const MaterialColor greenColor = Colors.green;
}
+5
View File
@@ -0,0 +1,5 @@
class AppDimens {
static const double maxWidth = 1080.0;
static const double maxHeight = 1920.0;
static const double bigTextSize = 13.0;
}
+6
View File
@@ -0,0 +1,6 @@
class AppImages {
static const String notFoundPicture = "assets/images/404.png";
static const String guide1 = "assets/images/guide_1.png";
static const String guide2 = "assets/images/guide_2.png";
static const String guide3 = "assets/images/guide_3.png";
}
+14
View File
@@ -0,0 +1,14 @@
class AppStrings {
static const String appName = 'UUVPN';
static const String token = 'token';
static const String authData = 'auth_data';
static const String userInfo = 'USER_INFO';
static const String userSubscribe = 'USER_SUBSCRIBE';
static const String serverNode = 'SERVER_NODE';
static const String selectServer = 'SELECT_SERVER';
static const String selectServerIndex = 'SELECT_SERVER_INDEX';
static const String selectServerNode = 'SELECT_SERVER_NODE';
static const String isFirstOpen = "IS_FIRST_OPEN";
static const String openDoor = '开启加速服务';
static const String crispWebsiteId = 'b7b8fcd4-9857-42b7-a39d-51fb4930130d';
}
+15
View File
@@ -0,0 +1,15 @@
class AppUrls {
static const String baseUrl = "https://api.0009.uk";
static const String baseApiUrl = '$baseUrl/api/v1'; // 基础接口地址
static const String login = '$baseApiUrl/passport/auth/login';
static const String register = '$baseApiUrl/passport/auth/register';
static const String getQuickLoginUrl =
'$baseApiUrl/passport/auth/getQuickLoginUrl';
static const String userSubscribe = '$baseApiUrl/user/getSubscribe';
static const String plan = '$baseApiUrl/guest/plan/fetch';
static const String server = '$baseApiUrl/user/server/fetch';
static const String userInfo = '$baseApiUrl/user/info';
}
+30
View File
@@ -0,0 +1,30 @@
// To parse this JSON data, do
//
// final loginEntity = loginEntityFromMap(jsonString);
import 'dart:convert';
class LoginEntity {
LoginEntity({
required this.token,
required this.authData,
});
final String token;
final String authData;
factory LoginEntity.fromJson(String str) =>
LoginEntity.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory LoginEntity.fromMap(Map<String, dynamic> json) => LoginEntity(
token: json["token"],
authData: json["auth_data"],
);
Map<String, dynamic> toMap() => {
"token": token,
"auth_token": authData,
};
}
+96
View File
@@ -0,0 +1,96 @@
// To parse this JSON data, do
//
// final planEntity = planEntityFromMap(jsonString);
import 'package:meta/meta.dart';
import 'dart:convert';
List<PlanEntity> planEntityFromList(List data) => List<PlanEntity>.from(data.map((x) => PlanEntity.fromMap(x)));
class PlanEntity {
PlanEntity({
required this.id,
required this.groupId,
required this.transferEnable,
required this.name,
required this.show,
required this.sort,
required this.renew,
required this.content,
required this.monthPrice,
required this.quarterPrice,
required this.halfYearPrice,
required this.yearPrice,
required this.twoYearPrice,
required this.threeYearPrice,
required this.onetimePrice,
required this.resetPrice,
required this.createdAt,
required this.updatedAt,
});
final int id;
final int groupId;
final int transferEnable;
final String name;
final int show;
final dynamic sort;
final int renew;
final String? content;
final int? monthPrice;
final int? quarterPrice;
final int? halfYearPrice;
final int? yearPrice;
final int? twoYearPrice;
final int? threeYearPrice;
final int? onetimePrice;
final int? resetPrice;
final DateTime? createdAt;
final DateTime? updatedAt;
factory PlanEntity.fromJson(String str) => PlanEntity.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PlanEntity.fromMap(Map<String, dynamic> json) => PlanEntity(
id: json["id"],
groupId: json["group_id"],
transferEnable: json["transfer_enable"],
name: json["name"],
show: json["show"],
sort: json["sort"],
renew: json["renew"],
content: json["content"],
monthPrice: json["month_price"],
quarterPrice: json["quarter_price"],
halfYearPrice: json["half_year_price"],
yearPrice: json["year_price"],
twoYearPrice: json["two_year_price"],
threeYearPrice: json["three_year_price"],
onetimePrice: json["onetime_price"],
resetPrice: json["reset_price"],
createdAt: json["created_at"] == null ? null : DateTime.fromMillisecondsSinceEpoch(json["created_at"] * 1000),
updatedAt: json["updated_at"] == null ? null : DateTime.fromMillisecondsSinceEpoch(json["updated_at"] * 1000),
);
Map<String, dynamic> toMap() => {
"id": id,
"group_id": groupId,
"transfer_enable": transferEnable,
"name": name,
"show": show,
"sort": sort,
"renew": renew,
"content": content,
"month_price": monthPrice,
"quarter_price": quarterPrice,
"half_year_price": halfYearPrice,
"year_price": yearPrice,
"two_year_price": twoYearPrice,
"three_year_price": threeYearPrice,
"onetime_price": onetimePrice,
"reset_price": resetPrice,
"created_at": createdAt == null ? null : createdAt!.millisecondsSinceEpoch ~/ 1000,
"updated_at": updatedAt == null ? null : updatedAt!.millisecondsSinceEpoch ~/ 1000,
};
}
+91
View File
@@ -0,0 +1,91 @@
// To parse this JSON data, do
//
// final serverEntity = serverEntityFromMap(jsonString);
import 'dart:convert';
List<ServerEntity> serverEntityFromList(List<dynamic> data) =>
List<ServerEntity>.from(data.map((x) => ServerEntity.fromMap((x))));
class ServerEntity {
ServerEntity({
required this.id,
required this.groupId,
required this.parentId,
required this.tags,
required this.name,
required this.rate,
required this.host,
required this.port,
required this.serverPort,
required this.cipher,
required this.show,
required this.sort,
required this.createdAt,
required this.updatedAt,
required this.type,
required this.lastCheckAt,
});
final int id;
final List<String> groupId;
final int? parentId;
final List<String> tags;
final String name;
final String rate;
final String host;
final int port;
final int serverPort;
final String cipher;
final int show;
final int sort;
Duration? ping;
final int createdAt;
final int updatedAt;
final String type;
final String lastCheckAt;
factory ServerEntity.fromJson(String str) =>
ServerEntity.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory ServerEntity.fromMap(Map<String, dynamic> json) => ServerEntity(
id: json["id"],
groupId: List<String>.from(json["group_id"]?.map((x) => x) ?? []),
parentId: json["parent_id"],
tags: List<String>.from(json["tags"]?.map((x) => x) ?? []),
name: "" + json["name"],
rate: json["rate"],
host: json["host"] + "",
port: int.parse(json["port"] ?? "0"),
serverPort:
int.parse(json["server_port"] ?? "0"), //json["server_port"],
cipher: json["cipher"] ?? "",
show: json["show"],
sort: json["sort"],
createdAt: json["created_at"],
updatedAt: json["updated_at"],
type: json["type"],
lastCheckAt: "", //json["last_check_at"] +
);
Map<String, dynamic> toMap() => {
"id": id,
"group_id": List<dynamic>.from(groupId.map((x) => x)),
"parent_id": parentId,
"tags": List<dynamic>.from(tags.map((x) => x)),
"name": name,
"rate": rate,
"host": host,
"port": port,
"server_port": serverPort,
"cipher": cipher,
"show": show,
"sort": sort,
"created_at": createdAt,
"updated_at": updatedAt,
"type": type,
"last_check_at": lastCheckAt,
};
}
+99
View File
@@ -0,0 +1,99 @@
// To parse this JSON data, do
//
// final userEntity = userEntityFromMap(jsonString);
import 'package:meta/meta.dart';
import 'dart:convert';
class UserEntity {
UserEntity({
required this.email,
required this.transferEnable,
required this.lastLoginAt,
required this.createdAt,
required this.banned,
required this.remindExpire,
required this.remindTraffic,
required this.expiredAt,
required this.balance,
required this.commissionBalance,
required this.planId,
@required this.discount,
@required this.commissionRate,
@required this.telegramId,
required this.uuid,
required this.avatarUrl,
});
final String email;
final int transferEnable;
final DateTime? lastLoginAt;
final DateTime? createdAt;
final int banned;
final int remindExpire;
final int remindTraffic;
final DateTime? expiredAt;
final int balance;
final int commissionBalance;
final int planId;
final dynamic discount;
final dynamic commissionRate;
final dynamic telegramId;
final String uuid;
final String avatarUrl;
factory UserEntity.fromJson(String str) =>
UserEntity.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory UserEntity.fromMap(Map<String, dynamic> json) => UserEntity(
email: json["email"],
transferEnable: json["transfer_enable"],
lastLoginAt: json["last_login_at"] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json["last_login_at"] * 1000),
createdAt: json["created_at"] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json["created_at"] * 1000),
banned: json["banned"],
remindExpire: json["remind_expire"],
remindTraffic: json["remind_traffic"],
expiredAt: json["expired_at"] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json["expired_at"] * 1000),
balance: json["balance"],
commissionBalance: json["commission_balance"],
planId: json["plan_id"] ?? 0,
discount: json["discount"] ?? 0,
commissionRate: json["commission_rate"],
telegramId: json["telegram_id"],
uuid: json["uuid"],
avatarUrl: json["avatar_url"],
);
Map<String, dynamic> toMap() => {
"email": email,
"transfer_enable": transferEnable,
"last_login_at": lastLoginAt == null
? null
: lastLoginAt!.millisecondsSinceEpoch ~/ 1000,
"created_at": createdAt == null
? null
: createdAt!.millisecondsSinceEpoch ~/ 1000,
"banned": banned,
"remind_expire": remindExpire,
"remind_traffic": remindTraffic,
"expired_at": expiredAt == null
? null
: expiredAt!.millisecondsSinceEpoch ~/ 1000,
"balance": balance,
"commission_balance": commissionBalance,
"plan_id": planId,
"discount": discount,
"commission_rate": commissionRate,
"telegram_id": telegramId,
"uuid": uuid,
"avatar_url": avatarUrl,
};
}
@@ -0,0 +1,156 @@
// To parse this JSON data, do
//
// final userSubscribeEntity = userSubscribeEntityFromMap(jsonString);
import 'package:meta/meta.dart';
import 'dart:convert';
class UserSubscribeEntity {
UserSubscribeEntity({
required this.planId,
required this.token,
required this.expiredAt,
required this.u,
required this.d,
required this.transferEnable,
required this.email,
required this.plan,
required this.subscribeUrl,
required this.resetDay,
});
final int planId;
final String token;
final int expiredAt;
final int u;
final int d;
final int transferEnable;
final String email;
final Plan? plan;
final String subscribeUrl;
final int? resetDay;
factory UserSubscribeEntity.fromJson(String str) =>
UserSubscribeEntity.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory UserSubscribeEntity.fromMap(Map<String, dynamic> json) =>
UserSubscribeEntity(
planId: json["plan_id"] ?? 0,
token: json["token"],
expiredAt: json["expired_at"] ?? 0,
u: json["u"],
d: json["d"],
transferEnable: json["transfer_enable"],
email: json["email"],
plan: json["plan"] == null ? null : Plan.fromMap(json["plan"]),
subscribeUrl: json["subscribe_url"],
resetDay: json["reset_day"],
);
Map<String, dynamic> toMap() => {
"plan_id": planId,
"token": token,
"expired_at": expiredAt,
"u": u,
"d": d,
"transfer_enable": transferEnable,
"email": email,
"plan": plan?.toMap(),
"subscribe_url": subscribeUrl,
"reset_day": resetDay,
};
}
class Plan {
Plan({
required this.id,
required this.groupId,
required this.transferEnable,
required this.name,
required this.show,
required this.sort,
required this.renew,
required this.content,
required this.monthPrice,
required this.quarterPrice,
required this.halfYearPrice,
required this.yearPrice,
required this.twoYearPrice,
required this.threeYearPrice,
required this.onetimePrice,
required this.resetPrice,
required this.resetTrafficMethod,
required this.createdAt,
required this.updatedAt,
});
final int id;
final int groupId;
final int transferEnable;
final String name;
final int show;
final int sort;
final int renew;
final String? content;
final int? monthPrice;
final int? quarterPrice;
final int? halfYearPrice;
final int? yearPrice;
final int? twoYearPrice;
final int? threeYearPrice;
final int? onetimePrice;
final int? resetPrice;
final int? resetTrafficMethod;
final int? createdAt;
final int? updatedAt;
factory Plan.fromJson(String str) => Plan.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory Plan.fromMap(Map<String, dynamic> json) => Plan(
id: json["id"],
groupId: json["group_id"],
transferEnable: json["transfer_enable"],
name: json["name"],
show: json["show"],
sort: json["sort"] ?? 0,
renew: json["renew"],
content: json["content"] ?? "",
monthPrice: json["month_price"] ?? 0,
quarterPrice: json["quarter_price"] ?? 0,
halfYearPrice: json["half_year_price"] ?? 0,
yearPrice: json["year_price"] ?? 0,
twoYearPrice: json["two_year_price"] ?? 0,
threeYearPrice: json["three_year_price"] ?? 0,
onetimePrice: json["onetime_price"] ?? 0,
resetPrice: json["reset_price"] ?? 0,
resetTrafficMethod: json["reset_traffic_method"] ?? 0,
createdAt: json["created_at"] ?? 0,
updatedAt: json["updated_at"] ?? 0,
);
Map<String, dynamic> toMap() => {
"id": id,
"group_id": groupId,
"transfer_enable": transferEnable,
"name": name,
"show": show,
"sort": sort,
"renew": renew,
"content": content,
"month_price": monthPrice,
"quarter_price": quarterPrice,
"half_year_price": halfYearPrice,
"year_price": yearPrice,
"two_year_price": twoYearPrice,
"three_year_price": threeYearPrice,
"onetime_price": onetimePrice,
"reset_price": resetPrice,
"reset_traffic_method": resetTrafficMethod,
"created_at": createdAt,
"updated_at": updatedAt,
};
}
+11
View File
@@ -0,0 +1,11 @@
/*后台接口api*/
import 'package:uuvpn/common/public.dart';
class Apis {
static String appid = "IPOSih2134KHJKLDIO";
static String token =
"${Global.baseUrl}flutter/gettoken.php/?s=App.JYApp_Main.GetToken";
static String getLaunchAds =
"${Global.baseUrl}flutter/gettoken.php/?s=App.JYApp_Main.GetLaunchAds";
static String getRecommandList = "${Global.baseUrl}flutter/recommand.php";
}
+24
View File
@@ -0,0 +1,24 @@
import 'http_exceptions.dart';
class ApiResponse<T> implements Exception {
Status status;
T? data;
DioHttpException? exception;
/**
* 成功 网络请求
*/
ApiResponse.completed(this.data) : status = Status.COMPLETED;
/**
* 错误 网络请求
*/
ApiResponse.error(this.exception) : status = Status.ERROR;
@override
String toString() {
return "Status : $status \n Message : $exception \n Data : $data";
}
}
enum Status { COMPLETED, ERROR }
+20
View File
@@ -0,0 +1,20 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'http_exceptions.dart';
/// 错误处理拦截器
class ErrorInterceptor extends Interceptor {
Duration durationTime = Duration(seconds: 2);
@override
onError(DioError err, ErrorInterceptorHandler handler) {
// error统一处理
DioHttpException appException = DioHttpException.create(err);
// 错误提示
print('DioError===: ${appException.toString()}');
// err.error = appException;
super.onError(err, handler);
}
}
+238
View File
@@ -0,0 +1,238 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:uuvpn/common/public.dart';
import 'error_interceptor.dart';
class Http {
///超时时间
static const int CONNECT_TIMEOUT = 30000;
static const int RECEIVE_TIMEOUT = 30000;
static Http _instance = Http._internal();
factory Http() => _instance;
Dio? dio;
CancelToken _cancelToken = new CancelToken();
Http._internal() {
if (dio == null) {
// BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
BaseOptions options = new BaseOptions(
connectTimeout: Duration(seconds: CONNECT_TIMEOUT),
// 响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: Duration(seconds: RECEIVE_TIMEOUT),
headers: {},
);
if (options.contentType != null &&
options.headers.containsKey(Headers.contentTypeHeader)) {
options.headers.remove(Headers.contentTypeHeader);
}
dio = new Dio(options);
// 添加拦截器
dio!.interceptors.add(ErrorInterceptor());
dio!.interceptors.add(LogInterceptor());
}
}
/// 读取本地配置
Future<Map<String, dynamic>?> getAuthorizationHeader() async {
var headers;
String? accessToken = SpUtil.getString("SP_TOKEN", defValue: null);
if (accessToken != null) {
int expiresM = SpUtil.getInt("SP_EXPIRES_M");
// token过期(有5分钟缓冲时间),刷新token
if (DateTime.now().millisecondsSinceEpoch > expiresM) {
// accessToken = await Auth.refreshToken();
}
headers = {"Authorization": 'Bearer $accessToken'};
}
return headers;
}
///初始化公共属性
///
/// [baseUrl] 地址前缀
/// [connectTimeout] 连接超时赶时间
/// [receiveTimeout] 接收超时赶时间
/// [interceptors] 基础拦截器
void init(
{String? baseUrl,
int? connectTimeout,
int? receiveTimeout,
List<Interceptor>? interceptors}) {
dio!.options = dio!.options.copyWith(
baseUrl: baseUrl,
connectTimeout: Duration(seconds: connectTimeout ?? 0),
receiveTimeout: Duration(seconds: receiveTimeout ?? 0),
);
if (interceptors != null && interceptors.isNotEmpty) {
dio!.interceptors.addAll(interceptors);
}
}
/// 设置headers
void setHeaders(Map<String, dynamic> map) {
dio!.options.headers.addAll(map);
}
/*
* 取消请求
*
* 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
* 所以参数可选
*/
void cancelRequests({CancelToken? token}) {
token ?? _cancelToken.cancel("cancelled");
}
/// restful get 操作
Future get(
String path, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false,
String? cacheKey,
bool cacheDisk = false,
bool withoutToken = false,
}) async {
Options requestOptions = options ?? Options();
requestOptions = requestOptions.copyWith(extra: {
"refresh": refresh,
"cacheKey": cacheKey,
"cacheDisk": cacheDisk,
});
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
Map<String, dynamic>? headers = requestOptions.headers;
if (_authorization != null &&
(headers == null || headers["Authorization"] == null) &&
!withoutToken) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
Response response;
response = await dio!.get(path,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
/// restful post 操作
Future post(
String path, {
Map<String, dynamic>? params,
data,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
if (requestOptions.headers == null ||
requestOptions.headers!["Authorization"] == null) {
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
if (_authorization != null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
}
var response = await dio!.post(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
/// restful put 操作
Future put(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
if (_authorization != null &&
(requestOptions.headers == null ||
requestOptions.headers!["Authorization"] == null)) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio!.put(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
/// restful patch 操作
Future patch(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
if (_authorization != null &&
(requestOptions.headers == null ||
requestOptions.headers!["Authorization"] == null)) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio!.patch(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
/// restful delete 操作
Future delete(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
if (_authorization != null &&
(requestOptions.headers == null ||
requestOptions.headers!["Authorization"] == null)) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio!.delete(path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
/// restful post form 表单提交操作
Future postForm(
String path, {
required Map<String, dynamic> data,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String, dynamic>? _authorization = await getAuthorizationHeader();
if (_authorization != null &&
(requestOptions.headers == null ||
requestOptions.headers!["Authorization"] == null)) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio!.post(path,
data: FormData.fromMap(data),
queryParameters: data,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken);
return response.data;
}
}
+130
View File
@@ -0,0 +1,130 @@
import 'package:dio/dio.dart';
/// 自定义异常
class DioHttpException implements Exception {
final String? message;
final int? code;
DioHttpException([
this.code,
this.message,
]);
String toString() {
return "$code$message";
}
factory DioHttpException.create(DioException error) {
switch (error.type) {
case DioExceptionType.unknown:
{
// 网络异常
return BadRequestException(-2, "无网络");
}
case DioErrorType.cancel:
{
return BadRequestException(-1, "请求取消");
}
case DioErrorType.connectionTimeout:
{
return BadRequestException(-1, "连接超时");
}
case DioErrorType.sendTimeout:
{
return BadRequestException(-1, "请求超时");
}
case DioErrorType.receiveTimeout:
{
return BadRequestException(-1, "响应超时");
}
case DioErrorType.badResponse:
{
try {
int? errCode = error.response!.statusCode;
switch (errCode) {
case 400:
{
String? msgOf400;
try {
msgOf400 = error.response!.data["message"];
} on Exception catch (e) {
print('****** 400 exception: $e');
}
return BadRequestException(errCode, msgOf400 ?? "请求语法错误");
}
case 401:
{
return UnauthorisedException(errCode, "没有权限");
}
case 403:
{
return UnauthorisedException(errCode, "服务器拒绝执行");
}
case 404:
{
return UnauthorisedException(errCode, "无法连接服务器");
}
case 405:
{
return UnauthorisedException(errCode, "请求方法被禁止");
}
case 500:
{
return UnauthorisedException(
errCode, error.response?.statusMessage ?? "服务器内部错误");
}
case 502:
{
return UnauthorisedException(errCode, "无效的请求");
}
case 503:
{
return UnauthorisedException(errCode, "服务器挂了");
}
case 505:
{
return UnauthorisedException(errCode, "不支持HTTP协议请求");
}
default:
{
return DioHttpException(
errCode, error.response?.statusMessage);
}
}
} on Exception catch (_) {
return DioHttpException(-1, "未知错误");
}
}
break;
default:
{
return DioHttpException(-1, error.message);
}
}
}
}
/// 请求错误
class BadRequestException extends DioHttpException {
BadRequestException([int? code, String? message]) : super(code, message);
}
/// 未认证异常
class UnauthorisedException extends DioHttpException {
UnauthorisedException([int? code, String? message]) : super(code, message);
}
+123
View File
@@ -0,0 +1,123 @@
import 'package:dio/dio.dart';
import 'http.dart';
import 'package:sync_http/sync_http.dart';
class HttpUtils {
static void init(
{String? baseUrl,
int? connectTimeout,
int? receiveTimeout,
List<Interceptor>? interceptors}) {
Http().init(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
interceptors: interceptors);
}
static void setHeaders(Map<String, dynamic> map) {
Http().setHeaders(map);
}
static void cancelRequests({CancelToken? token}) {
Http().cancelRequests(token: token);
}
static Future get(
String path, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false,
String? cacheKey,
bool cacheDisk = false,
bool withoutToken = false,
}) async {
// SyncHttpClient.getUrl(path)
return await Http().get(path,
params: params,
options: options,
cancelToken: cancelToken,
refresh: refresh,
cacheKey: cacheKey,
withoutToken: withoutToken);
}
static Future post(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().post(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future put(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().put(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future patch(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().patch(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future delete(
String path, {
data,
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().delete(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future postForm(
String path, {
required Map<String, dynamic> data,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().postForm(
path,
data: data,
options: options,
cancelToken: cancelToken,
);
}
}
+61
View File
@@ -0,0 +1,61 @@
{
"@@last_modified": "2022-08-25T17:45:16.782917",
"title": "Flutter APP",
"wodedingyue": "My Subscription",
"guoqi": "Expired",
"yiyong": "Used",
"zongji": "Total",
"dinggoutaocan": "Sub Package",
"xuangou": "Buy",
"yidingyue": "Subscribed",
"yilianjie": "Connected",
"yiduankai": "Disconnected",
"qingxiandenglu":"Please login first",
"qingxiandingyuetaocan":"Please subscribed first",
"taocanguoqichongxindingyue":"Package has expired, please subscribe again",
"chagnqiyouxiao":"Subscribed Never Expires",
"renew":"View",
"welcome":"welcome",
"logout":"logout",
"xuanzeliahjiedian":"Select Services",
"yiduankai2": "Disconnected",
"pleaseenter":"Please enter the correct email address",
"passwordcan":"Password can not be null",
"passwordcannot":"Password cannot be less than 6 characters",
"loginfailed":"Login failed, please try again",
"registrationfailed":"Registration failed, please try again",
"mail":"Mail",
"password":"Password",
"zhengzailinajie":"Connecting, please wait...",
"zhengzaiduankailianjie":"Disconnecting, please wait...",
"confirmpassword":"Confirm Password",
"twopasswords":"Two passwords do not match",
"forgetthepassword":"Forget the password?",
"login":"Login",
"register":"Register",
"resetpassword":"Reset Password",
"sure":"Sure",
"thesystemwill":"The system will send a reset password email to your mailbox, please pay attention to check it",
"sentsuccessfully":"Sent successfully",
"returnstring":"Return",
"qingxuanzefuwqjiedian":"Please select a server node",
"pingallnodes":"Click Ping All",
"timeout":"timeout",
"alertsss":"alert",
"wanttoexit":"Are you sure you want to exit?",
"cancelss":"cancel",
"exitout":"exit",
"clicktoselectanothernode":"Click to select another node",
"nodefornullcheckissubscripts":"The Service list is empty, \nPlease Login first.",
"foreveryfree1":"Notable features of UU: \n * No credit card required\n * You can try premium features for free for 7 days \n* Do not keep any user logs\n* Simple, one-click connection VPN\n* Automatically connects you to the fastest VPN server",
"@title": {
"description": "Title for the Demo application",
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
+61
View File
@@ -0,0 +1,61 @@
{
"@@last_modified": "2022-08-25T15:53:28.107195",
"title": "Flutter APP",
"wodedingyue": "我的订阅",
"guoqi": "过期",
"yiyong": "已用",
"zongji": "总计",
"dinggoutaocan": "订阅套餐",
"xuangou": "购买",
"yidingyue": "已订购",
"yilianjie": "已连接",
"yiduankai": "已断开连接",
"qingxiandenglu":"请先登录",
"qingxiandingyuetaocan":"请先订阅下方套餐",
"taocanguoqichongxindingyue":"套餐已过期,请重新订阅",
"chagnqiyouxiao":"长期有效",
"renew":"查看",
"welcome":"欢迎光临",
"logout":"退出",
"xuanzeliahjiedian":"选择连接节点",
"yiduankai2": "已断开连接",
"pleaseenter":"请输入正确邮箱",
"passwordcan":"密码不能为空",
"passwordcannot":"密码不能小于6位",
"loginfailed":"登陆失败,请重试",
"registrationfailed":"注册失败,请重试",
"mail":"邮箱",
"password":"密码",
"confirmpassword":"确定密码",
"twopasswords":"两次密码不匹配",
"forgetthepassword":"忘记密码?",
"login":"登录",
"register":"注册",
"resetpassword":"重置密码",
"sure":"确定",
"thesystemwill":"系统将向您的邮箱发送一封重置密码邮件,请注意查收",
"sentsuccessfully":"发送成功",
"returnstring":"返回",
"qingxuanzefuwqjiedian":"请选择服务器节点",
"pingallnodes":"点击Ping所有节点",
"timeout":"超时",
"alertsss":"提示",
"wanttoexit":"确定退出吗?",
"cancelss":"取消",
"exitout":"退出",
"zhengzailinajie":"正在连接中,请稍后...",
"zhengzaiduankailianjie":"正在断开中,请稍后...",
"clicktoselectanothernode":"点击选择其他节点",
"nodefornullcheckissubscripts":"节点列表为空,请确认是否已经订阅 ",
"foreveryfree1":"Notable features of UU: \n * No credit card required\n * You can try premium features for free for 7 days \n* Do not keep any user logs\n* Simple, one-click connection VPN\n* Automatically connects you to the fastest VPN server",
"@title": {
"description": "Title for the Demo application",
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
+90
View File
@@ -0,0 +1,90 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' as services;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/plan_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/router/routers.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'model/UserPreference.dart';
import 'model/themeCollection.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var appModel = AppModel();
var userViewModel = UserModel();
var userSubscribeModel = UserSubscribeModel();
var serverModel = ServerModel();
var planModel = PlanModel();
await userViewModel.refreshData(); // Add this line
await ScreenUtil.ensureScreenSize();
runApp(MultiProvider(providers: [
ChangeNotifierProvider<AppModel>.value(value: appModel),
ChangeNotifierProvider<UserModel>.value(value: userViewModel),
ChangeNotifierProvider<UserSubscribeModel>.value(value: userSubscribeModel),
ChangeNotifierProvider<ServerModel>.value(value: serverModel),
ChangeNotifierProvider<ThemeCollection>.value(value: ThemeCollection()),
ChangeNotifierProvider<UserPreference>.value(value: UserPreference()),
ChangeNotifierProvider<PlanModel>.value(value: planModel)
], child: SailApp()));
}
class SailApp extends StatelessWidget {
SailApp({Key? key}) : super(key: key) {
final router = FluroRouter();
Routers.configureRoutes(router);
Application.router = router;
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// // var userViewModel = Provider.of<UserModel>(context);
// // var onceuse = userViewModel.getOnceUse();
// // print("onceuse.toString(): ${onceuse.toString()}");
// String onceuse = "0";
// if (onceuse == "1") {
// } else {
// }
AppModel appModel = Provider.of<AppModel>(context);
services.SystemChrome.setPreferredOrientations([
services.DeviceOrientation.portraitUp,
services.DeviceOrientation.portraitDown
]);
return MaterialApp(
// <--- /!\ Add the builder
title: AppStrings.appName,
navigatorKey: Application.navigatorKey,
debugShowCheckedModeBanner: false,
onGenerateRoute: Application.router?.generator,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('en', 'US'), // 美式英语
Locale('zh', 'CN'), // 简体中文
//其它Locales
],
// theme: appModel.themeData, //固定主题
theme: Provider.of<ThemeCollection>(context).getActiveTheme,
);
}
}
+35
View File
@@ -0,0 +1,35 @@
//ignore_for_file: file_names
import 'dart:async';
import 'package:flutter/cupertino.dart';
class UserPreference extends ChangeNotifier {
// Initally Location index is 0
int locationIndex = 0;
// Change current location by call setlocationIndex method(function)
void setlocationIndex(int index) {
locationIndex = index;
notifyListeners();
}
// Setup for coundown service
Duration duration = Duration.zero;
bool isCountDownStart = false;
final Stream _stream = Stream.periodic(const Duration(seconds: 1));
UserPreference() {
_stream.listen((event) {
if (isCountDownStart) {
duration += const Duration(seconds: 1);
notifyListeners();
}
});
}
void get countDownSwitch {
isCountDownStart = !isCountDownStart;
notifyListeners();
}
}
+239
View File
@@ -0,0 +1,239 @@
abstract class Flags {
/// Database of all flags in format : List<Map<String, String>>
/// eg. {"name": "India", "imagePath": "in.svg"}
static List<Map<String, String>> get list => [
{"name": "Andorra", "imagePath": "ad.svg"},
{"name": "United Arab Emirates", "imagePath": "ae.svg"},
{"name": "Antigua and Barbuda", "imagePath": "ag.svg"},
{"name": "Anguilla", "imagePath": "ai.svg"},
{"name": "Albania", "imagePath": "al.svg"},
{"name": "Armenia", "imagePath": "am.svg"},
{"name": "Angola", "imagePath": "ao.svg"},
{"name": "American Samoa", "imagePath": "as.svg"},
{"name": "Austria", "imagePath": "at.svg"},
{"name": "Australia", "imagePath": "au.svg"},
{"name": "Aruba", "imagePath": "aw.svg"},
{"name": "Åland Islands", "imagePath": "ax.svg"},
{"name": "Azerbaijan", "imagePath": "az.svg"},
{"name": "Bosnia and Herzegovina", "imagePath": "ba.svg"},
{"name": "Barbados", "imagePath": "bb.svg"},
{"name": "Bangladesh", "imagePath": "bd.svg"},
{"name": "Belgium", "imagePath": "be.svg"},
{"name": "Burkina Faso", "imagePath": "bf.svg"},
{"name": "Bulgaria", "imagePath": "bg.svg"},
{"name": "Bahrain", "imagePath": "bh.svg"},
{"name": "Burundi", "imagePath": "bi.svg"},
{"name": "Benin", "imagePath": "bj.svg"},
{"name": "Saint Barthélemy", "imagePath": "bl.svg"},
{"name": "Bermuda", "imagePath": "bm.svg"},
{"name": "Brunei Darussalam", "imagePath": "bn.svg"},
{"name": "Caribbean Netherlands", "imagePath": "bq.svg"},
{"name": "Bahamas", "imagePath": "bs.svg"},
{"name": "Bhutan", "imagePath": "bt.svg"},
{"name": "Bouvet Island", "imagePath": "bv.svg"},
{"name": "Botswana", "imagePath": "bw.svg"},
{"name": "Belize", "imagePath": "bz.svg"},
{"name": "Canada", "imagePath": "ca.svg"},
{"name": "Cocos (Keeling) Islands", "imagePath": "cc.svg"},
{"name": "Congo", "imagePath": "cd.svg"},
{"name": "Central African Republic", "imagePath": "cf.svg"},
{"name": "Republic of the Congo", "imagePath": "cg.svg"},
{"name": "Switzerland", "imagePath": "ch.svg"},
{"name": "Cook Islands", "imagePath": "ck.svg"},
{"name": "Chile", "imagePath": "cl.svg"},
{"name": "Cameroon", "imagePath": "cm.svg"},
{"name": "Colombia", "imagePath": "co.svg"},
{"name": "Costa Rica", "imagePath": "cr.svg"},
{"name": "Cuba", "imagePath": "cu.svg"},
{"name": "Cape Verde", "imagePath": "cv.svg"},
{"name": "Curaçao", "imagePath": "cw.svg"},
{"name": "Christmas Island", "imagePath": "cx.svg"},
{"name": "Cyprus", "imagePath": "cy.svg"},
{"name": "Czech Republic", "imagePath": "cz.svg"},
{"name": "Germany", "imagePath": "de.svg"},
{"name": "Djibouti", "imagePath": "dj.svg"},
{"name": "Denmark", "imagePath": "dk.svg"},
{"name": "Algeria", "imagePath": "dz.svg"},
{"name": "Estonia", "imagePath": "ee.svg"},
{"name": "Egypt", "imagePath": "eg.svg"},
{"name": "Western Sahara", "imagePath": "eh.svg"},
{"name": "Eritrea", "imagePath": "er.svg"},
{"name": "Spain", "imagePath": "es.svg"},
{"name": "Ethiopia", "imagePath": "et.svg"},
{"name": "Europe", "imagePath": "eu.svg"},
{"name": "Finland", "imagePath": "fi.svg"},
{"name": "Fiji", "imagePath": "fj.svg"},
{"name": "Micronesia", "imagePath": "fm.svg"},
{"name": "Faroe Islands", "imagePath": "fo.svg"},
{"name": "France", "imagePath": "fr.svg"},
{"name": "Gabon", "imagePath": "ga.svg"},
{"name": "England", "imagePath": "gb-eng.svg"},
{"name": "Northern Ireland", "imagePath": "gb-nir.svg"},
{"name": "Wales", "imagePath": "gb-wls.svg"},
{"name": "United Kingdom", "imagePath": "gb.svg"},
{"name": "Grenada", "imagePath": "gd.svg"},
{"name": "Georgia", "imagePath": "ge.svg"},
{"name": "French Guiana", "imagePath": "gf.svg"},
{"name": "Guernsey", "imagePath": "gg.svg"},
{"name": "Ghana", "imagePath": "gh.svg"},
{"name": "Gibraltar", "imagePath": "gi.svg"},
{"name": "Greenland", "imagePath": "gl.svg"},
{"name": "Gambia", "imagePath": "gm.svg"},
{"name": "Guinea", "imagePath": "gn.svg"},
{"name": "Guadeloupe", "imagePath": "gp.svg"},
{"name": "Equatorial Guinea", "imagePath": "gq.svg"},
{"name": "Greece", "imagePath": "gr.svg"},
{"name": "Guatemala", "imagePath": "gt.svg"},
{"name": "Guam", "imagePath": "gu.svg"},
{"name": "Guinea-Bissau", "imagePath": "gw.svg"},
{"name": "Guyana", "imagePath": "gy.svg"},
{"name": "Hong Kong", "imagePath": "hk.svg"},
{"name": "Heard Island and McDonald Islands", "imagePath": "hm.svg"},
{"name": "Honduras", "imagePath": "hn.svg"},
{"name": "Croatia", "imagePath": "hr.svg"},
{"name": "Haiti", "imagePath": "ht.svg"},
{"name": "Hungary", "imagePath": "hu.svg"},
{"name": "Indonesia", "imagePath": "id.svg"},
{"name": "Ireland", "imagePath": "ie.svg"},
{"name": "Israel", "imagePath": "il.svg"},
{"name": "Isle of Man", "imagePath": "im.svg"},
{"name": "India", "imagePath": "in.svg"},
{"name": "British Indian Ocean Territory", "imagePath": "io.svg"},
{"name": "Iraq", "imagePath": "iq.svg"},
{"name": "Iran, Islamic Republic of", "imagePath": "ir.svg"},
{"name": "Iceland", "imagePath": "is.svg"},
{"name": "Italy", "imagePath": "it.svg"},
{"name": "Jersey", "imagePath": "je.svg"},
{"name": "Jamaica", "imagePath": "jm.svg"},
{"name": "Jordan", "imagePath": "jo.svg"},
{"name": "Japan", "imagePath": "jp.svg"},
{"name": "Kenya", "imagePath": "ke.svg"},
{"name": "Kyrgyzstan", "imagePath": "kg.svg"},
{"name": "Cambodia", "imagePath": "kh.svg"},
{"name": "Comoros", "imagePath": "km.svg"},
{"name": "Saint Kitts and Nevis", "imagePath": "kn.svg"},
{"name": "Korea", "imagePath": "kp.svg"},
{"name": "Korea, Republic of", "imagePath": "kr.svg"},
{"name": "Kuwait", "imagePath": "kw.svg"},
{"name": "Kazakhstan", "imagePath": "kz.svg"},
{"name": "Laos", "imagePath": "la.svg"},
{"name": "Lebanon", "imagePath": "lb.svg"},
{"name": "Saint Lucia", "imagePath": "lc.svg"},
{"name": "Liechtenstein", "imagePath": "li.svg"},
{"name": "Liberia", "imagePath": "lr.svg"},
{"name": "Lesotho", "imagePath": "ls.svg"},
{"name": "Lithuania", "imagePath": "lt.svg"},
{"name": "Luxembourg", "imagePath": "lu.svg"},
{"name": "Latvia", "imagePath": "lv.svg"},
{"name": "Libya", "imagePath": "ly.svg"},
{"name": "Morocco", "imagePath": "ma.svg"},
{"name": "Monaco", "imagePath": "mc.svg"},
{"name": "Moldova, Republic of", "imagePath": "md.svg"},
{"name": "Montenegro", "imagePath": "me.svg"},
{"name": "Saint Martin", "imagePath": "mf.svg"},
{"name": "Madagascar", "imagePath": "mg.svg"},
{"name": "Marshall Islands", "imagePath": "mh.svg"},
{"name": "North Macedonia", "imagePath": "mk.svg"},
{"name": "Mali", "imagePath": "ml.svg"},
{"name": "Myanmar", "imagePath": "mm.svg"},
{"name": "Mongolia", "imagePath": "mn.svg"},
{"name": "Macao", "imagePath": "mo.svg"},
{"name": "Martinique", "imagePath": "mq.svg"},
{"name": "Mauritania", "imagePath": "mr.svg"},
{"name": "Montserrat", "imagePath": "ms.svg"},
{"name": "Malta", "imagePath": "mt.svg"},
{"name": "Mauritius", "imagePath": "mu.svg"},
{"name": "Maldives", "imagePath": "mv.svg"},
{"name": "Malawi", "imagePath": "mw.svg"},
{"name": "Malaysia", "imagePath": "my.svg"},
{"name": "Mozambique", "imagePath": "mz.svg"},
{"name": "Namibia", "imagePath": "na.svg"},
{"name": "New Caledonia", "imagePath": "nc.svg"},
{"name": "Niger", "imagePath": "ne.svg"},
{"name": "Norfolk Island", "imagePath": "nf.svg"},
{"name": "Nigeria", "imagePath": "ng.svg"},
{"name": "Nicaragua", "imagePath": "ni.svg"},
{"name": "Netherlands", "imagePath": "nl.svg"},
{"name": "Norway", "imagePath": "no.svg"},
{"name": "Nauru", "imagePath": "nr.svg"},
{"name": "Niue", "imagePath": "nu.svg"},
{"name": "New Zealand", "imagePath": "nz.svg"},
{"name": "Oman", "imagePath": "om.svg"},
{"name": "Panama", "imagePath": "pa.svg"},
{"name": "French Polynesia", "imagePath": "pf.svg"},
{"name": "Papua New Guinea", "imagePath": "pg.svg"},
{"name": "Philippines", "imagePath": "ph.svg"},
{"name": "Pakistan", "imagePath": "pk.svg"},
{"name": "Poland", "imagePath": "pl.svg"},
{"name": "Saint Pierre and Miquelon", "imagePath": "pm.svg"},
{"name": "Pitcairn", "imagePath": "pn.svg"},
{"name": "Puerto Rico", "imagePath": "pr.svg"},
{"name": "Palestine", "imagePath": "ps.svg"},
{"name": "Portugal", "imagePath": "pt.svg"},
{"name": "Palau", "imagePath": "pw.svg"},
{"name": "Paraguay", "imagePath": "py.svg"},
{"name": "Qatar", "imagePath": "qa.svg"},
{"name": "Réunion", "imagePath": "re.svg"},
{"name": "Romania", "imagePath": "ro.svg"},
{"name": "Serbia", "imagePath": "rs.svg"},
{"name": "Russian Federation", "imagePath": "ru.svg"},
{"name": "Rwanda", "imagePath": "rw.svg"},
{"name": "Saudi Arabia", "imagePath": "sa.svg"},
{"name": "Solomon Islands", "imagePath": "sb.svg"},
{"name": "Seychelles", "imagePath": "sc.svg"},
{"name": "Sudan", "imagePath": "sd.svg"},
{"name": "Sweden", "imagePath": "se.svg"},
{"name": "Singapore", "imagePath": "sg.svg"},
{
"name": "Saint Helena, Ascension and Tristan da Cunha",
"imagePath": "sh.svg"
},
{"name": "Slovenia", "imagePath": "si.svg"},
{"name": "Svalbard and Jan Mayen Islands", "imagePath": "sj.svg"},
{"name": "Slovakia", "imagePath": "sk.svg"},
{"name": "Sierra Leone", "imagePath": "sl.svg"},
{"name": "San Marino", "imagePath": "sm.svg"},
{"name": "Senegal", "imagePath": "sn.svg"},
{"name": "Somalia", "imagePath": "so.svg"},
{"name": "Suriname", "imagePath": "sr.svg"},
{"name": "South Sudan", "imagePath": "ss.svg"},
{"name": "Sao Tome and Principe", "imagePath": "st.svg"},
{"name": "Sint Maarten", "imagePath": "sx.svg"},
{"name": "Syrian Arab Republic", "imagePath": "sy.svg"},
{"name": "Swaziland", "imagePath": "sz.svg"},
{"name": "Chad", "imagePath": "td.svg"},
{"name": "French Southern Territories", "imagePath": "tf.svg"},
{"name": "Togo", "imagePath": "tg.svg"},
{"name": "Thailand", "imagePath": "th.svg"},
{"name": "Tajikistan", "imagePath": "tj.svg"},
{"name": "Tokelau", "imagePath": "tk.svg"},
{"name": "Timor-Leste", "imagePath": "tl.svg"},
{"name": "Turkmenistan", "imagePath": "tm.svg"},
{"name": "Tunisia", "imagePath": "tn.svg"},
{"name": "Tonga", "imagePath": "to.svg"},
{"name": "Turkey", "imagePath": "tr.svg"},
{"name": "Trinidad and Tobago", "imagePath": "tt.svg"},
{"name": "Tuvalu", "imagePath": "tv.svg"},
{"name": "Taiwan (Republic of China)", "imagePath": "tw.svg"},
{"name": "Tanzania, United Republic of", "imagePath": "tz.svg"},
{"name": "Ukraine", "imagePath": "ua.svg"},
{"name": "Uganda", "imagePath": "ug.svg"},
{"name": "US Minor Outlying Islands", "imagePath": "um.svg"},
{"name": "United States", "imagePath": "us.svg"},
{"name": "Uruguay", "imagePath": "uy.svg"},
{"name": "Uzbekistan", "imagePath": "uz.svg"},
{"name": "Holy See", "imagePath": "va.svg"},
{"name": "Saint Vincent and the Grenadines", "imagePath": "vc.svg"},
{"name": "Venezuela", "imagePath": "ve.svg"},
{"name": "Virgin Islands, British", "imagePath": "vg.svg"},
{"name": "Virgin Islands, U.S.", "imagePath": "vi.svg"},
{"name": "Vietnam", "imagePath": "vn.svg"},
{"name": "Vanuatu", "imagePath": "vu.svg"},
{"name": "Samoa", "imagePath": "ws.svg"},
{"name": "Kosovo", "imagePath": "xk.svg"},
{"name": "Yemen", "imagePath": "ye.svg"},
{"name": "Mayotte", "imagePath": "yt.svg"},
{"name": "Zambia", "imagePath": "zm.svg"},
{"name": "Zimbabwe", "imagePath": "zw.svg"}
];
}
+34
View File
@@ -0,0 +1,34 @@
// ignore_for_file: file_names
import 'package:flutter/material.dart';
import 'package:uuvpn/resources/app_theme.dart';
class ThemeCollection extends ChangeNotifier {
/// _colorSwatch method or function take RGB color as argument and after processing
/// it will return Map<int, Color> where int consist colors varients
Map<int, Color> _colorSwatch(
int r,
int g,
int b,
) =>
{
50: Color.fromRGBO(r, g, b, 0.1),
100: Color.fromRGBO(r, g, b, 0.2),
200: Color.fromRGBO(r, g, b, 0.3),
300: Color.fromRGBO(r, g, b, 0.4),
400: Color.fromRGBO(r, g, b, 0.5),
500: Color.fromRGBO(r, g, b, 0.6),
600: Color.fromRGBO(r, g, b, 0.7),
700: Color.fromRGBO(r, g, b, 0.8),
800: Color.fromRGBO(r, g, b, 0.9),
900: Color.fromRGBO(r, g, b, 1),
};
bool isDarkActive = true;
void setDarkTheme(bool value) {
isDarkActive = value;
notifyListeners();
}
ThemeData get getActiveTheme =>
isDarkActive ? AppTheme.lightThemeData : AppTheme.darkThemeData;
}
+190
View File
@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:uuvpn/adapters/leaf_ffi/config.dart';
import 'package:uuvpn/channels/Platform.dart';
import 'package:uuvpn/channels/vpn_manager.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/common_util.dart';
class AppModel extends BaseModel {
VpnManager vpnManager = VpnManager();
VpnStatus vpnStatus = VpnStatus.disconnected;
bool isOn = false;
bool isconnectordisconnct = false;
DateTime? connectedDate;
PageController pageController = PageController(initialPage: 0);
String appTitle = AppStrings.appName;
Config config = Config();
ThemeData themeData = ThemeData(
primarySwatch: AppColors.themeColor,
visualDensity: VisualDensity.adaptivePlatformDensity,
);
AppModel() {
General general = General(
loglevel: 'info',
logoutput: '{{leafLogFile}}',
dnsServer: ['223.5.5.5', '114.114.114.114'],
tunFd: '{{tunFd}}',
apiInterface: '127.0.0.1',
apiPort: 1087,
socksInterface: '127.0.0.1',
socksPort: 1086,
alwaysRealIp: ['tracker', 'apple.com'],
routingDomainResolve: true);
List<Rule> rules = [];
// rules.add(Rule(typeField: 'EXTERNAL', target: 'Direct', filter: 'site:cn'));
rules.add(Rule(typeField: 'FINAL', target: 'Direct'));
config.general = general;
config.rules = rules;
}
final Map _tabMap = {
0: '主页',
1: '套餐',
2: '节点',
3: '我的',
};
void jumpToPage(int page) {
// pageController.jumpToPage(page);
appTitle = _tabMap[page];
notifyListeners();
}
void getStatus() async {
vpnStatus = await vpnManager.getStatus();
if (vpnStatus == VpnStatus.connected) {
isOn = true;
getConnectedDate();
notifyListeners();
} else if (vpnStatus == VpnStatus.disconnected) {
isOn = false;
notifyListeners();
} else {
isconnectordisconnct = true;
notifyListeners();
}
}
void getConnectedDate() async {
if (Platform.isAndroid || Platform.isMacOS) {
} else {
var date = await vpnManager.getConnectedDate();
//print("date: $date");
connectedDate = date;
}
}
void togglePowerButton() async {
if (vpnStatus == VpnStatus.connecting) {
Fluttertoast.showToast(
msg: "Connecting, please wait...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0);
return;
}
if (vpnStatus == VpnStatus.disconnecting) {
Fluttertoast.showToast(
msg: "Disconnecting, please wait...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0);
return;
}
if (vpnStatus == VpnStatus.connected) {
vpnStatus = VpnStatus.disconnecting;
}
if (vpnStatus == VpnStatus.disconnected) {
vpnStatus = VpnStatus.connecting;
}
await vpnManager.toggle();
notifyListeners();
}
void getTunnelLog() async {
if (Platform.isIOS) {
var log = await vpnManager.getTunnelLog();
print("log: $log");
}
}
void getTunnelConfiguration() async {
if (Platform.isIOS) {
var conf = await vpnManager.getTunnelConfiguration();
print("config: $conf");
}
}
void setConfigProxies(UserModel userModel, ServerModel serverModel) async {
List<Proxy> proxies = [];
List<ProxyGroup> proxyGroups = [];
List<String> actors = [];
for (var server in serverModel.serverEntityList) {
Proxy proxy = Proxy(
tag: server.name,
protocol: server.type,
address: server.host,
port: server.port,
encryptMethod: server.cipher,
password: userModel.userEntity!.uuid);
proxies.add(proxy);
actors.add(server.name);
}
if (actors.isNotEmpty) {
proxyGroups.add(ProxyGroup(
tag: "UrlTest",
protocol: 'url-test',
actors: actors,
checkInterval: 600));
config.rules?.last.target = "UrlTest";
}
config.proxies = proxies;
config.proxyGroups = proxyGroups;
print("-----------------config-----------------");
print(config);
print("-----------------config-----------------");
vpnManager.setTunnelConfiguration(config.toString());
}
void setConfigRule(String tag) async {
var proxy = config.proxies?.where((proxies) => proxies.tag == tag);
if (proxy == null || proxy.isEmpty) {
return;
}
config.rules?.last.target = tag;
print("-----------------config-----------------");
print(config);
print("-----------------config-----------------");
vpnManager.setTunnelConfiguration(config.toString());
}
}
+32
View File
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:uuvpn/models/page_state.dart';
import 'package:uuvpn/utils/common_util.dart';
class BaseModel extends ChangeNotifier {
PageState pageState = PageState.loading;
bool _isDispose = false;
late String errorMessage;
bool get isDispose => _isDispose;
@override
void notifyListeners() {
//print("view model notifyListeners");
if (!_isDispose) {
super.notifyListeners();
}
}
void errorNotify(String error) {
pageState = PageState.error;
errorMessage = error;
notifyListeners();
}
@override
void dispose() {
_isDispose = true;
//print("view model dispose");
super.dispose();
}
}
+26
View File
@@ -0,0 +1,26 @@
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/service/user_service.dart';
class LoginModel extends BaseModel {
final UserService _userService = UserService();
final UserModel _userModel;
LoginModel(this._userModel);
// 登陆方法
login(String? account, String? passWord) async {
var parameters = {'email': account, 'password': passWord};
return _userService.login(parameters)?.then((loginEntity) async {
_userModel.setToken(loginEntity);
return _userService.info();
}).then((userEntity) {
_userModel.setUserInfo(userEntity);
notifyListeners();
return userEntity;
});
}
}
+6
View File
@@ -0,0 +1,6 @@
enum PageState {
loading, //加载中
hasData, //有数据
empty, //无数据
error, //加载失败
}
+24
View File
@@ -0,0 +1,24 @@
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/service/plan_service.dart';
class PlanModel extends BaseModel {
final PlanService _planService = PlanService();
List<PlanEntity> _planEntityList = [];
List<PlanEntity> get planEntityList => _planEntityList;
// 获取套餐列表
void fetchPlanList() async {
try {
// bool xxx = await platform.invokeMethod("getStatus");
// result = xxx ? 1 : 0;
_planEntityList = (await _planService.plan())!;
notifyListeners();
} on Exception catch (e) {
print(e.toString());
}
}
}
+176
View File
@@ -0,0 +1,176 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter_icmp_ping/flutter_icmp_ping.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/entity/server_entity.dart';
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/service/server_service.dart';
import 'package:uuvpn/utils/shared_preferences_util.dart';
import 'package:uuvpn/utils/common_util.dart';
enum PingType { ping, tcp }
class ServerModel extends BaseModel {
List<ServerEntity> _serverEntityList = [];
ServerEntity? _selectServerEntity;
int _selectServerIndex = 0;
final ServerService _serverService = ServerService();
List<ServerEntity> get serverEntityList => _serverEntityList;
ServerEntity? get selectServerEntity => _selectServerEntity;
int get selectServerIndex => _selectServerIndex;
getServerList({bool forceRefresh = false}) async {
bool result = false;
List<dynamic> data = await SharedPreferencesUtil.getInstance()
?.getList(AppStrings.serverNode) ??
[];
List<dynamic> newData =
List.from(data.map((e) => Map<String, dynamic>.from(jsonDecode(e))));
print("$newData");
if (newData.isEmpty || forceRefresh) {
setServerEntityList(await _serverService.server());
} else {
_serverEntityList = serverEntityFromList(newData);
}
notifyListeners();
result = true;
return result;
}
void pingAll() async {
for (int i = 0; i < _serverEntityList.length; i++) {
var duration = const Duration(milliseconds: 300);
await Future.delayed(duration);
ping(i);
}
}
void ping(int index, {PingType type = PingType.tcp}) {
ServerEntity serverEntity = _serverEntityList[index];
String host = serverEntity.host;
int serverPort = serverEntity.port;
//print("host=$host");
//print("serverPort=$serverPort");
switch (type) {
case PingType.ping:
try {
final ping =
Ping(host, count: 1, timeout: 1.0, interval: 1.0, ipv6: false);
ping.stream.listen((event) {
print(event);
if (event.error != null) {
var duration = const Duration(minutes: 1);
_serverEntityList[index].ping = duration;
} else if (event.response != null) {
_serverEntityList[index].ping = event.response?.time;
}
notifyListeners();
ping.stop();
});
} catch (e) {
rethrow;
}
break;
case PingType.tcp:
Stopwatch stopwatch = Stopwatch()..start();
Socket.connect(host, serverPort, timeout: const Duration(seconds: 3))
.then((socket) {
socket.destroy();
var duration = stopwatch.elapsed;
_serverEntityList[index].ping = duration;
notifyListeners();
return duration;
}).catchError((error) {
var duration = const Duration(minutes: 1);
_serverEntityList[index].ping = duration;
throw error;
});
break;
default:
throw Error();
}
}
getSelectServer() async {
Map<String, dynamic> data = await SharedPreferencesUtil.getInstance()
?.getMap(AppStrings.selectServer) ??
<String, dynamic>{};
int index = int.parse(await SharedPreferencesUtil.getInstance()
?.getString(AppStrings.selectServerIndex) ??
'0');
if (data.isEmpty) {
return null;
}
_selectServerEntity = ServerEntity.fromMap(data);
_selectServerIndex = index;
notifyListeners();
return _selectServerEntity;
}
setServerEntityList(List<ServerEntity> serverEntityList) {
_serverEntityList = serverEntityList;
_saveServerEntityList();
}
setSelectServerEntity(ServerEntity selectServerEntity) {
_selectServerEntity = selectServerEntity;
_saveSelectServerEntity();
notifyListeners();
}
setSelectServerIndex(int index) {
_selectServerIndex = index;
_saveSelectServerIndex();
notifyListeners();
}
_saveServerEntityList() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setList(
AppStrings.serverNode, _serverEntityList);
}
_saveSelectServerEntity() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setMap(
AppStrings.selectServer, _selectServerEntity!.toMap());
}
_saveSelectServerIndex() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setString(
AppStrings.selectServerIndex, _selectServerIndex.toString());
}
}
+120
View File
@@ -0,0 +1,120 @@
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/entity/login_entity.dart';
import 'package:uuvpn/entity/user_entity.dart';
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/utils/shared_preferences_util.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserModel extends BaseModel {
String? _token;
String? _authData;
UserEntity? _userEntity;
bool _isLogin = false;
String? get token => _token;
String? get authData => _authData;
UserEntity? get userEntity => _userEntity;
bool get isLogin => _isLogin;
String? _isonceLogin;
Future<void> checkHasLogin(context, Function callback) async {
if (!isLogin) {
NavigatorUtil.goLogin(context);
} else {
return callback();
}
}
refreshData() async {
String token = await SharedPreferencesUtil.getInstance()
?.getString(AppStrings.token) ??
'';
String authData = await SharedPreferencesUtil.getInstance()
?.getString(AppStrings.authData) ??
'';
if (token != null &&
token.isNotEmpty &&
authData != null &&
authData.isNotEmpty) {
_isLogin = true;
_token = token;
_authData = authData;
Map<String, dynamic> userEntityMap =
await SharedPreferencesUtil.getInstance()
?.getMap(AppStrings.userInfo) ??
<String, dynamic>{};
_userEntity = UserEntity.fromMap(userEntityMap);
notifyListeners();
}
}
logout() {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
sharedPreferencesUtil?.clear();
refreshData();
}
_saveOnceUserUse() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
_isonceLogin = "1";
await sharedPreferencesUtil?.setString("diyicidakaiapp", "1");
}
getOnceUse() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.getString("diyicidakaiapp");
// String? value = await sharedPreferencesUtil?.getString("diyicidakaiapp");
// if (value != null && value != "") {
// return value;
// } else {
// return "0";
// }
}
_saveUserToken(LoginEntity loginEntity) async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setString(AppStrings.token, loginEntity.token);
}
_setUserAuthData(LoginEntity loginEntity) async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setString(
AppStrings.authData, loginEntity.authData);
}
_saveUserInfo() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setMap(
AppStrings.userInfo, _userEntity?.toMap());
}
setToken(LoginEntity loginEntity) {
_token = loginEntity.token;
_authData = loginEntity.authData;
_isLogin = true;
_saveUserToken(loginEntity);
_setUserAuthData(loginEntity);
}
setUserInfo(UserEntity? userEntity) {
_userEntity = userEntity;
_saveUserInfo();
}
}
@@ -0,0 +1,45 @@
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/models/base_model.dart';
import 'package:uuvpn/service/user_service.dart';
import 'package:uuvpn/utils/shared_preferences_util.dart';
class UserSubscribeModel extends BaseModel {
UserSubscribeEntity? _userSubscribeEntity;
final UserService _userService = UserService();
UserSubscribeEntity? get userSubscribeEntity => _userSubscribeEntity;
Future<bool> getUserSubscribe({bool forceRefresh = false}) async {
bool result = false;
// Map<String, dynamic>? data = await SharedPreferencesUtil.getInstance() ?.getMap(AppStrings.userSubscribe);
Map<String, dynamic>? data;
if (data == null || data.isEmpty || forceRefresh) {
setUserSubscribeEntity(await _userService.userSubscribe());
} else {
_userSubscribeEntity = UserSubscribeEntity.fromMap(data);
}
notifyListeners();
result = true;
return result;
}
setUserSubscribeEntity(UserSubscribeEntity? userSubscribeEntity) {
_userSubscribeEntity = userSubscribeEntity;
_saveUserSubscribe();
}
_saveUserSubscribe() async {
SharedPreferencesUtil? sharedPreferencesUtil =
SharedPreferencesUtil.getInstance();
await sharedPreferencesUtil?.setMap(
AppStrings.userSubscribe, _userSubscribeEntity?.toMap());
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:uuvpn/constant/app_images.dart';
import 'package:uuvpn/constant/app_strings.dart';
class NotFindPage extends StatelessWidget {
const NotFindPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(AppStrings.appName),
),
body: Container(
alignment: Alignment.center,
child: Image.asset(
AppImages.notFoundPicture,
width: 200,
height: 100,
color: const Color(0xFFff5722),
),
));
}
}
+155
View File
@@ -0,0 +1,155 @@
//ProOnecePage.dart
//ignore_for_file: file_names
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/utils/l10n.dart';
import '/model/themeCollection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class ProOnecePage extends StatelessWidget {
const ProOnecePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Center(
child: ListView(
shrinkWrap: true,
children: [
// SvgPicture.asset(
// 'assets/logo.svg',
// cacheColorFilter: true,
// color: AppColors.greenColor,
// ),
const SizedBox(height: 12),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: ' ',
style: TextStyle(
fontFamily: 'Aquire',
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 24),
children: const [
TextSpan(
text: 'Your privacy comes first',
style: TextStyle(
fontFamily: 'Roboto', fontWeight: FontWeight.w500))
])),
Container(
margin: EdgeInsets.only(left: 20, right: 20),
child: RichText(
textAlign: TextAlign.left,
text: TextSpan(
text: """
We do not keep logs of your online activities and never associate any domains or applications that you use with you, your device,IP address, or email. Betternet collects a minimal amount of data to offer you a fast and reliable VPN service. We collect:
Device-specific information like OS version hardware modeland IP address to optimize our network connection to you.We do not store or log your IP address after you disconnect from the VPN
Aggregated anonymous website activity datatoperform analytics on our service and to ensure you can reliably access certain websites orapps
For more information, please read our Privacy Policy
""",
style: TextStyle(
fontFamily: 'Roboto',
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 17),
),
)),
const SizedBox(height: 32),
// decoratedButton(context, '1 MONTH', '1.99', '\$/month', false),
// decoratedButton(context, '1 YEAR', '0.99', '\$/month', true),
GestureDetector(
onTap: () => {Navigator.maybePop(context)},
child: Container(
alignment: Alignment.center,
height: kToolbarHeight,
margin: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
color: Color.fromARGB(255, 34, 128, 15).withOpacity(0.7)),
child: Text(
'Accept and continue',
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
color: AppColors.whiteColor,
),
),
),
),
Text(
' ',
textAlign: TextAlign.center,
style: Theme.of(context).primaryTextTheme.labelMedium,
),
],
),
);
}
Container decoratedButton(BuildContext context, String lt, String rt_1,
String rt_2, bool isDiscount) {
return Container(
height: kToolbarHeight,
margin: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 24.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
gradient: const LinearGradient(
colors: [AppColors.greenColor, Color.fromARGB(255, 7, 52, 7)],
transform: GradientRotation(5))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
lt,
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
),
if (isDiscount)
Container(
margin: const EdgeInsets.all(4.0),
padding: const EdgeInsets.symmetric(
horizontal: 6.0, vertical: 2.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15)),
child: const Text(
'50% OFF',
style: TextStyle(fontSize: 10, color: Colors.black),
),
),
],
),
RichText(
text: TextSpan(
text: rt_1,
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
children: [
TextSpan(
text: ' $rt_2',
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
)
])),
],
),
);
}
}
+295
View File
@@ -0,0 +1,295 @@
//ignore_for_file: file_names
import 'package:flutter/cupertino.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/plan_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/widgets/logo_bar.dart';
import 'package:uuvpn/widgets/my_subscribe.dart';
import 'package:uuvpn/widgets/plan_list.dart';
import '/routes/proRoute.dart';
import '/routes/settingsRoute.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../model/themeCollection.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
@override
AccountState createState() => AccountState();
}
class AccountState extends State<AccountPage> {
void onLogoutTap(context, _userModel) {
showDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(context.l10n.alertsss),
content: Column(
children: <Widget>[
SizedBox(
height: 10,
),
Align(
child: Text(
context.l10n.wanttoexit,
style: TextStyle(fontSize: 18),
),
alignment: Alignment(0, 0),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(context.l10n.cancelss),
onPressed: () {
Navigator.pop(context);
//print("取消");
},
),
CupertinoDialogAction(
child: Text(
context.l10n.exitout,
style: TextStyle(color: Colors.red),
),
onPressed: () {
//print("确定");
_userModel.logout();
NavigatorUtil.goLogin(context);
},
),
],
);
});
}
customListTile(BuildContext context, String title, String icon,
{Widget? trailing,
Icon? sysicon,
String? subtitle,
VoidCallback? onTap}) =>
ListTile(
onTap: onTap ?? null,
minLeadingWidth: 35,
dense: true,
title: Text(title,
style: Theme.of(context).primaryTextTheme.labelMedium),
subtitle: subtitle != null
? Text(
subtitle,
style: Theme.of(context).primaryTextTheme.labelMedium,
)
: null,
// leading: sysicon ??
// SvgPicture.asset(
// icon,
// // color: Theme.of(context).colorScheme.secondary,
// width: 24,
// cacheColorFilter: true,
// color: AppColors.greenColor,
// alignment: Alignment.centerRight,
// ),
trailing: trailing ?? null);
upgradeButton(context) => GestureDetector(
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (builder) => const ProRoute())),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
gradient: const LinearGradient(
colors: [AppColors.greenColor, Color.fromARGB(255, 9, 54, 21)],
transform: GradientRotation(5))),
child: Text(
'Upgrade',
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
),
));
Divider get divider => Divider(
indent: 16,
endIndent: 16,
color: Colors.grey.withAlpha(50),
thickness: 1);
Future<void> _launchUrl(_url) async {
if (!await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Scaffold(
backgroundColor: isDarkTheme ? Color(0xff0B0415) : Colors.white,
appBar: AppBar(
foregroundColor: isDarkTheme ? Colors.white : Color(0xff0B0415),
backgroundColor: Colors.transparent,
title: Text(
'Settings',
),
),
body: build2(context),
);
}
Widget build2(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
AppModel _appModel = Provider.of<AppModel>(context);
UserModel userModel = Provider.of<UserModel>(context);
PlanModel _planModel = Provider.of<PlanModel>(context);
UserSubscribeModel _userSubscribeModel =
Provider.of<UserSubscribeModel>(context);
UserSubscribeModel userSubscribeModel =
Provider.of<UserSubscribeModel>(context);
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Padding(
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(75),
// right: ScreenUtil().setWidth(75)),
// child: LogoBar(
// isOn: _appModel.isOn,
// ),
// ),
// SvgPicture.asset(
// isDarkTheme
// ? 'assets/darkNoResults.svg'
// : 'assets/lightNoResults.svg',
// width: MediaQuery.of(context).size.width * 0.75,
// fit: BoxFit.cover,
// ),
Padding(
padding: EdgeInsets.only(top: ScreenUtil().setWidth(30)),
child: MySubscribe(
isLogin: userModel.isLogin,
isOn: _appModel.isOn,
userSubscribeEntity: _userSubscribeModel.userSubscribeEntity,
),
),
// Padding(
// padding: EdgeInsets.only(top: ScreenUtil().setWidth(30)),
// child: PlanList(
// isOn: _appModel.isOn,
// userSubscribeEntity: _userSubscribeModel.userSubscribeEntity,
// plans: _planModel.planEntityList,
// ),
// ),
userModel.isLogin
? Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(15)))
: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'Looks like Youre not signed in yet.',
style: Theme.of(context).primaryTextTheme.labelMedium,
),
),
// style: ButtonStyle(
// backgroundColor: const Color(0xff353351),
// textColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(64)),
// ),
// TextButton(onPressed: () {}, child: const Text('SIGN IN')),
userModel.isLogin
? customListTile(context, 'User', 'assets/id.svg',
subtitle: userSubscribeModel?.userSubscribeEntity?.email ??
context.l10n.welcome,
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {
onLogoutTap(context, userModel);
},
icon:
Icon(Icons.exit_to_app, size: 30, color: Colors.red)))
: customListTile(
context, context.l10n.login, 'assets/profile.svg',
onTap: () =>
//Navigator.of(context).push(MaterialPageRoute(
// builder: (builder) => const SettingsRoute()))
NavigatorUtil.goLogin(context)),
// divider,
// customListTile(context, 'Base Plan', 'assets/active.svg',
// trailing: upgradeButton(context)),
// // divider,
// customListTile(
// context,
// 'Restore',
// 'assets/history.svg',
// ),
divider,
customListTile(context, 'Settings', 'assets/settings.svg',
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {},
icon: Icon(Icons.arrow_forward_ios,
size: 20,
color: isDarkTheme
? Color.fromARGB(255, 148, 145, 145)
: Color.fromARGB(255, 190, 187, 187))),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (builder) => const SettingsRoute()))),
divider,
Text("App Version: 1.0.6",
style: Theme.of(context).primaryTextTheme.labelMedium),
/*userModel.isLogin
? GestureDetector(
onTap: () => {
// NavigatorUtil.goWebView(context, "Delete my account",
// "https://uuvpn.co/help/Help.php?userid=${userModel.userEntity?.email}")
NavigatorUtil.goWebView(context, "Delete My Account",
"https://go.crisp.chat/chat/embed/?website_id=3ed83170-f288-4c23-acd4-30c1e557948b&user_email=${userModel.userEntity?.email}")
},
child: Container(
alignment: Alignment.center,
height: kToolbarHeight - 10,
margin: const EdgeInsets.symmetric(
horizontal: 32.0, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
color: isDarkTheme
? Color.fromARGB(255, 220, 66, 66)
: AppColors.greenColor),
child: Text(
'Delete My Account',
style: Theme.of(context)
.primaryTextTheme
.headline6!
.copyWith(
color: isDarkTheme
? AppColors.whiteColor
: Color.fromARGB(255, 9, 30, 4)),
),
),
)
: SizedBox(),*/
],
),
);
}
}
+33
View File
@@ -0,0 +1,33 @@
import 'package:crisp/crisp.dart';
import 'package:flutter/widgets.dart';
import 'package:uuvpn/constant/app_strings.dart';
class CrispPage extends StatefulWidget {
const CrispPage({Key? key}) : super(key: key);
@override
CrispPageState createState() => CrispPageState();
}
class CrispPageState extends State<CrispPage> {
late CrispMain crispMain;
@override
void initState() {
super.initState();
crispMain = CrispMain(
websiteId: AppStrings.crispWebsiteId,
locale: 'zh-cn',
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: CrispView(
crispMain: crispMain,
clearCache: false,
));
}
}
+224
View File
@@ -0,0 +1,224 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/constant/app_dimens.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/plan_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/pages/accountPage.dart';
import 'package:uuvpn/pages/homePage.dart';
import 'package:uuvpn/pages/my_profile.dart';
import 'package:uuvpn/pages/plan/plan_page.dart';
import 'package:uuvpn/pages/proPage.dart';
import 'package:uuvpn/pages/server_list.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/routes/OnceNotice.dart';
import 'package:uuvpn/routes/proRoute.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/message_util.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/widgets/ProgressView.dart';
import 'package:uuvpn/widgets/home_widget.dart';
import 'package:uuvpn/widgets/power_btn.dart';
import 'package:uuvpn/widgets/sail_app_bar.dart';
import 'package:uuvpn/utils/common_util.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> with WidgetsBindingObserver {
late AppModel _appModel;
late ServerModel _serverModel;
late UserModel _userModel;
late UserSubscribeModel _userSubscribeModel;
late PlanModel _planModel;
bool _isLoadingData = false;
bool _initialStatus = false;
bool _isfinishedLoad = false;
late Timer _timer;
bool _isFirst = false;
int currentPage = 0;
late List<Map<String, dynamic>> _itemsList = const [
{
'name': 'Home',
'iconPath': 'assets/home.svg',
'icon': Icons.home_filled,
'route': HomeWidget()
},
{
'name': 'Nodes',
'iconPath': 'assets/logo2.svg',
'icon': Icons.rocket_launch,
'route': ServerListPage()
},
{
'name': 'Account',
'iconPath': 'assets/profile.svg',
'icon': Icons.person,
'route': AccountPage()
}
];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
createTimer();
// /判断是不是第一次启动
getFristBool();
Future.delayed(const Duration(seconds: 1), () {
//1秒后跳转到其他路由
if (_isFirst) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (builder) => const OnceNotice()));
}
});
}
void getFristBool() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
String key = "FristLaunchs";
if (preferences.containsKey(key)) {
setState(() => _isFirst = preferences.getBool(key)!);
preferences.setBool(key, false);
} else {
setState(() => _isFirst = true);
preferences.setBool(key, false);
}
}
@override
void dispose() {
cancelTimer();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void createTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_appModel.getStatus();
});
}
void cancelTimer() {
_timer.cancel();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_planModel.fetchPlanList();
_appModel.getStatus();
print("_appModel.isOn: ${_appModel.isOn}");
}
}
@override
void didChangeDependencies() async {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
_userSubscribeModel = Provider.of<UserSubscribeModel>(context);
_serverModel = Provider.of<ServerModel>(context);
_planModel = Provider.of<PlanModel>(context);
if (_userModel.isLogin && !_isLoadingData) {
_isLoadingData = true;
await _userSubscribeModel.getUserSubscribe();
await _serverModel.getServerList(forceRefresh: true);
await _serverModel.getSelectServer();
_appModel.setConfigProxies(_userModel, _serverModel);
}
if (!_initialStatus) {
_initialStatus = true;
_planModel.fetchPlanList();
}
if (_userModel.userEntity?.uuid != null) {
setState(() {
_isfinishedLoad = true;
});
}
}
sendEmail(recipient, subject, body) async {
final String url = 'mailto:$recipient?subject=$subject&body=$body';
if (!await launchUrl(Uri.parse(url))) {
throw Exception('Could not launch $url');
}
}
@override
Widget build(BuildContext context) {
ScreenUtil.init(context,
designSize: const Size(AppDimens.maxWidth, AppDimens.maxHeight));
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Scaffold(
backgroundColor: isDarkTheme ? Color(0xff0B0415) : Colors.white,
appBar: AppBar(
shadowColor: Colors.transparent,
backgroundColor: Colors.transparent,
title: SvgPicture.asset(
'assets/text2.svg',
height: 28,
color: isDarkTheme ? Colors.white : Colors.black,
),
actions: <Widget>[
IconButton(
color: isDarkTheme ? Colors.white : Colors.black,
icon: const Icon(Icons.support_agent),
tooltip: 'support_agent',
onPressed: () {
sendEmail("admin@uuvpn.co", "UUVPN Question Help", "");
},
),
IconButton(
icon: const Icon(Icons.public),
color: isDarkTheme ? Colors.white : Colors.black,
tooltip: 'public',
onPressed: () {
if (_serverModel.serverEntityList.isEmpty) {
MessageUtil.toast(context.l10n.nodefornullcheckissubscripts);
} else {
NavigatorUtil.goServerList(context);
}
},
),
IconButton(
color: isDarkTheme ? Colors.white : Colors.black,
icon: const Icon(Icons.menu),
tooltip: 'menu',
onPressed: () {
NavigatorUtil.goSettings(context);
// setState(() {
// });
},
),
],
),
body: HomeWidget());
}
}
+158
View File
@@ -0,0 +1,158 @@
//ignore_for_file: file_names
import 'dart:math';
import '/model/flags.dart';
import '../model/UserPreference.dart';
import '../model/themeCollection.dart';
import 'package:provider/provider.dart';
import '/routes/chooseLocationRoute.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class HomePagesss extends StatelessWidget {
const HomePagesss({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
int currentLocIndex = 0;
Widget netSpeed(IconData icon, Color color) => Row(
children: [
Icon(
icon,
color: color,
),
Builder(builder: (context) {
return RichText(
text: TextSpan(
style: Theme.of(context).primaryTextTheme.labelMedium,
text:
Provider.of<UserPreference>(context).isCountDownStart
? Random().nextInt(500).toString() + ' '
: '___',
children: const [
TextSpan(text: 'KB/S', style: TextStyle(fontSize: 12))
]));
})
],
);
return Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 6,
color:
isDarkTheme ? const Color(0xff181227) : const Color(0xffF5F5F6),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Builder(builder: (context) {
currentLocIndex =
Provider.of<UserPreference>(context).locationIndex;
return ListTile(
leading: SvgPicture.asset(
'assets/flags/${Flags.list[currentLocIndex]['imagePath']}',
width: 42,
alignment: Alignment.center,
),
trailing: SizedBox(
width: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Icon(
Icons.signal_cellular_alt_rounded,
color: Colors.white,
),
IconButton(
icon: Icon(
Icons.navigate_next_outlined,
color: Theme.of(context).iconTheme.color,
),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (builder) =>
const ChooseLocationRoute()))),
],
),
),
title: Text(
Flags.list[currentLocIndex]['name'] as String,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Theme.of(context).primaryTextTheme.labelMedium,
),
subtitle: Text('IP: 79.110.53.95',
style: Theme.of(context).primaryTextTheme.labelMedium),
);
}),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
netSpeed(
Icons.south_rounded,
Theme.of(context).primaryColor,
),
netSpeed(
Icons.north_rounded,
Theme.of(context).primaryColor,
),
],
),
),
SvgPicture.asset(
'assets/map.svg',
width: MediaQuery.of(context).size.width,
color: isDarkTheme ? const Color(0xff38323F) : const Color(0xffC7B4E3),
fit: BoxFit.cover,
),
Builder(builder: (_context) {
var countDown = Provider.of<UserPreference>(_context);
return GestureDetector(
onTap: () => Provider.of<UserPreference>(context, listen: false)
.countDownSwitch,
child: Card(
elevation: 6,
color:
isDarkTheme ? const Color(0xff181227) : const Color(0xffF5F5F6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100)),
child: SizedBox.square(
dimension: 75 * 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
countDown.isCountDownStart
? 'assets/stop.svg'
: 'assets/powOn.svg',
width: countDown.isCountDownStart ? 35 : 50,
color: countDown.isCountDownStart
? Colors.redAccent.shade200
: Theme.of(context).colorScheme.secondary,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
countDown.isCountDownStart
? '${countDown.duration.inHours} : ${countDown.duration.inMinutes % 60} : ${countDown.duration.inSeconds % 60}'
: 'Start',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.secondary,
),
),
)
]),
),
),
);
})
]);
}
}
+125
View File
@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/login_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/service/user_service.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/constant/app_strings.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
LoginPageState createState() => LoginPageState();
}
class LoginPageState extends State<LoginPage> {
Duration get loginTime => const Duration(milliseconds: 2250);
late UserModel _userModel;
late LoginModel _loginModel;
static String? _emailValidator(value) {
if (value.isEmpty ||
!RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$').hasMatch(value)) {
return Application.navigatorKey.currentState?.context.l10n.pleaseenter;
}
return null;
}
static String? _passwordValidator(String? value) {
if (value?.isEmpty == true) {
return Application.navigatorKey.currentState?.context.l10n.passwordcan;
}
if (value?.length == null || value!.length < 6) {
return Application.navigatorKey.currentState?.context.l10n.passwordcannot;
}
return null;
}
Future<String?> _login(LoginData data) async {
String? result;
try {
await _loginModel.login(data.name, data.password);
} catch (error) {
result = Application.navigatorKey.currentState?.context.l10n.loginfailed;
}
return result;
}
Future<String?> _register(SignupData data) async {
String? result;
try {
await UserService()
.register({'email': data.name, 'password': data.password});
await _loginModel.login(data.name, data.password);
} catch (error) {
result = Application
.navigatorKey.currentState?.context.l10n.registrationfailed;
}
return result;
}
Future<String?> _recoverPassword(String name) {
return Future.delayed(loginTime).then((_) {
return null;
});
}
@override
Widget build(BuildContext context) {
_userModel = Provider.of<UserModel>(context);
_loginModel = LoginModel(_userModel);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return FlutterLogin(
// title: AppStrings.appName,
theme: LoginTheme(
pageColorLight: Color.fromARGB(255, 28, 57, 15),
pageColorDark: const Color.fromARGB(255, 33, 107, 35)),
logo: AssetImage('assets/logo2.png'),
onLogin: _login,
onSignup: _register,
messages: LoginMessages(
userHint: Application.navigatorKey.currentState!.context.l10n.mail,
passwordHint:
Application.navigatorKey.currentState!.context.l10n.password,
confirmPasswordHint: Application
.navigatorKey.currentState!.context.l10n.confirmpassword,
confirmPasswordError:
Application.navigatorKey.currentState!.context.l10n.twopasswords,
forgotPasswordButton: Application
.navigatorKey.currentState!.context.l10n.forgetthepassword,
loginButton:
Application.navigatorKey.currentState!.context.l10n.login,
signupButton:
Application.navigatorKey.currentState!.context.l10n.register,
recoverPasswordIntro:
Application.navigatorKey.currentState!.context.l10n.resetpassword,
recoverPasswordButton:
Application.navigatorKey.currentState!.context.l10n.sure,
recoverPasswordDescription:
Application.navigatorKey.currentState!.context.l10n.thesystemwill,
recoverPasswordSuccess: Application
.navigatorKey.currentState!.context.l10n.sentsuccessfully,
goBackButton:
Application.navigatorKey.currentState!.context.l10n.returnstring),
onSubmitAnimationCompleted: () {
NavigatorUtil.goHomePage(context);
},
onRecoverPassword: _recoverPassword,
userValidator: _emailValidator,
passwordValidator: _passwordValidator,
);
}
}
+289
View File
@@ -0,0 +1,289 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/service/user_service.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/widgets/bottom_block.dart';
import 'package:uuvpn/widgets/profile_widget.dart';
class MyProfile extends StatefulWidget {
const MyProfile({Key? key}) : super(key: key);
@override
MyProfileState createState() => MyProfileState();
}
class MyProfileState extends State<MyProfile> {
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
void onLogoutTap() {
showDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(context.l10n.alertsss),
content: Column(
children: <Widget>[
SizedBox(
height: 10,
),
Align(
child: Text(
context.l10n.wanttoexit,
style: TextStyle(fontSize: 18),
),
alignment: Alignment(0, 0),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(context.l10n.cancelss),
onPressed: () {
Navigator.pop(context);
//print("取消");
},
),
CupertinoDialogAction(
child: Text(
context.l10n.exitout,
style: TextStyle(color: Colors.red),
),
onPressed: () {
//print("确定");
_userModel.logout();
NavigatorUtil.goLogin(context);
},
),
],
);
});
}
void onWebLinkTap(String name, String link) => _userModel.checkHasLogin(
context,
() => UserService().getQuickLoginUrl({'redirect': link})?.then((value) {
NavigatorUtil.goWebView(context, name, value);
}));
@override
Widget build(BuildContext context) {
return Scrollbar(
child: SingleChildScrollView(
child: Column(
children: [
Container(
padding: EdgeInsets.only(
right: ScreenUtil().setWidth(32),
left: ScreenUtil().setWidth(32),
top: ScreenUtil().setHeight(32),
bottom: ScreenUtil().setHeight(32)),
child: Material(
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: Column(
children: <Widget>[
ProfileWidget(
avatar: _userModel.userEntity?.avatarUrl,
userName: _userModel.userEntity?.email ?? "欢迎光临",
onTap: onLogoutTap,
),
Container(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey[300]!)),
),
),
),
FinanceWidget(onWebLinkTap: onWebLinkTap),
Container(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey[300]!)),
),
),
),
AccountWidget(onWebLinkTap: onWebLinkTap),
],
),
),
),
),
const BottomBlock(),
],
),
));
}
}
class AccountWidget extends StatelessWidget {
const AccountWidget({Key? key, required this.onWebLinkTap}) : super(key: key);
final dynamic onWebLinkTap;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"账户",
style: TextStyle(
color: Color(0xFFADADAD),
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
],
),
),
Container(
padding: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => onWebLinkTap("个人中心", '/profile'),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"🙍 个人中心",
style: TextStyle(
fontSize: 18,
),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
),
),
Container(
padding: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => onWebLinkTap("我的工单", "/ticket"),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"🎫 我的工单",
style: TextStyle(
fontSize: 18,
),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
),
),
Container(
padding: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => onWebLinkTap("流量明细", "traffic"),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"🔖 流量明细",
style: TextStyle(
fontSize: 18,
),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
),
),
],
),
);
}
}
class FinanceWidget extends StatelessWidget {
const FinanceWidget({Key? key, required this.onWebLinkTap}) : super(key: key);
final dynamic onWebLinkTap;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"财务",
style: TextStyle(
color: Color(0xFFADADAD),
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
],
),
),
Container(
padding: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => onWebLinkTap("我的订单", "/order"),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"💳 我的订单",
style: TextStyle(
fontSize: 18,
),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
),
),
Container(
padding: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => onWebLinkTap("我的邀请", "/invite"),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
"🫲 我的邀请",
style: TextStyle(
fontSize: 18,
),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
),
)
],
),
);
}
}
+17
View File
@@ -0,0 +1,17 @@
import 'package:uuvpn/widgets/sliding_cards.dart';
import 'package:flutter/material.dart';
class PlanPage extends StatelessWidget {
const PlanPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const <Widget>[
SlidingCardsView(),
],
);
}
}
+227
View File
@@ -0,0 +1,227 @@
//ignore_for_file: file_names
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/utils/l10n.dart';
import '/model/themeCollection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class ProPage extends StatelessWidget {
const ProPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Map<String, dynamic>> _list = [
{
'svg': '1',
'title': 'Anonymous',
'icon': 'assets/fi-sr-incognito.svg',
'icon2': Icons.groups_3,
'description': 'Hide your ip with anonymous surfing'
},
{
'svg': '1',
'title': 'Fast Safe',
'icon': 'assets/fi-sr-rocket.svg',
'icon2': Icons.rocket_launch,
'description': 'Up to >=1 Mb/s bandwidth to explore'
},
{
'svg': '1',
'title': 'No Ads',
'icon': 'assets/fi-sr-add.svg',
'icon2': Icons.no_adult_content,
'description': 'App without annoying ads'
},
{
'svg': '0',
'title': 'All Free',
'icon2': Icons.sentiment_very_satisfied,
'description': 'Really free, no kidding'
},
{
'svg': '0',
'title': 'Quit Anytime',
'icon2': Icons.delete_forever,
'description': 'Server does not record any user data'
}
];
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Center(
child: ListView(
// shrinkWrap: true,
children: [
// SvgPicture.asset(
// 'assets/logo.svg',
// cacheColorFilter: true,
// color: AppColors.greenColor,
// ),
// const SizedBox(height: 12),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'Free ',
style: TextStyle(
fontFamily: 'Aquire',
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 24),
children: const [
TextSpan(
text: 'Features',
style: TextStyle(
fontFamily: 'Roboto', fontWeight: FontWeight.w500))
])),
const SizedBox(height: 32),
/*childAspectRatio: 16 / 10,
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 14), */
GridView(
padding:
const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 16 / 4,
crossAxisCount: 1,
mainAxisSpacing: 8,
crossAxisSpacing: 14),
children: List.generate(
_list.length,
(index) => Column(
children: [
Row(
children: [
Icon(_list[index]['icon2'] as IconData,
size: 30, color: AppColors.greenColor),
const SizedBox(width: 8.0),
Text(
_list[index]['title'] as String,
style: TextStyle(
color: AppColors.greenColor, fontSize: 24),
)
],
),
const SizedBox(height: 8.0),
Row(
children: [
// Icon(_list[index]['icon2'] as IconData,
// size: 30, color: AppColors.greenColor),
const SizedBox(width: 40.0),
Container(
width: ScreenUtil().screenWidth / 1.5,
child: Text(
_list[index]['description'] as String,
overflow: TextOverflow.visible,
textDirection: TextDirection.ltr,
softWrap: true,
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: 'Aquire',
// backgroundColor: AppColors.yellowColor,
color: isDarkTheme
? Colors.white
: Colors.black,
fontSize: 20)),
)
],
),
],
))),
// decoratedButton(context, '1 MONTH', '1.99', '\$/month', false),
// decoratedButton(context, '1 YEAR', '0.99', '\$/month', true),
// GestureDetector(
// onTap: () => Application.showMsg(context,
// "Notable features of UU: \n\n* No credit card required\n\n* You can try premium features for free for 7 days\n\n* Do not keep any user logs\n\n* Simple, one-click connection VPN\n\n* Automatically connects you to the fastest VPN server"),
// child: Container(
// alignment: Alignment.center,
// height: kToolbarHeight,
// margin: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 8),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(65),
// color: isDarkTheme
// ? AppColors.greenColor
// : AppColors.greenColor),
// child: Text(
// 'FOREVER FOR FREE',
// style: Theme.of(context).primaryTextTheme.headline6!.copyWith(
// color: isDarkTheme
// ? AppColors.whiteColor
// : Color.fromARGB(255, 9, 30, 4)),
// ),
// ),
// ),
Text(
'Please rest assured to use.',
textAlign: TextAlign.center,
style: Theme.of(context).primaryTextTheme.labelMedium,
),
],
),
);
}
Container decoratedButton(BuildContext context, String lt, String rt_1,
String rt_2, bool isDiscount) {
return Container(
height: kToolbarHeight,
margin: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 24.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
gradient: const LinearGradient(
colors: [AppColors.greenColor, Color.fromARGB(255, 7, 52, 7)],
transform: GradientRotation(5))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
lt,
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
),
if (isDiscount)
Container(
margin: const EdgeInsets.all(4.0),
padding: const EdgeInsets.symmetric(
horizontal: 6.0, vertical: 2.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15)),
child: const Text(
'50% OFF',
style: TextStyle(fontSize: 10, color: Colors.black),
),
),
],
),
RichText(
text: TextSpan(
text: rt_1,
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
children: [
TextSpan(
text: ' $rt_2',
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
)
])),
],
),
);
}
}
+307
View File
@@ -0,0 +1,307 @@
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:flutter/material.dart';
import 'package:uuvpn/utils/common_util.dart';
import 'package:uuvpn/utils/l10n.dart';
class ServerListPage extends StatefulWidget {
const ServerListPage({Key? key}) : super(key: key);
@override
ServerListPageState createState() => ServerListPageState();
}
class ServerListPageState extends State<ServerListPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
late AppModel _appModel;
late ServerModel _serverModel;
@override
void didChangeDependencies() async {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_serverModel = Provider.of<ServerModel>(context);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Material(
color: Colors.transparent,
child: Container(
padding: EdgeInsets.all(ScreenUtil().setWidth(40)),
child: _contentWidget(),
),
);
}
Widget _contentWidget() {
if (_serverModel.serverEntityList.isEmpty) {
return _emptyWidget();
}
return _serversContainerWidget();
}
Widget _emptyWidget() {
return Center(
child: Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: _appModel.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: _appModel.isOn ? Colors.white : AppColors.darkSurfaceColor,
child: Container(
alignment: Alignment.center,
child: Text(
context.l10n.nodefornullcheckissubscripts,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setWidth(40),
color: _appModel.isOn ? Colors.black : Colors.white),
),
),
),
),
);
}
Widget _serversContainerWidget() {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RichText(
text: TextSpan(
text: context.l10n.xuanzeliahjiedian,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w700,
color: _appModel.isOn
? isDarkTheme
? Colors.white
: Colors.black
: isDarkTheme
? Colors.white
: Colors.black),
children: [
TextSpan(
text: '',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.normal,
color: _appModel.isOn
? AppColors.grayColor
: Colors.black))
])),
InkWell(
onTap: _serverModel.pingAll,
child: Text(context.l10n.pingallnodes,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.normal,
color: _appModel.isOn
? AppColors.greenColor
: Colors.green)),
)
],
),
const SizedBox(
height: 20,
),
Expanded(
child: _serverListWidget(),
)
],
);
}
Widget _tagsWidget(List<String>? tags) {
if (tags == null || tags.isEmpty) {
return Container();
}
for (var i = 0; i < tags.length; i++) {
tags[i] = tags[i].replaceAll(' ', '');
if (tags[i].isEmpty) {
tags.removeAt(i);
}
}
var tagsWidget = tags
.map((tag) => Chip(
backgroundColor: AppColors.themeColor,
padding: const EdgeInsets.all(5),
label: Text(
tag,
style: const TextStyle(
color: Colors.black87,
fontSize: 12,
),
)))
.toList();
return Row(
children: tagsWidget,
);
}
Widget _serverListWidget() {
return ListView.separated(
controller: ModalScrollController.of(context),
physics: const ClampingScrollPhysics(),
itemCount: _serverModel.serverEntityList.length,
itemBuilder: (_, index) => InkWell(
onTap: () {
_serverModel
.setSelectServerEntity(_serverModel.serverEntityList[index]);
_serverModel.setSelectServerIndex(index);
_appModel.setConfigRule(_serverModel.serverEntityList[index].name);
},
child: Material(
borderRadius: BorderRadius.circular(10),
color: Colors.grey.withAlpha(60),
// color: _serverModel.selectServerIndex == index
// ? Theme.of(context).highlightColor
// : Theme.of(context).cardColor,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
SizedBox(
width: ScreenUtil().setWidth(10),
),
CircleAvatar(
radius: ScreenUtil().setWidth(10),
backgroundColor:
(DateTime.now().microsecondsSinceEpoch / 1000000 -
(int.tryParse(_serverModel
.serverEntityList[index]
.lastCheckAt) ??
0) <
60 * 10)
? Colors.green
: Colors.red,
),
const SizedBox(
width: 15,
),
_serverModel.serverEntityList[index].name.contains("US-")
? SvgPicture.asset(
'assets/flags/us.svg',
height: 20,
)
: (_serverModel.serverEntityList[index].name
.contains("Taiwan")
? SvgPicture.asset(
'assets/flags/tw.svg',
height: 20,
)
: _serverModel.serverEntityList[index].name
.contains("Hongkong")
? SvgPicture.asset(
'assets/flags/hk.svg',
height: 20,
)
: _serverModel.serverEntityList[index].name
.contains("Japan")
? SvgPicture.asset(
'assets/flags/jp.svg',
height: 20,
)
: SizedBox()),
const SizedBox(
width: 15,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_serverModel.serverEntityList[index].name,
style: Provider.of<ThemeCollection>(context)
.getActiveTheme
.primaryTextTheme
.bodyLarge,
),
const SizedBox(
width: 15,
),
_tagsWidget(_serverModel.serverEntityList[index].tags),
],
)
],
),
Row(
children: [
_serverModel.serverEntityList[index].ping != null
? Container(
padding: EdgeInsets.only(
right: ScreenUtil().setWidth(10)),
child: Text(
_serverModel.serverEntityList[index].ping!
.inSeconds >
10
? context.l10n.timeout
: "${_serverModel.serverEntityList[index].ping!.inMilliseconds}ms",
style: TextStyle(
color: _serverModel.serverEntityList[index]
.ping!.inSeconds >
10
? Colors.red
: Colors.green),
),
)
: Container(),
InkWell(
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: () => _serverModel.ping(index),
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(10),
horizontal: ScreenUtil().setWidth(30)),
child: Text(
'ping',
style: TextStyle(
color: Colors.green[800],
fontWeight: FontWeight.w500),
)),
),
_serverModel.selectServerIndex == index
? Icon(Icons.check_circle,
color: Colors.green, size: 20)
: Container(),
],
)
],
),
),
),
),
separatorBuilder: (_, index) => const SizedBox(height: 10),
);
}
}
+51
View File
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewWidget extends StatefulWidget {
final String? url;
final String? name;
const WebViewWidget({Key? key, this.url, this.name}) : super(key: key);
@override
WebViewWidgetState createState() => WebViewWidgetState();
}
class WebViewWidgetState extends State<WebViewWidget> {
late WebViewController controller;
final String _javaScript = '''
const styles = `
#page-header {
display: none;
}
#main-container {
padding-top: 0 !important;
}
`
const styleSheet = document.createElement("style")
styleSheet.innerText = styles
document.head.appendChild(styleSheet)
''';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name!),
centerTitle: true,
),
body: WebView(
initialUrl:
widget.url?.isEmpty == true ? AppStrings.appName : widget.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) => this.controller = controller,
onPageFinished: (url) {
print('url=$url');
controller.runJavascript(_javaScript);
},
),
);
}
}
+91
View File
@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
class AppColors {
static const primary = Color(0xffef233c);
static const secondary = Color(0xff272b30);
static const primaryBackground = Color(0xff1a1d1f);
static const secondaryBackground = Color(0xff272b30);
static const primaryText = Color(0xffa9aaac);
static const secondaryText = Colors.white;
static const primaryBtnText = Colors.white;
static const error = Colors.red;
static const black = Colors.black;
static const inactiveColor = Color(0x26ffffff);
static const transparent = Colors.transparent;
static const ratingIconColor = Color(0xffffbe21);
static const circleDotColor = Color(0x33ffffff);
static const iconContainerColor = Color(0xB2272830);
static const gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color.fromARGB(26, 252, 252, 252),
Color.fromARGB(0, 255, 255, 255),
]);
static const primarySwatch = MaterialColor(
0xFF15141F,
<int, Color>{
50: Color(0xFFE3F2FD),
100: Color(0xFFBBDEFB),
200: Color(0xFF90CAF9),
300: Color(0xFF64B5F6),
400: Color(0xFF42A5F5),
500: primaryColor,
600: Color(0xFF1E88E5),
700: Color(0xFF1976D2),
800: Color(0xFF1565C0),
900: Color(0xFF0D47A1),
},
);
static const darkPrimarySwatch = MaterialColor(
0xFF15141F,
<int, Color>{
50: Color(0xFFE3F2FD),
100: Color(0xFFBBDEFB),
200: Color(0xFF90CAF9),
300: Color(0xFF64B5F6),
400: Color(0xFF42A5F5),
500: darkPrimaryColor,
600: Color(0xFF1E88E5),
700: Color(0xFF1976D2),
800: Color(0xFF1565C0),
900: Color(0xFF0D47A1),
},
);
/// Colors For the Light Theme
static const backgroundColor = Color(0xFFFFFFFF);
static const onBackgroundColor = Color(0xFF15141F);
static const primaryColor = Color(0xFFFFFFFF);
static const onPrimaryColor = Color(0xFF15141F);
static const secondaryColor = Color(0xFFE21221);
static const onSecondaryColor = Color(0xFFFFFFFF);
static const surfaceColor = Color(0xFFFFFFFF);
static const onSurfaceColor = Color(0xFF15141F);
static const errorColor = Color(0xFFF44336);
static const onErrorColor = Color(0xFFFFFFFF);
static const highEmphasized = Color(0xFFFFFFFF);
static const mediumEmphasized = Color(0xFFBCBCBC);
/// Colors For The dark theme
static const darkBackgroundColor = Color(0xFF15141F);
static const darkOnBackgroundColor = Color(0xFFFFFFFF);
static const darkPrimaryColor = Color(0xFF15141F);
static const darkOnPrimaryColor = Color(0xFFFFFFFF);
static const darkSecondaryColor = Color(0xFFE21221);
static const darkOnSecondaryColor = Color(0xFFFF8F71);
static const darkSurfaceColor = Color(0xFF211F30);
static const darkOnSurfaceColor = Color(0xFF211F30);
static const darkErrorColor = Color(0xFFF44336);
static const darkOnErrorColor = Color(0xFFFFFFFF);
static const darkHighEmphasized = Color(0xFFFFFFFF);
static const darkMediumEmphasized = Color(0xFFBCBCBC);
}
const appBgColor = Color(0xFF000000);
const primary = Color(0xFFFC2D55);
const secondary = Color(0xFF19D5F1);
const white = Color(0xFFFFFFFF);
const black = Color(0xFF000000);
+3
View File
@@ -0,0 +1,3 @@
class AppConstants {
static const int carouselSliderItemsCount = 4;
}
+117
View File
@@ -0,0 +1,117 @@
import 'package:flutter/cupertino.dart';
// import 'package:go_router/go_router.dart';
// import 'package:movies_app/core/presentation/pages/main_page.dart';
// import 'package:movies_app/movies/presentation/views/movie_details_view.dart';
// import 'package:movies_app/movies/presentation/views/movies_view.dart';
// import 'package:movies_app/movies/presentation/views/popular_movies_view.dart';
// import 'package:movies_app/movies/presentation/views/top_rated_movies_view.dart';
// import 'package:movies_app/search/presentation/views/search_view.dart';
// import 'package:movies_app/tv_shows/presentation/views/popular_tv_shows_view.dart';
// import 'package:movies_app/tv_shows/presentation/views/top_rated_tv_shows_view.dart';
// import 'package:movies_app/tv_shows/presentation/views/tv_show_details_view.dart';
// import 'package:movies_app/tv_shows/presentation/views/tv_shows_view.dart';
// import 'package:movies_app/core/resources/app_routes.dart';
// import 'package:movies_app/watchlist/presentation/views/watchlist_view.dart';
// const String moviesPath = '/movies';
// const String movieDetailsPath = 'movieDetails/:movieId';
// const String popularMoviesPath = 'popularMovies';
// const String topRatedMoviesPath = 'topRatedMovies';
// const String tvShowsPath = '/tvShows';
// const String tvShowDetailsPath = 'tvShowDetails/:tvShowId';
// const String popularTVShowsPath = 'popularTVShows';
// const String topRatedTVShowsPath = 'topRatedTVShows';
// const String searchPath = '/search';
// const String watchlistPath = '/watchlist';
// class AppRouter {
// GoRouter router = GoRouter(
// initialLocation: moviesPath,
// routes: [
// ShellRoute(
// builder: (context, state, child) => MainPage(child: child),
// routes: [
// GoRoute(
// name: AppRoutes.moviesRoute,
// path: moviesPath,
// pageBuilder: (context, state) => const NoTransitionPage(
// child: MoviesView(),
// ),
// routes: [
// GoRoute(
// name: AppRoutes.movieDetailsRoute,
// path: movieDetailsPath,
// pageBuilder: (context, state) => CupertinoPage(
// child: MovieDetailsView(
// movieId: int.parse(state.params['movieId']!),
// ),
// ),
// ),
// GoRoute(
// name: AppRoutes.popularMoviesRoute,
// path: popularMoviesPath,
// pageBuilder: (context, state) => const CupertinoPage(
// child: PopularMoviesView(),
// ),
// ),
// GoRoute(
// name: AppRoutes.topRatedMoviesRoute,
// path: topRatedMoviesPath,
// pageBuilder: (context, state) => const CupertinoPage(
// child: TopRatedMoviesView(),
// ),
// ),
// ],
// ),
// GoRoute(
// name: AppRoutes.tvShowsRoute,
// path: tvShowsPath,
// pageBuilder: (context, state) => const NoTransitionPage(
// child: TVShowsView(),
// ),
// routes: [
// GoRoute(
// name: AppRoutes.tvShowDetailsRoute,
// path: tvShowDetailsPath,
// pageBuilder: (context, state) => CupertinoPage(
// child: TVShowDetailsView(
// tvShowId: int.parse(state.params['tvShowId']!),
// ),
// ),
// ),
// GoRoute(
// name: AppRoutes.popularTvShowsRoute,
// path: popularTVShowsPath,
// pageBuilder: (context, state) => const CupertinoPage(
// child: PopularTVShowsView(),
// ),
// ),
// GoRoute(
// name: AppRoutes.topRatedTvShowsRoute,
// path: topRatedTVShowsPath,
// pageBuilder: (context, state) => const CupertinoPage(
// child: TopRatedTVShowsView(),
// ),
// ),
// ],
// ),
// GoRoute(
// name: AppRoutes.searchRoute,
// path: searchPath,
// pageBuilder: (context, state) => const NoTransitionPage(
// child: SearchView(),
// ),
// ),
// GoRoute(
// name: AppRoutes.watchlistRoute,
// path: watchlistPath,
// pageBuilder: (context, state) => const NoTransitionPage(
// child: WatchlistView(),
// ),
// ),
// ],
// )
// ],
// );
// }
+14
View File
@@ -0,0 +1,14 @@
class AppRoutes {
static const String moviesRoute = 'movies';
static const String movieDetailsRoute = 'movieDetails';
static const String popularMoviesRoute = 'popularMovies';
static const String topRatedMoviesRoute = 'topRatedMovies';
static const String tvShowsRoute = 'tvShows';
static const String tvShowDetailsRoute = 'tvShowDetails';
static const String popularTvShowsRoute = 'popularTvShowsRoute';
static const String topRatedTvShowsRoute = 'topRatedTvShowsRoute';
static const String searchRoute = 'search';
static const String watchlistRoute = 'watchlist';
}
+5
View File
@@ -0,0 +1,5 @@
class AppShape {
static const largeShape = 25.0;
static const normalShaper = 20.0;
static const smallShape = 10.0;
}
+37
View File
@@ -0,0 +1,37 @@
class AppStrings {
static const String appTitle = 'Movies App';
static const String seeAll = 'see all';
static const String popularMovies = 'Popular movies';
static const String topRatedMovies = 'Top rated movies';
static const String story = 'Story';
static const String videos = 'Videos';
static const String cast = 'Cast';
static const String reviews = 'Reviews';
static const String similar = 'Similar';
static const String showLess = 'Show less';
static const String showMore = 'Show more';
static const String movies = 'Movies';
static const String shows = 'Shows';
static const String search = 'Search';
static const String watchlist = 'Watchlist';
static const String popularShows = 'Popular shows';
static const String topRatedShows = 'Top rated shows';
static const String lastEpisodeOnAir = 'Last Episode on Air';
static const String seasons = 'Seasons';
static const String season = 'Season';
static const String episodes = 'Episodes';
static const String episode = 'Episode';
static const String airDate = 'Air date:';
static const String lastEpisode = 'Last Episode';
static const String searchText =
'By typing in search bar, Movia search in movies and series and then show you the best results.';
static const String searchHint = 'Search for Movies, Series...';
static const String watchlistIsEmpty = 'Watchlist is empty';
static const String watchlistText =
'After adding movies and series to watchlist, they will appear here.';
static const String oops = 'Ooops';
static const String tryAgainLater = 'Please try again later';
static const String errorMessage = 'Something went wrong';
static const String tryAgain = 'try again';
static const String noResults = 'No results';
}
+230
View File
@@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:uuvpn/resources/app_colors.dart';
import 'package:uuvpn/resources/app_typography.dart';
ThemeData getApplicationTheme() {
return ThemeData(
// main colors
scaffoldBackgroundColor: AppColors.primaryBackground,
// Bottom nav bar theme
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.secondaryBackground,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.primaryText,
type: BottomNavigationBarType.fixed,
),
// app bar theme
appBarTheme: AppBarTheme(
backgroundColor: AppColors.primaryBackground,
elevation: 0,
centerTitle: true,
systemOverlayStyle: SystemUiOverlayStyle.light,
titleTextStyle: _getTextStyle(
fontSize: 18,
color: AppColors.secondaryText,
),
),
// text theme
textTheme: TextTheme(
titleMedium: _getTextStyle(
fontSize: 20,
color: AppColors.secondaryText,
),
titleSmall: _getTextStyle(
fontSize: 18,
color: AppColors.secondaryText,
),
bodyLarge: _getTextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.primaryText,
),
bodyMedium: _getTextStyle(
fontSize: 14,
color: AppColors.secondaryText,
),
bodySmall: _getTextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.primaryText,
),
),
);
}
TextStyle _getTextStyle({
required double fontSize,
FontWeight fontWeight = FontWeight.w600,
required Color color,
}) {
return GoogleFonts.poppins(
fontSize: fontSize,
fontWeight: fontWeight,
color: color,
);
}
class AppTheme {
static final flatButtonStyle = TextButton.styleFrom(
textStyle: AppTypography.labelMedium,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)),
),
);
static final raisedButtonStyle = ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: AppColors.secondaryColor,
textStyle: AppTypography.labelMedium,
minimumSize: const Size(88, 36),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
);
static final outlineButtonStyle = OutlinedButton.styleFrom(
textStyle: AppTypography.labelMedium,
side: const BorderSide(color: AppColors.onPrimaryColor, width: 2),
padding: const EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
);
static final lightThemeData = ThemeData(
useMaterial3: true,
primarySwatch: AppColors.primarySwatch,
splashColor: AppColors.secondaryColor,
scaffoldBackgroundColor: AppColors.backgroundColor,
fontFamily: 'Urbanist',
appBarTheme: AppBarTheme(
// systemOverlayStyle: SystemUiOverlayStyle.light,
backgroundColor: AppColors.backgroundColor,
iconTheme: const IconThemeData(
color: AppColors.onBackgroundColor,
),
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.onPrimaryColor,
),
),
textTheme: TextTheme(
bodyLarge: AppTypography.bodyLarge.copyWith(
color: AppColors.onPrimaryColor,
),
bodyMedium: AppTypography.bodyMedium.copyWith(
color: AppColors.onPrimaryColor,
),
bodySmall: AppTypography.bodySmall.copyWith(
color: AppColors.onPrimaryColor,
),
labelLarge: AppTypography.labelLarge.copyWith(
color: AppColors.onPrimaryColor,
),
labelMedium: AppTypography.labelMedium.copyWith(
color: AppColors.onPrimaryColor,
),
labelSmall: AppTypography.labelSmall.copyWith(
color: AppColors.onPrimaryColor,
),
titleLarge: AppTypography.titleLarge.copyWith(
color: AppColors.onPrimaryColor,
),
titleMedium: AppTypography.titleMedium.copyWith(
color: AppColors.onPrimaryColor,
),
titleSmall: AppTypography.titleSmall.copyWith(
color: AppColors.onPrimaryColor,
),
),
colorScheme: const ColorScheme(
brightness: Brightness.dark,
primary: AppColors.primaryColor,
onPrimary: AppColors.onPrimaryColor,
secondary: AppColors.secondaryColor,
onSecondary: AppColors.onSecondaryColor,
background: AppColors.backgroundColor,
onBackground: AppColors.onBackgroundColor,
error: AppColors.errorColor,
onError: AppColors.onErrorColor,
surface: AppColors.surfaceColor,
onSurface: AppColors.onSurfaceColor,
),
elevatedButtonTheme: ElevatedButtonThemeData(style: raisedButtonStyle),
textButtonTheme: TextButtonThemeData(style: flatButtonStyle),
outlinedButtonTheme: OutlinedButtonThemeData(style: outlineButtonStyle),
bottomSheetTheme:
BottomSheetThemeData(backgroundColor: Colors.black.withOpacity(0)),
);
static final darkThemeData = ThemeData(
useMaterial3: true,
primarySwatch: AppColors.darkPrimarySwatch,
splashColor: AppColors.darkSecondaryColor,
scaffoldBackgroundColor: AppColors.darkBackgroundColor,
fontFamily: 'Urbanist',
appBarTheme: AppBarTheme(
// systemOverlayStyle: SystemUiOverlayStyle.dark,
backgroundColor: AppColors.darkBackgroundColor,
iconTheme: const IconThemeData(
color: AppColors.darkOnBackgroundColor,
),
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.darkOnPrimaryColor,
),
),
textTheme: TextTheme(
bodyLarge: AppTypography.bodyLarge.copyWith(
color: AppColors.darkOnPrimaryColor,
),
bodyMedium: AppTypography.bodyMedium.copyWith(
color: AppColors.darkOnPrimaryColor,
),
bodySmall: AppTypography.bodySmall.copyWith(
color: AppColors.darkOnPrimaryColor,
),
labelLarge: AppTypography.labelLarge.copyWith(
color: AppColors.darkOnPrimaryColor,
),
labelMedium: AppTypography.labelMedium.copyWith(
color: AppColors.darkOnPrimaryColor,
),
labelSmall: AppTypography.labelSmall.copyWith(
color: AppColors.darkOnPrimaryColor,
),
titleLarge: AppTypography.titleLarge.copyWith(
color: AppColors.darkOnPrimaryColor,
),
titleMedium: AppTypography.titleMedium.copyWith(
color: AppColors.darkOnPrimaryColor,
),
titleSmall: AppTypography.titleSmall.copyWith(
color: AppColors.darkOnPrimaryColor,
),
),
colorScheme: const ColorScheme(
brightness: Brightness.dark,
primary: AppColors.darkPrimaryColor,
onPrimary: AppColors.darkOnPrimaryColor,
secondary: AppColors.darkSecondaryColor,
onSecondary: AppColors.darkOnSecondaryColor,
background: AppColors.darkBackgroundColor,
onBackground: AppColors.darkOnBackgroundColor,
error: AppColors.darkErrorColor,
onError: AppColors.darkOnErrorColor,
surface: AppColors.darkSurfaceColor,
onSurface: AppColors.darkOnSurfaceColor,
),
elevatedButtonTheme: ElevatedButtonThemeData(style: raisedButtonStyle),
textButtonTheme: TextButtonThemeData(style: flatButtonStyle),
outlinedButtonTheme: OutlinedButtonThemeData(style: outlineButtonStyle),
bottomSheetTheme:
BottomSheetThemeData(backgroundColor: Colors.black.withOpacity(0)),
);
}
+40
View File
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
class AppTypography {
static const titleLarge = TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w500,
);
static const titleMedium = TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
);
static const titleSmall = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
);
static const labelLarge = TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
);
static const labelMedium = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
);
static const labelSmall = TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
);
static const bodyLarge = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
);
static const bodyMedium = TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
);
static const bodySmall = TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.normal,
);
}
+62
View File
@@ -0,0 +1,62 @@
class AppMargin {
static const double m8 = 8.0;
static const double m10 = 10.0;
static const double m12 = 12.0;
static const double m14 = 14.0;
static const double m16 = 16.0;
static const double m18 = 18.0;
static const double m20 = 20.0;
}
class AppPadding {
static const double p2 = 2.0;
static const double p4 = 4.0;
static const double p6 = 6.0;
static const double p8 = 8.0;
static const double p12 = 12.0;
static const double p10 = 10.0;
static const double p14 = 14.0;
static const double p16 = 16.0;
static const double p18 = 18.0;
static const double p20 = 20.0;
static const double p24 = 24.0;
static const double p32 = 32.0;
static const double p36 = 36.0;
}
class AppSize {
static const double s0 = 0.0;
static const double s1 = 1.0;
static const double s4 = 4.0;
static const double s6 = 6.0;
static const double s8 = 8.0;
static const double s10 = 10.0;
static const double s12 = 12.0;
static const double s14 = 14.0;
static const double s15 = 15.0;
static const double s16 = 16.0;
static const double s18 = 18.0;
static const double s20 = 20.0;
static const double s22 = 22.0;
static const double s24 = 24.0;
static const double s30 = 30.0;
static const double s36 = 36.0;
static const double s40 = 40.0;
static const double s45 = 45.0;
static const double s60 = 60.0;
static const double s84 = 84.0;
static const double s100 = 100.0;
static const double s110 = 110.0;
static const double s120 = 120.0;
static const double s130 = 130.0;
static const double s140 = 140.0;
static const double s150 = 150.0;
static const double s160 = 160.0;
static const double s175 = 175.0;
static const double s190 = 190.0;
static const double s200 = 200.0;
static const double s220 = 220.0;
static const double s240 = 240.0;
static const double s400 = 400.0;
static const double s800 = 800.0;
}
+44
View File
@@ -0,0 +1,44 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:uuvpn/utils/l10n.dart';
class Application {
static FluroRouter? router;
static GlobalKey<NavigatorState> navigatorKey = GlobalKey();
static showMsg(context, msg) {
showDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(context.l10n.alertsss),
content: Column(
children: <Widget>[
SizedBox(
height: 10,
),
Align(
child: Text(
msg,
style: TextStyle(fontSize: 18),
),
alignment: Alignment(0, 0),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(
context.l10n.sure,
style: TextStyle(color: Colors.red),
),
onPressed: () {
//print("确定");
Navigator.pop(context);
},
),
],
);
});
}
}
+54
View File
@@ -0,0 +1,54 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:uuvpn/pages/accountPage.dart';
import 'package:uuvpn/pages/home/home_page.dart';
import 'package:uuvpn/pages/404/not_find_page.dart';
import 'package:uuvpn/pages/login/login_page.dart';
import 'package:uuvpn/pages/plan/plan_page.dart';
import 'package:uuvpn/pages/server_list.dart';
import 'package:uuvpn/pages/webview_widget.dart';
import 'dart:convert';
/// 入口
Handler homeHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const HomePage();
});
/// 404页面
Handler notFindHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const NotFindPage();
});
/// 登录页
Handler loginHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const LoginPage();
});
/// 套餐页
Handler planHandle = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const PlanPage();
});
/// 服务器节点页
Handler serverListHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const ServerListPage();
});
/// WebView页
Handler webViewHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
var title = jsonDecode(parameters["titleName"]!.first);
var url = jsonDecode(parameters["url"]!.first);
return WebViewWidget(name: title, url: url);
});
///个人中心
Handler accountHandler = Handler(
handlerFunc: (BuildContext? context, Map<String, List<String>> parameters) {
return const AccountPage();
});
+28
View File
@@ -0,0 +1,28 @@
import 'package:fluro/fluro.dart';
import 'package:uuvpn/router/router_handlers.dart';
class Routers {
static String home = "/";
static String login = "/login";
static String plan = "/plan";
static String serverList = '/server-list';
static String webView = "/web-view";
static String account = "/account";
static void configureRoutes(FluroRouter router) {
router.notFoundHandler = notFindHandler;
router.define(home, handler: homeHandler);
router.define(login, handler: loginHandler);
router.define(plan, handler: planHandle);
router.define(serverList, handler: serverListHandler);
router.define(webView, handler: webViewHandler);
router.define(account, handler: accountHandler);
}
}
+28
View File
@@ -0,0 +1,28 @@
// OnceNotice.dart
//ignore_for_file: file_names
import 'package:uuvpn/pages/ProOnecePage.dart';
import '/pages/proPage.dart';
import 'package:flutter/material.dart';
class OnceNotice extends StatelessWidget {
const OnceNotice({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
actions: const [
Padding(
padding: EdgeInsets.all(8.0),
child: CloseButton(),
)
],
),
body: const ProOnecePage(),
);
}
}
@@ -0,0 +1,42 @@
//ignore_for_file: file_names
import '/model/UserPreference.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '/model/flags.dart';
import 'package:flutter/material.dart';
class ChooseLocationRoute extends StatelessWidget {
const ChooseLocationRoute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
shadowColor: Colors.transparent,
title: const Text('Choose location'),
),
body: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: Flags.list.length,
itemBuilder: (builder, index) => ListTile(
onTap: () {
Provider.of<UserPreference>(context, listen: false)
.setlocationIndex(index);
Navigator.of(context).pop();
},
leading: SvgPicture.asset(
'assets/flags/${Flags.list[index]['imagePath']}',
width: 42,
),
title: Text(
Flags.list[index]['name'] as String,
style: Theme.of(context).primaryTextTheme.labelMedium,
),
trailing: Icon(
Icons.navigate_next_rounded,
color: Theme.of(context).iconTheme.color,
)),
),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
//ignore_for_file: file_names
import '/pages/proPage.dart';
import 'package:flutter/material.dart';
class ProRoute extends StatelessWidget {
const ProRoute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
actions: const [
Padding(
padding: EdgeInsets.all(8.0),
child: CloseButton(),
)
],
),
body: const ProPage(),
);
}
}
+242
View File
@@ -0,0 +1,242 @@
//ignore_for_file: file_names
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/routes/proRoute.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:url_launcher/url_launcher.dart';
import '../model/themeCollection.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SettingsRoute extends StatefulWidget {
const SettingsRoute({Key? key}) : super(key: key);
@override
State<SettingsRoute> createState() => _SettingsRouteState();
}
class _SettingsRouteState extends State<SettingsRoute> {
int connectionValue = 0;
bool killSwitch = false, proVpn = false, notifySwitch = false;
List<String> connectionModes = ['IPSec', 'ISSR'];
setConnectionValue(int? value) {
setState(() {
connectionValue = value!;
});
}
customListTile(BuildContext context, String title, String icon,
{Widget? trailing,
Icon? sysicon,
String? subtitle,
VoidCallback? onTap}) =>
ListTile(
onTap: onTap ?? null,
minLeadingWidth: 35,
dense: true,
contentPadding: EdgeInsets.only(left: 24),
title: Text(title,
style: Theme.of(context).primaryTextTheme.labelMedium),
subtitle: subtitle != null
? Text(
subtitle,
style: Theme.of(context).primaryTextTheme.labelMedium,
)
: null,
// leading: sysicon ??
// SvgPicture.asset(
// icon,
// // color: Theme.of(context).colorScheme.secondary,
// width: 24,
// cacheColorFilter: true,
// color: AppColors.greenColor,
// alignment: Alignment.centerRight,
// ),
trailing: trailing ?? null);
upgradeButton(context) => GestureDetector(
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (builder) => const ProRoute())),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(65),
gradient: const LinearGradient(
colors: [AppColors.greenColor, Color.fromARGB(255, 9, 54, 21)],
transform: GradientRotation(5))),
child: Text(
'Upgrade',
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.white),
),
));
Divider get divider => Divider(
indent: 16,
endIndent: 16,
color: Colors.grey.withAlpha(50),
thickness: 1);
Future<void> _launchUrl(_url) async {
if (!await launchUrl(_url)) {
throw Exception('Could not launch $_url');
}
}
Column listTileSet(String title, String description, bool value,
Function(bool)? onChanged) =>
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
divider,
// const Divider(color: Colors.grey, thickness: 1),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24),
child: Text(title,
style: Theme.of(context)
.primaryTextTheme
.labelMedium!
.copyWith(color: Colors.grey)),
),
SwitchListTile(
activeColor: AppColors.greenColor,
inactiveTrackColor:
Provider.of<ThemeCollection>(context).isDarkActive
? AppColors.whiteColor.withAlpha(90)
: const Color(0xffD7D6D9),
contentPadding: EdgeInsets.only(left: 24),
title: Text(
description,
style: Theme.of(context).primaryTextTheme.labelMedium,
),
value: value,
onChanged: onChanged)
]);
@override
Widget build(BuildContext context) {
UserModel userModel = Provider.of<UserModel>(context);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
var themeData = Provider.of<ThemeCollection>(context);
return Scaffold(
backgroundColor: isDarkTheme ? Color(0xff0B0415) : Colors.white,
appBar: AppBar(
backgroundColor: Colors.transparent,
foregroundColor: isDarkTheme ? Colors.white : Color(0xff0B0415),
title: const Text('Settings'),
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 0.0, vertical: 8.0),
children: [
/*Text('Connection Mode',
style: Theme.of(context)
.primaryTextTheme
.subtitle1!
.copyWith(color: Colors.grey)),
const SizedBox(height: 4),
Column(
children: connectionModes.map((e) {
return ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
e,
style: Theme.of(context).primaryTextTheme.subtitle1,
),
trailing: Radio(
fillColor: MaterialStateProperty.all(
connectionModes[connectionValue] != e
? themeData.isDarkActive
? Colors.white70
: Colors.grey
: Theme.of(context).colorScheme.secondary,
),
value: connectionModes.indexOf(e),
groupValue: connectionValue,
onChanged: setConnectionValue),
);
}).toList()),
listTileSet(
'Kill Switch',
'Block internet when connecting or changing servers',
killSwitch,
(value) => setState(() => killSwitch = value)),
listTileSet('Connection', 'Connect when VPN starts', proVpn,
(value) => setState(() => proVpn = value)),*/
listTileSet(
'Notification',
'Show notification when VPN is not connected.',
notifySwitch,
(value) => setState(() => notifySwitch = value)),
listTileSet('Dark theme', 'Reduce glare & improve night viewing.',
themeData.isDarkActive, (value) {
themeData.setDarkTheme(value);
}),
divider,
customListTile(context, 'Rate us', '',
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {},
icon: Icon(Icons.arrow_forward_ios,
size: 20,
color: isDarkTheme
? Color.fromARGB(255, 148, 145, 145)
: Color.fromARGB(255, 190, 187, 187))),
sysicon: Icon(Icons.rate_review,
size: 30, color: AppColors.greenColor),
onTap: () => _launchUrl(Uri.parse(
"https://apps.apple.com/us/app/uuvpn-2023/id6449599228"))),
divider,
customListTile(context, 'Privacy policy', '',
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {},
icon: Icon(Icons.arrow_forward_ios,
size: 20,
color: isDarkTheme
? Color.fromARGB(255, 148, 145, 145)
: Color.fromARGB(255, 190, 187, 187))),
sysicon: Icon(Icons.private_connectivity,
size: 30, color: AppColors.greenColor),
onTap: () => NavigatorUtil.goWebView(context, "Privacy policy",
"https://uuvpn.co/privacy-policy/")),
divider,
customListTile(context, 'Terms&Conditions', '',
sysicon: Icon(Icons.format_align_center,
size: 30, color: AppColors.greenColor),
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {},
icon: Icon(Icons.arrow_forward_ios,
size: 20,
color: isDarkTheme
? Color.fromARGB(255, 148, 145, 145)
: Color.fromARGB(255, 190, 187, 187))), onTap: () {
NavigatorUtil.goWebView(
context, "Terms&Conditions", "https://uuvpn.co/terms/");
}),
divider,
customListTile(context, 'Online Help', '',
sysicon: Icon(Icons.format_align_center,
size: 30, color: AppColors.greenColor),
trailing: IconButton(
color: Theme.of(context).colorScheme.secondary,
onPressed: () {},
icon: Icon(Icons.arrow_forward_ios,
size: 20,
color: isDarkTheme
? Color.fromARGB(255, 148, 145, 145)
: Color.fromARGB(255, 190, 187, 187))), onTap: () {
// NavigatorUtil.goWebView(
// context, "Terms&Conditions", "https://uuvpn.co/terms/");
NavigatorUtil.goWebView(context, "Online Help",
"https://go.crisp.chat/chat/embed/?website_id=3ed83170-f288-4c23-acd4-30c1e557948b&user_email=${userModel.userEntity?.email}");
}),
divider,
],
),
);
}
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:uuvpn/constant/app_urls.dart';
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/utils/http_util.dart';
class PlanService {
Future<List<PlanEntity>>? plan() {
return HttpUtil.instance?.get(AppUrls.plan).then((result) {
return planEntityFromList(result['data']);
});
}
Future<PlanEntity>? planDetail(int id) {
return HttpUtil.instance
?.get(AppUrls.plan, parameters: {'id': id}).then((result) {
return PlanEntity.fromMap(result['data']);
});
}
}
+11
View File
@@ -0,0 +1,11 @@
import 'package:uuvpn/constant/app_urls.dart';
import 'package:uuvpn/entity/server_entity.dart';
import 'package:uuvpn/utils/http_util.dart';
class ServerService {
Future<List<ServerEntity>> server() {
return HttpUtil.instance.get(AppUrls.server).then((result) {
return serverEntityFromList(result['data']);
});
}
}
+44
View File
@@ -0,0 +1,44 @@
import 'package:uuvpn/constant/app_urls.dart';
import 'package:uuvpn/entity/login_entity.dart';
import 'package:uuvpn/entity/user_entity.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/utils/http_util.dart';
class UserService {
Future<LoginEntity>? login(Map<String, dynamic> parameters) {
return HttpUtil.instance
?.post(AppUrls.login, parameters: parameters)
.then((result) {
return LoginEntity.fromMap(result['data']);
});
}
Future<String>? getQuickLoginUrl(Map<String, dynamic> parameters) {
return HttpUtil.instance
?.post(AppUrls.getQuickLoginUrl, parameters: parameters)
.then((result) {
return result['data'];
});
}
Future<LoginEntity>? register(parameters) {
return HttpUtil.instance
?.post(AppUrls.register, parameters: parameters)
.then((result) {
return LoginEntity.fromMap(result['data']);
// return result['data'];
});
}
Future<UserSubscribeEntity>? userSubscribe() {
return HttpUtil.instance?.get(AppUrls.userSubscribe).then((result) {
return UserSubscribeEntity.fromMap(result['data']);
});
}
Future<UserEntity>? info() {
return HttpUtil.instance?.get(AppUrls.userInfo).then((result) {
return UserEntity.fromMap(result['data']);
});
}
}
+9
View File
@@ -0,0 +1,9 @@
import 'dart:core' as core;
import 'package:flutter/foundation.dart';
void print (core.Object object) {
if (kDebugMode) {
core.print(object);
}
}
+54
View File
@@ -0,0 +1,54 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:convert/convert.dart';
/// Encrypt Util.
class EncryptUtil {
/// md5 加密
static String encodeMd5(String data) {
var content = Utf8Encoder().convert(data);
var digest = md5.convert(content);
return hex.encode(digest.bytes);
}
/// 异或对称加密
static String xorCode(String res, String key) {
List<String> keyList = key.split(',');
List<int> codeUnits = res.codeUnits;
List<int> codes = [];
for (int i = 0, length = codeUnits.length; i < length; i++) {
int code = codeUnits[i] ^ int.parse(keyList[i % keyList.length]);
codes.add(code);
}
return String.fromCharCodes(codes);
}
/// 异或对称 Base64 加密
static String xorBase64Encode(String res, String key) {
String encode = xorCode(res, key);
encode = encodeBase64(encode);
return encode;
}
/// 异或对称 Base64 解密
static String xorBase64Decode(String res, String key) {
String encode = decodeBase64(res);
encode = xorCode(encode, key);
return encode;
}
/// Base64加密
static String encodeBase64(String data) {
var content = utf8.encode(data);
var digest = base64Encode(content);
return digest;
}
/// Base64解密
static String decodeBase64(String data) {
List<int> bytes = base64Decode(data);
String result = utf8.decode(bytes);
return result;
}
}
+136
View File
@@ -0,0 +1,136 @@
import 'package:dio/dio.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/router/routers.dart';
import 'package:uuvpn/utils/common_util.dart';
import 'package:uuvpn/utils/shared_preferences_util.dart';
class HttpUtil {
static HttpUtil get instance => _httpUtil;
static final HttpUtil _httpUtil = HttpUtil();
late Dio dio;
HttpUtil() {
BaseOptions options = BaseOptions(
connectTimeout: Duration(seconds: 10000),
receiveTimeout: Duration(seconds: 10000),
);
dio = Dio(options);
dio.interceptors
.add(InterceptorsWrapper(onRequest: (options, handler) async {
//print("========================请求数据===================");
//print("url=${options.uri.toString()}");
//print("headers=${options.headers}");
//print("params=${options.data}");
//如果token存在在请求参数加上token
await SharedPreferencesUtil.getInstance()
?.getString(AppStrings.token)
.then((token) {
if (token != null) {
options.queryParameters[AppStrings.token] = token;
//print("token=$token");
}
});
//如果auth_data存在在请求参数加上auth_data
await SharedPreferencesUtil.getInstance()
?.getString(AppStrings.authData)
.then((authData) {
if (authData != null) {
options.queryParameters[AppStrings.authData] = authData;
//print("authData=$authData");
}
});
return handler.next(options);
}, onResponse: (response, handler) {
//print("========================请求数据===================");
//print("code=${response.statusCode}");
if (response.statusCode! < 200 || response.statusCode! >= 300) {
if (response.statusCode == 403) {
Application.navigatorKey.currentState?.pushNamed(Routers.login);
}
return handler.reject(DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse));
}
return handler.next(response);
}, onError: (error, handler) {
//print("========================请求错误===================");
//print("message =${error.message}");
//print("code=${error.response?.statusCode}");
return handler.next(error);
}));
}
//get请求
Future get(String url,
{Map<String, dynamic>? parameters, Options? options}) async {
Response response;
if (parameters != null && options != null) {
response =
await dio.get(url, queryParameters: parameters, options: options);
} else if (parameters != null && options == null) {
response = await dio.get(url, queryParameters: parameters);
} else if (parameters == null && options != null) {
response = await dio.get(url, options: options);
} else {
response = await dio.get(url);
}
return response.data;
}
//post请求
Future post(String url,
{Map<String, dynamic>? parameters, Options? options}) async {
Response response;
if (parameters != null && options != null) {
response = await dio.post(url, data: parameters, options: options);
} else if (parameters != null && options == null) {
response = await dio.post(url, data: parameters);
} else if (parameters == null && options != null) {
response = await dio.post(url, options: options);
} else {
response = await dio.post(url);
}
return response.data;
}
//put请求
Future put(String url,
{Map<String, dynamic>? parameters, Options? options}) async {
Response response;
if (parameters != null && options != null) {
response = await dio.put(url, data: parameters, options: options);
} else if (parameters != null && options == null) {
response = await dio.put(url, data: parameters);
} else if (parameters == null && options != null) {
response = await dio.put(url, options: options);
} else {
response = await dio.put(url);
}
return response.data;
}
//delete请求
Future delete(String url,
{Map<String, dynamic>? parameters, Options? options}) async {
Response response;
if (parameters != null && options != null) {
response = await dio.delete(url, data: parameters, options: options);
} else if (parameters != null && options == null) {
response = await dio.delete(url, data: parameters);
} else if (parameters == null && options != null) {
response = await dio.delete(url, options: options);
} else {
response = await dio.delete(url);
}
return response.data;
}
}
+8
View File
@@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
export 'package:flutter_gen/gen_l10n/app_localizations.dart';
extension AppLocalizationsX on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this)!;
}
+49
View File
@@ -0,0 +1,49 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class MessageUtil {
static toast(String msg) {
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black,
textColor: Colors.white,
fontSize: 16.0);
}
static alert(String msg, BuildContext context, {Function? callback}) {
showCupertinoDialog(
context: context,
builder: (context) {
return Material(
color: Colors.transparent,
child: CupertinoAlertDialog(
title: Text("提示"),
content: Text(msg),
actions: <Widget>[
CupertinoButton(
child: Text("确定"),
onPressed: () {
Navigator.pop(context);
callback!();
}),
]),
);
},
);
}
static snack(String msg, BuildContext context) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(msg),
action: SnackBarAction(
label: "关闭",
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(),
),
));
}
}
+80
View File
@@ -0,0 +1,80 @@
import 'dart:convert';
import 'package:fluro/fluro.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:uuvpn/pages/accountPage.dart';
import 'package:uuvpn/pages/crisp_page.dart';
import 'package:uuvpn/pages/plan/plan_page.dart';
import 'package:uuvpn/pages/server_list.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/router/routers.dart';
class NavigatorUtil {
static goHomePage(BuildContext context) {
Application.router?.navigateTo(context, Routers.home,
transition: TransitionType.inFromRight, replace: true);
}
static goLogin(BuildContext context) {
Application.router?.navigateTo(context, Routers.login,
transition: TransitionType.inFromRight, replace: true);
}
static goPlan(BuildContext context) {
showCupertinoModalBottomSheet(
context: context, builder: (context) => const PlanPage());
// Application.router.navigateTo(context, Routers.plan,
// transition: TransitionType.inFromRight);
}
static goSettings(BuildContext context) {
// showCupertinoModalBottomSheet(
// context: context, builder: (context) => const AccountPage());
// Application.router.navigateTo(context, Routers.plan,
// transition: TransitionType.inFromRight);
// Application.router?.navigateTo(context, Routers.account,
// transition: TransitionType.inFromRight, replace: true);
Navigator.of(context)
.push(MaterialPageRoute(builder: (builder) => const AccountPage()));
}
static goServerList(BuildContext context) {
// showCupertinoModalBottomSheet(
// context: context, builder: (context) => const ServerListPage());
showCupertinoModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height /
1.3, // set height to half of the screen
child: Center(
child: const ServerListPage(),
),
);
},
);
// Application.router.navigateTo(context, Routers.serverList,
// transition: TransitionType.inFromRight);
}
static goToCrisp(BuildContext context) {
showCupertinoModalBottomSheet(
context: context, builder: (context) => const CrispPage());
}
static goWebView(BuildContext context, String titleName, String url) {
String encodeUrl = Uri.encodeComponent(jsonEncode(url));
String encodeTitleName = Uri.encodeComponent(jsonEncode(titleName));
return Application.router?.navigateTo(
context, "${Routers.webView}?titleName=$encodeTitleName&url=$encodeUrl",
transition: TransitionType.inFromRight);
}
static goBack(BuildContext context) {
Application.router?.pop(context);
}
}
@@ -0,0 +1,101 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesUtil {
SharedPreferencesUtil._();
static SharedPreferencesUtil? _instance;
late SharedPreferences sharedPreferences;
static SharedPreferencesUtil? getInstance() {
_instance ??= SharedPreferencesUtil._();
return _instance;
}
static saveData<T>(String key, T value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
switch (T) {
case String:
prefs.setString(key, value as String);
break;
case int:
prefs.setInt(key, value as int);
break;
case bool:
prefs.setBool(key, value as bool);
break;
case double:
prefs.setDouble(key, value as double);
break;
}
}
static Future<T?> getData<T>(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
T? res;
switch (T) {
case String:
res = prefs.getString(key) as T;
break;
case int:
res = prefs.getInt(key) as T;
break;
case bool:
res = prefs.getBool(key) as T;
break;
case double:
res = prefs.getDouble(key) as T;
break;
}
return res;
}
Future<bool> setBool(String tag, bool isFirst) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.setBool(tag, isFirst);
}
Future<bool> setString(String tag, String data) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.setString(tag, data);
}
Future<bool> setMap(String tag, Map<String, dynamic>? data) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.setString(tag, jsonEncode(data));
}
Future<bool> setList(String tag, List<dynamic>? data) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.setString(tag, jsonEncode(data));
}
Future<bool?> getBool(String tag) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.getBool(tag);
}
Future<String?> getString(String tag) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.getString(tag);
}
Future<Map<String, dynamic>> getMap(String tag) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return jsonDecode(sharedPreferences.getString(tag) ?? '{}');
}
Future<List<dynamic>> getList(String tag) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return jsonDecode(sharedPreferences.getString(tag) ?? '[]');
}
Future<bool> clear() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
return sharedPreferences.clear();
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
SliverGridDelegate gridDelegate(BuildContext context) {
return SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: MediaQuery.of(context).size.width > 500.0 ? 4 : 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 100 / 150,
);
}
+167
View File
@@ -0,0 +1,167 @@
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
/// SharedPreferences Util.
class SpUtil {
static SpUtil? _singleton;
static SharedPreferences? _prefs;
static Lock _lock = Lock();
static Future<SpUtil?> getInstance() async {
if (_singleton == null) {
await _lock.synchronized(() async {
if (_singleton == null) {
// 保持本地实例直到完全初始化。
var singleton = SpUtil._();
await singleton._init();
_singleton = singleton;
}
});
}
return _singleton;
}
SpUtil._();
Future _init() async {
_prefs = await SharedPreferences.getInstance();
}
/// put object.
static Future<bool>? putObject(String key, Object value) {
if (_prefs == null) return null;
return _prefs!.setString(key, value == null ? "" : json.encode(value));
}
/// get object.
static Map? getObject(String key) {
if (_prefs == null) return null;
String? _data = _prefs!.getString(key);
return (_data == null || _data.isEmpty) ? null : json.decode(_data);
}
/// get object.
static Object? getObject2(String key) {
if (_prefs == null) return null;
String? _data = _prefs!.getString(key);
return (_data == null || _data.isEmpty) ? null : json.decode(_data);
}
/// put object list.
static Future<bool>? putObjectList(String key, List<Object>? list) {
if (_prefs == null) return null;
List<String>? _dataList = list?.map((value) {
return json.encode(value);
}).toList();
return _prefs!.setStringList(key, _dataList ?? []);
}
/// get object list.
static List<Map?>? getObjectList(String key) {
if (_prefs == null) return null;
List<String>? dataLis = _prefs!.getStringList(key);
return dataLis?.map((value) {
Map? _dataMap = json.decode(value);
return _dataMap;
}).toList();
}
/// get string.
static String? getString(String key, {String? defValue = ''}) {
if (_prefs == null) return defValue;
return _prefs!.getString(key) ?? defValue;
}
/// put string.
static Future<bool>? putString(String? key, String value) {
if (_prefs == null) return null;
return _prefs!.setString(key!, value);
}
/// get bool.
static bool getBool(String key, {bool defValue = false}) {
if (_prefs == null) return defValue;
return _prefs!.getBool(key) ?? defValue;
}
/// put bool.
static Future<bool>? putBool(String key, bool value) {
if (_prefs == null) return null;
return _prefs!.setBool(key, value);
}
/// get int.
static int getInt(String key, {int defValue = 0}) {
if (_prefs == null) return defValue;
return _prefs!.getInt(key) ?? defValue;
}
/// put int.
static Future<bool>? putInt(String key, int value) {
if (_prefs == null) return null;
return _prefs!.setInt(key, value);
}
/// get double.
static double getDouble(String key, double defValue) {
if (_prefs == null) return defValue;
return _prefs!.getDouble(key) ?? defValue;
}
/// put double.
static Future<bool>? putDouble(String key, double value) {
if (_prefs == null) return null;
return _prefs!.setDouble(key, value);
}
/// get string list.
static List<String> getStringList(String key,
{List<String> defValue = const []}) {
if (_prefs == null) return defValue;
return _prefs!.getStringList(key) ?? defValue;
}
/// put string list.
static Future<bool>? putStringList(String key, List<String> value) {
if (_prefs == null) return null;
return _prefs!.setStringList(key, value);
}
/// get dynamic.
static dynamic getDynamic(String key, {Object? defValue}) {
if (_prefs == null) return defValue;
return _prefs!.get(key) ?? defValue;
}
/// have key.
static bool? haveKey(String key) {
if (_prefs == null) return null;
return _prefs!.getKeys().contains(key);
}
/// get keys.
static Set<String>? getKeys() {
if (_prefs == null) return null;
return _prefs!.getKeys();
}
/// remove.
static Future<bool>? remove(String key) {
if (_prefs == null) return null;
return _prefs!.remove(key);
}
/// clear.
static Future<bool>? clear() {
if (_prefs == null) return null;
return _prefs!.clear();
}
///Sp is initialized.
static bool isInitialized() {
return _prefs != null;
}
}
+2
View File
@@ -0,0 +1,2 @@
enum Status { success, pending, empty, error, noConnection }
+23
View File
@@ -0,0 +1,23 @@
const welcomeText = 'Welcome';
const welcomeContent = '';
const getStarted = 'Get Started';
const topMovies = 'Top Movies';
const entering = 'entering';
const choseFavoriteGenre =
'Choose your interests and get the best movie recommendations. Don\'t worry, you can always change it later.';
const skip = 'Skip';
const continui = 'Continue';
const seeAll = 'See all';
const populars = 'Popular';
const topRated = 'Top rated movies';
const home = 'Home';
const explore = 'Explore';
const myList = 'My List';
const profile = 'Profile';
const movies = 'Movies';
const tryAgain = 'Try again';
const errorMessage = 'Something went wrong!';
const emptyMessage = 'Your List is Empty';
const play = 'Play';
const releaseDate = 'Release date: ';
+27
View File
@@ -0,0 +1,27 @@
class TransferUtil {
double _transfer = 0;
int _level = 0;
List suffix = [
'B',
'KB',
'MB',
'GB',
'TB',
'PB'
];
String toHumanReadable(int transfer) {
_transfer = transfer.toDouble();
handleTransfer();
return '${_transfer.toStringAsFixed(2)} ${suffix[_level]}';
}
handleTransfer() {
if (_transfer > 1024) {
_transfer = _transfer / 1024;
++_level;
handleTransfer();
}
}
}
+3
View File
@@ -0,0 +1,3 @@
enum RequestStatus { loading, loaded, error }
enum GetAllRequestStatus { loading, loaded, error, fetchMoreError }
+12
View File
@@ -0,0 +1,12 @@
///无参数请求回调
typedef ParamVoidCallback = dynamic Function();
///回调一个参数
typedef ParamSingleCallback<D> = dynamic Function(D data);
///回到俩个参数
typedef ParamTwiceCallback<O, T> = dynamic Function(O dataOne, T dataTwo);
///回调三个参数
typedef ParamThreeCallback<O, T, K> = dynamic Function(
O dataOne, T dataTwo, K threeData);
+248
View File
@@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
// import 'package:uuvpn/go_router.dart';
import 'package:intl/intl.dart';
import 'package:uuvpn/resources/app_colors.dart';
import 'package:uuvpn/resources/app_routes.dart';
import 'package:uuvpn/resources/app_values.dart';
String getDate(String? date) {
if (date == null || date.isEmpty) {
return '';
}
final vals = date.split('-');
String year = vals[0];
int monthNb = int.parse(vals[1]);
String day = vals[2];
String month = '';
switch (monthNb) {
case 1:
month = 'Jan';
break;
case 2:
month = 'Feb';
break;
case 3:
month = 'Mar';
break;
case 4:
month = 'Apr';
break;
case 5:
month = 'May';
break;
case 6:
month = 'Jun';
break;
case 7:
month = 'Jul';
break;
case 8:
month = 'Aug';
break;
case 9:
month = 'Sep';
break;
case 10:
month = 'Oct';
break;
case 11:
month = 'Nov';
break;
case 12:
month = 'Dec';
break;
default:
break;
}
return '$month $day, $year';
}
String getPosterUrl(String? path) {
if (path != null) {
return ApiConstants.basePosterUrl + path;
} else {
return ApiConstants.moviePlaceHolder;
}
}
String getBackdropUrl(String? path) {
if (path != null) {
return ApiConstants.baseBackdropUrl + path;
} else {
return ApiConstants.moviePlaceHolder;
}
}
String getStillUrl(String? path) {
if (path != null) {
return ApiConstants.baseStillUrl + path;
} else {
return ApiConstants.stillPlaceHolder;
}
}
String getLength(int? runtime) {
if (runtime == null || runtime == 0) {
return '';
}
if (runtime < 60) {
return '${runtime}m';
}
if (runtime % 60 == 0) {
return '${runtime ~/ 60}h';
}
return '${runtime ~/ 60}h ${runtime % 60}m';
}
String getVotesCount(int voteCount) {
if (voteCount < 1000) {
return '($voteCount)';
}
return '(${voteCount ~/ 1000}k)';
}
String getProfileImageUrl(Map<String, dynamic> json) {
if (json['profile_path'] != null) {
return ApiConstants.baseProfileUrl + json['profile_path'];
} else {
return ApiConstants.castPlaceHolder;
}
}
class ApiConstants {
static String castPlaceHolder = "";
static String baseProfileUrl = "";
static var baseAvatarUrl = "";
static String avatarPlaceHolder = "";
static var baseVideoUrl = "";
static var basePosterUrl = "";
static String moviePlaceHolder = "";
static var baseStillUrl;
static var baseBackdropUrl;
static String stillPlaceHolder = "";
}
String getElapsedTime(String date) {
DateTime reviewDate = DateTime.parse(date);
DateTime today = DateTime.now();
Duration diff = today.difference(reviewDate);
if (diff.inDays >= 365) {
int years = diff.inDays ~/ 365;
return '${years}y';
} else if (diff.inDays >= 30) {
int months = diff.inDays ~/ 30;
return '${months}mo';
} else if (diff.inDays >= 7) {
int weeks = diff.inDays ~/ 7;
return '${weeks}w';
} else if (diff.inDays >= 1) {
return '${diff.inDays}d';
} else if (diff.inHours >= 1) {
int hours = diff.inHours ~/ 24;
return '${hours}h';
} else if (diff.inMinutes >= 1) {
int minutes = diff.inDays ~/ 60;
return '${minutes}min';
} else {
return 'Now';
}
}
String getGenres(List<dynamic> genres) {
if (genres.isNotEmpty) {
return genres.first['name'];
} else {
return '';
}
}
String getDateFromTimeSpan(int timestamp) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
String formattedTime = DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
return formattedTime;
}
String getAvatarUrl(String? path) {
if (path != null) {
if (path.startsWith('/https://www.gravatar.com/avatar')) {
return path.substring(1);
} else {
return ApiConstants.baseAvatarUrl + path;
}
} else {
return ApiConstants.avatarPlaceHolder;
}
}
String getTrailerUrl(Map<String, dynamic> json) {
List videos = json['videos']['results'];
if (videos.isNotEmpty) {
List trailers = videos.where((e) => e['type'] == 'Trailer').toList();
if (trailers.isNotEmpty) {
return ApiConstants.baseVideoUrl + trailers.last['key'];
} else {
return '';
}
} else {
return '';
}
}
void navigateToDetailsView(BuildContext context, Media media) {
if (media.isMovie) {
// context.pushNamed(
// AppRoutes.movieDetailsRoute,
// params: {'movieId': media.tmdbID.toString()},
// );
} else {
// context.pushNamed(
// AppRoutes.tvShowDetailsRoute,
// params: {'tvShowId': media.tmdbID.toString()},
// );
}
}
class Media {
bool get isMovie => false;
get tmdbID => null;
}
void showCustomBottomSheet(BuildContext context, Widget child) {
final size = MediaQuery.of(context).size.height;
showModalBottomSheet(
context: context,
backgroundColor: AppColors.secondaryBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(AppSize.s20),
),
),
builder: (context) {
return SizedBox(
height: size * 0.5,
child: child,
);
},
);
}
void showSnackBar(BuildContext context, String content) {
final snackBar = SnackBar(content: Text(content));
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(snackBar);
}
+176
View File
@@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:math';
import 'dart:ui' as ui;
// ui适配
class UiUtil {
static const Size _defaultSize = Size(360, 690);
static UiUtil? _instance;
static UiUtil get instance => UiUtil();
/// UI设计中手机尺寸
Size? _uiSize;
/// 控制字体是否要根据系统的“字体大小”辅助选项来进行缩放。默认值为false。
late bool _allowFontScaling;
///屏幕方向
Orientation? _orientation;
double? _pixelRatio;
double? _textScaleFactor;
late double _statusBarHeight;
late double _bottomBarHeight;
double? _screenWidth;
double? _screenHeight;
factory UiUtil() {
if (_instance == null) {
_instance = UiUtil._internal();
}
return _instance!;
}
UiUtil._internal() {
var window = WidgetsBinding.instance.window;
_pixelRatio = window.devicePixelRatio;
_statusBarHeight = window.padding.top;
_bottomBarHeight = window.padding.bottom;
_textScaleFactor = window.textScaleFactor;
_allowFontScaling = false;
_uiSize = _defaultSize;
}
static void init({
BuildContext? context,
Size designSize = _defaultSize,
Orientation orientation = Orientation.portrait,
bool allowFontScaling = false,
}) {
instance._init(
context: context,
designSize: designSize,
orientation: orientation,
allowFontScaling: allowFontScaling,
);
}
void _init({
BuildContext? context,
Size? designSize,
Orientation orientation = Orientation.portrait,
bool allowFontScaling = false,
}) {
this._uiSize = designSize;
this._allowFontScaling = allowFontScaling;
this._orientation = orientation;
if (orientation == Orientation.portrait) {
this._screenWidth = MediaQuery.of(context!).size.width;
this._screenHeight = MediaQuery.of(context).size.height;
} else {
this._screenWidth = MediaQuery.of(context!).size.height;
this._screenHeight = MediaQuery.of(context).size.width;
}
}
///获取屏幕方向
Orientation? get orientation => _orientation;
/// 每个逻辑像素的字体像素数,字体的缩放比例
double? get textScaleFactor => _textScaleFactor;
/// 设备的像素密度
double? get pixelRatio => _pixelRatio;
/// 当前设备宽度 dp
double get screenWidth => _screenWidth ?? _defaultSize.width;
///当前设备高度 dp
double get screenHeight => _screenHeight ?? _defaultSize.height;
/// 状态栏高度 dp 刘海屏会更高
double get statusBarHeight => _statusBarHeight / _pixelRatio!;
/// 底部安全区距离 dp
double get bottomBarHeight => _bottomBarHeight / _pixelRatio!;
/// 实际尺寸与UI设计的比例
double get scaleWidth => (_screenWidth ?? _uiSize!.width) / _uiSize!.width;
double get scaleHeight => (_screenWidth ?? _uiSize!.height) / _uiSize!.height;
double get scaleText => min(scaleWidth, scaleHeight);
/// 根据UI设计的设备宽度适配
double setWidth(num width) => width * scaleWidth;
/// 根据UI设计的设备高度适配
/// 高度适配主要针对想根据UI设计的一屏展示一样的效果
double setHeight(num height) => height * scaleHeight;
///根据宽度或高度中的较小值进行适配
double radius(num r) => r * scaleText;
///字体大小适配方法
double setSp(num fontSize, {bool? allowFontScalingSelf}) =>
allowFontScalingSelf == null
? (_allowFontScaling
? (fontSize * scaleText) * _textScaleFactor!
: (fontSize * scaleText))
: (allowFontScalingSelf
? (fontSize * scaleText) * _textScaleFactor!
: (fontSize * scaleText));
}
///适配文字
@deprecated
num setSp(num size) => UiUtil().setSp(size);
///自动适配,后面方便扩展
@deprecated
num auto(num size) => UiUtil().setWidth(size);
extension NumExtend on num {
///自动适配移动界面
double get dp {
//如果没初始化,需要初始化,防止web端直接导航页面报错
AutoUi.init();
return UiUtil().setWidth(this);
}
///配置文字,文字适配请用sp单位
double get sp {
//如果没初始化,需要初始化,防止web端直接导航页面报错
AutoUi.init();
return UiUtil().setWidth(this);
}
}
class AutoUi {
static AutoUi? _instance;
static void init() {
if (_instance == null) {
_instance = AutoUi();
initAutoUi(Get.context);
}
}
}
//界面适配
void initAutoUi(BuildContext? context) {
if (context != null) return;
UiUtil.init(
// 通过context获取设备像素大小
context: context,
// 设计尺寸
// designSize: Size(1920 / 2.72, 1080 / 2.72),
designSize: Size(1920, 1080),
);
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:uuvpn/constant/app_colors.dart';
class ProgressView extends StatelessWidget {
const ProgressView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(
color: AppColors.yellowColor,
),
),
);
}
}
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class BottomBlock extends StatelessWidget {
const BottomBlock({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100,
color: Colors.transparent,
);
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'dart:math';
import 'package:flutter/material.dart';
///
/// desc:
///
class CirclePainter extends CustomPainter {
final double progress;
final double strokeWidth;
final Color color;
Paint _paint = Paint();
CirclePainter(
{this.progress = 0.0, this.strokeWidth = 1.0, this.color = Colors.white});
@override
void paint(Canvas canvas, Size size) {
_paint
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
double radius = min(size.width, size.height) / 2;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2), radius * progress, _paint);
}
@override
bool shouldRepaint(covariant CirclePainter old) {
return progress != old.progress ||
strokeWidth != old.strokeWidth ||
color != old.color;
}
}
+232
View File
@@ -0,0 +1,232 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/channels/Platform.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class ConnectionStats extends StatefulWidget {
const ConnectionStats({Key? key}) : super(key: key);
@override
ConnectionStatsState createState() => ConnectionStatsState();
}
class ConnectionStatsState extends State<ConnectionStats> {
late UserModel _userModel;
late AppModel _appModel;
late ServerModel _serverModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
_appModel = Provider.of<AppModel>(context);
_serverModel = Provider.of<ServerModel>(context);
}
String toDateString(DateTime date) {
var duration = DateTime.now().difference(date);
var microseconds = duration.inMicroseconds;
var sign = (microseconds < 0) ? "-" : "";
var hours = microseconds ~/ Duration.microsecondsPerHour;
microseconds = microseconds.remainder(Duration.microsecondsPerHour);
var hoursPadding = hours.abs() < 10 ? "0" : "";
if (microseconds < 0) microseconds = -microseconds;
var minutes = microseconds ~/ Duration.microsecondsPerMinute;
microseconds = microseconds.remainder(Duration.microsecondsPerMinute);
var minutesPadding = minutes < 10 ? "0" : "";
var seconds = microseconds ~/ Duration.microsecondsPerSecond;
microseconds = microseconds.remainder(Duration.microsecondsPerSecond);
var secondsPadding = seconds < 10 ? "0" : "";
return "$sign$hoursPadding${hours.abs()}:"
"$minutesPadding$minutes:"
"$secondsPadding$seconds";
}
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
Color colortheme = isDarkTheme ? Colors.white : Colors.black;
//isDarkTheme ? Colors.white : Colors.black
if (Platform.isAndroid || Platform.isMacOS) {
return Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(75)),
child: TextButton(
onPressed: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.map,
color: isDarkTheme ? Colors.white : Colors.black,
size: 20),
Text(context.l10n.clicktoselectanothernode,
style: TextStyle(
fontSize: 18,
color: isDarkTheme ? Colors.white : Colors.black)),
Icon(Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black,
size: 20)
])),
),
],
);
}
return Column(
children: [
Text(toDateString(_appModel.connectedDate!),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 40,
color: colortheme,
)),
Padding(
padding: EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(75)),
child: TextButton(
onPressed: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.map,
color: isDarkTheme ? Colors.white : Colors.black, size: 20),
// Text(context.l10n.clicktoselectanothernode,
Text(" ${_serverModel.selectServerEntity?.name}",
style: TextStyle(
fontSize: 18,
color: isDarkTheme ? Colors.white : Colors.black)),
Icon(Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black, size: 20)
])),
),
/* Padding(
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75), vertical: 10),
child: Row(
children: [
// Download Stats
Row(children: [
// Download Icon
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Color(0xffff0000),
blurRadius: 13,
spreadRadius: -2)
],
color: const Color(0xffff0000),
borderRadius: BorderRadius.circular(13)),
padding: const EdgeInsets.all(5),
child: const Icon(
Icons.arrow_downward,
color: Colors.white,
),
),
// Labels
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"下行速度",
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w500),
),
RichText(
text: const TextSpan(
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w900),
children: [
TextSpan(text: "75.9"),
TextSpan(
text: " KB/s",
style:
TextStyle(fontWeight: FontWeight.normal)),
]),
)
],
),
)
]),
Expanded(child: Container()),
// Upload Stats
Row(children: [
// Upload Icon
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Color(0xff03a305),
blurRadius: 13,
spreadRadius: -2)
],
color: const Color(0xff03a305),
borderRadius: BorderRadius.circular(13)),
padding: const EdgeInsets.all(5),
child: const Icon(
Icons.arrow_upward,
color: Colors.white,
),
),
// Labels
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"上行速度",
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w500),
),
RichText(
text: const TextSpan(
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w900),
children: [
TextSpan(text: "29.6"),
TextSpan(
text: " KB/s",
style:
TextStyle(fontWeight: FontWeight.normal)),
]),
)
],
),
)
]),
],
),
)*/
],
);
}
}
+167
View File
@@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/plan_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/resources/app_colors.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/widgets/bottom_block.dart';
import 'package:uuvpn/widgets/connection_stats.dart';
import 'package:uuvpn/widgets/logo_bar.dart';
import 'package:uuvpn/widgets/my_subscribe.dart';
import 'package:uuvpn/widgets/plan_list.dart';
import 'package:uuvpn/widgets/power_btn.dart';
import 'package:uuvpn/widgets/select_location.dart';
import 'package:uuvpn/utils/common_util.dart';
import 'package:uuvpn/widgets/watermuticicel.dart';
class HomeWidget extends StatefulWidget {
const HomeWidget({Key? key}) : super(key: key);
@override
HomeWidgetState createState() => HomeWidgetState();
}
class HomeWidgetState extends State<HomeWidget>
with AutomaticKeepAliveClientMixin {
late AppModel _appModel;
late UserModel _userModel;
late UserSubscribeModel _userSubscribeModel;
late PlanModel _planModel;
late ServerModel _serverModel;
customListTile(BuildContext context, String title, String icon,
{Widget? trailing, String? subtitle, VoidCallback? onTap}) =>
ListTile(
onTap: onTap ?? null,
minLeadingWidth: 35,
dense: true,
title: Text(title,
style: Theme.of(context).primaryTextTheme.labelMedium),
subtitle: subtitle != null
? Text(
subtitle,
style: Theme.of(context).primaryTextTheme.labelMedium,
)
: null,
leading: SvgPicture.asset(
icon,
color: Theme.of(context).colorScheme.secondary,
width: 24,
alignment: Alignment.centerRight,
),
trailing: trailing ?? null);
@override
bool get wantKeepAlive => true;
final ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
// _controller.addListener(() {
// //print(_controller.offset);
// });
}
@override
void dispose() {
//为了避免内存泄露,需要调用_controller.dispose
_controller.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
_userSubscribeModel = Provider.of<UserSubscribeModel>(context);
_planModel = Provider.of<PlanModel>(context);
_serverModel = Provider.of<ServerModel>(context);
// print("didChangeDependencies");
}
@override
Widget build(BuildContext context) {
super.build(context);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return SingleChildScrollView(
controller: _controller,
physics: const BouncingScrollPhysics(),
child: Column(
children: [
// // Logo bar
// Padding(
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(75),
// right: ScreenUtil().setWidth(75)),
// child: LogoBar(
// isOn: _appModel.isOn,
// ),
// ),
const SizedBox(
height: 20,
),
// SvgPicture.asset(
// 'assets/map.svg',
// height: 230,
// width: ScreenUtil().screenWidth,
// color: _appModel.isOn
// ? AppColors.greenColor
// : isDarkTheme
// ? AppColors.darkSurfaceColor
// : Color.fromARGB(255, 133, 132, 132),
// ),
PowerButton(),
const SizedBox(
height: 40,
),
_appModel.isOn
? (_serverModel.selectServerEntity?.name != null
? Center(
child: Text('${context.l10n.yilianjie}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
)))
: Center(
child: Text(context.l10n.yilianjie,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
))))
: Center(
child: Text(context.l10n.yiduankai2,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
))),
const SizedBox(
height: 20,
),
_appModel.isOn
? ConnectionStats()
: const SizedBox(
height: 1,
),
const BottomBlock(),
],
));
}
}
+169
View File
@@ -0,0 +1,169 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class LogoBar extends StatelessWidget {
const LogoBar({
Key? key,
required this.isOn,
}) : super(key: key);
final bool isOn;
void onLogoutTap(context, _userModel) {
showDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(context.l10n.alertsss),
content: Column(
children: <Widget>[
SizedBox(
height: 10,
),
Align(
child: Text(
context.l10n.wanttoexit,
style: TextStyle(fontSize: 18),
),
alignment: Alignment(0, 0),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(context.l10n.cancelss),
onPressed: () {
Navigator.pop(context);
//print("取消");
},
),
CupertinoDialogAction(
child: Text(
context.l10n.exitout,
style: TextStyle(color: Colors.red),
),
onPressed: () {
//print("确定");
_userModel.logout();
NavigatorUtil.goLogin(context);
},
),
],
);
});
}
@override
Widget build(BuildContext context) {
AppModel appModel = Provider.of<AppModel>(context);
UserModel userModel = Provider.of<UserModel>(context);
UserSubscribeModel userSubscribeModel =
Provider.of<UserSubscribeModel>(context);
return Container(
margin: EdgeInsets.only(top: ScreenUtil().setWidth(60)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
AppStrings.appName,
style: TextStyle(
fontWeight: FontWeight.w900,
fontSize: ScreenUtil().setSp(60),
color: isOn ? AppColors.whiteColor : Colors.white,
),
),
Row(
children: [
// Material(
// color: isOn ? const Color(0x66000000) : AppColors.darkSurfaceColor,
// borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
// child: InkWell(
// borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
// onTap: () => NavigatorUtil.goToCrisp(context),
// child: Container(
// padding: EdgeInsets.symmetric(
// vertical: ScreenUtil().setWidth(10), horizontal: ScreenUtil().setWidth(30)),
// child: Text(
// "客服",
// style:
// TextStyle(fontSize: ScreenUtil().setSp(36), color: Colors.white, fontWeight: FontWeight.w500),
// ),
// ),
// ),
// ),
// Padding(padding: EdgeInsets.only(left: ScreenUtil().setWidth(15))),
Material(
color:
isOn ? const Color(0x66000000) : AppColors.darkSurfaceColor,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
child: InkWell(
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: () => appModel.jumpToPage(3),
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(10),
horizontal: ScreenUtil().setWidth(30)),
child: Text(
userSubscribeModel?.userSubscribeEntity?.email ??
context.l10n.welcome,
style: TextStyle(
fontSize: ScreenUtil().setSp(36),
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
),
userModel.isLogin
? Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(15)))
: Container(),
userModel.isLogin
? Material(
color: isOn
? const Color(0x66000000)
: AppColors.darkSurfaceColor,
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
child: InkWell(
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: () {
onLogoutTap(context, userModel);
// userModel.logout();
// NavigatorUtil.goLogin(context);
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(10),
horizontal: ScreenUtil().setWidth(30)),
child: Text(
context.l10n.logout,
style: TextStyle(
fontSize: ScreenUtil().setSp(36),
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
)
: Container(),
],
)
],
),
);
}
}
+299
View File
@@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/routes/OnceNotice.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/utils/transfer_util.dart';
class MySubscribe extends StatefulWidget {
const MySubscribe(
{Key? key,
required this.isLogin,
required this.isOn,
required this.userSubscribeEntity})
: super(key: key);
final bool isLogin;
final bool isOn;
final UserSubscribeEntity? userSubscribeEntity;
@override
MySubscribeState createState() => MySubscribeState();
}
class MySubscribeState extends State<MySubscribe> {
late AppModel _appModel;
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
//,color: isDarkTheme ? Colors.white : Colors.black,
//widget.isOn
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(75)),
child: Text(
context.l10n.yidingyue,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: isDarkTheme ? Colors.grey[400] : AppColors.grayColor,
fontWeight: FontWeight.w500),
),
),
SizedBox(height: ScreenUtil().setWidth(30)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(bottom: ScreenUtil().setWidth(10)),
child: _contentWidget(),
)
],
);
}
Widget _contentWidget() {
// print(widget.userSubscribeEntity?.plan.toJson().toString());
if (widget.userSubscribeEntity?.plan == null) {
return _emptyWidget();
}
// if (widget.userSubscribeEntity!.expiredAt * 1000 <
// DateTime.now().millisecondsSinceEpoch) {
// return _timeOutWidget();
// }
return _buildConnections();
}
Widget _emptyWidget() {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: isDarkTheme
? AppColors.darkSurfaceColor
: const Color.fromARGB(255, 196, 194, 194),
child: Container(
alignment: Alignment.center,
child: Text(
!widget.isLogin
? context.l10n.qingxiandenglu
: context.l10n.qingxiandingyuetaocan,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setWidth(40),
color: isDarkTheme ? Colors.white : Colors.black),
),
),
),
);
}
Widget _timeOutWidget() {
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: widget.isOn ? Colors.white : AppColors.darkSurfaceColor,
child: Container(
alignment: Alignment.center,
child: Text(
context.l10n.taocanguoqichongxindingyue,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setWidth(40),
color: widget.isOn ? Colors.black : Colors.white),
),
),
),
);
}
Widget _buildConnections() {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(240),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: isDarkTheme
? AppColors.whiteColor.withAlpha(20)
: Colors.grey[200],
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(30),
horizontal: ScreenUtil().setWidth(40)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.userSubscribeEntity!.plan?.name ?? "",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(35),
color:
!isDarkTheme ? Colors.black : Colors.white),
),
Padding(
padding: EdgeInsets.only(
left: ScreenUtil().setWidth(15))),
Text(
widget.userSubscribeEntity?.expiredAt != 0
? '${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(widget.userSubscribeEntity!.expiredAt * 1000))} ${context.l10n.guoqi}'
: context.l10n.chagnqiyouxiao,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(35),
color:
!isDarkTheme ? Colors.black : Colors.white),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: ScreenUtil().setWidth(480),
padding: EdgeInsets.only(
bottom: ScreenUtil().setWidth(15)),
child: LinearProgressIndicator(
backgroundColor:
!isDarkTheme ? Colors.grey[400] : Colors.white,
valueColor:
AlwaysStoppedAnimation(Colors.green[600]),
value: double.parse(((widget
.userSubscribeEntity!.u ??
0 + widget.userSubscribeEntity!.d ??
0) /
widget.userSubscribeEntity!
.transferEnable ??
1)
.toStringAsFixed(2)),
),
),
Text(
'${context.l10n.yiyong} ${TransferUtil().toHumanReadable(widget.userSubscribeEntity!.u + widget.userSubscribeEntity!.d)} / ${context.l10n.zongji} ${TransferUtil().toHumanReadable(widget.userSubscribeEntity!.transferEnable)}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(26),
color:
!isDarkTheme ? Colors.black : Colors.white),
)
],
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
/*Container(
width: ScreenUtil().setWidth(160),
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(right: ScreenUtil().setWidth(10)),
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.green[700],
disabledForegroundColor: Colors.black,
disabledBackgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
),
onPressed: () {
_userModel.checkHasLogin(
context,
() => Navigator.of(context).push(
MaterialPageRoute(
builder: (builder) => const OnceNotice()))
/*Fluttertoast.showToast(
msg: context.l10n.qingxuanzefuwqjiedian,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0)*/
//NavigatorUtil.goPlan(context)
);
//_appModel.getTunnelLog();
},
child: Text(
context.l10n.renew,
style: TextStyle(
color: Colors.white,
fontSize: ScreenUtil().setSp(36)),
),
),
),
SizedBox(
width: ScreenUtil().setWidth(160),
height: ScreenUtil().setWidth(90),
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.green[700],
disabledForegroundColor: Colors.black,
disabledBackgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
),
onPressed: () {
_appModel.getTunnelConfiguration();
},
child: Text(
'重置',
style: TextStyle(
color: Colors.white,
fontSize: ScreenUtil().setSp(36)),
),
),
)*/
],
)
],
),
),
));
}
}
+159
View File
@@ -0,0 +1,159 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class PlanList extends StatefulWidget {
const PlanList(
{Key? key,
required this.isOn,
required this.userSubscribeEntity,
required this.plans})
: super(key: key);
final bool isOn;
final UserSubscribeEntity? userSubscribeEntity;
final List<PlanEntity> plans;
@override
PlanListState createState() => PlanListState();
}
class PlanListState extends State<PlanList> with AutomaticKeepAliveClientMixin {
late UserModel _userModel;
@override
bool get wantKeepAlive => true;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(75)),
child: Text(
context.l10n.dinggoutaocan,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: widget.isOn ? AppColors.grayColor : Colors.grey[400],
fontWeight: FontWeight.w500),
),
),
SizedBox(height: ScreenUtil().setWidth(30)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(bottom: ScreenUtil().setWidth(10)),
child: Row(children: _buildConnections()),
)
],
);
}
List<Widget> _buildConnections() {
if (widget.userSubscribeEntity == null) {
return [Container()];
}
int boughtPlanId = widget.userSubscribeEntity!.expiredAt * 1000 <
DateTime.now().millisecondsSinceEpoch
? 0
: widget.userSubscribeEntity?.planId ?? 0;
List<Widget> list =
List.generate(widget.plans.length * 2 + 1, (i) => Container());
list[0] = SizedBox(width: ScreenUtil().setWidth(75));
for (var i = 1; i < list.length; i++) {
list[i] = Material(
elevation: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? 3
: 0
: 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? AppColors.darkSurfaceColor
: const Color(0x15000000)
: AppColors.darkSurfaceColor,
child: InkWell(
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: widget.isOn && widget.plans[i ~/ 2].id == boughtPlanId
? null
: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goPlan(context)),
child: Container(
// width: ScreenUtil().setWidth(300),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(40),
vertical: ScreenUtil().setWidth(30)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Country name
Text(
widget.plans[i ~/ 2].name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? Colors.white
: Colors.white
: Colors.white),
),
// Connection status
Padding(
padding: EdgeInsets.only(top: ScreenUtil().setWidth(40)),
child: widget.plans[i ~/ 2].id == boughtPlanId
? Row(
children: [
Icon(
Icons.shield,
size: ScreenUtil().setWidth(32),
color: const Color(0xFF1abb1d),
),
Text(
context.l10n.yidingyue,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: const Color(0xFF1abb1d),
fontWeight: FontWeight.bold),
)
],
)
: Text(context.l10n.xuangou,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
fontWeight: FontWeight.w500,
color:
widget.isOn ? Colors.red : Colors.red)),
)
],
))),
);
list[++i] = SizedBox(width: ScreenUtil().setWidth(30));
}
return list;
}
}
+155
View File
@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/message_util.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/widgets/watermuticicel.dart';
import 'package:uuvpn/widgets/waterrepper.dart';
class PowerButton extends StatefulWidget {
const PowerButton({Key? key}) : super(key: key);
@override
PowerButtonState createState() => PowerButtonState();
}
class PowerButtonState extends State<PowerButton> {
late AppModel _appModel;
late UserModel _userModel;
late ServerModel _serverModel;
bool light = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
_serverModel = Provider.of<ServerModel>(context);
light = _appModel.isOn; // || _appModel.isconnectordisconnct;
}
Future<void> pressConnectBtn() async {
if (_serverModel.selectServerEntity == null) {
Fluttertoast.showToast(
msg: context.l10n.qingxuanzefuwqjiedian,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0);
if (_serverModel.serverEntityList.isEmpty) {
MessageUtil.toast(context.l10n.nodefornullcheckissubscripts);
} else {
NavigatorUtil.goServerList(context);
}
return;
}
_appModel.togglePowerButton();
}
final MaterialStateProperty<Icon?> thumbIcon =
MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return const Icon(Icons.check);
}
return const Icon(Icons.close);
},
);
@override
Widget build(BuildContext context) {
//
return Column(
children: [
_appModel.isOn
? Container(
width: 330,
height: 330,
child: WaterRipple(
color: Colors.green,
duration: Duration(milliseconds: 2000),
)
// WaterMultipleCircleLoading(
// color: Colors.green,
// duration: Duration(milliseconds: 2500),
// ),
)
: Container(
height: 330,
),
/*InkWell(
splashColor: Color.fromARGB(255, 51, 117, 54),
onTap: () => _userModel.checkHasLogin(context, pressConnectBtn),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
child: Container(
padding: EdgeInsets.all(ScreenUtil().setWidth(20)),
child: Icon(
_appModel.isOn ? Icons.toggle_on : Icons.toggle_off,
size: ScreenUtil().setWidth(420),
color: _appModel.isOn
? Color.fromARGB(255, 47, 161, 53)
: Colors.grey,
)),
)*/
Transform.scale(
scale: 4.0,
child: Switch(
// thumbIcon: thumbIcon,
// This bool value toggles the switch.
value: light,
activeColor: Color.fromARGB(255, 47, 161, 53),
inactiveTrackColor:
Provider.of<ThemeCollection>(context).isDarkActive
? AppColors.whiteColor.withAlpha(90)
: const Color(0xffD7D6D9),
onChanged: (bool value) {
// This is called when the user toggles the switch.
setState(() {
light = value;
if (value) {
_userModel.checkHasLogin(context, pressConnectBtn);
} else {
_appModel.togglePowerButton();
}
});
},
),
),
],
);
/*return Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color:
_appModel.isOn ? const Color(0x20000000) : const Color(0xff606060),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(460)),
),
child: Material(
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
color: _appModel.isOn ? Color.fromARGB(255, 47, 161, 53) : Colors.grey,
child: InkWell(
splashColor: Color.fromARGB(255, 51, 117, 54),
onTap: () => _userModel.checkHasLogin(context, pressConnectBtn),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
child: Container(
padding: EdgeInsets.all(ScreenUtil().setWidth(20)),
child: Icon(
Icons.toggle_off,
size: ScreenUtil().setWidth(420),
color: Colors.white,
)),
),
),
);*/
}
}
+66
View File
@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class ProfileWidget extends StatelessWidget {
const ProfileWidget(
{Key? key, required this.userName, this.avatar, required this.onTap})
: super(key: key);
final String? avatar;
final String userName;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(right: 24, left: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
avatar != null
? Row(
children: <Widget>[
// ClipOval(child: Image(image: NetworkImage(avatar!), width: 40, height: 40)),
Padding(
padding: const EdgeInsets.only(left: 12),
child: Text(
userName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black),
),
)
],
)
: Text(
userName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black),
),
avatar != null
? Material(
color: const Color(0x66000000),
borderRadius: BorderRadius.circular(30),
child: InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 10),
child: const Text(
'退出',
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
)
: Container(),
],
),
);
}
}
@@ -0,0 +1,305 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:uuvpn/constant/app_colors.dart';
double minHeight = ScreenUtil().setHeight(260);
const double iconStartSize = 44;
const double iconEndSize = 120;
const double iconStartMarginTop = 36;
const double iconEndMarginTop = 80;
const double iconsVerticalSpacing = 24;
const double iconsHorizontalSpacing = 16;
class RecentConnectionBottomSheet extends StatefulWidget {
const RecentConnectionBottomSheet({Key? key}) : super(key: key);
@override
RecentConnectionBottomSheetState createState() =>
RecentConnectionBottomSheetState();
}
class RecentConnectionBottomSheetState
extends State<RecentConnectionBottomSheet>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double get maxHeight => MediaQuery.of(context).size.height;
double? get headerTopMargin =>
lerp(20, 20 + MediaQuery.of(context).padding.top);
double? get headerFontSize => lerp(14, 24);
double? get itemBorderRadius => lerp(8, 24);
double? get iconLeftBorderRadius => itemBorderRadius;
double? get iconRightBorderRadius => lerp(8, 0);
double? get iconSize => lerp(iconStartSize, iconEndSize);
double? iconTopMargin(int index) =>
lerp(iconStartMarginTop,
iconEndMarginTop + index * (iconsVerticalSpacing + iconEndSize))! +
headerTopMargin!;
double? iconLeftMargin(int index) =>
lerp(index * (iconsHorizontalSpacing + iconStartSize), 0);
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double? lerp(double min, double max) =>
lerpDouble(min, max, _controller.value);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
height: lerp(minHeight, maxHeight),
left: 0,
right: 0,
bottom: 0,
child: GestureDetector(
onTap: _toggle,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 32),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [AppColors.themeColor, Colors.pink],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
child: Stack(
children: <Widget>[
const MenuButton(),
SheetHeader(
fontSize: headerFontSize,
topMargin: headerTopMargin,
),
for (Event event in events) _buildFullItem(event),
for (Event event in events) _buildIcon(event),
],
),
),
),
);
},
);
}
Widget _buildIcon(Event event) {
int index = events.indexOf(event);
return Positioned(
height: iconSize,
width: iconSize,
top: iconTopMargin(index),
left: iconLeftMargin(index),
child: ClipRRect(
borderRadius: BorderRadius.horizontal(
left: Radius.circular(iconLeftBorderRadius!),
right: Radius.circular(iconRightBorderRadius!),
),
child: Image.asset(
'assets/${event.assetName}',
fit: BoxFit.cover,
alignment: Alignment(lerp(1, 0)!, 0),
),
),
);
}
Widget _buildFullItem(Event event) {
int index = events.indexOf(event);
return ExpandedEventItem(
topMargin: iconTopMargin(index),
leftMargin: iconLeftMargin(index),
height: iconSize,
isVisible: _controller.status == AnimationStatus.completed,
borderRadius: itemBorderRadius,
title: event.title,
date: event.date,
);
}
void _toggle() {
final bool isOpen = _controller.status == AnimationStatus.completed;
_controller.fling(velocity: isOpen ? -2 : 2);
}
void _handleDragUpdate(DragUpdateDetails details) {
_controller.value -= details.primaryDelta! / maxHeight;
}
void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating ||
_controller.status == AnimationStatus.completed) return;
final double flingVelocity =
details.velocity.pixelsPerSecond.dy / maxHeight;
if (flingVelocity < 0.0) {
_controller.fling(velocity: math.max(2.0, -flingVelocity));
} else if (flingVelocity > 0.0) {
_controller.fling(velocity: math.min(-2.0, -flingVelocity));
} else {
_controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
}
}
}
class ExpandedEventItem extends StatelessWidget {
final double? topMargin;
final double? leftMargin;
final double? height;
final bool isVisible;
final double? borderRadius;
final String title;
final String date;
const ExpandedEventItem(
{Key? key,
required this.topMargin,
required this.height,
required this.isVisible,
required this.borderRadius,
required this.title,
required this.date,
required this.leftMargin})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: topMargin,
left: leftMargin,
right: 0,
height: height,
child: AnimatedOpacity(
opacity: isVisible ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius!),
color: Colors.white,
),
padding: EdgeInsets.only(left: height!).add(const EdgeInsets.all(8)),
child: _buildContent(),
),
),
);
}
Widget _buildContent() {
return Column(
children: <Widget>[
Text(title, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Row(
children: <Widget>[
Text(
'1 ticket',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(width: 8),
Text(
date,
style: const TextStyle(
fontWeight: FontWeight.w300,
fontSize: 12,
color: Colors.grey,
),
),
],
),
const Spacer(),
Row(
children: <Widget>[
Icon(Icons.place, color: Colors.grey.shade400, size: 16),
Text(
'Science Park 10 25A',
style: TextStyle(color: Colors.grey.shade400, fontSize: 13),
)
],
)
],
);
}
}
final List<Event> events = [
Event('steve-johnson.jpeg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('efe-kurnaz.jpg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('rodion-kutsaev.jpeg', 'Dawan District Guangdong Hong Kong', '4.28-31'),
];
class Event {
final String assetName;
final String title;
final String date;
Event(this.assetName, this.title, this.date);
}
class SheetHeader extends StatelessWidget {
final double? fontSize;
final double? topMargin;
const SheetHeader({Key? key, required this.fontSize, required this.topMargin})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: topMargin,
child: Text(
'最近连接节点',
style: TextStyle(
color: Colors.grey[100],
fontSize: fontSize,
fontWeight: FontWeight.w500,
),
),
);
}
}
class MenuButton extends StatelessWidget {
const MenuButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Positioned(
right: 0,
bottom: 24,
child: Icon(
Icons.menu,
color: Colors.white,
size: 28,
),
);
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/models/app_model.dart';
class SailAppBar extends AppBar {
SailAppBar({Key? key, required this.appTitle}) : super(key: key);
final String appTitle;
@override
SailAppBarState createState() => SailAppBarState();
}
class SailAppBarState extends State<SailAppBar> {
late AppModel _appModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
}
@override
Widget build(BuildContext context) {
return AppBar(
title: Text(
widget.appTitle,
style: TextStyle(
color: _appModel.isOn ? Colors.black : Colors.white,
fontWeight: FontWeight.w900),
),
elevation: 0,
backgroundColor:
_appModel.isOn ? AppColors.yellowColor : AppColors.grayColor);
}
}
+86
View File
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class SelectLocation extends StatefulWidget {
const SelectLocation({
Key? key,
}) : super(key: key);
@override
SelectLocationState createState() => SelectLocationState();
}
class SelectLocationState extends State<SelectLocation> {
late ServerModel _serverModel;
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
_serverModel = Provider.of<ServerModel>(context);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Padding(
padding: EdgeInsets.symmetric(
vertical: 20, horizontal: ScreenUtil().setWidth(75)),
child: Material(
borderRadius: BorderRadius.circular(20),
color: Colors.green[600],
child: InkWell(
onTap: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
splashColor: Colors.grey,
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: AppColors.yellowColor.withAlpha(200),
blurRadius: 20,
spreadRadius: -6,
offset: const Offset(
0.0,
3.0,
),
)
]),
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 25),
child: Row(
children: [
Icon(
Icons.sailing,
color: isDarkTheme ? Colors.white : Colors.black,
),
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(10))),
Text(
_serverModel.selectServerEntity?.name ??
context.l10n.xuanzeliahjiedian,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black),
),
Expanded(child: Container()),
Icon(
Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black,
)
],
),
),
),
),
);
}
}
+306
View File
@@ -0,0 +1,306 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/service/plan_service.dart';
import 'package:uuvpn/service/user_service.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class SlidingCardsView extends StatefulWidget {
const SlidingCardsView({Key? key}) : super(key: key);
@override
SlidingCardsViewState createState() => SlidingCardsViewState();
}
class SlidingCardsViewState extends State<SlidingCardsView> {
late PageController pageController;
double pageOffset = 0;
List<PlanEntity> _planEntityList = [];
@override
void initState() {
super.initState();
pageController = PageController(viewportFraction: 0.8);
pageController.addListener(() {
setState(() => pageOffset = pageController.page!);
});
PlanService().plan()?.then((planEntityList) {
if (this.mounted) {
setState(() {
_planEntityList = planEntityList;
});
}
});
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.55,
child: PageView(
controller: pageController,
children: List.from(_planEntityList.map((e) => SlidingCard(
id: e.id,
name: e.name,
content: e.content ?? "",
date: e.createdAt?.toIso8601String(),
onetimePrice: (e.onetimePrice ?? 0.0) / 100,
monthPrice: (e.monthPrice ?? 0.0) / 100,
quarterPrice: (e.quarterPrice ?? 0.0) / 100,
halfYearPrice: (e.halfYearPrice ?? 0.0) / 100,
yearPrice: (e.yearPrice ?? 0.0) / 100,
twoYearPrice: (e.twoYearPrice ?? 0.0) / 100,
threeYearPrice: (e.threeYearPrice ?? 0.0) / 100,
assetName: 'steve-johnson.jpeg',
offset: pageOffset)))),
);
}
}
class SlidingCard extends StatelessWidget {
final int id;
final String name;
final String content;
final String? date;
final String assetName;
final double offset;
final double onetimePrice;
final double monthPrice;
final double quarterPrice;
final double halfYearPrice;
final double yearPrice;
final double twoYearPrice;
final double threeYearPrice;
const SlidingCard({
Key? key,
required this.id,
required this.name,
required this.content,
required this.date,
required this.assetName,
required this.offset,
required this.onetimePrice,
required this.monthPrice,
required this.quarterPrice,
required this.halfYearPrice,
required this.yearPrice,
required this.twoYearPrice,
required this.threeYearPrice,
}) : super(key: key);
double lowestPrice() {
List<double> list = [
onetimePrice,
monthPrice,
quarterPrice,
halfYearPrice,
yearPrice,
twoYearPrice,
threeYearPrice,
];
double min = double.maxFinite;
for (int i = 0; i < list.length; i++) {
if ((list[i] < min) && list[i] > 0) {
min = list[i];
}
}
return min;
}
@override
Widget build(BuildContext context) {
UserModel _userModel = Provider.of<UserModel>(context);
double gauss = math.exp(-(math.pow((offset.abs() - 0.5), 2) / 0.08));
return Transform.translate(
offset: Offset(-32 * gauss * offset.sign, 0),
child: Card(
margin: const EdgeInsets.only(left: 8, right: 8, bottom: 24),
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(32)),
child: Column(
children: <Widget>[
ClipRRect(
borderRadius:
const BorderRadius.vertical(top: Radius.circular(32)),
child: Container(
height: MediaQuery.of(context).size.height * 0.1,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.red, Colors.orange, Colors.indigo]),
),
child: Center(
child: Text('${name}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 25,
))),
),
),
// const SizedBox(height: 8),
Expanded(
child: CardContent(
id: id,
name: name,
desc: content,
date: date,
offset: gauss,
lowestPrice: lowestPrice(),
),
),
GestureDetector(
onTap: () {
_userModel.checkHasLogin(
context,
() => UserService().getQuickLoginUrl(
{'redirect': "/plan/${id}"})?.then((value) {
NavigatorUtil.goWebView(
context, "购买${name}订阅", value);
}));
},
child: ClipRRect(
borderRadius:
const BorderRadius.vertical(bottom: Radius.circular(32)),
child: Container(
height: MediaQuery.of(context).size.height * 0.1,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.orange, Colors.orange, Colors.orange]),
),
child: Center(
child: Text('点击购买 ¥${lowestPrice()}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20,
))),
),
),
),
],
),
),
);
}
}
class CardContent extends StatefulWidget {
final int id;
final String name;
final String desc;
final String? date;
final double offset;
final double lowestPrice;
const CardContent(
{Key? key,
required this.id,
required this.name,
required this.desc,
required this.date,
required this.offset,
required this.lowestPrice})
: super(key: key);
@override
CardContentState createState() => CardContentState();
}
class CardContentState extends State<CardContent> {
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Transform.translate(
offset: Offset(8 * widget.offset, 0),
child: Text(widget.desc,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
),
const SizedBox(height: 8),
// Transform.translate(
// offset: Offset(32 * widget.offset, 0),
// child: Text(
// widget.date!,
// style: const TextStyle(
// color: Colors.grey, fontWeight: FontWeight.bold),
// ),
// ),
// const Spacer(),
// Row(
// children: <Widget>[
// Transform.translate(
// offset: Offset(48 * widget.offset, 0),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// foregroundColor: Colors.white,
// backgroundColor: Colors.yellow,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(32),
// ),
// ),
// onPressed: () => _userModel.checkHasLogin(
// context,
// () => UserService().getQuickLoginUrl({
// 'redirect': "/plan/${widget.id}"
// })?.then((value) {
// NavigatorUtil.goWebView(context, "配置订阅", value);
// })),
// child: Transform.translate(
// offset: Offset(24 * widget.offset, 0),
// child: Text('购买',
// style: TextStyle(
// color: Colors.black87,
// fontSize: ScreenUtil().setSp(36))),
// ),
// ),
// ),
// const Spacer(),
// Transform.translate(
// offset: Offset(32 * widget.offset, 0),
// child: Text(
// '¥ ${widget.lowestPrice} 起',
// style: const TextStyle(
// fontWeight: FontWeight.bold,
// fontSize: 20,
// ),
// ),
// ),
// const SizedBox(width: 16),
// ],
// )
],
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More