From 015677d1a8ed645e922ac12d544c0b68c9158ed6 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 17 Oct 2026 02:24:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=20+=20=E4=B8=BB=E9=A2=98=E6=94=B9=E6=A9=99=E7=BA=A2?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E8=AE=BE=E8=AE=A1=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增登录页(Google / 手机号 / 邮箱 + 游客浏览 + 首登送金币) - seedColor 改橙红 #FF5B3D,themeMode 改 dark,背景对齐设计稿 #0B0C10 - app 启动入口改为 LoginPage --- lib/app.dart | 14 +- lib/pages/login/login_view.dart | 341 ++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 lib/pages/login/login_view.dart diff --git a/lib/app.dart b/lib/app.dart index 672cc11..1e1b0cc 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -11,7 +11,7 @@ import 'core/data/services/engagement_controller.dart'; import 'core/data/services/playback_session.dart'; import 'core/data/services/video_player_port.dart'; import 'l10n/app_localizations.dart'; -import 'pages/home/home_view.dart'; +import 'pages/login/login_view.dart'; class LumaDramaApp extends StatelessWidget { const LumaDramaApp({super.key, this.playerFactory = VideoPlayerPort.new}); @@ -49,23 +49,23 @@ class LumaDramaApp extends StatelessWidget { return supported.first; }, debugShowCheckedModeBanner: false, - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, theme: ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF6D4AFF), + seedColor: const Color(0xFFFF5B3D), ), - scaffoldBackgroundColor: const Color(0xFFF8F7FC), + scaffoldBackgroundColor: const Color(0xFF0B0C10), ), darkTheme: ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF9B86FF), + seedColor: const Color(0xFFFF5B3D), brightness: Brightness.dark, ), - scaffoldBackgroundColor: const Color(0xFF101019), + scaffoldBackgroundColor: const Color(0xFF0B0C10), ), - home: const HomePage(), + home: const LoginPage(), ), ), ); diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart new file mode 100644 index 0000000..f210714 --- /dev/null +++ b/lib/pages/login/login_view.dart @@ -0,0 +1,341 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; + +import '../home/home_view.dart'; + +/// 登录页:Google / 手机号 / 邮箱 三种登录入口 + 游客浏览。 +/// 当前为 UI 占位实现,真实登录逻辑在接入后端 /app/auth/** 后补充。 +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + static const _orange = Color(0xFFFF5B3D); + static const _purple = Color(0xFF9278EA); + + void _enterHome() { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const HomePage()), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Column( + children: [ + // 顶部:关闭 + 游客浏览 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _IconButton(icon: Icons.close, onTap: () {}), + TextButton( + onPressed: _enterHome, + style: TextButton.styleFrom( + foregroundColor: Colors.white54, + backgroundColor: Colors.white.withValues(alpha: 0.05), + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 8, + ), + shape: const StadiumBorder(), + ), + child: const Text('Guest Explore'), + ), + ], + ), + ), + // 中央内容 + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const _BrandLogo(), + const SizedBox(height: 20), + const Text( + 'DramaPulse', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.w800, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + const Text( + 'Sign in to sync your progress and coins', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.white54, + ), + ), + const SizedBox(height: 36), + _GoogleButton(onTap: _enterHome), + const SizedBox(height: 14), + _DarkButton( + icon: Icons.phone_iphone, + iconColor: _orange, + label: 'Continue with Phone Number', + onTap: _enterHome, + ), + const SizedBox(height: 14), + _DarkButton( + icon: Icons.email_outlined, + iconColor: _purple, + label: 'Continue with Email', + onTap: _enterHome, + ), + const SizedBox(height: 32), + const _BonusPill(), + ], + ), + ), + ), + ), + // 底部协议 + const Padding( + padding: EdgeInsets.only(bottom: 24), + child: Text( + 'By continuing, you agree to DramaPulse\'s\n' + 'Terms of Service · Privacy Policy · Content Policy', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 11, color: Colors.white38), + ), + ), + ], + ), + ), + ); + } +} + +class _IconButton extends StatelessWidget { + const _IconButton({required this.icon, required this.onTap}); + + final IconData icon; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(24), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.05), + border: Border.all(color: Colors.white12), + shape: BoxShape.circle, + ), + child: Icon(icon, color: Colors.white54, size: 20), + ), + ); + } +} + +class _BrandLogo extends StatelessWidget { + const _BrandLogo(); + + @override + Widget build(BuildContext context) { + return Container( + width: 80, + height: 80, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFFFF5B3D), Color(0xFFD8536C), Color(0xFF9278EA)], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.white24), + ), + child: const Icon(Icons.play_arrow_rounded, color: Colors.white, size: 40), + ); + } +} + +class _GoogleButton extends StatelessWidget { + const _GoogleButton({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return _AuthButton( + onTap: onTap, + background: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const _GoogleG(), + const SizedBox(width: 12), + Text( + 'Continue with Google', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey.shade900, + ), + ), + ], + ), + ); + } +} + +/// 简化版 Google「G」logo。 +class _GoogleG extends StatelessWidget { + const _GoogleG(); + + @override + Widget build(BuildContext context) { + return Container( + width: 20, + height: 20, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF4285F4), + ), + child: const Center( + child: Text( + 'G', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} + +class _DarkButton extends StatelessWidget { + const _DarkButton({ + required this.icon, + required this.iconColor, + required this.label, + required this.onTap, + }); + + final IconData icon; + final Color iconColor; + final String label; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return _AuthButton( + onTap: onTap, + background: const Color(0xFF161822), + border: const Color(0xFF232634), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: iconColor, size: 20), + const SizedBox(width: 12), + Text( + label, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ], + ), + ); + } +} + +class _AuthButton extends StatelessWidget { + const _AuthButton({ + required this.onTap, + required this.background, + required this.child, + this.border, + }); + + final VoidCallback onTap; + final Color background; + final Color? border; + final Widget child; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: 52, + child: Material( + color: background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: border == null ? BorderSide.none : BorderSide(color: border!), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + child: child, + ), + ), + ); + } +} + +class _BonusPill extends StatelessWidget { + const _BonusPill(); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.04), + border: Border.all(color: Colors.white12), + borderRadius: BorderRadius.circular(999), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 6, + height: 6, + decoration: const BoxDecoration( + color: Color(0xFF34D399), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + const Text( + 'Get ', + style: TextStyle(fontSize: 11, color: Colors.white54), + ), + const Text( + '100 Free Bonus Coins', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + const Text( + ' upon first sign-in', + style: TextStyle(fontSize: 11, color: Colors.white54), + ), + ], + ), + ); + } +}