update ios project

update ios project
This commit is contained in:
Zeus
2024-07-24 11:51:39 +08:00
parent 876d355b18
commit 1bf69048f3
275 changed files with 3372 additions and 1015 deletions
+18
View File
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:uuvpn/constant/app_colors.dart';
class ProgressView extends StatelessWidget {
const ProgressView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(
color: AppColors.yellowColor,
),
),
);
}
}
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class BottomBlock extends StatelessWidget {
const BottomBlock({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100,
color: Colors.transparent,
);
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'dart:math';
import 'package:flutter/material.dart';
///
/// desc:
///
class CirclePainter extends CustomPainter {
final double progress;
final double strokeWidth;
final Color color;
Paint _paint = Paint();
CirclePainter(
{this.progress = 0.0, this.strokeWidth = 1.0, this.color = Colors.white});
@override
void paint(Canvas canvas, Size size) {
_paint
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = color;
double radius = min(size.width, size.height) / 2;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2), radius * progress, _paint);
}
@override
bool shouldRepaint(covariant CirclePainter old) {
return progress != old.progress ||
strokeWidth != old.strokeWidth ||
color != old.color;
}
}
+232
View File
@@ -0,0 +1,232 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/channels/Platform.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class ConnectionStats extends StatefulWidget {
const ConnectionStats({Key? key}) : super(key: key);
@override
ConnectionStatsState createState() => ConnectionStatsState();
}
class ConnectionStatsState extends State<ConnectionStats> {
late UserModel _userModel;
late AppModel _appModel;
late ServerModel _serverModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
_appModel = Provider.of<AppModel>(context);
_serverModel = Provider.of<ServerModel>(context);
}
String toDateString(DateTime date) {
var duration = DateTime.now().difference(date);
var microseconds = duration.inMicroseconds;
var sign = (microseconds < 0) ? "-" : "";
var hours = microseconds ~/ Duration.microsecondsPerHour;
microseconds = microseconds.remainder(Duration.microsecondsPerHour);
var hoursPadding = hours.abs() < 10 ? "0" : "";
if (microseconds < 0) microseconds = -microseconds;
var minutes = microseconds ~/ Duration.microsecondsPerMinute;
microseconds = microseconds.remainder(Duration.microsecondsPerMinute);
var minutesPadding = minutes < 10 ? "0" : "";
var seconds = microseconds ~/ Duration.microsecondsPerSecond;
microseconds = microseconds.remainder(Duration.microsecondsPerSecond);
var secondsPadding = seconds < 10 ? "0" : "";
return "$sign$hoursPadding${hours.abs()}:"
"$minutesPadding$minutes:"
"$secondsPadding$seconds";
}
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
Color colortheme = isDarkTheme ? Colors.white : Colors.black;
//isDarkTheme ? Colors.white : Colors.black
if (Platform.isAndroid || Platform.isMacOS) {
return Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(75)),
child: TextButton(
onPressed: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.map,
color: isDarkTheme ? Colors.white : Colors.black,
size: 20),
Text(context.l10n.clicktoselectanothernode,
style: TextStyle(
fontSize: 18,
color: isDarkTheme ? Colors.white : Colors.black)),
Icon(Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black,
size: 20)
])),
),
],
);
}
return Column(
children: [
Text(toDateString(_appModel.connectedDate!),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 40,
color: colortheme,
)),
Padding(
padding: EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(75)),
child: TextButton(
onPressed: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.map,
color: isDarkTheme ? Colors.white : Colors.black, size: 20),
// Text(context.l10n.clicktoselectanothernode,
Text(" ${_serverModel.selectServerEntity?.name}",
style: TextStyle(
fontSize: 18,
color: isDarkTheme ? Colors.white : Colors.black)),
Icon(Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black, size: 20)
])),
),
/* Padding(
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75), vertical: 10),
child: Row(
children: [
// Download Stats
Row(children: [
// Download Icon
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Color(0xffff0000),
blurRadius: 13,
spreadRadius: -2)
],
color: const Color(0xffff0000),
borderRadius: BorderRadius.circular(13)),
padding: const EdgeInsets.all(5),
child: const Icon(
Icons.arrow_downward,
color: Colors.white,
),
),
// Labels
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"下行速度",
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w500),
),
RichText(
text: const TextSpan(
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w900),
children: [
TextSpan(text: "75.9"),
TextSpan(
text: " KB/s",
style:
TextStyle(fontWeight: FontWeight.normal)),
]),
)
],
),
)
]),
Expanded(child: Container()),
// Upload Stats
Row(children: [
// Upload Icon
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Color(0xff03a305),
blurRadius: 13,
spreadRadius: -2)
],
color: const Color(0xff03a305),
borderRadius: BorderRadius.circular(13)),
padding: const EdgeInsets.all(5),
child: const Icon(
Icons.arrow_upward,
color: Colors.white,
),
),
// Labels
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"上行速度",
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w500),
),
RichText(
text: const TextSpan(
style: TextStyle(
color: AppColors.grayColor,
fontWeight: FontWeight.w900),
children: [
TextSpan(text: "29.6"),
TextSpan(
text: " KB/s",
style:
TextStyle(fontWeight: FontWeight.normal)),
]),
)
],
),
)
]),
],
),
)*/
],
);
}
}
+167
View File
@@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/plan_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/resources/app_colors.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/widgets/bottom_block.dart';
import 'package:uuvpn/widgets/connection_stats.dart';
import 'package:uuvpn/widgets/logo_bar.dart';
import 'package:uuvpn/widgets/my_subscribe.dart';
import 'package:uuvpn/widgets/plan_list.dart';
import 'package:uuvpn/widgets/power_btn.dart';
import 'package:uuvpn/widgets/select_location.dart';
import 'package:uuvpn/utils/common_util.dart';
import 'package:uuvpn/widgets/watermuticicel.dart';
class HomeWidget extends StatefulWidget {
const HomeWidget({Key? key}) : super(key: key);
@override
HomeWidgetState createState() => HomeWidgetState();
}
class HomeWidgetState extends State<HomeWidget>
with AutomaticKeepAliveClientMixin {
late AppModel _appModel;
late UserModel _userModel;
late UserSubscribeModel _userSubscribeModel;
late PlanModel _planModel;
late ServerModel _serverModel;
customListTile(BuildContext context, String title, String icon,
{Widget? trailing, String? subtitle, VoidCallback? onTap}) =>
ListTile(
onTap: onTap ?? null,
minLeadingWidth: 35,
dense: true,
title: Text(title,
style: Theme.of(context).primaryTextTheme.labelMedium),
subtitle: subtitle != null
? Text(
subtitle,
style: Theme.of(context).primaryTextTheme.labelMedium,
)
: null,
leading: SvgPicture.asset(
icon,
color: Theme.of(context).colorScheme.secondary,
width: 24,
alignment: Alignment.centerRight,
),
trailing: trailing ?? null);
@override
bool get wantKeepAlive => true;
final ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
// _controller.addListener(() {
// //print(_controller.offset);
// });
}
@override
void dispose() {
//为了避免内存泄露,需要调用_controller.dispose
_controller.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
_userSubscribeModel = Provider.of<UserSubscribeModel>(context);
_planModel = Provider.of<PlanModel>(context);
_serverModel = Provider.of<ServerModel>(context);
// print("didChangeDependencies");
}
@override
Widget build(BuildContext context) {
super.build(context);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return SingleChildScrollView(
controller: _controller,
physics: const BouncingScrollPhysics(),
child: Column(
children: [
// // Logo bar
// Padding(
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(75),
// right: ScreenUtil().setWidth(75)),
// child: LogoBar(
// isOn: _appModel.isOn,
// ),
// ),
const SizedBox(
height: 20,
),
// SvgPicture.asset(
// 'assets/map.svg',
// height: 230,
// width: ScreenUtil().screenWidth,
// color: _appModel.isOn
// ? AppColors.greenColor
// : isDarkTheme
// ? AppColors.darkSurfaceColor
// : Color.fromARGB(255, 133, 132, 132),
// ),
PowerButton(),
const SizedBox(
height: 40,
),
_appModel.isOn
? (_serverModel.selectServerEntity?.name != null
? Center(
child: Text('${context.l10n.yilianjie}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
)))
: Center(
child: Text(context.l10n.yilianjie,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
))))
: Center(
child: Text(context.l10n.yiduankai2,
style: TextStyle(
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black,
fontSize: 25,
))),
const SizedBox(
height: 20,
),
_appModel.isOn
? ConnectionStats()
: const SizedBox(
height: 1,
),
const BottomBlock(),
],
));
}
}
+169
View File
@@ -0,0 +1,169 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/constant/app_strings.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/models/user_subscribe_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class LogoBar extends StatelessWidget {
const LogoBar({
Key? key,
required this.isOn,
}) : super(key: key);
final bool isOn;
void onLogoutTap(context, _userModel) {
showDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(context.l10n.alertsss),
content: Column(
children: <Widget>[
SizedBox(
height: 10,
),
Align(
child: Text(
context.l10n.wanttoexit,
style: TextStyle(fontSize: 18),
),
alignment: Alignment(0, 0),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
child: Text(context.l10n.cancelss),
onPressed: () {
Navigator.pop(context);
//print("取消");
},
),
CupertinoDialogAction(
child: Text(
context.l10n.exitout,
style: TextStyle(color: Colors.red),
),
onPressed: () {
//print("确定");
_userModel.logout();
NavigatorUtil.goLogin(context);
},
),
],
);
});
}
@override
Widget build(BuildContext context) {
AppModel appModel = Provider.of<AppModel>(context);
UserModel userModel = Provider.of<UserModel>(context);
UserSubscribeModel userSubscribeModel =
Provider.of<UserSubscribeModel>(context);
return Container(
margin: EdgeInsets.only(top: ScreenUtil().setWidth(60)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
AppStrings.appName,
style: TextStyle(
fontWeight: FontWeight.w900,
fontSize: ScreenUtil().setSp(60),
color: isOn ? AppColors.whiteColor : Colors.white,
),
),
Row(
children: [
// Material(
// color: isOn ? const Color(0x66000000) : AppColors.darkSurfaceColor,
// borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
// child: InkWell(
// borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
// onTap: () => NavigatorUtil.goToCrisp(context),
// child: Container(
// padding: EdgeInsets.symmetric(
// vertical: ScreenUtil().setWidth(10), horizontal: ScreenUtil().setWidth(30)),
// child: Text(
// "客服",
// style:
// TextStyle(fontSize: ScreenUtil().setSp(36), color: Colors.white, fontWeight: FontWeight.w500),
// ),
// ),
// ),
// ),
// Padding(padding: EdgeInsets.only(left: ScreenUtil().setWidth(15))),
Material(
color:
isOn ? const Color(0x66000000) : AppColors.darkSurfaceColor,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
child: InkWell(
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: () => appModel.jumpToPage(3),
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(10),
horizontal: ScreenUtil().setWidth(30)),
child: Text(
userSubscribeModel?.userSubscribeEntity?.email ??
context.l10n.welcome,
style: TextStyle(
fontSize: ScreenUtil().setSp(36),
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
),
userModel.isLogin
? Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(15)))
: Container(),
userModel.isLogin
? Material(
color: isOn
? const Color(0x66000000)
: AppColors.darkSurfaceColor,
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
child: InkWell(
borderRadius:
BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: () {
onLogoutTap(context, userModel);
// userModel.logout();
// NavigatorUtil.goLogin(context);
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(10),
horizontal: ScreenUtil().setWidth(30)),
child: Text(
context.l10n.logout,
style: TextStyle(
fontSize: ScreenUtil().setSp(36),
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
)
: Container(),
],
)
],
),
);
}
}
+299
View File
@@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/router/application.dart';
import 'package:uuvpn/routes/OnceNotice.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/utils/transfer_util.dart';
class MySubscribe extends StatefulWidget {
const MySubscribe(
{Key? key,
required this.isLogin,
required this.isOn,
required this.userSubscribeEntity})
: super(key: key);
final bool isLogin;
final bool isOn;
final UserSubscribeEntity? userSubscribeEntity;
@override
MySubscribeState createState() => MySubscribeState();
}
class MySubscribeState extends State<MySubscribe> {
late AppModel _appModel;
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
//,color: isDarkTheme ? Colors.white : Colors.black,
//widget.isOn
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(75)),
child: Text(
context.l10n.yidingyue,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: isDarkTheme ? Colors.grey[400] : AppColors.grayColor,
fontWeight: FontWeight.w500),
),
),
SizedBox(height: ScreenUtil().setWidth(30)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(bottom: ScreenUtil().setWidth(10)),
child: _contentWidget(),
)
],
);
}
Widget _contentWidget() {
// print(widget.userSubscribeEntity?.plan.toJson().toString());
if (widget.userSubscribeEntity?.plan == null) {
return _emptyWidget();
}
// if (widget.userSubscribeEntity!.expiredAt * 1000 <
// DateTime.now().millisecondsSinceEpoch) {
// return _timeOutWidget();
// }
return _buildConnections();
}
Widget _emptyWidget() {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: isDarkTheme
? AppColors.darkSurfaceColor
: const Color.fromARGB(255, 196, 194, 194),
child: Container(
alignment: Alignment.center,
child: Text(
!widget.isLogin
? context.l10n.qingxiandenglu
: context.l10n.qingxiandingyuetaocan,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setWidth(40),
color: isDarkTheme ? Colors.white : Colors.black),
),
),
),
);
}
Widget _timeOutWidget() {
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: widget.isOn ? Colors.white : AppColors.darkSurfaceColor,
child: Container(
alignment: Alignment.center,
child: Text(
context.l10n.taocanguoqichongxindingyue,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setWidth(40),
color: widget.isOn ? Colors.black : Colors.white),
),
),
),
);
}
Widget _buildConnections() {
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Container(
width: ScreenUtil().setWidth(1080),
height: ScreenUtil().setWidth(240),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(75),
vertical: ScreenUtil().setWidth(0)),
child: Material(
elevation: widget.isOn ? 3 : 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: isDarkTheme
? AppColors.whiteColor.withAlpha(20)
: Colors.grey[200],
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenUtil().setWidth(30),
horizontal: ScreenUtil().setWidth(40)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.userSubscribeEntity!.plan?.name ?? "",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(35),
color:
!isDarkTheme ? Colors.black : Colors.white),
),
Padding(
padding: EdgeInsets.only(
left: ScreenUtil().setWidth(15))),
Text(
widget.userSubscribeEntity?.expiredAt != 0
? '${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(widget.userSubscribeEntity!.expiredAt * 1000))} ${context.l10n.guoqi}'
: context.l10n.chagnqiyouxiao,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(35),
color:
!isDarkTheme ? Colors.black : Colors.white),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: ScreenUtil().setWidth(480),
padding: EdgeInsets.only(
bottom: ScreenUtil().setWidth(15)),
child: LinearProgressIndicator(
backgroundColor:
!isDarkTheme ? Colors.grey[400] : Colors.white,
valueColor:
AlwaysStoppedAnimation(Colors.green[600]),
value: double.parse(((widget
.userSubscribeEntity!.u ??
0 + widget.userSubscribeEntity!.d ??
0) /
widget.userSubscribeEntity!
.transferEnable ??
1)
.toStringAsFixed(2)),
),
),
Text(
'${context.l10n.yiyong} ${TransferUtil().toHumanReadable(widget.userSubscribeEntity!.u + widget.userSubscribeEntity!.d)} / ${context.l10n.zongji} ${TransferUtil().toHumanReadable(widget.userSubscribeEntity!.transferEnable)}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(26),
color:
!isDarkTheme ? Colors.black : Colors.white),
)
],
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
/*Container(
width: ScreenUtil().setWidth(160),
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(right: ScreenUtil().setWidth(10)),
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.green[700],
disabledForegroundColor: Colors.black,
disabledBackgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
),
onPressed: () {
_userModel.checkHasLogin(
context,
() => Navigator.of(context).push(
MaterialPageRoute(
builder: (builder) => const OnceNotice()))
/*Fluttertoast.showToast(
msg: context.l10n.qingxuanzefuwqjiedian,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0)*/
//NavigatorUtil.goPlan(context)
);
//_appModel.getTunnelLog();
},
child: Text(
context.l10n.renew,
style: TextStyle(
color: Colors.white,
fontSize: ScreenUtil().setSp(36)),
),
),
),
SizedBox(
width: ScreenUtil().setWidth(160),
height: ScreenUtil().setWidth(90),
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.green[700],
disabledForegroundColor: Colors.black,
disabledBackgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
),
onPressed: () {
_appModel.getTunnelConfiguration();
},
child: Text(
'重置',
style: TextStyle(
color: Colors.white,
fontSize: ScreenUtil().setSp(36)),
),
),
)*/
],
)
],
),
),
));
}
}
+159
View File
@@ -0,0 +1,159 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/entity/user_subscribe_entity.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class PlanList extends StatefulWidget {
const PlanList(
{Key? key,
required this.isOn,
required this.userSubscribeEntity,
required this.plans})
: super(key: key);
final bool isOn;
final UserSubscribeEntity? userSubscribeEntity;
final List<PlanEntity> plans;
@override
PlanListState createState() => PlanListState();
}
class PlanListState extends State<PlanList> with AutomaticKeepAliveClientMixin {
late UserModel _userModel;
@override
bool get wantKeepAlive => true;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(75)),
child: Text(
context.l10n.dinggoutaocan,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: widget.isOn ? AppColors.grayColor : Colors.grey[400],
fontWeight: FontWeight.w500),
),
),
SizedBox(height: ScreenUtil().setWidth(30)),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(bottom: ScreenUtil().setWidth(10)),
child: Row(children: _buildConnections()),
)
],
);
}
List<Widget> _buildConnections() {
if (widget.userSubscribeEntity == null) {
return [Container()];
}
int boughtPlanId = widget.userSubscribeEntity!.expiredAt * 1000 <
DateTime.now().millisecondsSinceEpoch
? 0
: widget.userSubscribeEntity?.planId ?? 0;
List<Widget> list =
List.generate(widget.plans.length * 2 + 1, (i) => Container());
list[0] = SizedBox(width: ScreenUtil().setWidth(75));
for (var i = 1; i < list.length; i++) {
list[i] = Material(
elevation: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? 3
: 0
: 0,
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
color: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? AppColors.darkSurfaceColor
: const Color(0x15000000)
: AppColors.darkSurfaceColor,
child: InkWell(
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(30)),
onTap: widget.isOn && widget.plans[i ~/ 2].id == boughtPlanId
? null
: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goPlan(context)),
child: Container(
// width: ScreenUtil().setWidth(300),
height: ScreenUtil().setWidth(200),
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(40),
vertical: ScreenUtil().setWidth(30)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Country name
Text(
widget.plans[i ~/ 2].name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: widget.isOn
? widget.plans[i ~/ 2].id == boughtPlanId
? Colors.white
: Colors.white
: Colors.white),
),
// Connection status
Padding(
padding: EdgeInsets.only(top: ScreenUtil().setWidth(40)),
child: widget.plans[i ~/ 2].id == boughtPlanId
? Row(
children: [
Icon(
Icons.shield,
size: ScreenUtil().setWidth(32),
color: const Color(0xFF1abb1d),
),
Text(
context.l10n.yidingyue,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
color: const Color(0xFF1abb1d),
fontWeight: FontWeight.bold),
)
],
)
: Text(context.l10n.xuangou,
style: TextStyle(
fontSize: ScreenUtil().setSp(32),
fontWeight: FontWeight.w500,
color:
widget.isOn ? Colors.red : Colors.red)),
)
],
))),
);
list[++i] = SizedBox(width: ScreenUtil().setWidth(30));
}
return list;
}
}
+155
View File
@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/app_model.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/message_util.dart';
import 'package:uuvpn/utils/navigator_util.dart';
import 'package:uuvpn/widgets/watermuticicel.dart';
import 'package:uuvpn/widgets/waterrepper.dart';
class PowerButton extends StatefulWidget {
const PowerButton({Key? key}) : super(key: key);
@override
PowerButtonState createState() => PowerButtonState();
}
class PowerButtonState extends State<PowerButton> {
late AppModel _appModel;
late UserModel _userModel;
late ServerModel _serverModel;
bool light = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
_userModel = Provider.of<UserModel>(context);
_serverModel = Provider.of<ServerModel>(context);
light = _appModel.isOn; // || _appModel.isconnectordisconnct;
}
Future<void> pressConnectBtn() async {
if (_serverModel.selectServerEntity == null) {
Fluttertoast.showToast(
msg: context.l10n.qingxuanzefuwqjiedian,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
textColor: Colors.white,
fontSize: 14.0);
if (_serverModel.serverEntityList.isEmpty) {
MessageUtil.toast(context.l10n.nodefornullcheckissubscripts);
} else {
NavigatorUtil.goServerList(context);
}
return;
}
_appModel.togglePowerButton();
}
final MaterialStateProperty<Icon?> thumbIcon =
MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return const Icon(Icons.check);
}
return const Icon(Icons.close);
},
);
@override
Widget build(BuildContext context) {
//
return Column(
children: [
_appModel.isOn
? Container(
width: 330,
height: 330,
child: WaterRipple(
color: Colors.green,
duration: Duration(milliseconds: 2000),
)
// WaterMultipleCircleLoading(
// color: Colors.green,
// duration: Duration(milliseconds: 2500),
// ),
)
: Container(
height: 330,
),
/*InkWell(
splashColor: Color.fromARGB(255, 51, 117, 54),
onTap: () => _userModel.checkHasLogin(context, pressConnectBtn),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
child: Container(
padding: EdgeInsets.all(ScreenUtil().setWidth(20)),
child: Icon(
_appModel.isOn ? Icons.toggle_on : Icons.toggle_off,
size: ScreenUtil().setWidth(420),
color: _appModel.isOn
? Color.fromARGB(255, 47, 161, 53)
: Colors.grey,
)),
)*/
Transform.scale(
scale: 4.0,
child: Switch(
// thumbIcon: thumbIcon,
// This bool value toggles the switch.
value: light,
activeColor: Color.fromARGB(255, 47, 161, 53),
inactiveTrackColor:
Provider.of<ThemeCollection>(context).isDarkActive
? AppColors.whiteColor.withAlpha(90)
: const Color(0xffD7D6D9),
onChanged: (bool value) {
// This is called when the user toggles the switch.
setState(() {
light = value;
if (value) {
_userModel.checkHasLogin(context, pressConnectBtn);
} else {
_appModel.togglePowerButton();
}
});
},
),
),
],
);
/*return Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color:
_appModel.isOn ? const Color(0x20000000) : const Color(0xff606060),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(460)),
),
child: Material(
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
color: _appModel.isOn ? Color.fromARGB(255, 47, 161, 53) : Colors.grey,
child: InkWell(
splashColor: Color.fromARGB(255, 51, 117, 54),
onTap: () => _userModel.checkHasLogin(context, pressConnectBtn),
borderRadius: BorderRadius.circular(ScreenUtil().setWidth(440)),
child: Container(
padding: EdgeInsets.all(ScreenUtil().setWidth(20)),
child: Icon(
Icons.toggle_off,
size: ScreenUtil().setWidth(420),
color: Colors.white,
)),
),
),
);*/
}
}
+66
View File
@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class ProfileWidget extends StatelessWidget {
const ProfileWidget(
{Key? key, required this.userName, this.avatar, required this.onTap})
: super(key: key);
final String? avatar;
final String userName;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(right: 24, left: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
avatar != null
? Row(
children: <Widget>[
// ClipOval(child: Image(image: NetworkImage(avatar!), width: 40, height: 40)),
Padding(
padding: const EdgeInsets.only(left: 12),
child: Text(
userName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black),
),
)
],
)
: Text(
userName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black),
),
avatar != null
? Material(
color: const Color(0x66000000),
borderRadius: BorderRadius.circular(30),
child: InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 10),
child: const Text(
'退出',
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
)
: Container(),
],
),
);
}
}
@@ -0,0 +1,305 @@
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:uuvpn/constant/app_colors.dart';
double minHeight = ScreenUtil().setHeight(260);
const double iconStartSize = 44;
const double iconEndSize = 120;
const double iconStartMarginTop = 36;
const double iconEndMarginTop = 80;
const double iconsVerticalSpacing = 24;
const double iconsHorizontalSpacing = 16;
class RecentConnectionBottomSheet extends StatefulWidget {
const RecentConnectionBottomSheet({Key? key}) : super(key: key);
@override
RecentConnectionBottomSheetState createState() =>
RecentConnectionBottomSheetState();
}
class RecentConnectionBottomSheetState
extends State<RecentConnectionBottomSheet>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double get maxHeight => MediaQuery.of(context).size.height;
double? get headerTopMargin =>
lerp(20, 20 + MediaQuery.of(context).padding.top);
double? get headerFontSize => lerp(14, 24);
double? get itemBorderRadius => lerp(8, 24);
double? get iconLeftBorderRadius => itemBorderRadius;
double? get iconRightBorderRadius => lerp(8, 0);
double? get iconSize => lerp(iconStartSize, iconEndSize);
double? iconTopMargin(int index) =>
lerp(iconStartMarginTop,
iconEndMarginTop + index * (iconsVerticalSpacing + iconEndSize))! +
headerTopMargin!;
double? iconLeftMargin(int index) =>
lerp(index * (iconsHorizontalSpacing + iconStartSize), 0);
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double? lerp(double min, double max) =>
lerpDouble(min, max, _controller.value);
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
height: lerp(minHeight, maxHeight),
left: 0,
right: 0,
bottom: 0,
child: GestureDetector(
onTap: _toggle,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 32),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [AppColors.themeColor, Colors.pink],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
child: Stack(
children: <Widget>[
const MenuButton(),
SheetHeader(
fontSize: headerFontSize,
topMargin: headerTopMargin,
),
for (Event event in events) _buildFullItem(event),
for (Event event in events) _buildIcon(event),
],
),
),
),
);
},
);
}
Widget _buildIcon(Event event) {
int index = events.indexOf(event);
return Positioned(
height: iconSize,
width: iconSize,
top: iconTopMargin(index),
left: iconLeftMargin(index),
child: ClipRRect(
borderRadius: BorderRadius.horizontal(
left: Radius.circular(iconLeftBorderRadius!),
right: Radius.circular(iconRightBorderRadius!),
),
child: Image.asset(
'assets/${event.assetName}',
fit: BoxFit.cover,
alignment: Alignment(lerp(1, 0)!, 0),
),
),
);
}
Widget _buildFullItem(Event event) {
int index = events.indexOf(event);
return ExpandedEventItem(
topMargin: iconTopMargin(index),
leftMargin: iconLeftMargin(index),
height: iconSize,
isVisible: _controller.status == AnimationStatus.completed,
borderRadius: itemBorderRadius,
title: event.title,
date: event.date,
);
}
void _toggle() {
final bool isOpen = _controller.status == AnimationStatus.completed;
_controller.fling(velocity: isOpen ? -2 : 2);
}
void _handleDragUpdate(DragUpdateDetails details) {
_controller.value -= details.primaryDelta! / maxHeight;
}
void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating ||
_controller.status == AnimationStatus.completed) return;
final double flingVelocity =
details.velocity.pixelsPerSecond.dy / maxHeight;
if (flingVelocity < 0.0) {
_controller.fling(velocity: math.max(2.0, -flingVelocity));
} else if (flingVelocity > 0.0) {
_controller.fling(velocity: math.min(-2.0, -flingVelocity));
} else {
_controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
}
}
}
class ExpandedEventItem extends StatelessWidget {
final double? topMargin;
final double? leftMargin;
final double? height;
final bool isVisible;
final double? borderRadius;
final String title;
final String date;
const ExpandedEventItem(
{Key? key,
required this.topMargin,
required this.height,
required this.isVisible,
required this.borderRadius,
required this.title,
required this.date,
required this.leftMargin})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: topMargin,
left: leftMargin,
right: 0,
height: height,
child: AnimatedOpacity(
opacity: isVisible ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius!),
color: Colors.white,
),
padding: EdgeInsets.only(left: height!).add(const EdgeInsets.all(8)),
child: _buildContent(),
),
),
);
}
Widget _buildContent() {
return Column(
children: <Widget>[
Text(title, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Row(
children: <Widget>[
Text(
'1 ticket',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(width: 8),
Text(
date,
style: const TextStyle(
fontWeight: FontWeight.w300,
fontSize: 12,
color: Colors.grey,
),
),
],
),
const Spacer(),
Row(
children: <Widget>[
Icon(Icons.place, color: Colors.grey.shade400, size: 16),
Text(
'Science Park 10 25A',
style: TextStyle(color: Colors.grey.shade400, fontSize: 13),
)
],
)
],
);
}
}
final List<Event> events = [
Event('steve-johnson.jpeg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('efe-kurnaz.jpg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('rodion-kutsaev.jpeg', 'Dawan District Guangdong Hong Kong', '4.28-31'),
];
class Event {
final String assetName;
final String title;
final String date;
Event(this.assetName, this.title, this.date);
}
class SheetHeader extends StatelessWidget {
final double? fontSize;
final double? topMargin;
const SheetHeader({Key? key, required this.fontSize, required this.topMargin})
: super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
top: topMargin,
child: Text(
'最近连接节点',
style: TextStyle(
color: Colors.grey[100],
fontSize: fontSize,
fontWeight: FontWeight.w500,
),
),
);
}
}
class MenuButton extends StatelessWidget {
const MenuButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Positioned(
right: 0,
bottom: 24,
child: Icon(
Icons.menu,
color: Colors.white,
size: 28,
),
);
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/models/app_model.dart';
class SailAppBar extends AppBar {
SailAppBar({Key? key, required this.appTitle}) : super(key: key);
final String appTitle;
@override
SailAppBarState createState() => SailAppBarState();
}
class SailAppBarState extends State<SailAppBar> {
late AppModel _appModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_appModel = Provider.of<AppModel>(context);
}
@override
Widget build(BuildContext context) {
return AppBar(
title: Text(
widget.appTitle,
style: TextStyle(
color: _appModel.isOn ? Colors.black : Colors.white,
fontWeight: FontWeight.w900),
),
elevation: 0,
backgroundColor:
_appModel.isOn ? AppColors.yellowColor : AppColors.grayColor);
}
}
+86
View File
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/constant/app_colors.dart';
import 'package:uuvpn/model/themeCollection.dart';
import 'package:uuvpn/models/server_model.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/utils/l10n.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class SelectLocation extends StatefulWidget {
const SelectLocation({
Key? key,
}) : super(key: key);
@override
SelectLocationState createState() => SelectLocationState();
}
class SelectLocationState extends State<SelectLocation> {
late ServerModel _serverModel;
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
_serverModel = Provider.of<ServerModel>(context);
bool isDarkTheme = Provider.of<ThemeCollection>(context).isDarkActive;
return Padding(
padding: EdgeInsets.symmetric(
vertical: 20, horizontal: ScreenUtil().setWidth(75)),
child: Material(
borderRadius: BorderRadius.circular(20),
color: Colors.green[600],
child: InkWell(
onTap: () => _userModel.checkHasLogin(
context, () => NavigatorUtil.goServerList(context)),
splashColor: Colors.grey,
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: AppColors.yellowColor.withAlpha(200),
blurRadius: 20,
spreadRadius: -6,
offset: const Offset(
0.0,
3.0,
),
)
]),
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 25),
child: Row(
children: [
Icon(
Icons.sailing,
color: isDarkTheme ? Colors.white : Colors.black,
),
Padding(
padding: EdgeInsets.only(left: ScreenUtil().setWidth(10))),
Text(
_serverModel.selectServerEntity?.name ??
context.l10n.xuanzeliahjiedian,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Colors.black),
),
Expanded(child: Container()),
Icon(
Icons.chevron_right,
color: isDarkTheme ? Colors.white : Colors.black,
)
],
),
),
),
),
);
}
}
+306
View File
@@ -0,0 +1,306 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:uuvpn/entity/plan_entity.dart';
import 'package:uuvpn/models/user_model.dart';
import 'package:uuvpn/service/plan_service.dart';
import 'package:uuvpn/service/user_service.dart';
import 'package:uuvpn/utils/navigator_util.dart';
class SlidingCardsView extends StatefulWidget {
const SlidingCardsView({Key? key}) : super(key: key);
@override
SlidingCardsViewState createState() => SlidingCardsViewState();
}
class SlidingCardsViewState extends State<SlidingCardsView> {
late PageController pageController;
double pageOffset = 0;
List<PlanEntity> _planEntityList = [];
@override
void initState() {
super.initState();
pageController = PageController(viewportFraction: 0.8);
pageController.addListener(() {
setState(() => pageOffset = pageController.page!);
});
PlanService().plan()?.then((planEntityList) {
if (this.mounted) {
setState(() {
_planEntityList = planEntityList;
});
}
});
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.55,
child: PageView(
controller: pageController,
children: List.from(_planEntityList.map((e) => SlidingCard(
id: e.id,
name: e.name,
content: e.content ?? "",
date: e.createdAt?.toIso8601String(),
onetimePrice: (e.onetimePrice ?? 0.0) / 100,
monthPrice: (e.monthPrice ?? 0.0) / 100,
quarterPrice: (e.quarterPrice ?? 0.0) / 100,
halfYearPrice: (e.halfYearPrice ?? 0.0) / 100,
yearPrice: (e.yearPrice ?? 0.0) / 100,
twoYearPrice: (e.twoYearPrice ?? 0.0) / 100,
threeYearPrice: (e.threeYearPrice ?? 0.0) / 100,
assetName: 'steve-johnson.jpeg',
offset: pageOffset)))),
);
}
}
class SlidingCard extends StatelessWidget {
final int id;
final String name;
final String content;
final String? date;
final String assetName;
final double offset;
final double onetimePrice;
final double monthPrice;
final double quarterPrice;
final double halfYearPrice;
final double yearPrice;
final double twoYearPrice;
final double threeYearPrice;
const SlidingCard({
Key? key,
required this.id,
required this.name,
required this.content,
required this.date,
required this.assetName,
required this.offset,
required this.onetimePrice,
required this.monthPrice,
required this.quarterPrice,
required this.halfYearPrice,
required this.yearPrice,
required this.twoYearPrice,
required this.threeYearPrice,
}) : super(key: key);
double lowestPrice() {
List<double> list = [
onetimePrice,
monthPrice,
quarterPrice,
halfYearPrice,
yearPrice,
twoYearPrice,
threeYearPrice,
];
double min = double.maxFinite;
for (int i = 0; i < list.length; i++) {
if ((list[i] < min) && list[i] > 0) {
min = list[i];
}
}
return min;
}
@override
Widget build(BuildContext context) {
UserModel _userModel = Provider.of<UserModel>(context);
double gauss = math.exp(-(math.pow((offset.abs() - 0.5), 2) / 0.08));
return Transform.translate(
offset: Offset(-32 * gauss * offset.sign, 0),
child: Card(
margin: const EdgeInsets.only(left: 8, right: 8, bottom: 24),
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(32)),
child: Column(
children: <Widget>[
ClipRRect(
borderRadius:
const BorderRadius.vertical(top: Radius.circular(32)),
child: Container(
height: MediaQuery.of(context).size.height * 0.1,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.red, Colors.orange, Colors.indigo]),
),
child: Center(
child: Text('${name}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 25,
))),
),
),
// const SizedBox(height: 8),
Expanded(
child: CardContent(
id: id,
name: name,
desc: content,
date: date,
offset: gauss,
lowestPrice: lowestPrice(),
),
),
GestureDetector(
onTap: () {
_userModel.checkHasLogin(
context,
() => UserService().getQuickLoginUrl(
{'redirect': "/plan/${id}"})?.then((value) {
NavigatorUtil.goWebView(
context, "购买${name}订阅", value);
}));
},
child: ClipRRect(
borderRadius:
const BorderRadius.vertical(bottom: Radius.circular(32)),
child: Container(
height: MediaQuery.of(context).size.height * 0.1,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Colors.orange, Colors.orange, Colors.orange]),
),
child: Center(
child: Text('点击购买 ¥${lowestPrice()}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20,
))),
),
),
),
],
),
),
);
}
}
class CardContent extends StatefulWidget {
final int id;
final String name;
final String desc;
final String? date;
final double offset;
final double lowestPrice;
const CardContent(
{Key? key,
required this.id,
required this.name,
required this.desc,
required this.date,
required this.offset,
required this.lowestPrice})
: super(key: key);
@override
CardContentState createState() => CardContentState();
}
class CardContentState extends State<CardContent> {
late UserModel _userModel;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_userModel = Provider.of<UserModel>(context);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Transform.translate(
offset: Offset(8 * widget.offset, 0),
child: Text(widget.desc,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
),
const SizedBox(height: 8),
// Transform.translate(
// offset: Offset(32 * widget.offset, 0),
// child: Text(
// widget.date!,
// style: const TextStyle(
// color: Colors.grey, fontWeight: FontWeight.bold),
// ),
// ),
// const Spacer(),
// Row(
// children: <Widget>[
// Transform.translate(
// offset: Offset(48 * widget.offset, 0),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// foregroundColor: Colors.white,
// backgroundColor: Colors.yellow,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(32),
// ),
// ),
// onPressed: () => _userModel.checkHasLogin(
// context,
// () => UserService().getQuickLoginUrl({
// 'redirect': "/plan/${widget.id}"
// })?.then((value) {
// NavigatorUtil.goWebView(context, "配置订阅", value);
// })),
// child: Transform.translate(
// offset: Offset(24 * widget.offset, 0),
// child: Text('购买',
// style: TextStyle(
// color: Colors.black87,
// fontSize: ScreenUtil().setSp(36))),
// ),
// ),
// ),
// const Spacer(),
// Transform.translate(
// offset: Offset(32 * widget.offset, 0),
// child: Text(
// '¥ ${widget.lowestPrice} 起',
// style: const TextStyle(
// fontWeight: FontWeight.bold,
// fontSize: 20,
// ),
// ),
// ),
// const SizedBox(width: 16),
// ],
// )
],
),
);
}
}
+86
View File
@@ -0,0 +1,86 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'circle_painter.dart';
///
/// desc:
///
class WaterMultipleCircleLoading extends StatefulWidget {
final Color color;
final Duration duration;
final Curve curve;
const WaterMultipleCircleLoading(
{Key? key,
this.color = Colors.white,
this.duration = const Duration(milliseconds: 1500),
this.curve = Curves.linear})
: super(key: key);
@override
_WaterMultipleCircleLoadingState createState() =>
_WaterMultipleCircleLoadingState();
}
class _WaterMultipleCircleLoadingState extends State<WaterMultipleCircleLoading>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation, _animation1, _animation2, _animation3;
@override
void initState() {
_controller = AnimationController(vsync: this, duration: widget.duration)
..repeat();
_animation = CurveTween(curve: Interval(0.0, 0.7, curve: widget.curve))
.animate(_controller);
_animation1 = CurveTween(curve: Interval(0.15, 0.8, curve: widget.curve))
.animate(_controller);
_animation2 = CurveTween(curve: Interval(0.3, 0.9, curve: widget.curve))
.animate(_controller);
_animation3 = CurveTween(curve: Interval(0.45, 1.0, curve: widget.curve))
.animate(_controller);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Stack(
children: [
_item(_animation),
_item(_animation1),
_item(_animation2),
_item(_animation3),
],
);
});
}
_item(Animation animation) {
return Positioned.fill(
child: Center(
child: FractionallySizedBox(
widthFactor: animation.value,
heightFactor: animation.value,
child: CustomPaint(
painter: CirclePainter(
progress: _controller.value,
color: widget.color.withOpacity(animation.value)),
),
),
));
}
}
+90
View File
@@ -0,0 +1,90 @@
import 'dart:math';
import 'package:flutter/material.dart';
///
/// desc:
///
class WaterRipple extends StatefulWidget {
final Color color;
final Duration duration;
final Curve curve;
final int count;
const WaterRipple(
{Key? key,
this.color = Colors.white,
this.count = 3,
this.duration = const Duration(milliseconds: 800),
this.curve = Curves.linear})
: super(key: key);
@override
_WaterRippleState createState() => _WaterRippleState();
}
class _WaterRippleState extends State<WaterRipple>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
_controller = AnimationController(vsync: this, duration: widget.duration)
..repeat();
_animation = CurveTween(curve: widget.curve).animate(_controller);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: WaterRipplePainter(_animation.value,
count: widget.count, color: widget.color),
);
},
);
}
}
class WaterRipplePainter extends CustomPainter {
final double progress;
final int count;
final Color color;
Paint _paint = Paint()..style = PaintingStyle.fill;
WaterRipplePainter(this.progress,
{this.count = 3, this.color = const Color(0xFF0080ff)});
@override
void paint(Canvas canvas, Size size) {
double radius = min(size.width / 2, size.height / 2);
for (int i = count; i >= 0; i--) {
final double opacity = (1.0 - ((i + progress) / (count + 1)));
final Color _color = color.withOpacity(opacity);
_paint..color = _color;
double _radius = radius * ((i + progress) / (count + 1));
canvas.drawCircle(
Offset(size.width / 2, size.height / 2), _radius, _paint);
}
}
@override
bool shouldRepaint(covariant WaterRipplePainter old) {
return progress != old.progress || color != old.color || count != old.count;
}
}