phone_login_view.dart 17.2 KB
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

import '../controllers/login_controller.dart';

class PhoneLoginView extends GetView<LoginController> {
  const PhoneLoginView({super.key});

  static const _designHeight = 812.0;
  static const _brandColor = Color(0xFF845EEE);
  static const _titleColor = Color(0xFF2C2020);
  static const _subtitleColor = Color(0xFF908B91);

  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: const SystemUiOverlayStyle(
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.dark,
        statusBarBrightness: Brightness.light,
        systemNavigationBarColor: Colors.transparent,
        systemNavigationBarIconBrightness: Brightness.dark,
      ),
      child: Scaffold(
        backgroundColor: Colors.white,
        body: LayoutBuilder(
          builder: (context, constraints) {
            final screenSize = MediaQuery.sizeOf(context);
            final width = constraints.maxWidth.isFinite
                ? constraints.maxWidth
                : screenSize.width;
            final height = constraints.maxHeight.isFinite
                ? constraints.maxHeight
                : screenSize.height;

            double top(double value) => height * value / _designHeight;

            return SingleChildScrollView(
              physics: const ClampingScrollPhysics(),
              child: SizedBox(
                height: height,
                width: width,
                child: Stack(
                  fit: StackFit.expand,
                  children: [
                    // Background Gradient
                    const DecoratedBox(
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          begin: Alignment.topCenter,
                          end: Alignment.bottomCenter,
                          colors: [
                            Color(0xFFECE3FF),
                            Color(0xFFEDE4FF),
                            Color(0xFFEEE6FF),
                            Color(0xFFF9F6FF),
                          ],
                          stops: [0, 0.33173, 0.74038, 1],
                        ),
                      ),
                    ),

                    // Top Back Button
                    Positioned(
                      top: top(48),
                      left: 12,
                      child: IconButton(
                        icon: const Icon(
                          Icons.arrow_back_ios_new_rounded,
                          color: _titleColor,
                          size: 22,
                        ),
                        onPressed: () {
                          Get.back();
                        },
                      ),
                    ),

                    // Top logo text & stylized icon
                    Positioned(
                      top: top(62),
                      left: 0,
                      right: 0,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Container(
                            padding: const EdgeInsets.all(6),
                            decoration: const BoxDecoration(
                              color: _brandColor,
                              shape: BoxShape.circle,
                            ),
                            child: const Icon(
                              Icons.favorite_rounded,
                              color: Colors.white,
                              size: 14,
                            ),
                          ),
                          const SizedBox(width: 8),
                          const Text(
                            'Double Feel',
                            style: TextStyle(
                              color: _titleColor,
                              fontSize: 18,
                              fontWeight: FontWeight.w700,
                              letterSpacing: 0.5,
                            ),
                          ),
                        ],
                      ),
                    ),

                    // Headers
                    Positioned(
                      top: top(147),
                      left: 30,
                      right: 30,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            '还没有Feel牌吗?',
                            style: TextStyle(
                              color: _titleColor,
                              fontSize: 26,
                              fontWeight: FontWeight.w800,
                            ),
                          ),
                          const SizedBox(height: 7),
                          const Text(
                            '注册/登录手机号,成为Double Feel的一员吧~',
                            style: TextStyle(
                              color: _subtitleColor,
                              fontSize: 14,
                              fontWeight: FontWeight.w400,
                            ),
                          ),
                        ],
                      ),
                    ),

                    // Card with input fields
                    Positioned(
                      top: top(240),
                      left: 16,
                      right: 16,
                      child: Container(
                        padding: const EdgeInsets.all(24),
                        decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.circular(32),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black.withValues(alpha: 0.04),
                              blurRadius: 24,
                              offset: const Offset(0, 8),
                            ),
                          ],
                        ),
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            const SizedBox(height: 12),
                            // Phone Number Field
                            Obx(() {
                              final phoneNotEmpty =
                                  controller.phoneInput.value.isNotEmpty;
                              return TextField(
                                controller: controller.phoneController,
                                keyboardType: TextInputType.phone,
                                maxLength: 13,
                                inputFormatters: [
                                  FilteringTextInputFormatter.allow(
                                      RegExp(r'[0-9\s]')),
                                  _PhoneTextInputFormatter(),
                                ],
                                style: const TextStyle(
                                  color: _titleColor,
                                  fontSize: 15,
                                  fontWeight: FontWeight.w500,
                                ),
                                decoration: InputDecoration(
                                  hintText: '请输入手机号',
                                  hintStyle: const TextStyle(
                                    color: _subtitleColor,
                                    fontSize: 14,
                                  ),
                                  counterText: '',
                                  filled: true,
                                  fillColor: const Color(0xFFF7F6FA),
                                  contentPadding: const EdgeInsets.symmetric(
                                    horizontal: 20,
                                    vertical: 16,
                                  ),
                                  border: OutlineInputBorder(
                                    borderRadius: BorderRadius.circular(16),
                                    borderSide: BorderSide.none,
                                  ),
                                  suffixIcon: phoneNotEmpty
                                      ? GestureDetector(
                                          onTap: () {
                                            controller.phoneController.clear();
                                          },
                                          child: const Icon(
                                            Icons.cancel,
                                            color: _subtitleColor,
                                            size: 20,
                                          ),
                                        )
                                      : null,
                                ),
                              );
                            }),
                            const SizedBox(height: 16),

                            // Verification Code Field Row
                            Obx(() {
                              final countdown =
                                  controller.countdownSeconds.value;
                              final isSending = controller.isSendingCode.value;
                              final isCounting = countdown > 0;

                              String btnText = '发送验证码';
                              if (isSending) {
                                btnText = '发送中';
                              } else if (isCounting) {
                                btnText = '已发送 ($countdown)';
                              }

                              final bool canSend = controller.canRequestCode;

                              return Row(
                                children: [
                                  Expanded(
                                    child: TextField(
                                      controller: controller.codeController,
                                      keyboardType: TextInputType.text,
                                      maxLength: 8,
                                      style: const TextStyle(
                                        color: _titleColor,
                                        fontSize: 15,
                                        fontWeight: FontWeight.w500,
                                      ),
                                      decoration: InputDecoration(
                                        hintText: '请输入验证码',
                                        hintStyle: const TextStyle(
                                          color: _subtitleColor,
                                          fontSize: 14,
                                        ),
                                        counterText: '',
                                        filled: true,
                                        fillColor: const Color(0xFFF7F6FA),
                                        contentPadding:
                                            const EdgeInsets.symmetric(
                                          horizontal: 20,
                                          vertical: 16,
                                        ),
                                        border: OutlineInputBorder(
                                          borderRadius:
                                              BorderRadius.circular(16),
                                          borderSide: BorderSide.none,
                                        ),
                                      ),
                                    ),
                                  ),
                                  const SizedBox(width: 12),
                                  SizedBox(
                                    height: 52,
                                    width: 110,
                                    child: ElevatedButton(
                                      onPressed: canSend
                                          ? controller.requestVerifyCode
                                          : null,
                                      style: ElevatedButton.styleFrom(
                                        backgroundColor: _brandColor,
                                        foregroundColor: Colors.white,
                                        disabledBackgroundColor: const Color(
                                            0xFFECE9F6), // subtle tint
                                        disabledForegroundColor: isCounting
                                            ? _brandColor
                                            : _subtitleColor,
                                        elevation: 0,
                                        padding: EdgeInsets.zero,
                                        shape: RoundedRectangleBorder(
                                          borderRadius:
                                              BorderRadius.circular(16),
                                        ),
                                        textStyle: const TextStyle(
                                          fontSize: 12,
                                          fontWeight: FontWeight.w700,
                                        ),
                                      ),
                                      child: Text(btnText),
                                    ),
                                  ),
                                ],
                              );
                            }),
                            const SizedBox(height: 24),

                            // Info label
                            const Text(
                              '未注册的手机号验证通过后将自动注册',
                              style: TextStyle(
                                color: Color(0xFFCCCCCC),
                                fontSize: 12,
                                fontWeight: FontWeight.w400,
                              ),
                              textAlign: TextAlign.center,
                            ),
                            const SizedBox(height: 16),

                            // Immediate Login Button
                            Obx(() {
                              final canLogin = controller.canLogin;
                              final isLoggingIn =
                                  controller.isLoggingIn.value;

                              return SizedBox(
                                width: double.infinity,
                                height: 56,
                                child: ElevatedButton(
                                  onPressed:
                                      canLogin ? controller.login : null,
                                  style: ElevatedButton.styleFrom(
                                    backgroundColor: _brandColor,
                                    foregroundColor: Colors.white,
                                    disabledBackgroundColor:
                                        _brandColor.withValues(alpha: 0.4),
                                    disabledForegroundColor: Colors.white,
                                    elevation: 0,
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(28),
                                    ),
                                    textStyle: const TextStyle(
                                      fontSize: 18,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                  child: isLoggingIn
                                      ? const SizedBox(
                                          width: 24,
                                          height: 24,
                                          child: CircularProgressIndicator(
                                            color: Colors.white,
                                            strokeWidth: 2.5,
                                          ),
                                        )
                                      : const Text('立即登录'),
                                ),
                              );
                            }),
                            const SizedBox(height: 12),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

class _PhoneTextInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    final text = newValue.text.replaceAll(' ', '');
    if (text.isEmpty) {
      return newValue;
    }

    final buffer = StringBuffer();
    for (int i = 0; i < text.length; i++) {
      buffer.write(text[i]);
      final nonLeadingIndex = i + 1;
      if (nonLeadingIndex == 3 || nonLeadingIndex == 7) {
        if (nonLeadingIndex < text.length) {
          buffer.write(' ');
        }
      }
    }

    final string = buffer.toString();
    return newValue.copyWith(
      text: string,
      selection: TextSelection.collapsed(offset: string.length),
    );
  }
}