feat(auth): 登录页改为 ymb 品牌并支持后台配置背景图

This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-30 17:14:30 +08:00
1 parent 22cc669bbe
commit e7399fa3fd
24 files changed
+378 -188

No files matched your search

+10
View File
@@ -14,6 +14,7 @@ import 'core/data/remote/catalog_api.dart';
import 'core/data/remote/category_api.dart';
import 'core/data/remote/comment_api.dart';
import 'core/data/remote/commerce_api.dart';
import 'core/data/remote/config_api.dart';
import 'core/data/remote/device_profile.dart';
import 'core/data/remote/download_api.dart';
import 'core/data/remote/interaction_api.dart';
@@ -35,6 +36,7 @@ import 'core/data/repositories/playlist_repository.dart';
import 'core/data/repositories/search_repository.dart';
import 'core/data/repositories/store_repository.dart';
import 'core/data/repositories/watch_history_repository.dart';
import 'core/data/services/app_config_controller.dart';
import 'core/data/services/auth_controller.dart';
import 'core/data/services/auth_session_store.dart';
import 'core/data/services/catalog_controller.dart';
@@ -208,6 +210,11 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
late final LocaleController _localeController = LocaleController();
/// 后台下发的启动配置(登录页背景等);拉不到就用本地默认。
late final AppConfigController _appConfigController = AppConfigController(
ConfigApi(_apiClient),
);
/// 登录期内共享的「已解锁收费集」:播放页进出、feed 与全屏切换都要沿用。
late final UnlockedEpisodeRegistry _unlockedEpisodes =
UnlockedEpisodeRegistry();
@@ -218,6 +225,7 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
// 恢复本地令牌与设备号;未登录也能正常浏览,不阻塞首屏
_auth.load();
unawaited(_localeController.load());
unawaited(_appConfigController.load());
// 登录/登出时同步收藏状态:收藏属于账号,不能跨账号串联
_auth.addListener(_onAuthChanged);
unawaited(_favoriteController.load());
@@ -252,6 +260,7 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
_walletController.dispose();
_interactionController.dispose();
_localeController.dispose();
_appConfigController.dispose();
_apiClient.close();
super.dispose();
}
@@ -304,6 +313,7 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
ChangeNotifierProvider.value(value: _walletController),
ChangeNotifierProvider.value(value: _interactionController),
ChangeNotifierProvider.value(value: _localeController),
ChangeNotifierProvider.value(value: _appConfigController),
ChangeNotifierProvider.value(value: _auth),
],
child: Consumer<LocaleController>(
+3 -2
View File
@@ -2,8 +2,9 @@
/// 开发环境配置。
///
/// 本机联调用:换 Wi-Fi、换网段后只改这里的 [baseUrl],地址不要带结尾斜杠。
/// 本机联调用:模拟器与桌面端可直接用回环地址,但 iOS/Android 真机必须用
/// 开发机的局域网 IP(真机上的 127.0.0.1 指向手机自己)。地址不要带结尾斜杠。
abstract final class DevConfig {
/// 开发机后端地址。
/// 开发机后端地址(Mac 当前 Wi-Fi 地址,换网络后需要同步修改)。
static const String baseUrl = 'http://192.168.31.243:8081';
}
+16
View File
@@ -0,0 +1,16 @@
// Author: fruit.com.cn
import 'api_client.dart';
/// 启动配置:`GET /app/config`(匿名,后台只下发白名单键)。
class ConfigApi {
const ConfigApi(this._client);
final ApiClient _client;
/// 白名单配置表;结构异常时返回空表,调用方按本地默认处理。
Future<Map<String, dynamic>> appConfig() async {
final data = await _client.get('/app/config');
return data is Map<String, dynamic> ? data : const <String, dynamic>{};
}
}
@@ -0,0 +1,31 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../remote/config_api.dart';
/// 后台下发的启动配置。
///
/// 失败一律静默:拿不到就用 App 内置默认,不阻塞启动、不弹错。
class AppConfigController extends ChangeNotifier {
AppConfigController(this._api);
final ConfigApi _api;
String _loginBackgroundUrl = '';
/// 登录页整屏背景图;空串表示用本地默认图。
String get loginBackgroundUrl => _loginBackgroundUrl;
Future<void> load() async {
try {
final config = await _api.appConfig();
final url = (config['loginBackgroundUrl'] as String? ?? '').trim();
if (url == _loginBackgroundUrl) return;
_loginBackgroundUrl = url;
notifyListeners();
} catch (error) {
debugPrint('[config] load failed: $error');
}
}
}
+9 -3
View File
@@ -35,6 +35,7 @@ class RemoteImage extends StatelessWidget {
this.fallbackIconKey,
this.fallbackAspectRatio,
this.fallbackColor = AppColors.raised,
this.fallbackWidget,
this.decodeWidth,
this.decodeHeight,
});
@@ -47,6 +48,9 @@ class RemoteImage extends StatelessWidget {
final Key? fallbackIconKey;
final double? fallbackAspectRatio;
final Color fallbackColor;
/// 自定义兜底:地址为空、加载中、加载失败时都显示它(优先于 [fallbackColor])。
final Widget? fallbackWidget;
final int? decodeWidth;
final int? decodeHeight;
@@ -77,9 +81,9 @@ class RemoteImage extends StatelessWidget {
// cover/contain 本身会保持宽高比,此时若同时强制 cacheWidth 和
// cacheHeight,解码会把竖版图强行拉成横版像素。只在显式指定了
// decodeHeight、或 fit 不保持宽高比时才限定解码高度。
final keepsAspectRatio =
fit == BoxFit.cover || fit == BoxFit.contain;
final cacheHeight = decodeHeight ??
final keepsAspectRatio = fit == BoxFit.cover || fit == BoxFit.contain;
final cacheHeight =
decodeHeight ??
(keepsAspectRatio || inferredHeight == null
? null
: resolveImageDecodePixels(inferredHeight, pixelRatio));
@@ -106,6 +110,8 @@ class RemoteImage extends StatelessWidget {
}
Widget _fallback({required bool brand}) {
final custom = fallbackWidget;
if (custom != null) return custom;
final mark = brand
? LayoutBuilder(
builder: (context, constraints) {
+6 -5
View File
@@ -1,6 +1,6 @@
{
"@@locale": "en",
"appName": "FruitShorts",
"appName": "ymb",
"demo": "DEMO",
"homeHeadline": "Watch now",
"homeSubtitle": "Free short drama preview.",
@@ -19,7 +19,7 @@
"searchCancel": "Cancel",
"featured": "Featured",
"recommended": "Recommended",
"fruitShortsBrand": "FruitShorts",
"fruitShortsBrand": "ymb",
"comingSoon": "Coming soon",
"authLegal": "By continuing, you agree to the Terms of Service and Privacy Policy.",
"authLegalLead": "By continuing, you agree to the",
@@ -408,8 +408,8 @@
"genreDramaMystery": "Drama · Mystery",
"continueWatchingHeading": "CONTINUE WATCHING",
"preferencesHeading": "PREFERENCES",
"brandMark": "FRUITSHORTS",
"shareFooter": "FruitShorts",
"brandMark": "YMB",
"shareFooter": "ymb",
"shortsTab": "Shorts",
"myListTab": "My List",
"watchFull": "Watch Full",
@@ -789,7 +789,7 @@
"offlineDownloads": "Downloads",
"watchPreferences": "Watch preferences",
"contactSupport": "Contact support",
"vipBrandName": "FruitShorts VIP",
"vipBrandName": "VIP",
"vipOpenNow": "Get VIP",
"vipBenefitFree": "Free full access",
"vipBenefitNoAds": "Ad-free viewing",
@@ -851,6 +851,7 @@
"dataUnavailable": "—",
"commonQuestions": "Common questions",
"aboutUs": "About us",
"aboutBrandName": "Your mini business",
"aboutDescription": "A focused, immersive place to watch short dramas.",
"versionLabel": "Version {version}",
"@versionLabel": {
+6 -5
View File
@@ -1,6 +1,6 @@
{
"@@locale": "id",
"appName": "FruitShorts",
"appName": "ymb",
"demo": "Demo",
"homeHeadline": "Tonton sekarang",
"homeSubtitle": "Pratinjau drama pendek gratis.",
@@ -19,7 +19,7 @@
"searchCancel": "Batal",
"featured": "Unggulan",
"recommended": "Rekomendasi",
"fruitShortsBrand": "FruitShorts",
"fruitShortsBrand": "ymb",
"comingSoon": "Segera hadir",
"authLegal": "Dengan melanjutkan, Anda menyetujui Ketentuan Layanan dan Kebijakan Privasi.",
"authLegalLead": "Dengan melanjutkan, Anda menyetujui",
@@ -387,8 +387,8 @@
"genreDramaMystery": "Drama · Misteri",
"continueWatchingHeading": "LANJUT TONTON",
"preferencesHeading": "PREFERENSI",
"brandMark": "FRUITSHORTS",
"shareFooter": "FruitShorts",
"brandMark": "YMB",
"shareFooter": "ymb",
"shortsTab": "Drama Pendek",
"myListTab": "Daftar Saya",
"watchFull": "Tonton Penuh",
@@ -751,7 +751,7 @@
"offlineDownloads": "Unduhan",
"watchPreferences": "Preferensi tontonan",
"contactSupport": "Hubungi dukungan",
"vipBrandName": "FruitShorts VIP",
"vipBrandName": "VIP",
"vipOpenNow": "Buka VIP",
"vipBenefitFree": "Nonton semua gratis",
"vipBenefitNoAds": "Bebas iklan",
@@ -813,6 +813,7 @@
"dataUnavailable": "—",
"commonQuestions": "Pertanyaan umum",
"aboutUs": "Tentang kami",
"aboutBrandName": "Your mini business",
"aboutDescription": "Tempat fokus dan imersif untuk menonton drama pendek.",
"versionLabel": "Versi {version}",
"@versionLabel": {
+11 -5
View File
@@ -103,7 +103,7 @@ abstract class AppLocalizations {
/// No description provided for @appName.
///
/// In en, this message translates to:
/// **'FruitShorts'**
/// **'ymb'**
String get appName;
/// No description provided for @demo.
@@ -217,7 +217,7 @@ abstract class AppLocalizations {
/// No description provided for @fruitShortsBrand.
///
/// In en, this message translates to:
/// **'FruitShorts'**
/// **'ymb'**
String get fruitShortsBrand;
/// No description provided for @comingSoon.
@@ -1627,13 +1627,13 @@ abstract class AppLocalizations {
/// No description provided for @brandMark.
///
/// In en, this message translates to:
/// **'FRUITSHORTS'**
/// **'YMB'**
String get brandMark;
/// No description provided for @shareFooter.
///
/// In en, this message translates to:
/// **'FruitShorts'**
/// **'ymb'**
String get shareFooter;
/// No description provided for @shortsTab.
@@ -3439,7 +3439,7 @@ abstract class AppLocalizations {
/// No description provided for @vipBrandName.
///
/// In en, this message translates to:
/// **'FruitShorts VIP'**
/// **'VIP'**
String get vipBrandName;
/// No description provided for @vipOpenNow.
@@ -3592,6 +3592,12 @@ abstract class AppLocalizations {
/// **'About us'**
String get aboutUs;
/// No description provided for @aboutBrandName.
///
/// In en, this message translates to:
/// **'Your mini business'**
String get aboutBrandName;
/// No description provided for @aboutDescription.
///
/// In en, this message translates to:
+8 -5
View File
@@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appName => 'FruitShorts';
String get appName => 'ymb';
@override
String get demo => 'DEMO';
@@ -67,7 +67,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get recommended => 'Recommended';
@override
String get fruitShortsBrand => 'FruitShorts';
String get fruitShortsBrand => 'ymb';
@override
String get comingSoon => 'Coming soon';
@@ -831,10 +831,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get preferencesHeading => 'PREFERENCES';
@override
String get brandMark => 'FRUITSHORTS';
String get brandMark => 'YMB';
@override
String get shareFooter => 'FruitShorts';
String get shareFooter => 'ymb';
@override
String get shortsTab => 'Shorts';
@@ -1795,7 +1795,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get contactSupport => 'Contact support';
@override
String get vipBrandName => 'FruitShorts VIP';
String get vipBrandName => 'VIP';
@override
String get vipOpenNow => 'Get VIP';
@@ -1886,6 +1886,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get aboutUs => 'About us';
@override
String get aboutBrandName => 'Your mini business';
@override
String get aboutDescription =>
'A focused, immersive place to watch short dramas.';
+8 -5
View File
@@ -9,7 +9,7 @@ class AppLocalizationsId extends AppLocalizations {
AppLocalizationsId([String locale = 'id']) : super(locale);
@override
String get appName => 'FruitShorts';
String get appName => 'ymb';
@override
String get demo => 'Demo';
@@ -67,7 +67,7 @@ class AppLocalizationsId extends AppLocalizations {
String get recommended => 'Rekomendasi';
@override
String get fruitShortsBrand => 'FruitShorts';
String get fruitShortsBrand => 'ymb';
@override
String get comingSoon => 'Segera hadir';
@@ -834,10 +834,10 @@ class AppLocalizationsId extends AppLocalizations {
String get preferencesHeading => 'PREFERENSI';
@override
String get brandMark => 'FRUITSHORTS';
String get brandMark => 'YMB';
@override
String get shareFooter => 'FruitShorts';
String get shareFooter => 'ymb';
@override
String get shortsTab => 'Drama Pendek';
@@ -1797,7 +1797,7 @@ class AppLocalizationsId extends AppLocalizations {
String get contactSupport => 'Hubungi dukungan';
@override
String get vipBrandName => 'FruitShorts VIP';
String get vipBrandName => 'VIP';
@override
String get vipOpenNow => 'Buka VIP';
@@ -1888,6 +1888,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get aboutUs => 'Tentang kami';
@override
String get aboutBrandName => 'Your mini business';
@override
String get aboutDescription =>
'Tempat fokus dan imersif untuk menonton drama pendek.';
+8 -5
View File
@@ -9,7 +9,7 @@ class AppLocalizationsZh extends AppLocalizations {
AppLocalizationsZh([String locale = 'zh']) : super(locale);
@override
String get appName => 'FruitShorts';
String get appName => 'ymb';
@override
String get demo => '演示';
@@ -66,7 +66,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get recommended => '推荐';
@override
String get fruitShortsBrand => 'FruitShorts';
String get fruitShortsBrand => 'ymb';
@override
String get comingSoon => '即将上线';
@@ -821,10 +821,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get preferencesHeading => '偏好设置';
@override
String get brandMark => 'FRUITSHORTS';
String get brandMark => 'YMB';
@override
String get shareFooter => 'FruitShorts';
String get shareFooter => 'ymb';
@override
String get shortsTab => '短剧';
@@ -1770,7 +1770,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get contactSupport => '联系客服';
@override
String get vipBrandName => 'FruitShorts VIP';
String get vipBrandName => 'VIP';
@override
String get vipOpenNow => '立即开通';
@@ -1861,6 +1861,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get aboutUs => '关于我们';
@override
String get aboutBrandName => 'Your mini business';
@override
String get aboutDescription => '专注于短剧观看与沉浸式追剧体验。';
+6 -5
View File
@@ -1,6 +1,6 @@
{
"@@locale": "zh",
"appName": "FruitShorts",
"appName": "ymb",
"demo": "演示",
"homeHeadline": "即刻观看",
"homeSubtitle": "免费短剧预览。",
@@ -19,7 +19,7 @@
"searchCancel": "取消",
"featured": "精选",
"recommended": "推荐",
"fruitShortsBrand": "FruitShorts",
"fruitShortsBrand": "ymb",
"comingSoon": "即将上线",
"authLegal": "继续即代表同意《用户协议》与《隐私政策》",
"authLegalLead": "继续即代表同意",
@@ -349,8 +349,8 @@
"genreDramaMystery": "剧情 · 悬疑",
"continueWatchingHeading": "继续观看",
"preferencesHeading": "偏好设置",
"brandMark": "FRUITSHORTS",
"shareFooter": "FruitShorts",
"brandMark": "YMB",
"shareFooter": "ymb",
"shortsTab": "短剧",
"myListTab": "片单",
"watchFull": "看全集",
@@ -713,7 +713,7 @@
"offlineDownloads": "离线缓存",
"watchPreferences": "观看偏好",
"contactSupport": "联系客服",
"vipBrandName": "FruitShorts VIP",
"vipBrandName": "VIP",
"vipOpenNow": "立即开通",
"vipBenefitFree": "全剧免费看",
"vipBenefitNoAds": "免广告打扰",
@@ -775,6 +775,7 @@
"dataUnavailable": "—",
"commonQuestions": "常见问题",
"aboutUs": "关于我们",
"aboutBrandName": "Your mini business",
"aboutDescription": "专注于短剧观看与沉浸式追剧体验。",
"versionLabel": "版本 {version}",
"@versionLabel": {
+9 -27
View File
@@ -300,7 +300,7 @@ class _AuthBrand extends StatelessWidget {
@override
Widget build(BuildContext context) {
final copy = _brandCopy(context, centered: !compact);
final copy = _brandCopy(centered: !compact);
if (compact) {
return Row(
key: const Key('auth-brand'),
@@ -324,32 +324,14 @@ class _AuthBrand extends StatelessWidget {
radius: compact ? _compactBrandMarkRadius : _brandMarkRadius,
);
Widget _brandCopy(BuildContext context, {required bool centered}) => Column(
crossAxisAlignment: centered
? CrossAxisAlignment.center
: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.appName,
textAlign: centered ? TextAlign.center : TextAlign.start,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 24,
fontWeight: FontWeight.w700,
height: 1.1,
),
),
const SizedBox(height: 6),
Text(
tagline,
textAlign: centered ? TextAlign.center : TextAlign.start,
style: const TextStyle(
color: AppColors.muted,
fontSize: 13,
height: 1.35,
),
),
],
Widget _brandCopy({required bool centered}) => Text(
tagline,
textAlign: centered ? TextAlign.center : TextAlign.start,
style: const TextStyle(
color: AppColors.muted,
fontSize: 13,
height: 1.35,
),
);
}
+71 -74
View File
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/formatting/error_text.dart';
import '../../core/data/services/app_config_controller.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/platform/haptics.dart';
import '../../core/platform/keyboard_inset.dart';
@@ -14,6 +15,7 @@ import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/centered_toast.dart';
import '../../core/widgets/remote_image.dart';
import '../../core/widgets/subpage_header.dart';
import '../../l10n/app_localizations.dart';
import 'auth_legal_links.dart';
@@ -28,7 +30,7 @@ const double _brandMarkSize = 88;
const double _compactBrandMarkSize = 48;
const double _brandMarkRadius = 26;
const double _compactBrandMarkRadius = 14;
const String _appLogoAsset = 'assets/icon/fruitshorts.png';
const String _appLogoAsset = 'assets/icon/ymb.png';
const double _contentMaxWidth = 440;
const double _fieldHeight = 52;
const double _fieldRadius = 12;
@@ -276,9 +278,7 @@ class _AuthPageState extends State<AuthPage> {
),
),
// 全屏海报背景:压暗保证文字可读,顶部再叠品牌红光。
Positioned.fill(
child: IgnorePointer(child: _AuthPosterBackdrop()),
),
Positioned.fill(child: IgnorePointer(child: _AuthPosterBackdrop())),
const Positioned.fill(
child: IgnorePointer(
child: DecoratedBox(
@@ -396,80 +396,67 @@ class _AuthPageState extends State<AuthPage> {
AppLocalizations l10n, {
required bool compact,
required double minHeight,
}) =>
TweenAnimationBuilder<double>(
key: const Key('auth-enter-animation'),
tween: Tween(begin: 0, end: 1),
duration: _enterDuration,
curve: Curves.easeOutCubic,
builder: (context, value, child) => Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, _enterSlideDistance * (1 - value)),
child: child,
),
),
child: _emailEntryOpen
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_AuthBrand(tagline: l10n.authBrandTagline, compact: compact),
SizedBox(height: compact ? _compactBrandGap : 28),
_authCard(l10n),
],
)
: _landing(l10n, minHeight: minHeight),
);
}) => TweenAnimationBuilder<double>(
key: const Key('auth-enter-animation'),
tween: Tween(begin: 0, end: 1),
duration: _enterDuration,
curve: Curves.easeOutCubic,
builder: (context, value, child) => Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, _enterSlideDistance * (1 - value)),
child: child,
),
),
child: _emailEntryOpen
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_AuthBrand(tagline: l10n.authBrandTagline, compact: compact),
SizedBox(height: compact ? _compactBrandGap : 28),
_authCard(l10n),
],
)
: _landing(l10n, minHeight: minHeight),
);
/// 落地页内容需要有限高度才能用 Spacer 三段分割,这里显式钉住可用高度。
Widget _landing(AppLocalizations l10n, {required double minHeight}) =>
SizedBox(
height: minHeight,
child: Column(
key: const Key('auth-landing'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(flex: 5),
Center(
child: _brandMarkWidget(
key: const Key('auth-brand-logo'),
size: _brandMarkSize,
radius: _brandMarkRadius,
key: const Key('auth-landing'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(flex: 5),
Center(
child: _brandMarkWidget(
key: const Key('auth-brand-logo'),
size: _brandMarkSize,
radius: _brandMarkRadius,
),
),
const Spacer(flex: 2),
_AuthEntryButton(
key: const Key('auth-entry-phone'),
label: l10n.authPhoneSignIn,
icon: Icons.phone_android_rounded,
primary: true,
onTap: _openPhoneSheet,
),
const SizedBox(height: 12),
_AuthEntryButton(
key: const Key('auth-entry-email'),
label: l10n.authEmailSignIn,
icon: Icons.mail_outline_rounded,
primary: false,
onTap: () => setState(() => _emailEntryOpen = true),
),
const SizedBox(height: 14),
const Opacity(opacity: 0.75, child: AuthLegalLinks()),
const Spacer(flex: 6),
],
),
),
const SizedBox(height: 10),
Text(
l10n.fruitShortsBrand,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.onSurface,
fontSize: 26,
fontWeight: FontWeight.w700,
letterSpacing: 2,
),
),
const Spacer(flex: 3),
_AuthEntryButton(
key: const Key('auth-entry-phone'),
label: l10n.authPhoneSignIn,
icon: Icons.phone_android_rounded,
primary: true,
onTap: _openPhoneSheet,
),
const SizedBox(height: 12),
_AuthEntryButton(
key: const Key('auth-entry-email'),
label: l10n.authEmailSignIn,
icon: Icons.mail_outline_rounded,
primary: false,
onTap: () => setState(() => _emailEntryOpen = true),
),
const SizedBox(height: 14),
const Opacity(opacity: 0.75, child: AuthLegalLinks()),
const Spacer(flex: 6),
],
),
);
Future<void> _openPhoneSheet() async {
@@ -578,16 +565,26 @@ class _AuthEntryButton extends StatelessWidget {
}
/// 登录页全屏海报背景(单张,无拼缝)。
///
/// 优先用后台下发的 `loginBackgroundUrl`(后台未配置或加载失败时回落到本地默认图)。
class _AuthPosterBackdrop extends StatelessWidget {
const _AuthPosterBackdrop();
static const String _defaultAsset = 'assets/auth/bg_3.jpg';
@override
Widget build(BuildContext context) {
return Image.asset(
final remoteUrl = context.watch<AppConfigController>().loginBackgroundUrl;
return RemoteImage(
key: const Key('auth-poster-backdrop'),
'assets/auth/bg_3.jpg',
remoteUrl,
fit: BoxFit.cover,
alignment: Alignment.center,
fallbackWidget: Image.asset(
_defaultAsset,
key: const Key('auth-poster-default'),
fit: BoxFit.cover,
alignment: Alignment.center,
),
);
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ class MineAboutPage extends StatelessWidget {
),
children: [
Text(
l10n.appName,
l10n.aboutBrandName,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'Outfit',