| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- import 'dart:io';
- import 'package:carousel_slider/carousel_slider.dart';
- import 'package:flutter/material.dart' hide ConnectionState;
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:get/get.dart';
- import 'package:nomo/app/constants/iconfont/iconfont.dart';
- import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
- import '../../../../config/theme/theme_extensions/theme_extension.dart';
- import '../../../../config/translations/strings_enum.dart';
- import '../../../base/base_view.dart';
- import '../../../constants/assets.dart';
- import '../../../routes/app_pages.dart';
- import '../../../widgets/click_opacity.dart';
- import '../../../widgets/country_icon.dart';
- import '../../../widgets/ix_image.dart';
- import '../widgets/connection_button.dart';
- import '../controllers/home_controller.dart';
- import '../widgets/menu_list.dart';
- class HomeView extends BaseView<HomeController> {
- const HomeView({super.key});
- @override
- bool get isPopScope => true;
- @override
- Widget buildContent(BuildContext context) {
- return SafeArea(
- child: Container(
- margin: EdgeInsets.symmetric(horizontal: 14.w),
- child: Stack(
- children: [
- Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Obx(
- () => ClickOpacity(
- onTap: () => Get.toNamed(Routes.SUBSCRIPTION),
- child: IXImage(
- source: controller.isPremium
- ? Assets.premium
- : Assets.free,
- width: controller.isPremium ? 92.w : 64.w,
- height: 28.w,
- sourceType: ImageSourceType.asset,
- ),
- ),
- ),
- ClickOpacity(
- child: Padding(
- padding: EdgeInsets.only(
- left: 10.w,
- right: 0.w,
- top: 10.w,
- bottom: 10.w,
- ),
- child: Icon(
- IconFont.icon09,
- size: 26.w,
- color: Get.reactiveTheme.hintColor,
- ),
- ),
- onTap: () {
- Get.toNamed(Routes.SETTING);
- // ErrorDialog.show(
- // message:
- // "The VPN was disconnected unexpectedly. Would you like to reconnect now to stay protected?",
- // );
- },
- ),
- ],
- ),
- Expanded(
- child: SmartRefresher(
- enablePullDown: true,
- enablePullUp: false,
- controller: controller.refreshController,
- onRefresh: controller.onRefresh,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 80.verticalSpaceFromWidth,
- Padding(
- padding: EdgeInsets.symmetric(vertical: 20.w),
- child: CarouselSlider(
- options: CarouselOptions(
- height: 80.w,
- viewportFraction: 1.0,
- ),
- items: [1, 2, 3, 4, 5].map((i) {
- return Builder(
- builder: (BuildContext context) {
- return IXImage(
- source: Assets.bannerTest,
- width: double.infinity,
- height: 80.w,
- sourceType: ImageSourceType.asset,
- borderRadius: 14.r,
- );
- },
- );
- }).toList(),
- ),
- ),
- Text(
- Strings.activeTime.tr,
- style: TextStyle(
- fontSize: 18.sp,
- height: 1.3,
- color: Get.reactiveTheme.hintColor,
- ),
- ),
- 2.verticalSpaceFromWidth,
- Obx(
- () => Text(
- controller.coreController.timer,
- style: TextStyle(
- fontSize: 28.sp,
- height: 1.2,
- color: Get.reactiveTheme.primaryColor,
- ),
- ),
- ),
- 20.verticalSpaceFromWidth,
- // 位置选择按钮和最近位置(叠在一起的效果)
- Stack(
- children: [
- Container(
- alignment: Alignment.center,
- margin: EdgeInsets.only(top: 138.w),
- child: _buildConnectionButton(),
- ),
- _buildLocationStack(),
- ],
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- Positioned(
- bottom: Platform.isAndroid ? 10.w : 0,
- left: 0,
- right: 0,
- child: MenuList(),
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildConnectionButton() {
- return Obx(
- () => ConnectionButton(
- state: controller.coreController.state,
- onTap: () {
- controller.coreController.handleConnection();
- },
- ),
- );
- }
- /// 构建位置堆叠效果(选中位置 + 最近位置)
- Widget _buildLocationStack() {
- return Obx(() {
- if (controller.selectedLocation == null) {
- return const SizedBox.shrink();
- }
- return Stack(
- children: [
- // 最近位置列表(背景层)
- if (controller.recentLocations.isNotEmpty)
- _buildRecentLocationsCard(),
- // 选中位置(前景层)
- _buildSelectedLocationCard(),
- ],
- );
- });
- }
- /// 构建选中位置卡片
- Widget _buildSelectedLocationCard() {
- return GestureDetector(
- onTap: () {
- Get.toNamed(Routes.NODE);
- },
- child: Obx(() {
- return Container(
- height: 56.w,
- width: double.maxFinite,
- padding: EdgeInsets.only(left: 16.w, right: 10.w),
- decoration: BoxDecoration(
- color: Get.reactiveTheme.highlightColor,
- borderRadius: BorderRadius.circular(12.r),
- ),
- child: Row(
- children: [
- // 国旗图标
- CountryIcon(
- countryCode: controller.selectedLocation?.country ?? '',
- width: 32.w,
- height: 24.w,
- borderRadius: 4.r,
- ),
- 10.horizontalSpace,
- // 位置名称
- Expanded(
- child: Text(
- '${controller.selectedLocation?.code ?? ''} - ${controller.selectedLocation?.name ?? ''}',
- style: TextStyle(
- fontSize: 16.sp,
- height: 1.5,
- fontWeight: FontWeight.w500,
- color: Get.reactiveTheme.textTheme.bodyLarge!.color,
- ),
- ),
- ),
- // 箭头图标
- Icon(
- IconFont.icon02,
- size: 20.w,
- color: Get.reactiveTheme.textTheme.bodyLarge!.color,
- ),
- ],
- ),
- );
- }),
- );
- }
- /// 构建最近位置卡片(支持展开/收缩)
- Widget _buildRecentLocationsCard() {
- return Obx(() {
- return Container(
- margin: EdgeInsets.symmetric(horizontal: 10.w),
- padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 56.w, bottom: 0),
- decoration: BoxDecoration(
- color: Get.reactiveTheme.cardColor,
- borderRadius: BorderRadius.circular(12.r),
- ),
- child: Column(
- children: [
- GestureDetector(
- behavior: HitTestBehavior.opaque,
- onTap: () {
- controller.isRecentLocationsExpanded =
- !controller.isRecentLocationsExpanded;
- },
- child: SizedBox(
- height: 44.w,
- child: Row(
- children: [
- Icon(
- IconFont.icon68,
- size: 16.w,
- color: Get.reactiveTheme.hintColor,
- ),
- SizedBox(width: 4.w),
- Text(
- Strings.recent.tr,
- style: TextStyle(
- fontSize: 12.sp,
- height: 1.2,
- color: Get.reactiveTheme.hintColor,
- ),
- ),
- const Spacer(),
- // 最近三个节点的国旗图标(收缩状态)或箭头(展开状态)
- Obx(() {
- return AnimatedOpacity(
- opacity: controller.isRecentLocationsExpanded
- ? 0.0
- : 1.0,
- duration: const Duration(milliseconds: 300),
- child: IgnorePointer(
- ignoring: controller.isRecentLocationsExpanded,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- ...controller.recentLocations.take(3).map((
- location,
- ) {
- return Container(
- margin: EdgeInsets.only(right: 4.w),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(5.r),
- border: Border.all(
- color: Get.reactiveTheme.canvasColor,
- width: 0.4.w,
- ),
- ),
- child: CountryIcon(
- countryCode: location.country ?? '',
- width: 24.w,
- height: 16.w,
- borderRadius: 4.r,
- ),
- );
- }),
- ],
- ),
- ),
- );
- }),
- Obx(() {
- return AnimatedRotation(
- turns: controller.isRecentLocationsExpanded
- ? 0.25
- : 0.0,
- duration: const Duration(milliseconds: 300),
- child: Icon(
- IconFont.icon02,
- size: 20.w,
- color: Get.reactiveTheme.hintColor,
- ),
- );
- }),
- ],
- ),
- ),
- ),
- // 最近位置列表(可折叠)
- Obx(() {
- return ClipRect(
- child: AnimatedAlign(
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- heightFactor: controller.isRecentLocationsExpanded
- ? 1.0
- : 0.0,
- alignment: Alignment.topLeft,
- child: AnimatedOpacity(
- opacity: controller.isRecentLocationsExpanded ? 1.0 : 0.0,
- duration: const Duration(milliseconds: 300),
- child: Column(
- children: controller.recentLocations.map((location) {
- return ClickOpacity(
- onTap: () {
- controller.isRecentLocationsExpanded =
- !controller.isRecentLocationsExpanded;
- controller.selectLocation(location);
- controller.handleConnect();
- },
- child: Column(
- children: [
- Divider(
- height: 1,
- color: Get.reactiveTheme.dividerColor,
- ),
- Container(
- margin: EdgeInsets.symmetric(vertical: 12.h),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- // 国旗图标
- CountryIcon(
- countryCode: location.country ?? '',
- width: 28.w,
- height: 21.w,
- borderRadius: 4.r,
- ),
- SizedBox(width: 10.w),
- // 位置信息
- Expanded(
- child: Text(
- '${location.code} - ${location.name}',
- style: TextStyle(
- fontSize: 14.sp,
- fontWeight: FontWeight.w500,
- color: Get.reactiveTheme.hintColor,
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }).toList(),
- ),
- ),
- ),
- );
- }),
- ],
- ),
- );
- });
- }
- }
|