|
|
@@ -7,11 +7,12 @@ import 'package:electronic_assistant/resource/string.gen.dart';
|
|
|
import 'package:electronic_assistant/utils/expand.dart';
|
|
|
import 'package:electronic_assistant/utils/toast_util.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
-import 'package:flutter/services.dart';
|
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
import 'package:get/get.dart';
|
|
|
+import '../../data/consts/constants.dart';
|
|
|
import '../files/view.dart';
|
|
|
import '../home/view.dart';
|
|
|
+import 'dart:math' as math;
|
|
|
|
|
|
class MainTabPage extends BasePage<MainController> {
|
|
|
MainTabPage({super.key});
|
|
|
@@ -32,7 +33,7 @@ class MainTabPage extends BasePage<MainController> {
|
|
|
}
|
|
|
if ((controller.lastPressedAt == null ||
|
|
|
DateTime.now().difference(controller.lastPressedAt!) >
|
|
|
- const Duration(seconds: 2))) {
|
|
|
+ const Duration(seconds: 2))) {
|
|
|
controller.setLastPressedAt(DateTime.now());
|
|
|
ToastUtil.showToast(StringName.exitAppTip.tr);
|
|
|
} else {
|
|
|
@@ -40,6 +41,8 @@ class MainTabPage extends BasePage<MainController> {
|
|
|
}
|
|
|
},
|
|
|
child: Scaffold(
|
|
|
+ extendBody: true,
|
|
|
+ backgroundColor: Colors.transparent,
|
|
|
key: controller.scaffoldKey,
|
|
|
body: Obx(() {
|
|
|
return pages[controller.currentIndex];
|
|
|
@@ -67,41 +70,58 @@ class MainTabPage extends BasePage<MainController> {
|
|
|
|
|
|
Widget buildAIChatBtn() {
|
|
|
return GestureDetector(
|
|
|
- onTap: () {
|
|
|
- controller.onChatClick();
|
|
|
- },
|
|
|
- child: Align(
|
|
|
- alignment: Alignment.bottomCenter,
|
|
|
- child: SizedBox(
|
|
|
- width: 62.w,
|
|
|
- child: AspectRatio(
|
|
|
- aspectRatio: 195 / 223,
|
|
|
- child: Assets.images.mainTabSecretary.image()),
|
|
|
- ),
|
|
|
+ onTap: () {
|
|
|
+ controller.onChatClick();
|
|
|
+ },
|
|
|
+ child: SizedBox(
|
|
|
+ width: 52.w,
|
|
|
+ child: AspectRatio(
|
|
|
+ aspectRatio: 1 / 1,
|
|
|
+ child: Assets.images.mainTabSecretary.image()),
|
|
|
));
|
|
|
}
|
|
|
|
|
|
- BottomAppBar buildBottomAppBar() {
|
|
|
- return BottomAppBar(
|
|
|
- color: Colors.white,
|
|
|
- height: 56.h,
|
|
|
- padding: EdgeInsets.zero,
|
|
|
- child: Flex(
|
|
|
- mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
|
- direction: Axis.horizontal,
|
|
|
- children: <Widget>[
|
|
|
- Expanded(
|
|
|
- flex: 1,
|
|
|
- child: bottomAppBarItem(0),
|
|
|
- ),
|
|
|
- SizedBox(width: 62.w),
|
|
|
- const SizedBox(),
|
|
|
- Expanded(
|
|
|
- flex: 1,
|
|
|
- child: bottomAppBarItem(1),
|
|
|
+ Widget buildBottomAppBar() {
|
|
|
+ return Container(
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: ColorName.black5.withOpacity(0.05), // 阴影颜色
|
|
|
+ blurRadius: 23, // 阴影模糊半径
|
|
|
+ spreadRadius: 2, // 阴影扩展半径
|
|
|
+ offset: const Offset(0, 0), // 阴影位置,向上偏移
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
+ child: BottomAppBar(
|
|
|
+ color: Colors.white,
|
|
|
+ height: Constants.bottomBarHeight,
|
|
|
+ padding: EdgeInsets.zero,
|
|
|
+ shape: const CusCircularNotchedRectangle(),
|
|
|
+ child: Flex(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
|
+ direction: Axis.horizontal,
|
|
|
+ children: <Widget>[
|
|
|
+ Expanded(
|
|
|
+ flex: 1,
|
|
|
+ child: bottomAppBarItem(0),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ width: 62.w,
|
|
|
+ height: double.infinity,
|
|
|
+ child: Align(
|
|
|
+ alignment: const Alignment(0.0, 0.6),
|
|
|
+ child: Text(StringName.mainTabAi.tr,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 10.sp, color: ColorName.tabUnCheckColor))),
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ flex: 1,
|
|
|
+ child: bottomAppBarItem(1),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -144,3 +164,88 @@ class MainTabPage extends BasePage<MainController> {
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class CusCircularNotchedRectangle extends NotchedShape {
|
|
|
+ /// Creates a [CusCircularNotchedRectangle].
|
|
|
+ ///
|
|
|
+ /// The same object can be used to create multiple shapes.
|
|
|
+ const CusCircularNotchedRectangle();
|
|
|
+
|
|
|
+ /// Creates a [Path] that describes a rectangle with a smooth circular notch.
|
|
|
+ ///
|
|
|
+ /// `host` is the bounding box for the returned shape. Conceptually this is
|
|
|
+ /// the rectangle to which the notch will be applied.
|
|
|
+ ///
|
|
|
+ /// `guest` is the bounding box of a circle that the notch accommodates. All
|
|
|
+ /// points in the circle bounded by `guest` will be outside of the returned
|
|
|
+ /// path.
|
|
|
+ ///
|
|
|
+ /// The notch is curve that smoothly connects the host's top edge and
|
|
|
+ /// the guest circle.
|
|
|
+ // TODO(amirh): add an example diagram here.
|
|
|
+ @override
|
|
|
+ Path getOuterPath(Rect host, Rect? guest) {
|
|
|
+ if (guest == null || !host.overlaps(guest)) {
|
|
|
+ return Path()..addRect(host);
|
|
|
+ }
|
|
|
+
|
|
|
+ // The guest's shape is a circle bounded by the guest rectangle.
|
|
|
+ // So the guest's radius is half the guest width.
|
|
|
+ final double notchRadius = guest.width / 2.0;
|
|
|
+
|
|
|
+ // We build a path for the notch from 3 segments:
|
|
|
+ // Segment A - a Bezier curve from the host's top edge to segment B.
|
|
|
+ // Segment B - an arc with radius notchRadius.
|
|
|
+ // Segment C - a Bezier curve from segment B back to the host's top edge.
|
|
|
+ //
|
|
|
+ // A detailed explanation and the derivation of the formulas below is
|
|
|
+ // available at: https://goo.gl/Ufzrqn
|
|
|
+
|
|
|
+ const double s1 = 20.0;
|
|
|
+ const double s2 = 6;
|
|
|
+
|
|
|
+ final double r = notchRadius;
|
|
|
+ final double a = -1.0 * r - s2;
|
|
|
+ final double b = host.top - guest.center.dy;
|
|
|
+
|
|
|
+ final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
|
|
|
+ final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
|
|
|
+ final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
|
|
|
+ final double p2yA = math.sqrt(r * r - p2xA * p2xA);
|
|
|
+ final double p2yB = math.sqrt(r * r - p2xB * p2xB);
|
|
|
+
|
|
|
+ final List<Offset?> p = List<Offset?>.filled(6, null);
|
|
|
+
|
|
|
+ // p0, p1, and p2 are the control points for segment A.
|
|
|
+ p[0] = Offset(a - s1, b);
|
|
|
+ p[1] = Offset(a, b);
|
|
|
+ final double cmp = b < 0 ? -1.0 : 1.0;
|
|
|
+ p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB);
|
|
|
+
|
|
|
+ // p3, p4, and p5 are the control points for segment B, which is a mirror
|
|
|
+ // of segment A around the y axis.
|
|
|
+ p[3] = Offset(-1.0 * p[2]!.dx, p[2]!.dy);
|
|
|
+ p[4] = Offset(-1.0 * p[1]!.dx, p[1]!.dy);
|
|
|
+ p[5] = Offset(-1.0 * p[0]!.dx, p[0]!.dy);
|
|
|
+
|
|
|
+ // translate all points back to the absolute coordinate system.
|
|
|
+ for (int i = 0; i < p.length; i += 1) {
|
|
|
+ p[i] = p[i]! + guest.center;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Path()
|
|
|
+ ..moveTo(host.left, host.top)
|
|
|
+ ..lineTo(p[0]!.dx, p[0]!.dy)
|
|
|
+ ..quadraticBezierTo(p[1]!.dx, p[1]!.dy, p[2]!.dx, p[2]!.dy)
|
|
|
+ ..arcToPoint(
|
|
|
+ p[3]!,
|
|
|
+ radius: Radius.circular(notchRadius),
|
|
|
+ clockwise: false,
|
|
|
+ )
|
|
|
+ ..quadraticBezierTo(p[4]!.dx, p[4]!.dy, p[5]!.dx, p[5]!.dy)
|
|
|
+ ..lineTo(host.right, host.top)
|
|
|
+ ..lineTo(host.right, host.bottom)
|
|
|
+ ..lineTo(host.left, host.bottom)
|
|
|
+ ..close();
|
|
|
+ }
|
|
|
+}
|