瀏覽代碼

[feat]增加亲密度分析报告页,搭建Tab页框架

hezihao 7 月之前
父節點
當前提交
6588b6b82f

+ 4 - 3
assets/color/color.xml

@@ -21,7 +21,8 @@
     <!--文字反色色彩-->
     <color name="inverseTextColor">#FFFFFFFF</color>
 
-
-
-
+    <!-- 主题色文字色彩 -->
+    <color name="colorBrand">#FF7D46FC</color>
+    <!-- 辅助色文字色彩1 -->
+    <color name="colorAuxiliary1">#FFBC87FF</color>
 </resources>

二進制
assets/images/bg_intimacy_analyse.webp


二進制
assets/images/icon_white_back_arrow.webp


+ 3 - 0
assets/string/base/string.xml

@@ -212,4 +212,7 @@
     <string name="keyboard_guide_ta_reply1">👋 欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复</string>
     <string name="keyboard_guide_ta_reply2">你睡了吗?</string>
     <string name="keyboard_guide_ta_reply3">我先去吃饭了,一会聊</string>
+
+    <string name="intimacy_analyse_tab_report">亲密分析报告</string>
+    <string name="intimacy_analyse_tab_screenshot_reply">截图回复</string>
 </resources>

+ 4 - 0
lib/di/get_it.config.dart

@@ -38,6 +38,7 @@ import '../module/character_custom/detail/character_custom_detail_controller.dar
 import '../module/character_custom/list/character_custom_list_controller.dart'
     as _i1059;
 import '../module/feedback/feedback_controller.dart' as _i876;
+import '../module/intimacy_analyse/intimacy_analyse_controller.dart' as _i977;
 import '../module/keyboard/keyboard_controller.dart' as _i161;
 import '../module/keyboard_guide/keyboard_guide_controller.dart' as _i248;
 import '../module/keyboard_manage/keyboard_manage_controller.dart' as _i922;
@@ -72,6 +73,9 @@ extension GetItInjectableX on _i174.GetIt {
     gh.factory<_i415.KeyboardMethodHandler>(
       () => _i415.KeyboardMethodHandler(),
     );
+    gh.factory<_i977.IntimacyAnalyseController>(
+      () => _i977.IntimacyAnalyseController(),
+    );
     gh.singleton<_i361.Dio>(
       () => networkModule.createStreamDio(),
       instanceName: 'streamDio',

+ 62 - 0
lib/module/intimacy_analyse/intimacy_analyse_controller.dart

@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:injectable/injectable.dart';
+import 'package:keyboard/base/base_controller.dart';
+import 'package:keyboard/resource/string.gen.dart';
+
+/// 亲密度分析Controller
+@injectable
+class IntimacyAnalyseController extends BaseController
+    with GetTickerProviderStateMixin {
+  /// Tab列表
+  RxList<String> tabBarList =
+      <String>[
+        StringName.intimacyAnalyseTabReport,
+        StringName.intimacyAnalyseTabScreenshotReply,
+      ].obs;
+
+  /// Tab控制器
+  late final TabController tabController = TabController(
+    length: tabBarList.length,
+    vsync: this,
+  );
+
+  /// PageView控制器
+  final PageController pageController = PageController();
+
+  /// 返回上一页
+  void clickBack() {
+    Get.back();
+  }
+
+  @override
+  void onInit() {
+    super.onInit();
+  }
+
+  @override
+  void onClose() {
+    tabController.dispose();
+    pageController.dispose();
+    super.onClose();
+  }
+
+  /// 处理Tab切换,联动PageView
+  void handleTabChange(int index) {
+    if (!tabController.indexIsChanging) {
+      pageController.animateToPage(
+        index,
+        duration: const Duration(milliseconds: 300),
+        curve: Curves.easeInToLinear,
+      );
+    }
+  }
+
+  /// 处理PageView切换,联动Tab
+  void handlePageChange(int index) {
+    tabController.animateTo(
+      index,
+      duration: const Duration(milliseconds: 300),
+    );
+  }
+}

+ 142 - 0
lib/module/intimacy_analyse/intimacy_analyse_page.dart

@@ -0,0 +1,142 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:keyboard/base/base_page.dart';
+import 'package:keyboard/resource/string.gen.dart';
+
+import '../../resource/assets.gen.dart';
+import '../../resource/colors.gen.dart';
+import '../../router/app_pages.dart';
+import '../../widget/tabbar/custom_tab_indicator.dart';
+import 'intimacy_analyse_controller.dart';
+
+/// 亲密度报告页
+class IntimacyAnalysePage extends BasePage<IntimacyAnalyseController> {
+  const IntimacyAnalysePage({super.key});
+
+  @override
+  bool immersive() {
+    // 开启沉浸式
+    return true;
+  }
+
+  @override
+  backgroundColor() {
+    return Colors.transparent;
+  }
+
+  static start() {
+    Get.toNamed(RoutePath.intimacyAnalyse);
+  }
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Scaffold(
+      backgroundColor: backgroundColor(),
+      body: Container(
+        decoration: BoxDecoration(
+          image: DecorationImage(
+            image: Assets.images.bgIntimacyAnalyse.provider(),
+            fit: BoxFit.fill,
+          ),
+        ),
+        child: Column(
+          children: [_buildStatusBar(), _buildTopBar(), _buildContent()],
+        ),
+      ),
+    );
+  }
+
+  /// 导航栏占位
+  Widget _buildStatusBar() {
+    return Container(
+      height: MediaQuery.of(Get.context!).padding.top,
+      color: backgroundColor(),
+    );
+  }
+
+  /// 顶部栏
+  Widget _buildTopBar() {
+    return Container(
+      height: kToolbarHeight,
+      width: double.infinity,
+      padding: EdgeInsets.symmetric(horizontal: 16.0),
+      child: ConstrainedBox(
+        constraints: BoxConstraints(minWidth: double.infinity),
+        child: Stack(
+          alignment: Alignment.center,
+          children: [
+            // 返回按钮
+            Positioned(
+              left: 16.0,
+              child: GestureDetector(
+                onTap: controller.clickBack,
+                child: Assets.images.iconWhiteBackArrow.image(
+                  width: 24.w,
+                  height: 24.h,
+                ),
+              ),
+            ),
+            // TabBar
+            Positioned(child: _buildTabBar()),
+          ],
+        ),
+      ),
+    );
+  }
+
+  /// 指示器
+  TabBar _buildTabBar() {
+    return TabBar(
+      // 是否可以滚动
+      isScrollable: true,
+      // 去除底部的黑线
+      dividerHeight: 0,
+      // 去除左边的边距,让Tab居中
+      tabAlignment: TabAlignment.start,
+      // 指示器的颜色
+      indicator: _buildGradientLineIndicator(),
+      // 选中时的颜色
+      labelStyle: TextStyle(
+        fontSize: 17.sp,
+        fontWeight: FontWeight.bold,
+        color: ColorName.white,
+      ),
+      // 未选中时的颜色
+      unselectedLabelStyle: TextStyle(
+        fontSize: 17.sp,
+        color: ColorName.white70,
+      ),
+      // 配置Tab数据
+      tabs:
+          controller.tabBarList.map((tab) {
+            return Tab(text: tab);
+          }).toList(),
+      controller: controller.tabController,
+      onTap: (index) {
+        controller.handleTabChange(index);
+      },
+    );
+  }
+
+  // 自定义渐变指示器
+  CustomTabIndicator _buildGradientLineIndicator() {
+    return CustomTabIndicator(width: 16.0, strokeCap: StrokeCap.round);
+  }
+
+  /// PageView
+  Widget _buildContent() {
+    return Expanded(
+      child: PageView(
+        controller: controller.pageController,
+        onPageChanged: (index) {
+          controller.handlePageChange(index);
+        },
+        children: [
+          Center(child: Text(StringName.intimacyAnalyseTabReport)),
+          Center(child: Text(StringName.intimacyAnalyseTabScreenshotReply)),
+        ],
+      ),
+    );
+  }
+}

+ 6 - 0
lib/module/mine/mine_controller.dart

@@ -16,6 +16,7 @@ import '../../data/repository/account_repository.dart';
 import '../../plugins/keyboard_android_platform.dart';
 import '../../plugins/keyboard_method_handler.dart';
 import '../../resource/colors.gen.dart';
+import '../intimacy_analyse/intimacy_analyse_page.dart';
 import '../keyboard_guide/keyboard_guide_page.dart';
 import '../profile/profile_page.dart';
 import '../store/discount/discount_controller.dart';
@@ -94,6 +95,11 @@ class MineController extends BaseController {
     KeyboardGuidePage.start();
   }
 
+  longClickTutorials() {
+    // TODO hezhiao,测试,跳转到亲密度报告引导页
+    IntimacyAnalysePage.start();
+  }
+
   clickPersonalProfile() {
     debugPrint('clickPersonalProfile');
     // SurpriseDialog.show(clickConfirm: StorePage.start);

+ 3 - 0
lib/module/mine/mine_view.dart

@@ -60,6 +60,7 @@ class MineView extends BaseView<MineController> {
             text: StringName.tutorials,
             funIcon: Assets.images.iconMineTutorials.path,
             onTap: controller.clickTutorials,
+            onLongTap: controller.longClickTutorials
           ),
 
           baseFunctionButton(
@@ -253,9 +254,11 @@ class MineView extends BaseView<MineController> {
     required String text,
     required String funIcon,
     required VoidCallback onTap,
+    VoidCallback? onLongTap,
   }) {
     return GestureDetector(
       onTap: onTap,
+      onLongPress: onLongTap,
       behavior: HitTestBehavior.opaque,
       child: Padding(
         padding: EdgeInsets.only(bottom: 12.h),

+ 10 - 0
lib/resource/assets.gen.dart

@@ -101,6 +101,10 @@ class $AssetsImagesGen {
   AssetGenImage get bgGoApp =>
       const AssetGenImage('assets/images/bg_go_app.webp');
 
+  /// File path: assets/images/bg_intimacy_analyse.webp
+  AssetGenImage get bgIntimacyAnalyse =>
+      const AssetGenImage('assets/images/bg_intimacy_analyse.webp');
+
   /// File path: assets/images/bg_keyboard.webp
   AssetGenImage get bgKeyboard =>
       const AssetGenImage('assets/images/bg_keyboard.webp');
@@ -598,6 +602,10 @@ class $AssetsImagesGen {
   AssetGenImage get iconWechatScanPayment =>
       const AssetGenImage('assets/images/icon_wechat_scan_payment.webp');
 
+  /// File path: assets/images/icon_white_back_arrow.webp
+  AssetGenImage get iconWhiteBackArrow =>
+      const AssetGenImage('assets/images/icon_white_back_arrow.webp');
+
   /// List of all assets
   List<AssetGenImage> get values => [
     bgCharacterBoyBanner,
@@ -614,6 +622,7 @@ class $AssetsImagesGen {
     bgDiscountTagTop,
     bgDiscountTitle,
     bgGoApp,
+    bgIntimacyAnalyse,
     bgKeyboard,
     bgKeyboardLove,
     bgKeyboardManage,
@@ -736,6 +745,7 @@ class $AssetsImagesGen {
     iconWechat,
     iconWechatPayment,
     iconWechatScanPayment,
+    iconWhiteBackArrow,
   ];
 }
 

+ 6 - 0
lib/resource/colors.gen.dart

@@ -79,6 +79,12 @@ class ColorName {
   /// Color: #6399FF
   static const Color colorAccentPrimary = Color(0xFF6399FF);
 
+  /// Color: #FFBC87FF
+  static const Color colorAuxiliary1 = Color(0xFFBC87FF);
+
+  /// Color: #FF7D46FC
+  static const Color colorBrand = Color(0xFF7D46FC);
+
   /// Color: #374BFF
   static const Color colorPrimary = Color(0xFF374BFF);
 

+ 4 - 0
lib/resource/string.gen.dart

@@ -150,6 +150,8 @@ class StringName {
   static final String keyboardGuideTaReply1 = 'keyboard_guide_ta_reply1'.tr; // 👋 欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复
   static final String keyboardGuideTaReply2 = 'keyboard_guide_ta_reply2'.tr; // 你睡了吗?
   static final String keyboardGuideTaReply3 = 'keyboard_guide_ta_reply3'.tr; // 我先去吃饭了,一会聊
+  static final String intimacyAnalyseTabReport = 'intimacy_analyse_tab_report'.tr; // 亲密分析报告
+  static final String intimacyAnalyseTabScreenshotReply = 'intimacy_analyse_tab_screenshot_reply'.tr; // 截图回复
 }
 class StringMultiSource {
   StringMultiSource._();
@@ -303,6 +305,8 @@ class StringMultiSource {
       'keyboard_guide_ta_reply1': '👋 欢迎使用【追爱小键盘】\n复制任意一句对话,点击人设体验回复',
       'keyboard_guide_ta_reply2': '你睡了吗?',
       'keyboard_guide_ta_reply3': '我先去吃饭了,一会聊',
+      'intimacy_analyse_tab_report': '亲密分析报告',
+      'intimacy_analyse_tab_screenshot_reply': '截图回复',
     },
   };
 }

+ 9 - 0
lib/router/app_pages.dart

@@ -6,6 +6,8 @@ import 'package:keyboard/module/character_custom/character_custom_controller.dar
 import 'package:keyboard/module/character_custom/detail/character_custom_detail_page.dart';
 import 'package:keyboard/module/character_custom/list/character_custom_list_controller.dart';
 import 'package:keyboard/module/feedback/feedback_controller.dart';
+import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_controller.dart';
+import 'package:keyboard/module/intimacy_analyse/intimacy_analyse_page.dart';
 import 'package:keyboard/module/keyboard/keyboard_controller.dart';
 import 'package:keyboard/module/keyboard_manage/keyboard_manage_controller.dart';
 import 'package:keyboard/module/login/login_controller.dart';
@@ -56,6 +58,9 @@ abstract class RoutePath {
 
   // 键盘引导页
   static const keyboardGuide = '/keyboardGuide';
+
+  // 亲密度分析页
+  static const intimacyAnalyse = '/intimacyAnalyse';
 }
 
 class AppBinding extends Bindings {
@@ -80,6 +85,7 @@ class AppBinding extends Bindings {
     lazyPut(() => getIt.get<ProfileController>());
     lazyPut(() => getIt.get<ProfileEditController>());
     lazyPut(() => getIt.get<KeyboardGuidePageController>());
+    lazyPut(() => getIt.get<IntimacyAnalyseController>());
   }
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -106,5 +112,8 @@ final generalPages = [
   GetPage(name: RoutePath.store, page: () => StorePage()),
   GetPage(name: RoutePath.profile, page: () => ProfilePage()),
   GetPage(name: RoutePath.profileEdit, page: () => ProfileEditPage()),
+  // 键盘引导页
   GetPage(name: RoutePath.keyboardGuide, page: () => KeyboardGuidePage()),
+  // 亲密度报告页
+  GetPage(name: RoutePath.intimacyAnalyse, page: () => IntimacyAnalysePage()),
 ];

+ 106 - 0
lib/widget/tabbar/custom_tab_indicator.dart

@@ -0,0 +1,106 @@
+import 'package:flutter/material.dart';
+
+/// 自定义Tab的指示器
+class CustomTabIndicator extends Decoration {
+  /// Create an underline style selected tab indicator.
+  ///
+  /// The [borderSide] and [insets] arguments must not be null.
+  const CustomTabIndicator({
+    this.width = 20,
+    this.strokeCap = StrokeCap.round,
+    this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
+    this.insets = EdgeInsets.zero,
+  });
+
+  /// The color and weight of the horizontal line drawn below the selected tab.
+  final BorderSide borderSide;
+
+  /// Locates the selected tab's underline relative to the tab's boundary.
+  ///
+  /// The [TabBar.indicatorSize] property can be used to define the tab
+  /// indicator's bounds in terms of its (centered) tab widget with
+  /// [TabBarIndicatorSize.label], or the entire tab with
+  /// [TabBarIndicatorSize.tab].
+  final EdgeInsetsGeometry insets;
+
+  /// 新增属性:控制器宽度
+  final double width;
+
+  /// 新增属性:控制器边角形状
+  final StrokeCap strokeCap;
+
+  @override
+  Decoration? lerpFrom(Decoration? a, double t) {
+    if (a is CustomTabIndicator) {
+      return CustomTabIndicator(
+        borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
+        insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  Decoration? lerpTo(Decoration? b, double t) {
+    if (b is CustomTabIndicator) {
+      return CustomTabIndicator(
+        borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
+        insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  BoxPainter createBoxPainter([VoidCallback? onChanged]) {
+    return _UnderlinePainter(this, onChanged);
+  }
+
+  /// 决定控制器宽度的方法
+  Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
+    final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
+
+    // 希望的宽度
+    double wantWidth = width;
+    // 取中间坐标
+    double cw = (indicator.left + indicator.right) / 2;
+
+    // 下划线靠左
+    // return Rect.fromLTWH(indicator.left,
+    // indicator.bottom - borderSide.width, wantWidth, borderSide.width);
+
+    //下划线居中
+    return Rect.fromLTWH(
+      cw - wantWidth / 2,
+      indicator.bottom - borderSide.width,
+      wantWidth,
+      borderSide.width,
+    );
+  }
+
+  @override
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    return Path()..addRect(_indicatorRectFor(rect, textDirection));
+  }
+}
+
+class _UnderlinePainter extends BoxPainter {
+  _UnderlinePainter(this.decoration, VoidCallback? onChanged)
+    : super(onChanged);
+
+  final CustomTabIndicator decoration;
+
+  /// 决定控制器边角形状的方法
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    assert(configuration.size != null);
+    final Rect rect = offset & configuration.size!;
+    final TextDirection textDirection = configuration.textDirection!;
+    final Rect indicator = decoration
+        ._indicatorRectFor(rect, textDirection)
+        .deflate(decoration.borderSide.width / 2.0);
+    final Paint paint =
+        decoration.borderSide.toPaint()..strokeCap = decoration.strokeCap;
+    canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
+  }
+}