login_view.dart 11.9 KB
import 'dart:math' as math;

import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
import 'package:doublefeel_flutter/core/theme/app_theme.dart';
import 'package:doublefeel_flutter/data/local/local_storage.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

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

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

  @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);
            var bottomInset = MediaQuery.paddingOf(context).bottom;
            bottomInset = bottomInset > 0 ? bottomInset : 34;
            final width = constraints.maxWidth.isFinite
                ? constraints.maxWidth
                : screenSize.width;
            final buttonWidth = math.min(280.0, math.max(0.0, width - 64.0));
            final rightOffset = (width - buttonWidth) / 2;
            double bottom(double value) => bottomInset + value;
            final l10n = context.l10n;
            return Stack(
              fit: StackFit.expand,
              children: [
                Column(
                  mainAxisSize: MainAxisSize.max,
                  children: [
                    Image.asset(
                      'assets/images/user_onboarding/bg_login.png',
                      fit: BoxFit.fitWidth,
                    ),
                    Expanded(
                        child: Container(
                      decoration: const BoxDecoration(
                        gradient: LinearGradient(
                            begin: Alignment(0.50, -0.00),
                            end: Alignment(0.50, 1.00),
                            colors: [
                              const Color(0xFFEEE6FF),
                              const Color(0xFFF8F6FF)
                            ]),
                      ),
                    )),
                  ],
                ),
                // Welcome texts
                Positioned(
                  bottom: bottom(253),
                  left: 0,
                  right: 0,
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Image.asset(
                        'assets/images/common/ic_double_feel_text.png',
                        width: 160,
                        height: 42,
                      ),
                      SizedBox(height: 8),
                      Text(
                        l10n.loginSlogan,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          color: context.colors.textSecondary,
                          fontSize: 12,
                          fontWeight: FontWeight.w400,
                          height: 1.50,
                          letterSpacing: 6,
                        ),
                      ),
                    ],
                  ),
                ),

                // Phone login button
                Positioned(
                  bottom: bottom(145),
                  left: 0,
                  right: 0,
                  child: Center(
                    child: _LoginButton(
                      width: buttonWidth,
                      label: l10n.loginWithPhone,
                      onPressed: controller.onPhoneLoginPressed,
                    ),
                  ),
                ),

                // Apple login button
                Positioned(
                  bottom: bottom(85),
                  left: 0,
                  right: 0,
                  child: Center(
                    child: _LoginButton(
                      width: buttonWidth,
                      label: l10n.loginWithApple,
                      onPressed: controller.onAppleLoginPressed,
                      icon: Image.asset(
                        'assets/images/common/ic_apple.png',
                        width: 24,
                        height: 24,
                        color: context.colors.textPrimary,
                      ),
                      buttonStyle: ElevatedButton.styleFrom(
                        backgroundColor: Colors.white,
                        foregroundColor: context.colors.textPrimary,
                        disabledBackgroundColor:
                            context.colors.textPrimary.withValues(alpha: 0.5),
                        disabledForegroundColor: context.colors.textPrimary,
                        elevation: 0,
                        shadowColor: Colors.transparent,
                        padding: EdgeInsets.symmetric(horizontal: 12),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(24),
                        ),
                        textStyle: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w700,
                        ),
                      ),
                    ),
                  ),
                ),
                if (Get.find<LocalStorage>().lastLoginMethod.isNotEmpty)
                  Positioned(
                    right: rightOffset - 4,
                    bottom: Get.find<LocalStorage>().lastLoginMethod == 'apple'
                        ? bottom(117)
                        : bottom(188),
                    child: Container(
                      padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                      decoration: ShapeDecoration(
                        color: const Color(0xFFFF9A6E),
                        shape: RoundedRectangleBorder(
                          side: BorderSide(width: 1, color: Colors.white),
                          borderRadius: BorderRadius.circular(25),
                        ),
                      ),
                      child: Text(
                        l10n.loginLastUsed,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 11,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                  ),
                // Terms agreement text + checkbox
                Positioned(
                  bottom: bottom(40),
                  left: 0,
                  right: 0,
                  child: const _AgreementText(),
                ),

                if (kDebugMode)
                  Positioned(
                    left: 0,
                    right: 0,
                    bottom: bottom(0),
                    child: Center(
                      child: TextButton(
                        onPressed: controller.onDebugPressed,
                        style: TextButton.styleFrom(
                          foregroundColor: context.colors.textSecondary
                              .withValues(alpha: 0.55),
                          textStyle: const TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                        child: Builder(builder: (context) {
                          final environmentConfig =
                              Get.find<AppEnvironmentConfig>();
                          return Text(environmentConfig.environment.value.name);
                        }),
                      ),
                    ),
                  ),
              ],
            );
          },
        ),
      ),
    );
  }
}

class _LoginButton extends StatelessWidget {
  const _LoginButton({
    required this.width,
    required this.label,
    required this.onPressed,
    this.icon,
    this.buttonStyle,
  });

  final double width;
  final String label;
  final Image? icon;
  final ButtonStyle? buttonStyle;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      height: 48,
      child: ElevatedButton(
        onPressed: onPressed,
        style: buttonStyle ??
            ElevatedButton.styleFrom(
              backgroundColor: context.colors.primary,
              foregroundColor: Colors.white,
              disabledBackgroundColor:
                  context.colors.primary.withValues(alpha: 0.5),
              disabledForegroundColor: Colors.white,
              elevation: 0,
              shadowColor: Colors.transparent,
              padding: EdgeInsets.symmetric(horizontal: icon == null ? 24 : 12),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(24),
              ),
              textStyle: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w700,
              ),
            ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (icon != null) ...[
              icon!,
              const SizedBox(width: 8),
            ],
            Flexible(
              child: Text(
                label,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _AgreementText extends GetView<LoginController> {
  const _AgreementText();

  static const _linkColor = Color(0xFF14121E);

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final baseStyle = TextStyle(
      color: context.colors.textSecondary,
      fontSize: 12,
      fontWeight: FontWeight.w400,
      height: 1.2,
    );
    final linkStyle = TextStyle(
      color: _linkColor,
      fontSize: 12,
      fontWeight: FontWeight.w600,
      height: 1.2,
      decoration: TextDecoration.underline,
      decorationColor: _linkColor,
    );

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Obx(() {
          final isChecked = controller.termsChecked.value;
          return GestureDetector(
            onTap: controller.toggleTermsChecked,
            child: Padding(
              padding: const EdgeInsets.only(right: 8.0, top: 4.0, bottom: 4.0),
              child: Icon(
                isChecked
                    ? Icons.check_circle_rounded
                    : Icons.radio_button_unchecked_rounded,
                color: isChecked
                    ? context.colors.primary
                    : context.colors.textSecondary,
                size: 18,
              ),
            ),
          );
        }),
        Text.rich(
          TextSpan(
            style: baseStyle,
            children: [
              TextSpan(text: l10n.loginAgreementPrefix),
              TextSpan(
                text: l10n.loginTerms,
                style: linkStyle,
                recognizer: TapGestureRecognizer()
                  ..onTap = controller.openUserTerms,
              ),
              TextSpan(text: l10n.loginAgreementAnd),
              TextSpan(
                text: l10n.loginPrivacy,
                style: linkStyle,
                recognizer: TapGestureRecognizer()
                  ..onTap = controller.openPrivacyPolicy,
              ),
            ],
          ),
          textAlign: TextAlign.center,
        ),
      ],
    );
  }
}