feat(tv-admin): fix action/focus flows, update app title, randomize unsplash, bump 1.0.9+10

This commit is contained in:
dwindown
2026-04-05 14:59:55 +07:00
parent c70a6baf7b
commit 98b8437e87
13 changed files with 144 additions and 131 deletions

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -14,11 +15,15 @@ class UnsplashBackground extends ConsumerStatefulWidget {
}
class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
final Random _rng = Random();
List<String> _urls = [];
int _currentIndex = 0;
Timer? _rotationTimer;
Timer? _keywordDebounceTimer;
int _fetchNonce = 0;
String? _lastKeyword;
int? _lastRotationHours;
bool? _lastUseUnsplash;
@override
void initState() {
@@ -30,18 +35,55 @@ class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
final settings = ref.read(settingsProvider);
_lastKeyword = settings.unsplashKeyword;
_lastRotationHours = settings.unsplashRotationHours;
_lastUseUnsplash = settings.useUnsplashBackground;
await _fetchImages(settings.unsplashKeyword);
if (settings.useUnsplashBackground) {
await _fetchImages(settings.unsplashKeyword, immediate: true);
}
_startTimer(settings.unsplashRotationHours);
}
Future<void> _fetchImages(String keyword) async {
final urls = await UnsplashService.instance.fetchLandscapeBackgrounds(keyword);
if (urls.isNotEmpty && mounted) {
setState(() {
_urls = urls;
_currentIndex = 0;
});
Future<void> _fetchImages(String keyword, {bool immediate = false}) async {
_keywordDebounceTimer?.cancel();
Future<void> runFetch() async {
if (!ref.read(settingsProvider).useUnsplashBackground) return;
final requestId = ++_fetchNonce;
final randomPage = 1 + _rng.nextInt(10);
final urls = await UnsplashService.instance.fetchLandscapeBackgrounds(
keyword,
page: randomPage,
);
if (!mounted || requestId != _fetchNonce) return;
if (urls.isNotEmpty) {
urls.shuffle(_rng);
setState(() {
_urls = urls;
_currentIndex = _rng.nextInt(_urls.length);
});
}
}
if (immediate) {
await runFetch();
return;
}
_keywordDebounceTimer = Timer(
const Duration(milliseconds: 1200),
() {
unawaited(runFetch());
},
);
}
void _nextRandomImage() {
if (_urls.isNotEmpty && mounted) {
if (_urls.length <= 1) return;
var nextIndex = _currentIndex;
while (nextIndex == _currentIndex) {
nextIndex = _rng.nextInt(_urls.length);
}
setState(() => _currentIndex = nextIndex);
}
}
@@ -50,17 +92,14 @@ class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
if (hours <= 0) return;
_rotationTimer = Timer.periodic(Duration(hours: hours), (_) {
if (_urls.isNotEmpty && mounted) {
setState(() {
_currentIndex = (_currentIndex + 1) % _urls.length;
});
}
_nextRandomImage();
});
}
@override
void dispose() {
_rotationTimer?.cancel();
_keywordDebounceTimer?.cancel();
super.dispose();
}
@@ -69,12 +108,20 @@ class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
final settings = ref.watch(settingsProvider);
// Watch for config changes
if (settings.useUnsplashBackground != _lastUseUnsplash) {
_lastUseUnsplash = settings.useUnsplashBackground;
if (settings.useUnsplashBackground && _urls.isEmpty) {
_fetchImages(settings.unsplashKeyword, immediate: true);
}
}
if (settings.unsplashKeyword != _lastKeyword) {
_lastKeyword = settings.unsplashKeyword;
// Re-fetch images organically
_fetchImages(settings.unsplashKeyword);
if (settings.useUnsplashBackground) {
_fetchImages(settings.unsplashKeyword);
}
}
if (settings.unsplashRotationHours != _lastRotationHours) {
_lastRotationHours = settings.unsplashRotationHours;
_startTimer(settings.unsplashRotationHours);