feat(home): localize greetings and address profile names

This commit is contained in:
zarzet committed 2026-10-18 03:11:44 +07:00
1 parent 1a6dc7366e
commit 4cee16e9e8
5 files changed
+132 -3

No files matched your search

+24
View File
@@ -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.",
+13
View File
@@ -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.",
+12 -3
View File
@@ -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<HomeTab>
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,
),
);
+45
View File
@@ -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);
}
+38
View File
@@ -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');
});
}