From acda5a22a69de3cfe72d075ea971f8f1f2d709c3 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:24:45 +0530 Subject: [PATCH] refactor: new chat page text --- lib/core/utils/user_display_name.dart | 85 +++++++++++++++++++ lib/features/chat/views/chat_page.dart | 15 +++- .../navigation/widgets/chats_drawer.dart | 41 +-------- .../onboarding/views/onboarding_sheet.dart | 77 ++++++++++------- lib/l10n/app_de.arb | 2 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_fr.arb | 2 +- lib/l10n/app_it.arb | 2 +- lib/l10n/app_localizations.dart | 4 +- lib/l10n/app_localizations_de.dart | 4 +- lib/l10n/app_localizations_en.dart | 4 +- lib/l10n/app_localizations_fr.dart | 4 +- lib/l10n/app_localizations_it.dart | 4 +- 13 files changed, 164 insertions(+), 84 deletions(-) create mode 100644 lib/core/utils/user_display_name.dart diff --git a/lib/core/utils/user_display_name.dart b/lib/core/utils/user_display_name.dart new file mode 100644 index 0000000..f6e8b92 --- /dev/null +++ b/lib/core/utils/user_display_name.dart @@ -0,0 +1,85 @@ +import '../models/user.dart' as models; + +String deriveUserDisplayName(dynamic user, {String fallback = 'User'}) { + if (user == null) { + return fallback; + } + + String normalize(String? value) { + if (value == null) return ''; + final trimmed = value.trim(); + return trimmed; + } + + String emailFallback(String? email) { + final trimmed = normalize(email); + if (trimmed.isEmpty) return ''; + final at = trimmed.indexOf('@'); + if (at > 0) { + return trimmed.substring(0, at); + } + return trimmed; + } + + if (user is models.User) { + final name = normalize(user.name); + if (name.isNotEmpty) return name; + + final username = normalize(user.username); + if (username.isNotEmpty) return username; + + final email = emailFallback(user.email); + if (email.isNotEmpty) return email; + + return fallback; + } + + if (user is Map) { + String? pick(Map source) { + for (final key in const [ + 'name', + 'display_name', + 'preferred_username', + 'username', + ]) { + final value = source[key]; + final normalized = normalize(value is String ? value : null); + if (normalized.isNotEmpty) { + return normalized; + } + } + return null; + } + + final topLevel = pick(user); + if (topLevel != null && topLevel.isNotEmpty) { + return topLevel; + } + + final nestedUser = user['user']; + if (nestedUser is Map) { + final nested = pick(nestedUser); + if (nested != null && nested.isNotEmpty) { + return nested; + } + final email = emailFallback(nestedUser['email'] as String?); + if (email.isNotEmpty) { + return email; + } + } + + final email = emailFallback(user['email'] as String?); + if (email.isNotEmpty) { + return email; + } + + return fallback; + } + + final asString = normalize(user.toString()); + if (asString.isNotEmpty) { + return asString; + } + + return fallback; +} diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 201e42f..985c59f 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -10,8 +10,10 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'dart:io' show Platform; import 'dart:async'; import '../../../core/providers/app_providers.dart'; +import '../../../core/auth/auth_state_manager.dart'; import '../providers/chat_providers.dart'; import '../../../core/utils/debug_logger.dart'; +import '../../../core/utils/user_display_name.dart'; import '../widgets/modern_chat_input.dart'; import '../widgets/user_message_bubble.dart'; @@ -847,6 +849,15 @@ class _ChatPageState extends ConsumerState { } Widget _buildEmptyState(ThemeData theme) { + final l10n = AppLocalizations.of(context)!; + final currentUserAsync = ref.watch(currentUserProvider); + final userFromProfile = currentUserAsync.maybeWhen( + data: (user) => user, + orElse: () => null, + ); + final dynamic authUser = ref.watch(authUserProvider); + final user = userFromProfile ?? authUser; + final greetingName = deriveUserDisplayName(user); return Center( child: SingleChildScrollView( padding: const EdgeInsets.all(Spacing.lg), @@ -886,7 +897,7 @@ class _ChatPageState extends ConsumerState { const SizedBox(height: Spacing.xl), Text( - AppLocalizations.of(context)!.onboardStartTitle, + l10n.onboardStartTitle(greetingName), style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w600, color: context.conduitTheme.textPrimary, @@ -897,7 +908,7 @@ class _ChatPageState extends ConsumerState { const SizedBox(height: Spacing.sm), Text( - AppLocalizations.of(context)!.typeBelowToBegin, + l10n.typeBelowToBegin, style: theme.textTheme.bodyLarge?.copyWith( color: context.conduitTheme.textSecondary, fontWeight: FontWeight.w400, diff --git a/lib/features/navigation/widgets/chats_drawer.dart b/lib/features/navigation/widgets/chats_drawer.dart index f8de88f..3de0067 100644 --- a/lib/features/navigation/widgets/chats_drawer.dart +++ b/lib/features/navigation/widgets/chats_drawer.dart @@ -15,7 +15,7 @@ import '../../../shared/utils/ui_utils.dart'; import '../../../shared/widgets/themed_dialogs.dart'; import '../../../core/auth/auth_state_manager.dart'; import 'package:conduit/l10n/app_localizations.dart'; -import '../../../core/models/user.dart' as models; +import '../../../core/utils/user_display_name.dart'; class ChatsDrawer extends ConsumerStatefulWidget { const ChatsDrawer({super.key}); @@ -1148,43 +1148,6 @@ class _ChatsDrawerState extends ConsumerState { ); final dynamic authUser = ref.watch(authUserProvider); final user = userFromProfile ?? authUser; - String _displayName(dynamic u) { - if (u == null) return 'User'; - if (u is models.User) { - return (u.name?.isNotEmpty == true ? u.name : u.username) ?? 'User'; - } - if (u is Map) { - final Map m = u; - String? _asString(dynamic v) => - v is String && v.trim().isNotEmpty ? v.trim() : null; - String? _pick(Map source) { - return _asString(source['name']) ?? - _asString(source['display_name']) ?? - _asString(source['preferred_username']) ?? - _asString(source['username']); - } - - final top = _pick(m); - if (top != null) return top; - final nestedUser = m['user']; - if (nestedUser is Map) { - final nested = _pick(nestedUser); - if (nested != null) return nested; - final nestedEmail = _asString(nestedUser['email']); - if (nestedEmail != null && nestedEmail.contains('@')) { - return nestedEmail.split('@').first; - } - } - final email = _asString(m['email']); - if (email != null && email.contains('@')) { - return email.split('@').first; - } - return 'User'; - } - // Fallback to string representation if some other type - final s = u.toString(); - return s.isNotEmpty ? s : 'User'; - } String _initial(String name) { if (name.isEmpty) return 'U'; @@ -1192,7 +1155,7 @@ class _ChatsDrawerState extends ConsumerState { return ch.toUpperCase(); } - final displayName = _displayName(user); + final displayName = deriveUserDisplayName(user); final initial = _initial(displayName); return Padding( padding: const EdgeInsets.fromLTRB(Spacing.sm, 0, Spacing.sm, Spacing.sm), diff --git a/lib/features/onboarding/views/onboarding_sheet.dart b/lib/features/onboarding/views/onboarding_sheet.dart index 9d870d6..ad8dc5c 100644 --- a/lib/features/onboarding/views/onboarding_sheet.dart +++ b/lib/features/onboarding/views/onboarding_sheet.dart @@ -1,29 +1,44 @@ -import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; -import '../../../shared/theme/theme_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_animate/flutter_animate.dart'; -import '../../../shared/widgets/sheet_handle.dart'; import 'package:conduit/l10n/app_localizations.dart'; -class OnboardingSheet extends StatefulWidget { +import '../../../core/auth/auth_state_manager.dart'; +import '../../../core/providers/app_providers.dart'; +import '../../../core/utils/user_display_name.dart'; +import '../../../shared/theme/theme_extensions.dart'; +import '../../../shared/widgets/sheet_handle.dart'; + +class OnboardingSheet extends ConsumerStatefulWidget { const OnboardingSheet({super.key}); @override - State createState() => _OnboardingSheetState(); + ConsumerState createState() => _OnboardingSheetState(); } -class _OnboardingSheetState extends State { +class _OnboardingSheetState extends ConsumerState { final PageController _controller = PageController(); int _index = 0; - late List<_OnboardingPage> _pages; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final l10n = AppLocalizations.of(context)!; - _pages = [ + void _next(int pageCount) { + if (_index < pageCount - 1) { + _controller.nextPage( + duration: AnimationDuration.fast, + curve: AnimationCurves.easeInOut, + ); + } else { + Navigator.pop(context); + } + } + + List<_OnboardingPage> _buildPages( + AppLocalizations l10n, + String greetingName, + ) { + return [ _OnboardingPage( - title: l10n.onboardStartTitle, + title: l10n.onboardStartTitle(greetingName), subtitle: l10n.onboardStartSubtitle, icon: CupertinoIcons.chat_bubble_2, bullets: [l10n.onboardStartBullet1, l10n.onboardStartBullet2], @@ -49,20 +64,20 @@ class _OnboardingSheetState extends State { ]; } - void _next() { - if (_index < _pages.length - 1) { - _controller.nextPage( - duration: AnimationDuration.fast, - curve: AnimationCurves.easeInOut, - ); - } else { - Navigator.pop(context); - } - } - @override Widget build(BuildContext context) { final height = MediaQuery.of(context).size.height; + final l10n = AppLocalizations.of(context)!; + final currentUserAsync = ref.watch(currentUserProvider); + final userFromProfile = currentUserAsync.maybeWhen( + data: (user) => user, + orElse: () => null, + ); + final dynamic authUser = ref.watch(authUserProvider); + final user = userFromProfile ?? authUser; + final greetingName = deriveUserDisplayName(user); + final pages = _buildPages(l10n, greetingName); + final pageCount = pages.length; return Container( height: height * 0.7, decoration: BoxDecoration( @@ -83,10 +98,10 @@ class _OnboardingSheetState extends State { Expanded( child: PageView.builder( controller: _controller, - itemCount: _pages.length, + itemCount: pageCount, onPageChanged: (i) => setState(() => _index = i), itemBuilder: (context, i) { - final page = _pages[i]; + final page = pages[i]; final content = _IllustratedPage(page: page); // Ensure content can scroll vertically when space is tight, // while keeping it centered when there is enough space. @@ -111,7 +126,7 @@ class _OnboardingSheetState extends State { const SizedBox(height: Spacing.md), Row( mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(_pages.length, (i) { + children: List.generate(pageCount, (i) { final active = i == _index; return AnimatedContainer( duration: AnimationDuration.fast, @@ -136,7 +151,7 @@ class _OnboardingSheetState extends State { TextButton( onPressed: () => Navigator.pop(context), child: Text( - AppLocalizations.of(context)!.skip, + l10n.skip, style: TextStyle( color: context.conduitTheme.textSecondary, ), @@ -144,7 +159,7 @@ class _OnboardingSheetState extends State { ), const Spacer(), FilledButton( - onPressed: _next, + onPressed: () => _next(pageCount), style: FilledButton.styleFrom( backgroundColor: context.conduitTheme.buttonPrimary, foregroundColor: context.conduitTheme.buttonPrimaryText, @@ -159,9 +174,7 @@ class _OnboardingSheetState extends State { ), ), child: Text( - _index == _pages.length - 1 - ? AppLocalizations.of(context)!.done - : AppLocalizations.of(context)!.next, + _index == pageCount - 1 ? l10n.done : l10n.next, ), ), ], diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 73db54e..a24e8d5 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -90,7 +90,7 @@ "skip": "Überspringen", "next": "Weiter", "done": "Fertig", - "onboardStartTitle": "Unterhaltung starten", + "onboardStartTitle": "Hallo, {username}", "onboardStartSubtitle": "Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.", "onboardStartBullet1": "Modellname oben antippen, um zu wechseln", "onboardStartBullet2": "Mit Neuer Chat den Kontext zurücksetzen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 59d10ed..e78cfde 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -202,8 +202,8 @@ "@next": {"description": "Onboarding: go to the next step."}, "done": "Done", "@done": {"description": "Onboarding: finish the flow."}, - "onboardStartTitle": "Start a conversation", - "@onboardStartTitle": {"description": "Onboarding card: start chatting title."}, + "onboardStartTitle": "Hello, {username}", + "@onboardStartTitle": {"description": "Onboarding card: start chatting title.", "placeholders": {"username": {"type": "String", "example": "Alex"}}}, "onboardStartSubtitle": "Choose a model, then type below to begin. Tap New Chat anytime.", "@onboardStartSubtitle": {"description": "Onboarding card: brief guidance to begin a chat."}, "onboardStartBullet1": "Tap the model name in the top bar to switch models", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 89cafd8..3e9f70b 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -90,7 +90,7 @@ "skip": "Ignorer", "next": "Suivant", "done": "Terminé", - "onboardStartTitle": "Commencer une conversation", + "onboardStartTitle": "Bonjour, {username}", "onboardStartSubtitle": "Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.", "onboardStartBullet1": "Touchez le nom du modèle en haut pour changer", "onboardStartBullet2": "Utilisez Nouveau chat pour réinitialiser le contexte", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 0792296..7036061 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -90,7 +90,7 @@ "skip": "Salta", "next": "Avanti", "done": "Fatto", - "onboardStartTitle": "Inizia una conversazione", + "onboardStartTitle": "Ciao, {username}", "onboardStartSubtitle": "Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.", "onboardStartBullet1": "Tocca il nome del modello in alto per cambiare", "onboardStartBullet2": "Usa Nuova chat per azzerare il contesto", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0761fbb..4e3400e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -651,8 +651,8 @@ abstract class AppLocalizations { /// Onboarding card: start chatting title. /// /// In en, this message translates to: - /// **'Start a conversation'** - String get onboardStartTitle; + /// **'Hello, {username}'** + String onboardStartTitle(String username); /// Onboarding card: brief guidance to begin a chat. /// diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 448b666..3444fd4 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -314,7 +314,9 @@ class AppLocalizationsDe extends AppLocalizations { String get done => 'Fertig'; @override - String get onboardStartTitle => 'Unterhaltung starten'; + String onboardStartTitle(String username) { + return 'Hallo, $username'; + } @override String get onboardStartSubtitle => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 33390de..2424d23 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -310,7 +310,9 @@ class AppLocalizationsEn extends AppLocalizations { String get done => 'Done'; @override - String get onboardStartTitle => 'Start a conversation'; + String onboardStartTitle(String username) { + return 'Hello, $username'; + } @override String get onboardStartSubtitle => diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 70fa841..7dee4ca 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -315,7 +315,9 @@ class AppLocalizationsFr extends AppLocalizations { String get done => 'Terminé'; @override - String get onboardStartTitle => 'Commencer une conversation'; + String onboardStartTitle(String username) { + return 'Bonjour, $username'; + } @override String get onboardStartSubtitle => diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 94c07b9..a255f13 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -310,7 +310,9 @@ class AppLocalizationsIt extends AppLocalizations { String get done => 'Fatto'; @override - String get onboardStartTitle => 'Inizia una conversazione'; + String onboardStartTitle(String username) { + return 'Ciao, $username'; + } @override String get onboardStartSubtitle =>