Commit code. Update time: 2023-06-25

This commit is contained in:
TK
2023-06-25 11:50:55 +08:00
parent 2d74227878
commit 39a5f2fa92
244 changed files with 26147 additions and 0 deletions
+26
View File
@@ -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),
),
));
}
}
+155
View File
@@ -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),
)
])),
],
),
);
}
}
+291
View File
@@ -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 Youre 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(),*/
],
),
);
}
}
+33
View File
@@ -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,
));
}
}
+412
View File
@@ -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),
)
],
),
))));
}
}*/
+160
View File
@@ -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,
),
),
)
]),
),
),
);
})
]);
}
}
+125
View File
@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package: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,
);
}
}
+289
View File
@@ -0,0 +1,289 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package: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),
],
),
),
)
],
),
);
}
}
+17
View File
@@ -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(),
],
);
}
}
+227
View File
@@ -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),
)
])),
],
),
);
}
}
+307
View File
@@ -0,0 +1,307 @@
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:provider/provider.dart';
import 'package: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),
);
}
}
+50
View File
@@ -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);
},
),
);
}
}