Commit code. Update time: 2023-06-25
This commit is contained in:
@@ -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 = "[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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:sail/channels/Platform.dart';
|
||||
import 'package:sail/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.sail_tunnel.sail/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.sail_tunnel.sail/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.sail_tunnel.sail/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.sail_tunnel.sail/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.sail_tunnel.sail/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.sail_tunnel.sail/vpn_manager");
|
||||
String result;
|
||||
try {
|
||||
result = await platform.invokeMethod("setTunnelConfiguration", conf);
|
||||
} on PlatformException catch (e) {
|
||||
print(e.toString());
|
||||
|
||||
rethrow;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class StringUtils {
|
||||
static bool isNullOrEmpty(String? str) {
|
||||
return str == null || str.isEmpty;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class AppDimens {
|
||||
static const double maxWidth = 1080.0;
|
||||
static const double maxHeight = 1920.0;
|
||||
static const double bigTextSize = 13.0;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
class AppUrls {
|
||||
// static const String baseUrl = 'https://user.51mdss.com'; // 基础接口地址
|
||||
// static const String baseUrl = "https://www.58mdss.com";
|
||||
static const String baseUrl = "https://gohash123.com";
|
||||
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 login = 'https://www.heyuegendan.com/vpn/login.php';
|
||||
// static const String register = 'https://www.heyuegendan.com/vpn/register.php';
|
||||
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';
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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: json["port"],
|
||||
serverPort: 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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*后台接口api*/
|
||||
import 'package:sail/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";
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -0,0 +1,19 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:sail/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: CONNECT_TIMEOUT,
|
||||
// 响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||
receiveTimeout: 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: connectTimeout,
|
||||
receiveTimeout: receiveTimeout,
|
||||
);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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(DioError error) {
|
||||
switch (error.type) {
|
||||
case DioErrorType.other:
|
||||
{
|
||||
// 网络异常
|
||||
return BadRequestException(-2, "无网络");
|
||||
}
|
||||
|
||||
case DioErrorType.cancel:
|
||||
{
|
||||
return BadRequestException(-1, "请求取消");
|
||||
}
|
||||
|
||||
case DioErrorType.connectTimeout:
|
||||
{
|
||||
return BadRequestException(-1, "连接超时");
|
||||
}
|
||||
|
||||
case DioErrorType.sendTimeout:
|
||||
{
|
||||
return BadRequestException(-1, "请求超时");
|
||||
}
|
||||
|
||||
case DioErrorType.receiveTimeout:
|
||||
{
|
||||
return BadRequestException(-1, "响应超时");
|
||||
}
|
||||
|
||||
case DioErrorType.response:
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/plan_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_subscribe_model.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/router/routers.dart';
|
||||
import 'package:sail/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
|
||||
]);
|
||||
// final size = MediaQuery.of(context).size;
|
||||
// final width = size.width;
|
||||
// final height = size.height;
|
||||
// print('width is $width; height is $height');
|
||||
// ScreenUtil.init(context);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// ignore_for_file: file_names
|
||||
import 'package:flutter/material.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 themeData = ThemeData(
|
||||
// primarySwatch: AppColors.themeColor,
|
||||
// visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
// );
|
||||
|
||||
ThemeData get getActiveTheme => isDarkActive ? _darkTheme : lightTheme;
|
||||
|
||||
// Let's define a light theme for our Application
|
||||
ThemeData get lightTheme => ThemeData(
|
||||
primarySwatch: MaterialColor(0xffFFFFFF, _colorSwatch(1, 117, 194)),
|
||||
primaryColor: Colors.green,
|
||||
// accentColor: const Color(0xffAE77FF),
|
||||
canvasColor: const Color(0xffFFFFFF),
|
||||
// backgroundColor: const Color(0xffFFFFFF),
|
||||
iconTheme: const IconThemeData(color: Colors.green),
|
||||
primaryTextTheme: TextTheme(
|
||||
bodyText1: const TextStyle(color: Colors.black, fontSize: 15),
|
||||
bodyText2: const TextStyle(color: Colors.black54, fontSize: 15),
|
||||
subtitle1: const TextStyle(color: Colors.black),
|
||||
headline3: const TextStyle(
|
||||
color: Colors.black, fontSize: 27, fontWeight: FontWeight.bold),
|
||||
headline6: const TextStyle(color: Colors.black),
|
||||
caption: TextStyle(
|
||||
color: Colors.grey.shade700, wordSpacing: -1, fontSize: 12)));
|
||||
|
||||
// Now define a dark theme for our Application
|
||||
ThemeData get _darkTheme => ThemeData(
|
||||
primarySwatch: MaterialColor(0xff0B0415, _colorSwatch(2, 86, 155)),
|
||||
primaryColor: Colors.green,
|
||||
// accentColor: const Color(0xffAE77FF),
|
||||
canvasColor: const Color(0xff0B0415),
|
||||
// backgroundColor: const Color(0xff0B0415),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
primaryTextTheme: const TextTheme(
|
||||
bodyText1: TextStyle(color: Colors.white, fontSize: 15),
|
||||
bodyText2: TextStyle(color: Colors.white70, fontSize: 15),
|
||||
subtitle1: TextStyle(color: Colors.white),
|
||||
headline3: TextStyle(
|
||||
color: Colors.white, fontSize: 27, fontWeight: FontWeight.bold),
|
||||
headline6: const TextStyle(color: Colors.white),
|
||||
caption:
|
||||
TextStyle(color: Colors.white54, wordSpacing: -1, fontSize: 12)));
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:sail/adapters/leaf_ffi/config.dart';
|
||||
import 'package:sail/channels/Platform.dart';
|
||||
import 'package:sail/channels/vpn_manager.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/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}}',
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/models/page_state.dart';
|
||||
import 'package:sail/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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
enum PageState {
|
||||
loading, //加载中
|
||||
hasData, //有数据
|
||||
empty, //无数据
|
||||
error, //加载失败
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:sail/entity/plan_entity.dart';
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_icmp_ping/flutter_icmp_ping.dart';
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:sail/entity/server_entity.dart';
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/service/server_service.dart';
|
||||
import 'package:sail/utils/shared_preferences_util.dart';
|
||||
import 'package:sail/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))));
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:sail/entity/login_entity.dart';
|
||||
import 'package:sail/entity/user_entity.dart';
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/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:sail/constant/app_strings.dart';
|
||||
import 'package:sail/entity/user_subscribe_entity.dart';
|
||||
import 'package:sail/models/base_model.dart';
|
||||
import 'package:sail/service/user_service.dart';
|
||||
import 'package:sail/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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/constant/app_images.dart';
|
||||
import 'package:sail/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),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//ProOnecePage.dart
|
||||
//ignore_for_file: file_names
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/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.headline6!.copyWith(
|
||||
color: AppColors.whiteColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
' ',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).primaryTextTheme.caption,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
.headline6!
|
||||
.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
|
||||
.headline6!
|
||||
.copyWith(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: ' $rt_2',
|
||||
style: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.bodyText1!
|
||||
.copyWith(color: Colors.white),
|
||||
)
|
||||
])),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
//ignore_for_file: file_names
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/plan_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/models/user_subscribe_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/widgets/logo_bar.dart';
|
||||
import 'package:sail/widgets/my_subscribe.dart';
|
||||
import 'package:sail/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.subtitle1),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).primaryTextTheme.caption,
|
||||
)
|
||||
: 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
|
||||
.bodyText1!
|
||||
.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(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.transparent,
|
||||
title: const 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 You’re not signed in yet.',
|
||||
style: Theme.of(context).primaryTextTheme.bodyText1,
|
||||
),
|
||||
),
|
||||
|
||||
// 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.subtitle1),
|
||||
/*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(),*/
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:crisp/crisp.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:sail/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,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/constant/app_dimens.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/plan_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/models/user_subscribe_model.dart';
|
||||
import 'package:sail/pages/accountPage.dart';
|
||||
import 'package:sail/pages/homePage.dart';
|
||||
import 'package:sail/pages/my_profile.dart';
|
||||
import 'package:sail/pages/plan/plan_page.dart';
|
||||
import 'package:sail/pages/proPage.dart';
|
||||
import 'package:sail/pages/server_list.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/routes/OnceNotice.dart';
|
||||
import 'package:sail/routes/proRoute.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/message_util.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/widgets/ProgressView.dart';
|
||||
import 'package:sail/widgets/home_widget.dart';
|
||||
import 'package:sail/widgets/power_btn.dart';
|
||||
import 'package:sail/widgets/sail_app_bar.dart';
|
||||
import 'package:sail/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) {
|
||||
print('state = $state');
|
||||
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_planModel.fetchPlanList();
|
||||
// _appModel.getStatus();
|
||||
// print("_appModel.isOn: ${_appModel.isOn}");
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
//激活状态
|
||||
}
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
//是否第一次打开app
|
||||
// Future.delayed(Duration(seconds: 3), () {
|
||||
|
||||
// _showCupertinoAlertDialog(
|
||||
// context: context,
|
||||
// title: "提示",
|
||||
// content: "您没有提交的权限,\n当前仅供查阅",
|
||||
// sureText: "确定"
|
||||
// );
|
||||
// });
|
||||
|
||||
// Provider.of<ThemeCollection>(context)
|
||||
// .getActiveTheme
|
||||
// .primaryTextTheme
|
||||
// .bodyLarge
|
||||
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
|
||||
|
||||
final ButtonStyle style = TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.transparent,
|
||||
title: SvgPicture.asset(
|
||||
'assets/text2.svg',
|
||||
height: 28,
|
||||
color: isDarkTheme ? Colors.white : Colors.black,
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.support_agent),
|
||||
tooltip: 'support_agent',
|
||||
onPressed: () {
|
||||
sendEmail("admin@uuvpn.co", "UUVPN Question Help", "");
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.public),
|
||||
tooltip: 'public',
|
||||
onPressed: () {
|
||||
if (_serverModel.serverEntityList.isEmpty) {
|
||||
MessageUtil.toast(context.l10n.nodefornullcheckissubscripts);
|
||||
} else {
|
||||
NavigatorUtil.goServerList(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
tooltip: 'menu',
|
||||
onPressed: () {
|
||||
NavigatorUtil.goSettings(context);
|
||||
// setState(() {
|
||||
|
||||
// });
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: HomeWidget());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.transparent,
|
||||
title: currentPage == 2
|
||||
? const Text('My Account')
|
||||
: SvgPicture.asset(
|
||||
'assets/text2.svg',
|
||||
height: 28,
|
||||
color: isDarkTheme ? Colors.white : Colors.black,
|
||||
),
|
||||
actions: currentPage == 0 ? null : null),
|
||||
/*[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => const ProRoute())),
|
||||
child: SvgPicture.asset(
|
||||
'assets/Features.svg',
|
||||
height: 20,
|
||||
color: AppColors.greenColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
Here Bottom Navigation Bar with some padding, margin,
|
||||
little bit color & border decoration*/
|
||||
bottomNavigationBar: Container(
|
||||
// margin: const EdgeInsets.only(left: 32, right: 32, bottom: 0),
|
||||
padding: const EdgeInsets.only(top: 0.0),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
const Color(0xff353351).withOpacity(isDarkTheme ? 0.3 : 0.05),
|
||||
borderRadius: BorderRadius.circular(0)),
|
||||
child: BottomNavigationBar(
|
||||
enableFeedback: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true,
|
||||
selectedLabelStyle:
|
||||
TextStyle(color: isDarkTheme ? Colors.white : Colors.black),
|
||||
selectedItemColor: Theme.of(context).primaryColor,
|
||||
unselectedItemColor: isDarkTheme ? Colors.white : Colors.black,
|
||||
currentIndex: currentPage,
|
||||
onTap: (value) => setState(() {
|
||||
currentPage = value;
|
||||
}),
|
||||
items: List.generate(
|
||||
_itemsList.length,
|
||||
(index) => BottomNavigationBarItem(
|
||||
icon: Icon(_itemsList[index]['icon'] as IconData,
|
||||
size: 30,
|
||||
color: index != currentPage
|
||||
? const Color(0xffB5AEBE)
|
||||
: Theme.of(context).primaryColor),
|
||||
/*SvgPicture.asset(
|
||||
_itemsList[index]['iconPath'] as String,
|
||||
height: index != currentPage ? 20 : 24,
|
||||
color: index != currentPage
|
||||
? const Color(0xffB5AEBE)
|
||||
: Theme.of(context).primaryColor),
|
||||
*/
|
||||
label: _itemsList[index]['name'] as String,
|
||||
))),
|
||||
),
|
||||
body: _itemsList[currentPage]['route'] as Widget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Widget buildold(BuildContext context) {
|
||||
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
|
||||
|
||||
ScreenUtil.init(context,
|
||||
designSize: const Size(AppDimens.maxWidth, AppDimens.maxHeight));
|
||||
|
||||
// if (!_isfinishedLoad) {
|
||||
// return const ProgressView();
|
||||
// }
|
||||
return Scaffold(
|
||||
// appBar: SailAppBar(
|
||||
// appTitle: _appModel.appTitle,
|
||||
// ),
|
||||
extendBody: true,
|
||||
backgroundColor:
|
||||
_appModel.isOn ? AppColors.greenColor : AppColors.grayColor,
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: HomeWidget(),
|
||||
),
|
||||
);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: _appModel.isOn
|
||||
? SystemUiOverlayStyle.dark
|
||||
: SystemUiOverlayStyle.light,
|
||||
child: Scaffold(
|
||||
appBar: SailAppBar(
|
||||
appTitle: _appModel.appTitle,
|
||||
),
|
||||
extendBody: true,
|
||||
backgroundColor:
|
||||
_appModel.isOn ? AppColors.greenColor : AppColors.grayColor,
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _appModel.pageController,
|
||||
children: const [
|
||||
HomeWidget(),
|
||||
PlanPage(),
|
||||
ServerListPage(),
|
||||
MyProfile()
|
||||
],
|
||||
)),
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.centerDocked,
|
||||
// floatingActionButton: const PowerButton(),
|
||||
bottomNavigationBar: ClipRRect(
|
||||
// borderRadius: BorderRadius.only(
|
||||
// topLeft: Radius.circular(ScreenUtil().setWidth(50)),
|
||||
// topRight: Radius.circular(ScreenUtil().setWidth(50))),
|
||||
child: BottomAppBar(
|
||||
// notchMargin: 8,
|
||||
// shape: const CircularNotchedRectangle(),
|
||||
color:
|
||||
_appModel.isOn ? AppColors.grayColor : AppColors.themeColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.home_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => _appModel.jumpToPage(0),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.wallet,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => _appModel.jumpToPage(1),
|
||||
),
|
||||
// SizedBox(
|
||||
// width: ScreenUtil().setWidth(50),
|
||||
// ),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.cloud_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => _appModel.jumpToPage(2),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => _appModel.jumpToPage(3),
|
||||
)
|
||||
],
|
||||
),
|
||||
))));
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,160 @@
|
||||
//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: TextStyle(
|
||||
fontSize: 16,
|
||||
color: isDarkTheme ? Colors.white : Colors.black),
|
||||
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.headline6,
|
||||
),
|
||||
subtitle: Text('IP: 79.110.53.95',
|
||||
style: Theme.of(context).primaryTextTheme.caption),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/login_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/service/user_service.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:sail/models/user_model.dart';
|
||||
import 'package:sail/service/user_service.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/widgets/bottom_block.dart';
|
||||
import 'package:sail/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),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:sail/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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
//ignore_for_file: file_names
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/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.caption,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
.headline6!
|
||||
.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
|
||||
.headline6!
|
||||
.copyWith(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: ' $rt_2',
|
||||
style: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.bodyText1!
|
||||
.copyWith(color: Colors.white),
|
||||
)
|
||||
])),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/utils/common_util.dart';
|
||||
import 'package:sail/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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,3 @@
|
||||
class AppConstants {
|
||||
static const int carouselSliderItemsCount = 4;
|
||||
}
|
||||
@@ -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(),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
@@ -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';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class AppShape {
|
||||
static const largeShape = 25.0;
|
||||
static const normalShaper = 20.0;
|
||||
static const smallShape = 10.0;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:sail/resources/app_colors.dart';
|
||||
import 'package:sail/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)),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/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);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/pages/accountPage.dart';
|
||||
import 'package:sail/pages/home/home_page.dart';
|
||||
import 'package:sail/pages/404/not_find_page.dart';
|
||||
import 'package:sail/pages/login/login_page.dart';
|
||||
import 'package:sail/pages/plan/plan_page.dart';
|
||||
import 'package:sail/pages/server_list.dart';
|
||||
import 'package:sail/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();
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:fluro/fluro.dart';
|
||||
import 'package:sail/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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// OnceNotice.dart
|
||||
//ignore_for_file: file_names
|
||||
import 'package:sail/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.subtitle1,
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.navigate_next_rounded,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
//ignore_for_file: file_names
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/routes/proRoute.dart';
|
||||
import 'package:sail/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.subtitle1),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).primaryTextTheme.caption,
|
||||
)
|
||||
: 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
|
||||
.bodyText1!
|
||||
.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
|
||||
.subtitle1!
|
||||
.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.subtitle1,
|
||||
),
|
||||
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(
|
||||
appBar: AppBar(
|
||||
shadowColor: Colors.transparent,
|
||||
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,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:sail/constant/app_urls.dart';
|
||||
import 'package:sail/entity/plan_entity.dart';
|
||||
import 'package:sail/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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:sail/constant/app_urls.dart';
|
||||
import 'package:sail/entity/server_entity.dart';
|
||||
import 'package:sail/utils/http_util.dart';
|
||||
|
||||
class ServerService {
|
||||
Future<List<ServerEntity>> server() {
|
||||
return HttpUtil.instance.get(AppUrls.server).then((result) {
|
||||
return serverEntityFromList(result['data']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:sail/constant/app_urls.dart';
|
||||
import 'package:sail/entity/login_entity.dart';
|
||||
import 'package:sail/entity/user_entity.dart';
|
||||
import 'package:sail/entity/user_subscribe_entity.dart';
|
||||
import 'package:sail/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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'dart:core' as core;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
void print (core.Object object) {
|
||||
if (kDebugMode) {
|
||||
core.print(object);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/router/routers.dart';
|
||||
import 'package:sail/utils/common_util.dart';
|
||||
import 'package:sail/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: 10000,
|
||||
receiveTimeout: 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(DioError(
|
||||
requestOptions: response.requestOptions,
|
||||
response: response,
|
||||
type: DioErrorType.response));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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)!;
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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:sail/pages/accountPage.dart';
|
||||
import 'package:sail/pages/crisp_page.dart';
|
||||
import 'package:sail/pages/plan/plan_page.dart';
|
||||
import 'package:sail/pages/server_list.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/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,
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
enum Status { success, pending, empty, error, noConnection }
|
||||
@@ -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: ';
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
enum RequestStatus { loading, loaded, error }
|
||||
|
||||
enum GetAllRequestStatus { loading, loaded, error, fetchMoreError }
|
||||
@@ -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);
|
||||
@@ -0,0 +1,248 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// import 'package:sail/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sail/resources/app_colors.dart';
|
||||
import 'package:sail/resources/app_routes.dart';
|
||||
import 'package:sail/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);
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sail/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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sail/channels/Platform.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/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)),
|
||||
]),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]),
|
||||
],
|
||||
),
|
||||
)*/
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/plan_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/models/user_subscribe_model.dart';
|
||||
import 'package:sail/resources/app_colors.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/widgets/bottom_block.dart';
|
||||
import 'package:sail/widgets/connection_stats.dart';
|
||||
import 'package:sail/widgets/logo_bar.dart';
|
||||
import 'package:sail/widgets/my_subscribe.dart';
|
||||
import 'package:sail/widgets/plan_list.dart';
|
||||
import 'package:sail/widgets/power_btn.dart';
|
||||
import 'package:sail/widgets/select_location.dart';
|
||||
import 'package:sail/utils/common_util.dart';
|
||||
import 'package:sail/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.subtitle1),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).primaryTextTheme.caption,
|
||||
)
|
||||
: 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;
|
||||
|
||||
final birthday = DateTime(2023, 06, 12);
|
||||
//当前日期
|
||||
final date2 = DateTime.now();
|
||||
//比较相差的天数
|
||||
final difference = date2.difference(birthday).inDays;
|
||||
print("比较相差的天数DateTime(2023, 06, 12):${difference}");
|
||||
return SingleChildScrollView(
|
||||
controller: _controller,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
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,
|
||||
),
|
||||
// Padding(
|
||||
// padding: EdgeInsets.symmetric(horizontal: 75, vertical: 30),
|
||||
// child: Stack(alignment: Alignment.center, children: [
|
||||
// // Image.asset(
|
||||
// // "assets/map.png",
|
||||
// // scale: 4,
|
||||
// // color: _appModel.isOn
|
||||
// // ? const Color(0x15000000)
|
||||
// // : AppColors.darkSurfaceColor,
|
||||
// // ),
|
||||
// const PowerButton(),
|
||||
// ])),
|
||||
|
||||
_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 SelectLocation(),
|
||||
|
||||
const BottomBlock(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/constant/app_strings.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/models/user_subscribe_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/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(),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/entity/user_subscribe_entity.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/router/application.dart';
|
||||
import 'package:sail/routes/OnceNotice.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/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)),
|
||||
),
|
||||
),
|
||||
)*/
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/entity/plan_entity.dart';
|
||||
import 'package:sail/entity/user_subscribe_entity.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/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;
|
||||
}
|
||||
}
|
||||
@@ -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:sail/constant/app_colors.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/app_model.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/utils/message_util.dart';
|
||||
import 'package:sail/utils/navigator_util.dart';
|
||||
import 'package:sail/widgets/watermuticicel.dart';
|
||||
import 'package:sail/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,
|
||||
)),
|
||||
),
|
||||
),
|
||||
);*/
|
||||
}
|
||||
}
|
||||
@@ -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,292 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:sail/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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sail/constant/app_colors.dart';
|
||||
import 'package:sail/model/themeCollection.dart';
|
||||
import 'package:sail/models/server_model.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/utils/l10n.dart';
|
||||
import 'package:sail/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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:sail/entity/plan_entity.dart';
|
||||
import 'package:sail/models/user_model.dart';
|
||||
import 'package:sail/service/plan_service.dart';
|
||||
import 'package:sail/service/user_service.dart';
|
||||
import 'package:sail/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
Reference in New Issue
Block a user