mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-14 23:20:25 +02:00
feat: show remote-config launch announcement on app start
Introduce AppRemoteConfigService which fetches a platform/version/locale-aware JSON payload from api.zarz.moe/v1/spotiflac-mobile/config and caches it in SharedPreferences. main_shell shows a one-shot announcement dialog (respecting dismissible, CTA, time window and version gates) when no update prompt is pending; dismissed IDs are persisted so each announcement surfaces only once. Tweaks bundled in: the service health dot loses its blur halo in favour of solid Material 3 tones, and AppInfo gains the remote config endpoint constant. The share listener and SAF migration hook stay synchronous inside the post-frame callback so share-intent URLs never race the network-bound checks. New unit tests cover the announcement CTA/active-window rules.
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:spotiflac_android/models/download_item.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/models/theme_settings.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/services/app_remote_config_service.dart';
|
||||
import 'package:spotiflac_android/services/download_request_payload.dart';
|
||||
import 'package:spotiflac_android/utils/artist_utils.dart';
|
||||
import 'package:spotiflac_android/utils/mime_utils.dart';
|
||||
@@ -507,4 +508,127 @@ void main() {
|
||||
expect(keys, contains('C:/Music/Song'));
|
||||
});
|
||||
});
|
||||
|
||||
group('AppRemoteConfig', () {
|
||||
test('parses announcement and donate payloads from API JSON', () {
|
||||
final config = AppRemoteConfig.fromJson({
|
||||
'announcement': {
|
||||
'id': 'hello-2026',
|
||||
'enabled': true,
|
||||
'title': 'Server message',
|
||||
'message': 'A clear message for users',
|
||||
'cta_enabled': true,
|
||||
'cta_label': 'Donate',
|
||||
'cta_url': 'https://example.test/donate',
|
||||
'starts_at': '2026-05-01T00:00:00Z',
|
||||
'ends_at': '2026-06-01T00:00:00Z',
|
||||
'min_version': '4.5.0',
|
||||
'priority': 'high',
|
||||
},
|
||||
'donate': {
|
||||
'enabled': true,
|
||||
'title': 'Support SpotiFLAC Mobile',
|
||||
'message': 'Help cover infrastructure.',
|
||||
'methods': [
|
||||
{
|
||||
'id': 'kofi',
|
||||
'title': 'Ko-fi',
|
||||
'subtitle': 'ko-fi.com/example',
|
||||
'url': 'https://ko-fi.com/example',
|
||||
'icon': 'kofi',
|
||||
'color': '#FF5E5B',
|
||||
},
|
||||
{
|
||||
'id': 'wallet',
|
||||
'title': 'USDT',
|
||||
'subtitle': 'TRC20',
|
||||
'wallet_address': 'T123',
|
||||
'icon': 'wallet',
|
||||
'color': '0xFF26A17B',
|
||||
},
|
||||
],
|
||||
'supporters': ['Alice', 'Bob'],
|
||||
'notices': ['No paywalls'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(config.announcement?.id, 'hello-2026');
|
||||
expect(config.announcement?.hasCta, isTrue);
|
||||
expect(
|
||||
config.announcement?.isActive(
|
||||
now: DateTime.utc(2026, 5, 11),
|
||||
currentVersion: '4.5.1',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(config.donate.title, 'Support SpotiFLAC Mobile');
|
||||
expect(config.donate.methods, hasLength(2));
|
||||
expect(config.donate.methods.first.color, 0xFFFF5E5B);
|
||||
expect(config.donate.methods.last.isWallet, isTrue);
|
||||
expect(config.donate.supporters, ['Alice', 'Bob']);
|
||||
expect(config.donate.notices, ['No paywalls']);
|
||||
});
|
||||
|
||||
test('requires enabled announcement CTA with label and url', () {
|
||||
final disabledCta = RemoteAnnouncement.fromJson({
|
||||
'id': 'notice',
|
||||
'title': 'Notice',
|
||||
'message': 'No button',
|
||||
'cta_label': 'Open',
|
||||
'cta_url': 'https://api.zarz.moe',
|
||||
});
|
||||
final missingLabel = RemoteAnnouncement.fromJson({
|
||||
'id': 'notice',
|
||||
'title': 'Notice',
|
||||
'message': 'No button',
|
||||
'cta_enabled': true,
|
||||
'cta_url': 'https://example.test',
|
||||
});
|
||||
final enabledCta = RemoteAnnouncement.fromJson({
|
||||
'id': 'notice',
|
||||
'title': 'Notice',
|
||||
'message': 'With button',
|
||||
'cta_enabled': true,
|
||||
'cta_label': 'Read More',
|
||||
'cta_url': 'https://example.test',
|
||||
});
|
||||
|
||||
expect(disabledCta.hasCta, isFalse);
|
||||
expect(missingLabel.hasCta, isFalse);
|
||||
expect(enabledCta.hasCta, isTrue);
|
||||
expect(enabledCta.ctaLabel, 'Read More');
|
||||
});
|
||||
|
||||
test('filters inactive announcements by window and app version', () {
|
||||
final announcement = RemoteAnnouncement.fromJson({
|
||||
'id': 'future',
|
||||
'title': 'Future',
|
||||
'message': 'Not yet',
|
||||
'starts_at': '2026-06-01T00:00:00Z',
|
||||
'min_version': '4.6.0',
|
||||
});
|
||||
|
||||
expect(
|
||||
announcement.isActive(
|
||||
now: DateTime.utc(2026, 5, 11),
|
||||
currentVersion: '4.5.1',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
announcement.isActive(
|
||||
now: DateTime.utc(2026, 6, 2),
|
||||
currentVersion: '4.5.1',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
announcement.isActive(
|
||||
now: DateTime.utc(2026, 6, 2),
|
||||
currentVersion: '4.6.0',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user