mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-04 19:57:55 +02:00
fix: iOS path migration, local greeting timezone, ICU plural warnings
- iOS: Auto-migrate file paths when container UUID changes after app update - Greeting: Use device local time instead of extension response - i18n: Fix 16 ICU plural syntax warnings in Spanish and Portuguese
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## [3.2.1] - 2026-01-22
|
||||
|
||||
> **Note:** Starting from the next release, version format will change from `major.minor.patch` to `year.month.day` (e.g., 26.1.23).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **iOS History Migration**: Fixed "File not found" after updating from 3.1.x to 3.2.0 (container UUID change)
|
||||
- **Home Feed Greeting**: Fixed wrong timezone - now uses device local time instead of extension
|
||||
- **Deezer Track Position**: Fallback to index+1 when API returns 0 for track position
|
||||
- **Spanish & Portuguese Plurals**: Fixed 16 ICU syntax warnings in localization files
|
||||
|
||||
---
|
||||
|
||||
## [3.2.0] - 2026-01-22
|
||||
|
||||
> **Note:** Starting from v3.2.0, changelogs will be concise.
|
||||
|
||||
@@ -193,6 +193,14 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
_historyLog.i('Migrated history from SharedPreferences to SQLite');
|
||||
}
|
||||
|
||||
// Migrate iOS paths if container UUID changed after app update
|
||||
if (Platform.isIOS) {
|
||||
final pathsMigrated = await _db.migrateIosContainerPaths();
|
||||
if (pathsMigrated) {
|
||||
_historyLog.i('Migrated iOS container paths after app update');
|
||||
}
|
||||
}
|
||||
|
||||
final jsonList = await _db.getAll();
|
||||
final items = jsonList
|
||||
.map((e) => DownloadHistoryItem.fromJson(e))
|
||||
|
||||
@@ -109,6 +109,20 @@ class ExploreState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate greeting based on local device time
|
||||
String _getLocalGreeting() {
|
||||
final hour = DateTime.now().hour;
|
||||
if (hour >= 5 && hour < 12) {
|
||||
return 'Good morning';
|
||||
} else if (hour >= 12 && hour < 17) {
|
||||
return 'Good afternoon';
|
||||
} else if (hour >= 17 && hour < 21) {
|
||||
return 'Good evening';
|
||||
} else {
|
||||
return 'Good night';
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider for explore/home feed state
|
||||
class ExploreNotifier extends Notifier<ExploreState> {
|
||||
@override
|
||||
@@ -201,9 +215,14 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
_log.d('First item: name=${firstItem.name}, artists=${firstItem.artists}, type=${firstItem.type}');
|
||||
}
|
||||
|
||||
// Always use local device time for greeting to avoid timezone issues
|
||||
// Extension greeting may use wrong timezone (UTC or Spotify account timezone)
|
||||
final localGreeting = _getLocalGreeting();
|
||||
_log.d('Greeting from extension: $greeting, using local: $localGreeting');
|
||||
|
||||
state = ExploreState(
|
||||
isLoading: false,
|
||||
greeting: greeting,
|
||||
greeting: localGreeting,
|
||||
sections: sections,
|
||||
lastFetched: DateTime.now(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -7,6 +8,9 @@ import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
final _log = AppLogger('HistoryDatabase');
|
||||
|
||||
/// Cached current iOS container path for path normalization
|
||||
String? _currentContainerPath;
|
||||
|
||||
/// SQLite database service for download history
|
||||
/// Provides O(1) lookups by spotify_id and isrc with proper indexing
|
||||
class HistoryDatabase {
|
||||
@@ -78,6 +82,106 @@ class HistoryDatabase {
|
||||
// Future migrations go here
|
||||
}
|
||||
|
||||
// ==================== iOS Path Normalization ====================
|
||||
|
||||
/// Pattern to match iOS container paths
|
||||
/// Example: /var/mobile/Containers/Data/Application/UUID-HERE/Documents/...
|
||||
static final _iosContainerPattern = RegExp(
|
||||
r'/var/mobile/Containers/Data/Application/[A-F0-9\-]+/',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
/// Initialize and cache the current iOS container path
|
||||
Future<void> _initContainerPath() async {
|
||||
if (!Platform.isIOS || _currentContainerPath != null) return;
|
||||
|
||||
try {
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
// Extract container path up to and including the UUID folder
|
||||
// e.g., /var/mobile/Containers/Data/Application/UUID/
|
||||
final match = _iosContainerPattern.firstMatch(docDir.path);
|
||||
if (match != null) {
|
||||
_currentContainerPath = match.group(0);
|
||||
_log.d('iOS container path: $_currentContainerPath');
|
||||
}
|
||||
} catch (e) {
|
||||
_log.w('Failed to get iOS container path: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize iOS file path by replacing old container UUID with current one
|
||||
/// This fixes the issue where iOS changes container UUID after app updates
|
||||
String _normalizeIosPath(String? filePath) {
|
||||
if (filePath == null || filePath.isEmpty) return filePath ?? '';
|
||||
if (!Platform.isIOS || _currentContainerPath == null) return filePath;
|
||||
|
||||
// Check if path contains an iOS container path
|
||||
if (_iosContainerPattern.hasMatch(filePath)) {
|
||||
final normalized = filePath.replaceFirst(_iosContainerPattern, _currentContainerPath!);
|
||||
if (normalized != filePath) {
|
||||
_log.d('Normalized iOS path: $filePath -> $normalized');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/// Migrate iOS paths in database to use current container UUID
|
||||
/// This is called once after app update if container changed
|
||||
Future<bool> migrateIosContainerPaths() async {
|
||||
if (!Platform.isIOS) return false;
|
||||
|
||||
await _initContainerPath();
|
||||
if (_currentContainerPath == null) return false;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final lastContainer = prefs.getString('ios_last_container_path');
|
||||
|
||||
// Skip if container hasn't changed
|
||||
if (lastContainer == _currentContainerPath) {
|
||||
_log.d('iOS container path unchanged, skipping migration');
|
||||
return false;
|
||||
}
|
||||
|
||||
_log.i('iOS container changed: $lastContainer -> $_currentContainerPath');
|
||||
|
||||
try {
|
||||
final db = await database;
|
||||
|
||||
// Get all items with iOS paths
|
||||
final rows = await db.query('history', columns: ['id', 'file_path']);
|
||||
int updatedCount = 0;
|
||||
|
||||
for (final row in rows) {
|
||||
final id = row['id'] as String;
|
||||
final oldPath = row['file_path'] as String?;
|
||||
|
||||
if (oldPath != null && _iosContainerPattern.hasMatch(oldPath)) {
|
||||
final newPath = _normalizeIosPath(oldPath);
|
||||
if (newPath != oldPath) {
|
||||
await db.update(
|
||||
'history',
|
||||
{'file_path': newPath},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save current container path
|
||||
await prefs.setString('ios_last_container_path', _currentContainerPath!);
|
||||
|
||||
_log.i('iOS path migration complete: $updatedCount paths updated');
|
||||
return updatedCount > 0;
|
||||
} catch (e, stack) {
|
||||
_log.e('iOS path migration failed: $e', e, stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate data from SharedPreferences to SQLite
|
||||
/// Returns true if migration was performed, false if already migrated
|
||||
Future<bool> migrateFromSharedPreferences() async {
|
||||
@@ -153,6 +257,7 @@ class HistoryDatabase {
|
||||
}
|
||||
|
||||
/// Convert DB row (snake_case) to JSON format (camelCase)
|
||||
/// Also normalizes iOS paths if container UUID changed
|
||||
Map<String, dynamic> _dbRowToJson(Map<String, dynamic> row) {
|
||||
return {
|
||||
'id': row['id'],
|
||||
@@ -161,7 +266,7 @@ class HistoryDatabase {
|
||||
'albumName': row['album_name'],
|
||||
'albumArtist': row['album_artist'],
|
||||
'coverUrl': row['cover_url'],
|
||||
'filePath': row['file_path'],
|
||||
'filePath': _normalizeIosPath(row['file_path'] as String?),
|
||||
'service': row['service'],
|
||||
'downloadedAt': row['downloaded_at'],
|
||||
'isrc': row['isrc'],
|
||||
|
||||
Reference in New Issue
Block a user