Commit code. Update time: 2023-06-25
This commit is contained in:
@@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user