update ios project
update ios project
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomBlock extends StatelessWidget {
|
||||
const BottomBlock({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 100,
|
||||
color: Colors.transparent,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
///
|
||||
/// desc:
|
||||
///
|
||||
class CirclePainter extends CustomPainter {
|
||||
final double progress;
|
||||
|
||||
final double strokeWidth;
|
||||
final Color color;
|
||||
|
||||
Paint _paint = Paint();
|
||||
|
||||
CirclePainter(
|
||||
{this.progress = 0.0, this.strokeWidth = 1.0, this.color = Colors.white});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
_paint
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..color = color;
|
||||
|
||||
double radius = min(size.width, size.height) / 2;
|
||||
canvas.drawCircle(
|
||||
Offset(size.width / 2, size.height / 2), radius * progress, _paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CirclePainter old) {
|
||||
return progress != old.progress ||
|
||||
strokeWidth != old.strokeWidth ||
|
||||
color != old.color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package: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)),
|
||||
]),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]),
|
||||
],
|
||||
),
|
||||
)*/
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
)*/
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)),
|
||||
),
|
||||
),
|
||||
);*/
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
// ],
|
||||
// )
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user