Ver Fonte

[new]新增登录页面

Destiny há 1 ano atrás
pai
commit
f46f1753fb

BIN
assets/images/bg_login.webp


BIN
assets/images/icon_login_logo.webp


BIN
assets/images/icon_select_false.webp


BIN
assets/images/icon_select_true.webp


BIN
assets/images/icon_talk_start_close.webp


+ 10 - 1
lib/base/base_controller.dart

@@ -1,3 +1,12 @@
+import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 
-class BaseController extends GetxController {}
+class BaseController extends GetxController {
+  /// 隐藏键盘
+  void hideKeyboard(BuildContext context) {
+    FocusScopeNode currentFocus = FocusScope.of(context);
+    if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
+      FocusManager.instance.primaryFocus!.unfocus();
+    }
+  }
+}

+ 17 - 7
lib/base/base_page.dart

@@ -1,10 +1,11 @@
+import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:get/get.dart';
 
 import '../resource/colors.gen.dart';
 
-abstract class BasePage<T extends GetxController> extends GetView<T> {
+abstract class BasePage<T extends BaseController> extends GetView<T> {
   const BasePage({super.key});
 
   @override
@@ -14,15 +15,24 @@ abstract class BasePage<T extends GetxController> extends GetView<T> {
         statusBarColor: Colors.transparent, // 设置状态栏颜色为透明
         statusBarIconBrightness: _getStatusBarDarkFont(),
       ),
-      child: Container(
-        color: backgroundColor(),
-        child: immersive()
-            ? buildBody(context)
-            : SafeArea(child: buildBody(context)),
+      child: GestureDetector(
+        onTap: () {
+          // controller.hideKeyboard(context);
+          backgroundOnTapEvent();
+        },
+        child: Container(
+          color: backgroundColor(),
+          child: immersive()
+              ? buildBody(context)
+              : SafeArea(child: buildBody(context)),
+        ),
       ),
     );
   }
 
+  // 点击空白处
+  void backgroundOnTapEvent() {}
+
   Brightness _getStatusBarDarkFont() {
     return statusBarDarkFont() ? Brightness.dark : Brightness.light;
   }
@@ -40,4 +50,4 @@ abstract class BasePage<T extends GetxController> extends GetView<T> {
   Color backgroundColor() {
     return ColorName.bgColorPrimary;
   }
-}
+}

+ 1 - 1
lib/data/api/atmob_api.dart

@@ -37,4 +37,4 @@ abstract class AtmobApi {
       @Body() AgendaRequest request);
 }
 
-final atmobApi = AtmobApi(defaultDio, baseUrl: Constants.baseUrl);
+final atmobApi = AtmobApi(defaultDio, baseUrl: Constants.baseUrl);

+ 77 - 35
lib/module/chat/start/view.dart

@@ -1,5 +1,3 @@
-import 'dart:ffi';
-
 import 'package:electronic_assistant/base/base_page.dart';
 import 'package:electronic_assistant/module/chat/start/controller.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
@@ -7,6 +5,7 @@ import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:electronic_assistant/utils/expand.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
 
 class ChatStartPage extends BasePage<ChatStartController> {
   const ChatStartPage({super.key});
@@ -18,10 +17,16 @@ class ChatStartPage extends BasePage<ChatStartController> {
 
   @override
   Color backgroundColor() {
-    // TODO: implement backgroundColor
     return ColorName.transparent;
   }
 
+  // 点击空白处收起键盘
+  @override
+  void backgroundOnTapEvent() {
+    super.backgroundOnTapEvent();
+    FocusScope.of(Get.context!).requestFocus(FocusNode());
+  }
+
   @override
   Widget buildBody(BuildContext context) {
     return Container(
@@ -39,42 +44,79 @@ class ChatStartPage extends BasePage<ChatStartController> {
             width: double.infinity,
             child: Assets.images.bgTalkStart.image(),
           ),
-          Column(
-            children: [
-              buildTopBar(),
-              buildDescripe(),
-              buildInputTF(),
-              Expanded(child: SizedBox()),
-              TextButton(
+          Scaffold(
+            // resizeToAvoidBottomInset: false,
+            backgroundColor: Colors.transparent,
+            appBar: AppBar(
+              scrolledUnderElevation: 0,
+              leading: IconButton(
                 onPressed: () {
-                  print(ScreenUtil().bottomBarHeight);
+                  Navigator.pop(context);
                 },
-                child: Container(
-                  margin: EdgeInsets.only(
-                      left: 16.w,
-                      right: 16.w,
-                      bottom: ScreenUtil().bottomBarHeight + 6.h),
-                  height: 48.h,
-                  width: double.infinity,
-                  alignment: Alignment.center,
-                  decoration: BoxDecoration(
-                    gradient: LinearGradient(
-                      colors: ['#6177F2'.toColor(), '#8B9DFF'.toColor()],
-                      stops: const [0, 1.0],
-                    ),
-                    borderRadius: BorderRadius.circular(8),
-                  ),
-                  child: Text(
-                    "下一步",
-                    style: TextStyle(
-                      color: ColorName.white,
-                      fontSize: 16,
-                      fontWeight: FontWeight.w500,
-                    ),
+                icon: UnconstrainedBox(
+                  child: SizedBox(
+                    width: 28.w,
+                    height: 28.w,
+                    child: Assets.images.iconTalkStartClose.image(),
                   ),
                 ),
-              )
-            ],
+              ),
+              backgroundColor: Colors.transparent,
+              title: Text(
+                "定制你的秘书",
+                textAlign: TextAlign.center,
+                style: TextStyle(
+                  fontSize: 16.w,
+                  color: ColorName.primaryTextColor,
+                  fontWeight: FontWeight.w500,
+                ),
+              ),
+            ),
+            body: SingleChildScrollView(
+              physics: const ClampingScrollPhysics(),
+              child: SizedBox(
+                width: double.infinity,
+                height: ScreenUtil().screenHeight -
+                    80.h -
+                    AppBar().preferredSize.height,
+                child: Column(
+                  children: [
+                    buildDescripe(),
+                    buildInputTF(),
+                    const Expanded(child: SizedBox()),
+                    GestureDetector(
+                      onTap: () {
+                        print(ScreenUtil().bottomBarHeight);
+                      },
+                      child: Container(
+                        margin: EdgeInsets.only(
+                            left: 16.w,
+                            right: 16.w,
+                            bottom: ScreenUtil().bottomBarHeight + 6.h),
+                        height: 48.h,
+                        width: double.infinity,
+                        alignment: Alignment.center,
+                        decoration: BoxDecoration(
+                          gradient: LinearGradient(
+                            colors: ['#6177F2'.toColor(), '#8B9DFF'.toColor()],
+                            stops: const [0, 1.0],
+                          ),
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                        child: const Text(
+                          "下一步",
+                          style: TextStyle(
+                            color: ColorName.white,
+                            fontSize: 16,
+                            fontWeight: FontWeight.w500,
+                          ),
+                        ),
+                      ),
+                    )
+                  ],
+                ),
+              ),
+            ),
           ),
         ],
       ),

+ 2 - 0
lib/module/home/controller.dart

@@ -36,6 +36,8 @@ class HomePageController extends BaseController {
           duration: const Duration(milliseconds: 500));
     }).whenComplete(() {
       refreshController.refreshCompleted();
+    }).catchError((e) {
+      accountRepository.logout();
     });
   }
 

+ 3 - 0
lib/module/home/view.dart

@@ -1,5 +1,6 @@
 import 'package:electronic_assistant/base/base_page.dart';
 import 'package:electronic_assistant/data/bean/talks.dart';
+import 'package:electronic_assistant/data/repositories/account_repository.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
 import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:electronic_assistant/resource/string.gen.dart';
@@ -41,6 +42,7 @@ class HomePage extends BasePage<HomePageController> {
                 controller: controller.refreshController,
                 onRefresh: () {
                   controller.requestHomeData();
+                  // controller.refreshController.refreshCompleted();
                 },
                 child: CustomScrollView(
                   slivers: [
@@ -162,6 +164,7 @@ class HomePage extends BasePage<HomePageController> {
               ),
             )),
         onTap: () {
+          accountRepository.logout();
           ToastUtil.showToast('GoStore');
         });
   }

+ 11 - 0
lib/module/login/controller.dart

@@ -7,6 +7,7 @@ import 'package:get/get.dart';
 class LoginController extends BaseController {
   final phone = "".obs;
   final code = "".obs;
+  final isAgree = false.obs;
 
   @override
   void onInit() {
@@ -27,6 +28,16 @@ class LoginController extends BaseController {
   }
 
   void login() {
+    if (code.value.isEmpty) {
+      ToastUtil.showToast("请输入验证码");
+      return;
+    }
+
+    if (!isAgree.value) {
+      ToastUtil.showToast("请先阅读并同意《隐私政策》和《用户使用协议》");
+      return;
+    }
+
     if (phone.value.isEmpty || code.value.isEmpty) {
       return;
     }

+ 320 - 32
lib/module/login/view.dart

@@ -1,5 +1,12 @@
 import 'package:electronic_assistant/base/base_page.dart';
+import 'package:electronic_assistant/resource/assets.gen.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:electronic_assistant/widget/login_code_btn.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
 
 import 'controller.dart';
 
@@ -7,42 +14,323 @@ class LoginPage extends BasePage<LoginController> {
   const LoginPage({super.key});
 
   @override
+  bool immersive() {
+    return true;
+  }
+
+  // 点击空白处收起键盘
+  @override
+  void backgroundOnTapEvent() {
+    super.backgroundOnTapEvent();
+    FocusScope.of(Get.context!).requestFocus(FocusNode());
+  }
+
+  @override
   Widget buildBody(BuildContext context) {
-    return Scaffold(
-      body: Column(
-        children: <Widget>[
-          TextField(
-            onChanged: (text) {
-              controller.setPhone(text);
-            },
-            decoration: const InputDecoration(
-                labelText: "手机号",
-                hintText: "您的手机号",
-                prefixIcon: Icon(Icons.person)),
+    return Stack(
+      children: [
+        buildBackgroundImage(),
+        Scaffold(
+          resizeToAvoidBottomInset: false,
+          backgroundColor: Colors.transparent,
+          appBar: AppBar(
+            leading: IconButton(
+              icon: const Icon(Icons.arrow_back_ios),
+              onPressed: () {
+                Navigator.pop(context);
+              },
+            ),
+            backgroundColor: Colors.transparent,
+            systemOverlayStyle: SystemUiOverlayStyle.dark,
+          ),
+          body: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Container(
+                margin: EdgeInsets.only(left: 28.w, top: 64.h),
+                width: 194.w,
+                height: 71.h,
+                child: Image(image: Assets.images.iconLoginLogo.provider()),
+              ),
+              Container(
+                margin: EdgeInsets.only(top: 44.h, left: 28.w, right: 28.w),
+                alignment: Alignment.center,
+                height: 44.h,
+                decoration: BoxDecoration(
+                  border: Border(
+                    bottom: BorderSide(
+                      width: 1.0,
+                      color: "#F0F0F0".toColor(),
+                    ),
+                  ),
+                ),
+                child: TextField(
+                  maxLines: 1,
+                  textAlignVertical: TextAlignVertical.center,
+                  textInputAction: TextInputAction.search,
+                  decoration: InputDecoration(
+                    hintText: '输入手机号码',
+                    hintStyle:
+                        TextStyle(fontSize: 16, color: "#AFAFAF".toColor()),
+                    labelStyle: const TextStyle(
+                      fontSize: 16,
+                      color: ColorName.primaryTextColor,
+                    ),
+                    contentPadding: const EdgeInsets.all(0),
+                    border:
+                        const OutlineInputBorder(borderSide: BorderSide.none),
+                    enabled: true,
+                  ),
+                  style: TextStyle(fontSize: 14.sp),
+                  onChanged: (value) {
+                    controller.setPhone(value);
+                  },
+                ),
+              ),
+              Container(
+                margin: EdgeInsets.only(top: 36.h, left: 28.w, right: 28.w),
+                alignment: Alignment.center,
+                height: 44.h,
+                decoration: BoxDecoration(
+                  border: Border(
+                    bottom: BorderSide(
+                      width: 1.0,
+                      color: "#F0F0F0".toColor(),
+                    ),
+                  ),
+                ),
+                child: Row(
+                  children: [
+                    Expanded(
+                      child: TextField(
+                        maxLines: 1,
+                        textAlignVertical: TextAlignVertical.center,
+                        textInputAction: TextInputAction.search,
+                        decoration: InputDecoration(
+                          hintText: '输入验证码',
+                          hintStyle: TextStyle(
+                              fontSize: 16, color: "#AFAFAF".toColor()),
+                          labelStyle: const TextStyle(
+                            fontSize: 16,
+                            color: ColorName.primaryTextColor,
+                          ),
+                          contentPadding: const EdgeInsets.all(0),
+                          border: const OutlineInputBorder(
+                              borderSide: BorderSide.none),
+                          enabled: true,
+                        ),
+                        style: TextStyle(fontSize: 14.sp),
+                        onChanged: (value) {
+                          controller.setCode(value);
+                        },
+                      ),
+                    ),
+                    LoginCodeBtn(
+                      onTapCallback: () {
+                        controller.getUserCode();
+                      },
+                      available: true,
+                    ),
+                  ],
+                ),
+              ),
+              GestureDetector(
+                onTap: () {
+                  controller.login();
+                },
+                child: Container(
+                  margin: EdgeInsets.only(
+                    top: 54.h,
+                    left: 28.w,
+                    right: 28.w,
+                  ),
+                  height: 48.h,
+                  alignment: Alignment.center,
+                  decoration: BoxDecoration(
+                    gradient: LinearGradient(
+                      colors: ['#6177F2'.toColor(), '#8B9DFF'.toColor()],
+                      stops: const [0, 1.0],
+                    ),
+                    borderRadius: BorderRadius.circular(8),
+                  ),
+                  child: const Text(
+                    "登录",
+                    style: TextStyle(
+                      color: ColorName.white,
+                      fontSize: 16,
+                      fontWeight: FontWeight.w500,
+                    ),
+                  ),
+                ),
+              ),
+              Container(
+                margin: EdgeInsets.only(left: 28.w, top: 16.h),
+                child: Row(
+                  children: [
+                    Obx(
+                      () {
+                        return GestureDetector(
+                          onTap: () {
+                            controller.isAgree.value =
+                                !controller.isAgree.value;
+                          },
+                          child: SizedBox(
+                              width: 20.w,
+                              height: 20.w,
+                              child: controller.isAgree.value
+                                  ? Assets.images.iconSelectTrue.image()
+                                  : Assets.images.iconSelectFalse.image()),
+                        );
+                      },
+                    ),
+                    Text(
+                      "我已阅读并同意",
+                      style: TextStyle(
+                        color: "#AFAFAF".toColor(),
+                        fontSize: 12,
+                      ),
+                    ),
+                    GestureDetector(
+                      onTap: () {},
+                      child: Text(
+                        "《隐私政策》",
+                        style: TextStyle(
+                          color: "#5E8BFF".toColor(),
+                          fontSize: 12,
+                        ),
+                      ),
+                    ),
+                    Text(
+                      "和",
+                      style: TextStyle(
+                        color: "#AFAFAF".toColor(),
+                        fontSize: 12,
+                      ),
+                    ),
+                    GestureDetector(
+                      onTap: () {},
+                      child: Text(
+                        "《用户使用协议》",
+                        style: TextStyle(
+                          color: "#5E8BFF".toColor(),
+                          fontSize: 12,
+                        ),
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+    // return Scaffold(
+    //   body: Column(
+    //     children: <Widget>[
+    //       TextField(
+    //         onChanged: (text) {
+    //           controller.setPhone(text);
+    //         },
+    //         decoration: const InputDecoration(
+    //             labelText: "手机号",
+    //             hintText: "您的手机号",
+    //             prefixIcon: Icon(Icons.person)),
+    //       ),
+    //       TextField(
+    //         onChanged: (text) {
+    //           controller.setCode(text);
+    //         },
+    //         decoration: const InputDecoration(
+    //             labelText: "验证码",
+    //             hintText: "您的验证码",
+    //             prefixIcon: Icon(Icons.lock)),
+    //       ),
+    //       ElevatedButton(
+    //         child: const Text("获取验证码"),
+    //         onPressed: () {
+    //           controller.getUserCode();
+    //         },
+    //       ),
+    //       ElevatedButton(
+    //         child: const Text("登录"),
+    //         onPressed: () {
+    //           controller.login();
+    //         },
+    //       )
+    //     ],
+    //   ),
+    // );
+  }
+
+  Widget buildBackgroundImage() {
+    return Image(image: Assets.images.bgLogin.provider());
+  }
+
+  Widget buildBackgroundGradient() {
+    return Stack(
+      children: [
+        Container(
+          width: 1.sw,
+          height: 1.sh,
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                '#FFC0CF'.toColor().withOpacity(0.62),
+                '#F6F6F6'.toColor().withOpacity(0)
+              ],
+              begin: Alignment.topCenter,
+              end: Alignment.bottomCenter,
+              stops: const [0, 1.0],
+            ),
+          ),
+        ),
+        Container(
+          width: 1.sw,
+          height: 1.sh,
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                '#CADCFD'.toColor().withOpacity(0.72),
+                '#F6F5F8'.toColor().withOpacity(0.72)
+              ],
+              begin: Alignment.topCenter,
+              end: Alignment.bottomCenter,
+              stops: const [0, 1.0],
+            ),
           ),
-          TextField(
-            onChanged: (text) {
-              controller.setCode(text);
-            },
-            decoration: const InputDecoration(
-                labelText: "验证码",
-                hintText: "您的验证码",
-                prefixIcon: Icon(Icons.lock)),
+        ),
+        Container(
+          width: 1.sw,
+          height: 1.sh,
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                '#B0BCFF'.toColor(),
+                '#FFFFFF'.toColor(),
+              ],
+              begin: Alignment.topCenter,
+              end: Alignment.bottomCenter,
+              stops: const [0, 1.0],
+            ),
           ),
-          ElevatedButton(
-            child: const Text("获取验证码"),
-            onPressed: () {
-              controller.getUserCode();
-            },
+        ),
+        Container(
+          width: 1.sw,
+          height: 1.sh,
+          decoration: BoxDecoration(
+            gradient: LinearGradient(
+              colors: [
+                '#7E92FF'.toColor(),
+                '#B0BCFF'.toColor(),
+              ],
+              begin: Alignment.topCenter,
+              end: Alignment.bottomCenter,
+              stops: const [0, 1.0],
+            ),
           ),
-          ElevatedButton(
-            child: const Text("登录"),
-            onPressed: () {
-              controller.login();
-            },
-          )
-        ],
-      ),
+        ),
+      ],
     );
   }
 }

+ 1 - 1
lib/utils/expand.dart

@@ -34,4 +34,4 @@ extension DurationExtension on double? {
       return '${hours}h${minutes}m${seconds}s';
     }
   }
-}
+}

+ 115 - 0
lib/widget/login_code_btn.dart

@@ -0,0 +1,115 @@
+import 'dart:async';
+import 'package:electronic_assistant/utils/expand.dart';
+import 'package:flutter/material.dart';
+
+/// 墨水瓶(`InkWell`)可用时使用的字体样式。
+final TextStyle _availableStyle = TextStyle(
+  fontSize: 14.0,
+  color: "#6177F2".toColor(),
+);
+
+/// 墨水瓶(`InkWell`)不可用时使用的样式。
+final TextStyle _unavailableStyle = TextStyle(
+  fontSize: 14.0,
+  color: "#AFAFAF".toColor(),
+);
+
+class LoginCodeBtn extends StatefulWidget {
+  /// 倒计时的秒数,默认60秒。
+  final int countdown;
+
+  /// 用户点击时的回调函数。
+  final Function onTapCallback;
+
+  /// 是否可以获取验证码,默认为`false`。
+  final bool available;
+
+  const LoginCodeBtn({
+    super.key,
+    this.countdown = 60,
+    required this.onTapCallback,
+    this.available = false,
+  });
+
+  @override
+  _LoginCodeBtnState createState() => _LoginCodeBtnState();
+}
+
+class _LoginCodeBtnState extends State<LoginCodeBtn> {
+  /// 倒计时的计时器。
+  late Timer timer;
+
+  /// 当前倒计时的秒数。
+  late int _seconds;
+
+  /// 当前墨水瓶(`InkWell`)的字体样式。
+  TextStyle inkWellStyle = _availableStyle;
+
+  /// 当前墨水瓶(`InkWell`)的文本。
+  String _verifyStr = '获取验证码';
+
+  @override
+  void initState() {
+    super.initState();
+    _seconds = widget.countdown;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _cancelTimer();
+  }
+
+  /// 启动倒计时的计时器。
+  void _startTimer() {
+    // 计时器(`Timer`)组件的定期(`periodic`)构造函数,创建一个新的重复计时器。
+    timer = Timer.periodic(const Duration(seconds: 1), (timer) {
+      if (_seconds == 0) {
+        _cancelTimer();
+        _seconds = widget.countdown;
+        inkWellStyle = _availableStyle;
+        setState(() {});
+        return;
+      }
+      _seconds--;
+      _verifyStr = '$_seconds' 's后重新获取';
+      setState(() {});
+      if (_seconds == 0) {
+        _verifyStr = '重新发送';
+      }
+    });
+  }
+
+  /// 取消倒计时的计时器。
+  void _cancelTimer() {
+    // 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。
+    timer.cancel();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // 墨水瓶(`InkWell`)组件,响应触摸的矩形区域。
+    return widget.available
+        ? InkWell(
+            onTap: (_seconds == widget.countdown)
+                ? () {
+                    _startTimer();
+                    inkWellStyle = _unavailableStyle;
+                    _verifyStr = '$_seconds' 's后重新获取';
+                    setState(() {});
+                    widget.onTapCallback();
+                  }
+                : null,
+            child: Text(
+              '  $_verifyStr  ',
+              style: inkWellStyle,
+            ),
+          )
+        : InkWell(
+            child: Text(
+              '  获取验证码  ',
+              style: _unavailableStyle,
+            ),
+          );
+  }
+}