diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7dae792b..4a77eda3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -7253,6 +7253,30 @@ "profileRemovePhoto": "Remove photo", "profilePhotoError": "Choose a valid image smaller than 20 MB.", "profileSaveError": "Could not save your profile. Please try again.", + "homeGreetingMorning1": "Good morning", + "homeGreetingMorning2": "Rise and shine", + "homeGreetingMorning3": "Fresh start", + "homeGreetingAfternoon1": "Good afternoon", + "homeGreetingAfternoon2": "Afternoon groove", + "homeGreetingAfternoon3": "Keep the vibe going", + "homeGreetingEvening1": "Good evening", + "homeGreetingEvening2": "Time to unwind", + "homeGreetingEvening3": "Golden hour", + "homeGreetingNight1": "Good night", + "homeGreetingNight2": "Late-night session", + "homeGreetingNight3": "Quiet hours", + "homeGreetingNamed": "{greeting}, {name}", + "@homeGreetingNamed": { + "description": "Home greeting followed by the first name from the user's profile", + "placeholders": { + "greeting": { + "type": "String" + }, + "name": { + "type": "String" + } + } + }, "artistUpcomingConcerts": "Upcoming Concerts", "artistAllUpcomingConcerts": "All Upcoming Concerts", "artistNoUpcomingConcerts": "No upcoming concerts.", diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index c969c093..ed57fd5a 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -7258,6 +7258,19 @@ "profileRemovePhoto": "Hapus foto", "profilePhotoError": "Pilih foto yang di bawah 20MB.", "profileSaveError": "Tidak dapat menyimpan profil Anda. Coba lagi.", + "homeGreetingMorning1": "Selamat pagi", + "homeGreetingMorning2": "Pagi yang cerah", + "homeGreetingMorning3": "Awali harimu", + "homeGreetingAfternoon1": "Selamat siang", + "homeGreetingAfternoon2": "Siang yang santai", + "homeGreetingAfternoon3": "Tetap semangat", + "homeGreetingEvening1": "Selamat malam", + "homeGreetingEvening2": "Waktunya rileks", + "homeGreetingEvening3": "Senja yang syahdu", + "homeGreetingNight1": "Selamat malam", + "homeGreetingNight2": "Teman begadang", + "homeGreetingNight3": "Malam yang tenang", + "homeGreetingNamed": "{greeting}, {name}", "artistUpcomingConcerts": "Konser Mendatang", "artistAllUpcomingConcerts": "Semua Konser Mendatang", "artistNoUpcomingConcerts": "Tiada konser mendatang.", diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 1162ff76..1d38cab6 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -40,6 +40,7 @@ import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; import 'package:spotiflac_android/providers/recent_access_provider.dart'; import 'package:spotiflac_android/providers/explore_provider.dart'; +import 'package:spotiflac_android/providers/user_profile_provider.dart'; import 'package:spotiflac_android/widgets/explore_featured_section.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/screens/track_metadata_screen.dart'; @@ -58,6 +59,7 @@ import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; import 'package:spotiflac_android/utils/editorial_notes.dart'; +import 'package:spotiflac_android/utils/home_greeting.dart'; import 'package:spotiflac_android/screens/playlist_screen.dart'; import 'package:spotiflac_android/screens/downloaded_album_screen.dart'; import 'package:spotiflac_android/widgets/download_service_picker.dart'; @@ -1079,13 +1081,20 @@ class _HomeTabState extends ConsumerState final exploreSections = ref.watch( exploreProvider.select((s) => s.sections), ); - final exploreGreeting = ref.watch( - exploreProvider.select((s) => s.greeting), + // The feed decides whether a greeting shows; its words + // are localized here and address the profile name. + final showGreeting = + ref.watch(exploreProvider.select((s) => s.greeting)) != + null; + final profileName = ref.watch( + userProfileProvider.select((p) => p.value?.name ?? ''), ); return SliverMainAxisGroup( slivers: _buildExploreSections( exploreSections, - exploreGreeting, + showGreeting + ? homeGreeting(context.l10n, name: profileName) + : null, colorScheme, ), ); diff --git a/lib/utils/home_greeting.dart b/lib/utils/home_greeting.dart new file mode 100644 index 00000000..d72351fd --- /dev/null +++ b/lib/utils/home_greeting.dart @@ -0,0 +1,45 @@ +import 'dart:math'; + +import 'package:spotiflac_android/l10n/app_localizations.dart'; + +/// Picked once per launch: the greeting holds still while browsing and +/// changes the next time the app opens. +final int _sessionVariant = Random().nextInt(1 << 16); + +/// A time-of-day Home greeting, addressed by the profile's first name when +/// one is set. +String homeGreeting( + AppLocalizations l10n, { + String name = '', + DateTime? now, + int? variant, +}) { + final hour = (now ?? DateTime.now()).hour; + final options = switch (hour) { + >= 5 && < 12 => [ + l10n.homeGreetingMorning1, + l10n.homeGreetingMorning2, + l10n.homeGreetingMorning3, + ], + >= 12 && < 17 => [ + l10n.homeGreetingAfternoon1, + l10n.homeGreetingAfternoon2, + l10n.homeGreetingAfternoon3, + ], + >= 17 && < 21 => [ + l10n.homeGreetingEvening1, + l10n.homeGreetingEvening2, + l10n.homeGreetingEvening3, + ], + _ => [ + l10n.homeGreetingNight1, + l10n.homeGreetingNight2, + l10n.homeGreetingNight3, + ], + }; + final phrase = options[(variant ?? _sessionVariant) % options.length]; + final trimmed = name.trim(); + if (trimmed.isEmpty) return phrase; + // A full name reads stiff in a headline. + return l10n.homeGreetingNamed(phrase, trimmed.split(RegExp(r'\s+')).first); +} diff --git a/test/home_greeting_test.dart b/test/home_greeting_test.dart new file mode 100644 index 00000000..215714f2 --- /dev/null +++ b/test/home_greeting_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spotiflac_android/l10n/app_localizations.dart'; +import 'package:spotiflac_android/utils/home_greeting.dart'; + +void main() { + late AppLocalizations l10n; + + setUpAll(() async { + l10n = await AppLocalizations.delegate.load(const Locale('en')); + }); + + String at(int hour, {String name = '', int variant = 0}) => homeGreeting( + l10n, + name: name, + now: DateTime(2026, 10, 18, hour), + variant: variant, + ); + + test('follows the time of day', () { + expect(at(8), 'Good morning'); + expect(at(13), 'Good afternoon'); + expect(at(18), 'Good evening'); + expect(at(23), 'Good night'); + expect(at(3), 'Good night'); + }); + + test('variants rotate within the same part of the day', () { + final phrases = {for (var i = 0; i < 3; i++) at(8, variant: i)}; + expect(phrases, {'Good morning', 'Rise and shine', 'Fresh start'}); + expect(at(8, variant: 3), at(8)); + }); + + test('addresses the profile by first name only', () { + expect(at(23, name: ' Alex Rivera '), 'Good night, Alex'); + expect(at(23, name: ' '), 'Good night'); + }); +}