refactor: remove cloud save feature entirely

This feature was deemed unnecessary and was adding complexity to the app.

Removed:
- lib/services/cloud_upload_service.dart
- lib/providers/upload_queue_provider.dart
- lib/screens/settings/cloud_settings_page.dart
- lib/screens/settings/widgets/ (cloud status hero card)
- All cloud* settings from AppSettings model
- All cloud-related localization keys (~70 keys)
- Cloud settings from settings_provider.dart
- Cloud menu item from settings_tab.dart
- Cloud upload trigger from download_queue_provider.dart

Dependencies removed:
- webdav_client: ^1.2.2
- dartssh2: ^2.13.0

CHANGELOG updated to remove all cloud save references.
This commit is contained in:
zarzet
2026-02-03 23:10:19 +07:00
parent a2eb89e230
commit 99e0d3d361
27 changed files with 1818 additions and 9088 deletions
+1 -21
View File
@@ -53,30 +53,16 @@
- Shows "Already in Library" instead of "Download complete"
- Skips adding duplicate entry if track already in download history
- Reads actual quality from existing file (bit depth, sample rate)
- **Cloud Upload with WebDAV & SFTP**: Automatically upload downloaded files to your NAS or cloud storage
- Full WebDAV support (Synology DSM, Nextcloud, QNAP, ownCloud)
- Full SFTP support (any SSH server with SFTP enabled)
- Connection test with detailed error messages
- Upload queue with progress tracking
- Retry failed uploads and clear completed items
- Recent uploads list in Cloud Save settings
- **SFTP Host Key Security (TOFU)**: Verify host keys on connect and block mismatches
- **Reset SFTP Host Keys**: Clear the saved host key for the current server or all servers
### Dependencies
- Added `webdav_client: ^1.2.2` for WebDAV protocol support
- Added `dartssh2: ^2.13.0` for SFTP protocol support
- Added `flutter_secure_storage: ^9.2.2` for storing cloud passwords securely
- Added `flutter_secure_storage: ^9.2.2` for storing secrets securely
### Changed
- Cloud upload passwords are now stored in secure storage instead of SharedPreferences
- Spotify client secrets are now stored in secure storage instead of SharedPreferences
- Extension HTTP sandbox now enforces HTTPS and blocks private IPs resolved via DNS
- Extension file sandbox now validates paths using boundary-safe checks
- WebDAV now defaults to HTTPS; insecure HTTP requires explicit opt-in
- WebDAV error messages are now localized in the UI
- **Albums grid now unified**: Downloaded and local albums render in a single grid (no layout gaps)
- **LocalAlbumScreen UI consistency**: Track items, disc separators, and selection bottom bar now match DownloadedAlbumScreen
@@ -107,12 +93,6 @@
- Choose between "WiFi + Mobile Data" or "WiFi Only"
- Downloads automatically pause on mobile data and resume on WiFi
- **Cloud Save Settings**: New settings page for cloud/NAS upload configuration
- Settings > Cloud Save - configure auto-upload to NAS or cloud storage
- Support for WebDAV (Synology, Nextcloud, QNAP) and SFTP providers
- Server URL, username, password, and remote path configuration
- Test connection button (actual upload implementation coming in v3.4.0)
### Dependencies
- Added `connectivity_plus: ^6.0.3` for network state detection
+45 -502
View File
@@ -73,8 +73,7 @@ import 'app_localizations_zh.dart';
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
@@ -82,8 +81,7 @@ abstract class AppLocalizations {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
@@ -95,13 +93,12 @@ abstract class AppLocalizations {
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
@@ -121,7 +118,7 @@ abstract class AppLocalizations {
Locale('tr'),
Locale('zh'),
Locale('zh', 'CN'),
Locale('zh', 'TW'),
Locale('zh', 'TW')
];
/// App name - DO NOT TRANSLATE
@@ -3718,282 +3715,6 @@ abstract class AppLocalizations {
/// **'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'**
String get settingsDownloadNetworkSubtitle;
/// Settings menu item for cloud upload
///
/// In en, this message translates to:
/// **'Cloud Save'**
String get settingsCloudSave;
/// Subtitle for cloud save menu item
///
/// In en, this message translates to:
/// **'Auto-upload to NAS or cloud storage'**
String get settingsCloudSaveSubtitle;
/// Cloud settings page title
///
/// In en, this message translates to:
/// **'Cloud Save'**
String get cloudSettingsTitle;
/// General section header
///
/// In en, this message translates to:
/// **'General'**
String get cloudSettingsSectionGeneral;
/// Toggle to enable cloud upload
///
/// In en, this message translates to:
/// **'Enable Cloud Upload'**
String get cloudSettingsEnable;
/// Subtitle for enable toggle
///
/// In en, this message translates to:
/// **'Automatically upload files after download completes'**
String get cloudSettingsEnableSubtitle;
/// Provider section header
///
/// In en, this message translates to:
/// **'Cloud Provider'**
String get cloudSettingsSectionProvider;
/// Provider selection setting
///
/// In en, this message translates to:
/// **'Provider'**
String get cloudSettingsProvider;
/// Provider picker description
///
/// In en, this message translates to:
/// **'Select where to upload your downloaded files'**
String get cloudSettingsProviderDescription;
/// Server config section header
///
/// In en, this message translates to:
/// **'Server Configuration'**
String get cloudSettingsSectionServer;
/// Server URL field label
///
/// In en, this message translates to:
/// **'Server URL'**
String get cloudSettingsServerUrl;
/// Username field label
///
/// In en, this message translates to:
/// **'Username'**
String get cloudSettingsUsername;
/// Password field label
///
/// In en, this message translates to:
/// **'Password'**
String get cloudSettingsPassword;
/// Remote path field label
///
/// In en, this message translates to:
/// **'Remote Folder Path'**
String get cloudSettingsRemotePath;
/// Test connection button
///
/// In en, this message translates to:
/// **'Test Connection'**
String get cloudSettingsTestConnection;
/// Info card explaining the feature
///
/// In en, this message translates to:
/// **'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.'**
String get cloudSettingsInfo;
/// Section header for upload queue status
///
/// In en, this message translates to:
/// **'Upload Queue'**
String get cloudSettingsUploadQueue;
/// Button to retry failed uploads
///
/// In en, this message translates to:
/// **'Retry Failed'**
String get cloudSettingsRetryFailed;
/// Button to clear completed uploads
///
/// In en, this message translates to:
/// **'Clear Done'**
String get cloudSettingsClearDone;
/// Section header for recent uploads list
///
/// In en, this message translates to:
/// **'Recent Uploads'**
String get cloudSettingsRecentUploads;
/// Button/title to reset SFTP host key for current server
///
/// In en, this message translates to:
/// **'Reset SFTP Host Key'**
String get cloudSettingsResetSftpHostKey;
/// Button/title to reset all saved SFTP host keys
///
/// In en, this message translates to:
/// **'Reset All SFTP Host Keys'**
String get cloudSettingsResetAllSftpHostKeys;
/// Dialog message for resetting SFTP host key
///
/// In en, this message translates to:
/// **'This will forget the saved host key for this server. The next connection will save a new key.'**
String get cloudSettingsResetSftpHostKeyMessage;
/// Dialog message for resetting all SFTP host keys
///
/// In en, this message translates to:
/// **'This will forget all saved SFTP host keys. Next connections will save new keys.'**
String get cloudSettingsResetAllSftpHostKeysMessage;
/// Dialog confirm button for reset action
///
/// In en, this message translates to:
/// **'Reset'**
String get cloudSettingsResetConfirm;
/// Dialog confirm button for reset all action
///
/// In en, this message translates to:
/// **'Reset All'**
String get cloudSettingsResetAllConfirm;
/// Validation message when server URL is missing
///
/// In en, this message translates to:
/// **'Server URL is required'**
String get cloudSettingsServerUrlRequired;
/// Snackbar after resetting SFTP host key
///
/// In en, this message translates to:
/// **'SFTP host key reset. Connect again to save a new key.'**
String get cloudSettingsResetSftpHostKeySuccess;
/// Snackbar when no host key exists for the current server
///
/// In en, this message translates to:
/// **'No stored host key found for this server.'**
String get cloudSettingsResetSftpHostKeyNotFound;
/// Snackbar after clearing all SFTP host keys
///
/// In en, this message translates to:
/// **'{count, plural, =1{Cleared 1 SFTP host key.} other{Cleared {count} SFTP host keys.}}'**
String cloudSettingsResetAllSftpHostKeysCleared(num count);
/// Snackbar when no SFTP host keys exist
///
/// In en, this message translates to:
/// **'No stored SFTP host keys found.'**
String get cloudSettingsResetAllSftpHostKeysNone;
/// Toggle/title for allowing insecure HTTP WebDAV connections
///
/// In en, this message translates to:
/// **'Allow HTTP (Insecure)'**
String get cloudSettingsAllowHttpTitle;
/// Subtitle warning for allowing insecure HTTP
///
/// In en, this message translates to:
/// **'Sends credentials without TLS. Not recommended.'**
String get cloudSettingsAllowHttpSubtitle;
/// Dialog warning message for enabling insecure HTTP
///
/// In en, this message translates to:
/// **'HTTP does not encrypt your credentials. Only enable if you trust the network.'**
String get cloudSettingsAllowHttpMessage;
/// Dialog confirm button for enabling insecure HTTP
///
/// In en, this message translates to:
/// **'Allow HTTP'**
String get cloudSettingsAllowHttpConfirm;
/// WebDAV error when URL scheme is missing
///
/// In en, this message translates to:
/// **'Invalid URL: scheme is required'**
String get webdavErrorInvalidScheme;
/// WebDAV error when HTTPS is required
///
/// In en, this message translates to:
/// **'WebDAV URL must use https'**
String get webdavErrorHttpsRequired;
/// WebDAV error when hostname is missing
///
/// In en, this message translates to:
/// **'Invalid URL: hostname is required'**
String get webdavErrorInvalidHost;
/// WebDAV error when authentication fails
///
/// In en, this message translates to:
/// **'Authentication failed. Check username and password.'**
String get webdavErrorAuthFailed;
/// WebDAV error when access is forbidden
///
/// In en, this message translates to:
/// **'Access denied. Check permissions on the server.'**
String get webdavErrorForbidden;
/// WebDAV error when path is not found
///
/// In en, this message translates to:
/// **'Server path not found. Check the URL.'**
String get webdavErrorNotFound;
/// WebDAV error when connection fails
///
/// In en, this message translates to:
/// **'Cannot connect to server. Check URL and network.'**
String get webdavErrorConnectionFailed;
/// WebDAV error for TLS issues
///
/// In en, this message translates to:
/// **'SSL/TLS error. Server certificate may be invalid.'**
String get webdavErrorTlsError;
/// WebDAV error when connection times out
///
/// In en, this message translates to:
/// **'Connection timed out. Server may be unreachable.'**
String get webdavErrorTimeout;
/// WebDAV error when server storage is full
///
/// In en, this message translates to:
/// **'Insufficient storage on server.'**
String get webdavErrorInsufficientStorage;
/// WebDAV fallback error message
///
/// In en, this message translates to:
/// **'Upload failed. Please try again.'**
String get webdavErrorUnknown;
/// Empty queue state title
///
/// In en, this message translates to:
@@ -4689,154 +4410,9 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{count, plural, =1{1 hour ago} other{{count} hours ago}}'**
String timeHoursAgo(int count);
/// WebDAV provider option with examples
///
/// In en, this message translates to:
/// **'WebDAV (Synology, Nextcloud, QNAP)'**
String get cloudProviderWebdav;
/// SFTP provider option
///
/// In en, this message translates to:
/// **'SFTP (SSH File Transfer)'**
String get cloudProviderSftp;
/// No cloud provider selected
///
/// In en, this message translates to:
/// **'Not Configured'**
String get cloudProviderNotConfigured;
/// WebDAV provider title in picker
///
/// In en, this message translates to:
/// **'WebDAV'**
String get cloudProviderWebdavTitle;
/// WebDAV provider subtitle in picker
///
/// In en, this message translates to:
/// **'Synology, Nextcloud, QNAP, ownCloud'**
String get cloudProviderWebdavSubtitle;
/// SFTP provider title in picker
///
/// In en, this message translates to:
/// **'SFTP'**
String get cloudProviderSftpTitle;
/// SFTP provider subtitle in picker
///
/// In en, this message translates to:
/// **'SSH File Transfer Protocol'**
String get cloudProviderSftpSubtitle;
/// Error when testing connection without server URL
///
/// In en, this message translates to:
/// **'Server URL is required'**
String get cloudTestErrorServerUrlRequired;
/// Error when testing connection without credentials
///
/// In en, this message translates to:
/// **'Username and password are required'**
String get cloudTestErrorCredentialsRequired;
/// Success message for WebDAV connection test
///
/// In en, this message translates to:
/// **'Connected to WebDAV server'**
String get cloudTestSuccessWebdav;
/// Success message for SFTP connection test
///
/// In en, this message translates to:
/// **'Connected to SFTP server'**
String get cloudTestSuccessSftp;
/// Error when testing connection without provider
///
/// In en, this message translates to:
/// **'No provider selected'**
String get cloudTestErrorNoProvider;
/// Connection test success message
///
/// In en, this message translates to:
/// **'Success: {message}'**
String connectionTestSuccess(String message);
/// Upload queue status - waiting
///
/// In en, this message translates to:
/// **'Pending'**
String get uploadStatusPending;
/// Upload queue status - in progress
///
/// In en, this message translates to:
/// **'Uploading'**
String get uploadStatusUploading;
/// Upload queue status - completed
///
/// In en, this message translates to:
/// **'Done'**
String get uploadStatusDone;
/// Upload queue status - error
///
/// In en, this message translates to:
/// **'Failed'**
String get uploadStatusFailed;
/// Status when cloud save is disabled
///
/// In en, this message translates to:
/// **'Cloud Save Off'**
String get cloudStatusDisabled;
/// Subtitle when cloud save is disabled
///
/// In en, this message translates to:
/// **'Enable to auto-upload tracks'**
String get cloudStatusDisabledSubtitle;
/// Status when no provider selected
///
/// In en, this message translates to:
/// **'Select Provider'**
String get cloudStatusNoProvider;
/// Subtitle when no provider selected
///
/// In en, this message translates to:
/// **'Choose a cloud service'**
String get cloudStatusNoProviderSubtitle;
/// Status when settings missing
///
/// In en, this message translates to:
/// **'Setup Required'**
String get cloudStatusNotConfigured;
/// Subtitle when settings missing
///
/// In en, this message translates to:
/// **'Configure your server details'**
String get cloudStatusNotConfiguredSubtitle;
/// Status when cloud save is active
///
/// In en, this message translates to:
/// **'Connected'**
String get cloudStatusActive;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
@@ -4845,91 +4421,58 @@ class _AppLocalizationsDelegate
}
@override
bool isSupported(Locale locale) => <String>[
'de',
'en',
'es',
'fr',
'hi',
'id',
'ja',
'ko',
'nl',
'pt',
'ru',
'tr',
'zh',
].contains(locale.languageCode);
bool isSupported(Locale locale) => <String>['de', 'en', 'es', 'fr', 'hi', 'id', 'ja', 'ko', 'nl', 'pt', 'ru', 'tr', 'zh'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when language+country codes are specified.
switch (locale.languageCode) {
case 'es':
{
switch (locale.countryCode) {
case 'ES':
return AppLocalizationsEsEs();
}
break;
}
case 'pt':
{
switch (locale.countryCode) {
case 'PT':
return AppLocalizationsPtPt();
}
break;
}
case 'zh':
{
switch (locale.countryCode) {
case 'CN':
return AppLocalizationsZhCn();
case 'TW':
return AppLocalizationsZhTw();
}
break;
}
case 'es': {
switch (locale.countryCode) {
case 'ES': return AppLocalizationsEsEs();
}
break;
}
case 'pt': {
switch (locale.countryCode) {
case 'PT': return AppLocalizationsPtPt();
}
break;
}
case 'zh': {
switch (locale.countryCode) {
case 'CN': return AppLocalizationsZhCn();
case 'TW': return AppLocalizationsZhTw();
}
break;
}
}
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
case 'es':
return AppLocalizationsEs();
case 'fr':
return AppLocalizationsFr();
case 'hi':
return AppLocalizationsHi();
case 'id':
return AppLocalizationsId();
case 'ja':
return AppLocalizationsJa();
case 'ko':
return AppLocalizationsKo();
case 'nl':
return AppLocalizationsNl();
case 'pt':
return AppLocalizationsPt();
case 'ru':
return AppLocalizationsRu();
case 'tr':
return AppLocalizationsTr();
case 'zh':
return AppLocalizationsZh();
case 'de': return AppLocalizationsDe();
case 'en': return AppLocalizationsEn();
case 'es': return AppLocalizationsEs();
case 'fr': return AppLocalizationsFr();
case 'hi': return AppLocalizationsHi();
case 'id': return AppLocalizationsId();
case 'ja': return AppLocalizationsJa();
case 'ko': return AppLocalizationsKo();
case 'nl': return AppLocalizationsNl();
case 'pt': return AppLocalizationsPt();
case 'ru': return AppLocalizationsRu();
case 'tr': return AppLocalizationsTr();
case 'zh': return AppLocalizationsZh();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.',
'that was used.'
);
}
File diff suppressed because it is too large Load Diff
+89 -417
View File
@@ -12,8 +12,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get appName => 'SpotiFLAC';
@override
String get appDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get appDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get navHome => 'Home';
@@ -102,15 +101,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get historyNoAlbums => 'No album downloads';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'No single downloads';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => 'Search history...';
@@ -158,8 +155,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get downloadAskQuality => 'Ask Quality Before Download';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'Filename Format';
@@ -171,8 +167,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get downloadSeparateSingles => 'Separate Singles';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'Best Available';
@@ -229,8 +224,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsPrimaryProvider => 'Primary Provider';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => 'Use Extension Providers';
@@ -261,15 +253,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsEmbedLyrics => 'Embed Lyrics';
@override
String get optionsEmbedLyricsSubtitle =>
'Embed synced lyrics into FLAC files';
String get optionsEmbedLyricsSubtitle => 'Embed synced lyrics into FLAC files';
@override
String get optionsMaxQualityCover => 'Max Quality Cover';
@override
String get optionsMaxQualityCoverSubtitle =>
'Download highest resolution cover art';
String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@@ -283,8 +273,7 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -296,8 +285,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsCheckUpdates => 'Check for Updates';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => 'Update Channel';
@@ -309,15 +297,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsUpdateChannelPreview => 'Get preview releases';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'Clear Download History';
@override
String get optionsClearHistorySubtitle =>
'Remove all downloaded tracks from history';
String get optionsClearHistorySubtitle => 'Remove all downloaded tracks from history';
@override
String get optionsDetailedLogging => 'Detailed Logging';
@@ -340,8 +326,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => 'Required - tap to configure';
@override
String get optionsSpotifyWarning =>
'Spotify requires your own API credentials. Get them free from developer.spotify.com';
String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com';
@override
String get extensionsTitle => 'Extensions';
@@ -405,8 +390,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
@override
String get aboutLogoArtist =>
'The talented artist who created our beautiful app logo!';
String get aboutLogoArtist => 'The talented artist who created our beautiful app logo!';
@override
String get aboutTranslators => 'Translators';
@@ -466,34 +450,28 @@ class AppLocalizationsEn extends AppLocalizations {
String get aboutVersion => 'Version';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get albumTitle => 'Album';
@@ -598,8 +576,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupStoragePermission => 'Storage Permission';
@override
String get setupStoragePermissionSubtitle =>
'Required to save downloaded files';
String get setupStoragePermissionSubtitle => 'Required to save downloaded files';
@override
String get setupStoragePermissionGranted => 'Permission granted';
@@ -626,19 +603,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupStorageAccessRequired => 'Storage Access Required';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => 'Open Settings';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -657,8 +631,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupUseDefaultFolder => 'Use Default Folder?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'Use Default';
@@ -667,15 +640,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupDownloadLocationTitle => 'Download Location';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'App Documents Folder';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'Choose from Files';
@@ -684,12 +655,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'Select iCloud or other location';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
@@ -716,8 +685,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupStorageRequired => 'Storage Permission Required';
@override
String get setupStorageDescription =>
'SpotiFLAC needs storage permission to save your downloaded music files.';
String get setupStorageDescription => 'SpotiFLAC needs storage permission to save your downloaded music files.';
@override
String get setupNotificationGranted => 'Notification Permission Granted!';
@@ -726,8 +694,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupNotificationEnable => 'Enable Notifications';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'Download Folder Selected!';
@@ -736,8 +703,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupFolderChoose => 'Choose Download Folder';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'Change Folder';
@@ -749,8 +715,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (Optional)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Use Spotify API';
@@ -768,8 +733,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupEnterClientSecret => 'Enter Spotify Client Secret';
@override
String get setupGetFreeCredentials =>
'Get your free API credentials from the Spotify Developer Dashboard.';
String get setupGetFreeCredentials => 'Get your free API credentials from the Spotify Developer Dashboard.';
@override
String get setupEnableNotifications => 'Enable Notifications';
@@ -778,12 +742,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => 'Skip for now';
@@ -801,12 +763,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get setupSkipAndStart => 'Skip & Start';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'Get credentials from developer.spotify.com';
String get setupGetCredentialsFromSpotify => 'Get credentials from developer.spotify.com';
@override
String get dialogCancel => 'Cancel';
@@ -857,8 +817,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get dialogDiscardChanges => 'Discard Changes?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'Download Failed';
@@ -876,8 +835,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get dialogClearAll => 'Clear All';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'Remove from device?';
@@ -886,8 +844,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get dialogRemoveExtension => 'Remove Extension';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => 'Uninstall Extension?';
@@ -901,8 +858,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get dialogClearHistoryTitle => 'Clear History';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => 'Delete Selected';
@@ -997,8 +953,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get snackbarProviderPrioritySaved => 'Provider priority saved';
@override
String get snackbarMetadataProviderSaved =>
'Metadata provider priority saved';
String get snackbarMetadataProviderSaved => 'Metadata provider priority saved';
@override
String snackbarExtensionInstalled(String extensionName) {
@@ -1020,8 +975,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get errorRateLimited => 'Rate Limited';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,23 +1142,19 @@ class AppLocalizationsEn extends AppLocalizations {
String get folderOrganizationByArtistAlbum => 'Artist/Album';
@override
String get folderOrganizationDescription =>
'Organize downloaded files into folders';
String get folderOrganizationDescription => 'Organize downloaded files into folders';
@override
String get folderOrganizationNoneSubtitle => 'All files in download folder';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => 'Update Available';
@@ -1263,12 +1213,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get providerPriorityTitle => 'Provider Priority';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => 'Built-in';
@@ -1280,19 +1228,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get metadataProviderPriority => 'Metadata Provider Priority';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'Metadata Priority';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'No rate limits';
@@ -1364,19 +1309,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get logIssueSummary => 'Issue Summary';
@override
String get logIspBlockingDescription =>
'Your ISP may be blocking access to download services';
String get logIspBlockingDescription => 'Your ISP may be blocking access to download services';
@override
String get logIspBlockingSuggestion =>
'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
String get logIspBlockingSuggestion => 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
@override
String get logRateLimitedDescription => 'Too many requests to the service';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => 'Connection issues detected';
@@ -1385,12 +1327,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get logNetworkErrorSuggestion => 'Check your internet connection';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1416,8 +1356,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get credentialsTitle => 'Spotify Credentials';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'Client ID';
@@ -1471,8 +1410,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get lyricsMode => 'Lyrics Mode';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1484,8 +1422,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get lyricsModeExternal => 'External .lrc file';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => 'Both';
@@ -1645,8 +1582,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get trackDeleteConfirmTitle => 'Remove from device?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1798,15 +1734,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get extensionsNoExtensions => 'No extensions installed';
@override
String get extensionsNoExtensionsSubtitle =>
'Install .spotiflac-ext files to add new providers';
String get extensionsNoExtensionsSubtitle => 'Install .spotiflac-ext files to add new providers';
@override
String get extensionsInstallButton => 'Install Extension';
@override
String get extensionsInfoTip =>
'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
String get extensionsInfoTip => 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
@override
String get extensionsInstalledSuccess => 'Extension installed successfully';
@@ -1818,19 +1752,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get extensionsDownloadPrioritySubtitle => 'Set download service order';
@override
String get extensionsNoDownloadProvider =>
'No extensions with download provider';
String get extensionsNoDownloadProvider => 'No extensions with download provider';
@override
String get extensionsMetadataPriority => 'Metadata Priority';
@override
String get extensionsMetadataPrioritySubtitle =>
'Set search & metadata source order';
String get extensionsMetadataPrioritySubtitle => 'Set search & metadata source order';
@override
String get extensionsNoMetadataProvider =>
'No extensions with metadata provider';
String get extensionsNoMetadataProvider => 'No extensions with metadata provider';
@override
String get extensionsSearchProvider => 'Search Provider';
@@ -1839,8 +1770,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get extensionsNoCustomSearch => 'No extensions with custom search';
@override
String get extensionsSearchProviderDescription =>
'Choose which service to use for searching tracks';
String get extensionsSearchProviderDescription => 'Choose which service to use for searching tracks';
@override
String get extensionsCustomSearch => 'Custom search';
@@ -1882,8 +1812,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1895,12 +1824,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote =>
'Actual quality depends on track availability from the service';
String get qualityNote => 'Actual quality depends on track availability from the service';
@override
String get downloadAskBeforeDownload => 'Ask Before Download';
@@ -1990,15 +1917,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get queueClearAll => 'Clear All';
@override
String get queueClearAllMessage =>
'Are you sure you want to clear all downloads?';
String get queueClearAllMessage => 'Are you sure you want to clear all downloads?';
@override
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -2010,8 +1935,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2023,170 +1947,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
@@ -2222,8 +1983,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get albumFolderArtistYearAlbum => 'Artist / [Year] Album';
@override
String get albumFolderArtistYearAlbumSubtitle =>
'Albums/Artist Name/[2005] Album Name/';
String get albumFolderArtistYearAlbumSubtitle => 'Albums/Artist Name/[2005] Album Name/';
@override
String get albumFolderAlbumOnly => 'Album Only';
@@ -2241,8 +2001,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => 'Delete Selected';
@@ -2352,8 +2111,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get discographySelectAlbums => 'Select Albums...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'Fetching tracks...';
@@ -2400,16 +2158,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2430,8 +2185,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2443,8 +2197,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2462,8 +2215,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2475,15 +2227,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2521,8 +2271,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2613,81 +2362,4 @@ class AppLocalizationsEn extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
File diff suppressed because it is too large Load Diff
+89 -417
View File
@@ -12,8 +12,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get appName => 'SpotiFLAC';
@override
String get appDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get appDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get navHome => 'Home';
@@ -102,15 +101,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get historyNoAlbums => 'No album downloads';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'No single downloads';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => 'Search history...';
@@ -158,8 +155,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get downloadAskQuality => 'Ask Quality Before Download';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'Filename Format';
@@ -171,8 +167,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get downloadSeparateSingles => 'Separate Singles';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'Best Available';
@@ -229,8 +224,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsPrimaryProvider => 'Primary Provider';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => 'Use Extension Providers';
@@ -261,15 +253,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsEmbedLyrics => 'Embed Lyrics';
@override
String get optionsEmbedLyricsSubtitle =>
'Embed synced lyrics into FLAC files';
String get optionsEmbedLyricsSubtitle => 'Embed synced lyrics into FLAC files';
@override
String get optionsMaxQualityCover => 'Max Quality Cover';
@override
String get optionsMaxQualityCoverSubtitle =>
'Download highest resolution cover art';
String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@@ -283,8 +273,7 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -296,8 +285,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsCheckUpdates => 'Check for Updates';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => 'Update Channel';
@@ -309,15 +297,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsUpdateChannelPreview => 'Get preview releases';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'Clear Download History';
@override
String get optionsClearHistorySubtitle =>
'Remove all downloaded tracks from history';
String get optionsClearHistorySubtitle => 'Remove all downloaded tracks from history';
@override
String get optionsDetailedLogging => 'Detailed Logging';
@@ -340,8 +326,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => 'Required - tap to configure';
@override
String get optionsSpotifyWarning =>
'Spotify requires your own API credentials. Get them free from developer.spotify.com';
String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com';
@override
String get extensionsTitle => 'Extensions';
@@ -405,8 +390,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
@override
String get aboutLogoArtist =>
'The talented artist who created our beautiful app logo!';
String get aboutLogoArtist => 'The talented artist who created our beautiful app logo!';
@override
String get aboutTranslators => 'Translators';
@@ -466,34 +450,28 @@ class AppLocalizationsFr extends AppLocalizations {
String get aboutVersion => 'Version';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get albumTitle => 'Album';
@@ -598,8 +576,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupStoragePermission => 'Storage Permission';
@override
String get setupStoragePermissionSubtitle =>
'Required to save downloaded files';
String get setupStoragePermissionSubtitle => 'Required to save downloaded files';
@override
String get setupStoragePermissionGranted => 'Permission granted';
@@ -626,19 +603,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupStorageAccessRequired => 'Storage Access Required';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => 'Open Settings';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -657,8 +631,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupUseDefaultFolder => 'Use Default Folder?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'Use Default';
@@ -667,15 +640,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupDownloadLocationTitle => 'Download Location';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'App Documents Folder';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'Choose from Files';
@@ -684,12 +655,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'Select iCloud or other location';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
@@ -716,8 +685,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupStorageRequired => 'Storage Permission Required';
@override
String get setupStorageDescription =>
'SpotiFLAC needs storage permission to save your downloaded music files.';
String get setupStorageDescription => 'SpotiFLAC needs storage permission to save your downloaded music files.';
@override
String get setupNotificationGranted => 'Notification Permission Granted!';
@@ -726,8 +694,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupNotificationEnable => 'Enable Notifications';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'Download Folder Selected!';
@@ -736,8 +703,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupFolderChoose => 'Choose Download Folder';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'Change Folder';
@@ -749,8 +715,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (Optional)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Use Spotify API';
@@ -768,8 +733,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupEnterClientSecret => 'Enter Spotify Client Secret';
@override
String get setupGetFreeCredentials =>
'Get your free API credentials from the Spotify Developer Dashboard.';
String get setupGetFreeCredentials => 'Get your free API credentials from the Spotify Developer Dashboard.';
@override
String get setupEnableNotifications => 'Enable Notifications';
@@ -778,12 +742,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => 'Skip for now';
@@ -801,12 +763,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get setupSkipAndStart => 'Skip & Start';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'Get credentials from developer.spotify.com';
String get setupGetCredentialsFromSpotify => 'Get credentials from developer.spotify.com';
@override
String get dialogCancel => 'Cancel';
@@ -857,8 +817,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get dialogDiscardChanges => 'Discard Changes?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'Download Failed';
@@ -876,8 +835,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get dialogClearAll => 'Clear All';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'Remove from device?';
@@ -886,8 +844,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get dialogRemoveExtension => 'Remove Extension';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => 'Uninstall Extension?';
@@ -901,8 +858,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get dialogClearHistoryTitle => 'Clear History';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => 'Delete Selected';
@@ -997,8 +953,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get snackbarProviderPrioritySaved => 'Provider priority saved';
@override
String get snackbarMetadataProviderSaved =>
'Metadata provider priority saved';
String get snackbarMetadataProviderSaved => 'Metadata provider priority saved';
@override
String snackbarExtensionInstalled(String extensionName) {
@@ -1020,8 +975,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get errorRateLimited => 'Rate Limited';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,23 +1142,19 @@ class AppLocalizationsFr extends AppLocalizations {
String get folderOrganizationByArtistAlbum => 'Artist/Album';
@override
String get folderOrganizationDescription =>
'Organize downloaded files into folders';
String get folderOrganizationDescription => 'Organize downloaded files into folders';
@override
String get folderOrganizationNoneSubtitle => 'All files in download folder';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => 'Update Available';
@@ -1263,12 +1213,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get providerPriorityTitle => 'Provider Priority';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => 'Built-in';
@@ -1280,19 +1228,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get metadataProviderPriority => 'Metadata Provider Priority';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'Metadata Priority';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'No rate limits';
@@ -1364,19 +1309,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get logIssueSummary => 'Issue Summary';
@override
String get logIspBlockingDescription =>
'Your ISP may be blocking access to download services';
String get logIspBlockingDescription => 'Your ISP may be blocking access to download services';
@override
String get logIspBlockingSuggestion =>
'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
String get logIspBlockingSuggestion => 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
@override
String get logRateLimitedDescription => 'Too many requests to the service';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => 'Connection issues detected';
@@ -1385,12 +1327,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get logNetworkErrorSuggestion => 'Check your internet connection';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1416,8 +1356,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get credentialsTitle => 'Spotify Credentials';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'Client ID';
@@ -1471,8 +1410,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get lyricsMode => 'Lyrics Mode';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1484,8 +1422,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get lyricsModeExternal => 'External .lrc file';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => 'Both';
@@ -1645,8 +1582,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get trackDeleteConfirmTitle => 'Remove from device?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1798,15 +1734,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get extensionsNoExtensions => 'No extensions installed';
@override
String get extensionsNoExtensionsSubtitle =>
'Install .spotiflac-ext files to add new providers';
String get extensionsNoExtensionsSubtitle => 'Install .spotiflac-ext files to add new providers';
@override
String get extensionsInstallButton => 'Install Extension';
@override
String get extensionsInfoTip =>
'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
String get extensionsInfoTip => 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
@override
String get extensionsInstalledSuccess => 'Extension installed successfully';
@@ -1818,19 +1752,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get extensionsDownloadPrioritySubtitle => 'Set download service order';
@override
String get extensionsNoDownloadProvider =>
'No extensions with download provider';
String get extensionsNoDownloadProvider => 'No extensions with download provider';
@override
String get extensionsMetadataPriority => 'Metadata Priority';
@override
String get extensionsMetadataPrioritySubtitle =>
'Set search & metadata source order';
String get extensionsMetadataPrioritySubtitle => 'Set search & metadata source order';
@override
String get extensionsNoMetadataProvider =>
'No extensions with metadata provider';
String get extensionsNoMetadataProvider => 'No extensions with metadata provider';
@override
String get extensionsSearchProvider => 'Search Provider';
@@ -1839,8 +1770,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get extensionsNoCustomSearch => 'No extensions with custom search';
@override
String get extensionsSearchProviderDescription =>
'Choose which service to use for searching tracks';
String get extensionsSearchProviderDescription => 'Choose which service to use for searching tracks';
@override
String get extensionsCustomSearch => 'Custom search';
@@ -1882,8 +1812,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1895,12 +1824,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote =>
'Actual quality depends on track availability from the service';
String get qualityNote => 'Actual quality depends on track availability from the service';
@override
String get downloadAskBeforeDownload => 'Ask Before Download';
@@ -1990,15 +1917,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get queueClearAll => 'Clear All';
@override
String get queueClearAllMessage =>
'Are you sure you want to clear all downloads?';
String get queueClearAllMessage => 'Are you sure you want to clear all downloads?';
@override
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -2010,8 +1935,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2023,170 +1947,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
@@ -2222,8 +1983,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get albumFolderArtistYearAlbum => 'Artist / [Year] Album';
@override
String get albumFolderArtistYearAlbumSubtitle =>
'Albums/Artist Name/[2005] Album Name/';
String get albumFolderArtistYearAlbumSubtitle => 'Albums/Artist Name/[2005] Album Name/';
@override
String get albumFolderAlbumOnly => 'Album Only';
@@ -2241,8 +2001,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => 'Delete Selected';
@@ -2352,8 +2111,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get discographySelectAlbums => 'Select Albums...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'Fetching tracks...';
@@ -2400,16 +2158,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2430,8 +2185,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2443,8 +2197,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2462,8 +2215,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2475,15 +2227,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2521,8 +2271,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2613,81 +2362,4 @@ class AppLocalizationsFr extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
+89 -417
View File
@@ -12,8 +12,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get appName => 'SpotiFlac';
@override
String get appDescription =>
'स्पॉटीफाई ट्रैक डाउनलोड करें टाइडल, क्वाबज एवं एवं अमेजन म्यूजिक से उच्चतम क्वालिटी में।';
String get appDescription => 'स्पॉटीफाई ट्रैक डाउनलोड करें टाइडल, क्वाबज एवं एवं अमेजन म्यूजिक से उच्चतम क्वालिटी में।';
@override
String get navHome => 'होम';
@@ -102,15 +101,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get historyNoAlbums => 'No album downloads';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'No single downloads';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => 'Search history...';
@@ -158,8 +155,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get downloadAskQuality => 'Ask Quality Before Download';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'Filename Format';
@@ -171,8 +167,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get downloadSeparateSingles => 'Separate Singles';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'Best Available';
@@ -229,8 +224,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsPrimaryProvider => 'Primary Provider';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsHi extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => 'Use Extension Providers';
@@ -261,15 +253,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsEmbedLyrics => 'Embed Lyrics';
@override
String get optionsEmbedLyricsSubtitle =>
'Embed synced lyrics into FLAC files';
String get optionsEmbedLyricsSubtitle => 'Embed synced lyrics into FLAC files';
@override
String get optionsMaxQualityCover => 'Max Quality Cover';
@override
String get optionsMaxQualityCoverSubtitle =>
'Download highest resolution cover art';
String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@@ -283,8 +273,7 @@ class AppLocalizationsHi extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -296,8 +285,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsCheckUpdates => 'Check for Updates';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => 'Update Channel';
@@ -309,15 +297,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsUpdateChannelPreview => 'Get preview releases';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'Clear Download History';
@override
String get optionsClearHistorySubtitle =>
'Remove all downloaded tracks from history';
String get optionsClearHistorySubtitle => 'Remove all downloaded tracks from history';
@override
String get optionsDetailedLogging => 'Detailed Logging';
@@ -340,8 +326,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => 'Required - tap to configure';
@override
String get optionsSpotifyWarning =>
'Spotify requires your own API credentials. Get them free from developer.spotify.com';
String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com';
@override
String get extensionsTitle => 'Extensions';
@@ -405,8 +390,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
@override
String get aboutLogoArtist =>
'The talented artist who created our beautiful app logo!';
String get aboutLogoArtist => 'The talented artist who created our beautiful app logo!';
@override
String get aboutTranslators => 'Translators';
@@ -466,34 +450,28 @@ class AppLocalizationsHi extends AppLocalizations {
String get aboutVersion => 'Version';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get albumTitle => 'Album';
@@ -598,8 +576,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupStoragePermission => 'Storage Permission';
@override
String get setupStoragePermissionSubtitle =>
'Required to save downloaded files';
String get setupStoragePermissionSubtitle => 'Required to save downloaded files';
@override
String get setupStoragePermissionGranted => 'Permission granted';
@@ -626,19 +603,16 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupStorageAccessRequired => 'Storage Access Required';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => 'Open Settings';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -657,8 +631,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupUseDefaultFolder => 'Use Default Folder?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'Use Default';
@@ -667,15 +640,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupDownloadLocationTitle => 'Download Location';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'App Documents Folder';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'Choose from Files';
@@ -684,12 +655,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'Select iCloud or other location';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
@@ -716,8 +685,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupStorageRequired => 'Storage Permission Required';
@override
String get setupStorageDescription =>
'SpotiFLAC needs storage permission to save your downloaded music files.';
String get setupStorageDescription => 'SpotiFLAC needs storage permission to save your downloaded music files.';
@override
String get setupNotificationGranted => 'Notification Permission Granted!';
@@ -726,8 +694,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupNotificationEnable => 'Enable Notifications';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'Download Folder Selected!';
@@ -736,8 +703,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupFolderChoose => 'Choose Download Folder';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'Change Folder';
@@ -749,8 +715,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (Optional)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Use Spotify API';
@@ -768,8 +733,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupEnterClientSecret => 'Enter Spotify Client Secret';
@override
String get setupGetFreeCredentials =>
'Get your free API credentials from the Spotify Developer Dashboard.';
String get setupGetFreeCredentials => 'Get your free API credentials from the Spotify Developer Dashboard.';
@override
String get setupEnableNotifications => 'Enable Notifications';
@@ -778,12 +742,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => 'Skip for now';
@@ -801,12 +763,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get setupSkipAndStart => 'Skip & Start';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'Get credentials from developer.spotify.com';
String get setupGetCredentialsFromSpotify => 'Get credentials from developer.spotify.com';
@override
String get dialogCancel => 'Cancel';
@@ -857,8 +817,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get dialogDiscardChanges => 'Discard Changes?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'Download Failed';
@@ -876,8 +835,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get dialogClearAll => 'Clear All';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'Remove from device?';
@@ -886,8 +844,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get dialogRemoveExtension => 'Remove Extension';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => 'Uninstall Extension?';
@@ -901,8 +858,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get dialogClearHistoryTitle => 'Clear History';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => 'Delete Selected';
@@ -997,8 +953,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get snackbarProviderPrioritySaved => 'Provider priority saved';
@override
String get snackbarMetadataProviderSaved =>
'Metadata provider priority saved';
String get snackbarMetadataProviderSaved => 'Metadata provider priority saved';
@override
String snackbarExtensionInstalled(String extensionName) {
@@ -1020,8 +975,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get errorRateLimited => 'Rate Limited';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,23 +1142,19 @@ class AppLocalizationsHi extends AppLocalizations {
String get folderOrganizationByArtistAlbum => 'Artist/Album';
@override
String get folderOrganizationDescription =>
'Organize downloaded files into folders';
String get folderOrganizationDescription => 'Organize downloaded files into folders';
@override
String get folderOrganizationNoneSubtitle => 'All files in download folder';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => 'Update Available';
@@ -1263,12 +1213,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get providerPriorityTitle => 'Provider Priority';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => 'Built-in';
@@ -1280,19 +1228,16 @@ class AppLocalizationsHi extends AppLocalizations {
String get metadataProviderPriority => 'Metadata Provider Priority';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'Metadata Priority';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'No rate limits';
@@ -1364,19 +1309,16 @@ class AppLocalizationsHi extends AppLocalizations {
String get logIssueSummary => 'Issue Summary';
@override
String get logIspBlockingDescription =>
'Your ISP may be blocking access to download services';
String get logIspBlockingDescription => 'Your ISP may be blocking access to download services';
@override
String get logIspBlockingSuggestion =>
'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
String get logIspBlockingSuggestion => 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
@override
String get logRateLimitedDescription => 'Too many requests to the service';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => 'Connection issues detected';
@@ -1385,12 +1327,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get logNetworkErrorSuggestion => 'Check your internet connection';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1416,8 +1356,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get credentialsTitle => 'Spotify Credentials';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'Client ID';
@@ -1471,8 +1410,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get lyricsMode => 'Lyrics Mode';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1484,8 +1422,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get lyricsModeExternal => 'External .lrc file';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => 'Both';
@@ -1645,8 +1582,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get trackDeleteConfirmTitle => 'Remove from device?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1798,15 +1734,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get extensionsNoExtensions => 'No extensions installed';
@override
String get extensionsNoExtensionsSubtitle =>
'Install .spotiflac-ext files to add new providers';
String get extensionsNoExtensionsSubtitle => 'Install .spotiflac-ext files to add new providers';
@override
String get extensionsInstallButton => 'Install Extension';
@override
String get extensionsInfoTip =>
'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
String get extensionsInfoTip => 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
@override
String get extensionsInstalledSuccess => 'Extension installed successfully';
@@ -1818,19 +1752,16 @@ class AppLocalizationsHi extends AppLocalizations {
String get extensionsDownloadPrioritySubtitle => 'Set download service order';
@override
String get extensionsNoDownloadProvider =>
'No extensions with download provider';
String get extensionsNoDownloadProvider => 'No extensions with download provider';
@override
String get extensionsMetadataPriority => 'Metadata Priority';
@override
String get extensionsMetadataPrioritySubtitle =>
'Set search & metadata source order';
String get extensionsMetadataPrioritySubtitle => 'Set search & metadata source order';
@override
String get extensionsNoMetadataProvider =>
'No extensions with metadata provider';
String get extensionsNoMetadataProvider => 'No extensions with metadata provider';
@override
String get extensionsSearchProvider => 'Search Provider';
@@ -1839,8 +1770,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get extensionsNoCustomSearch => 'No extensions with custom search';
@override
String get extensionsSearchProviderDescription =>
'Choose which service to use for searching tracks';
String get extensionsSearchProviderDescription => 'Choose which service to use for searching tracks';
@override
String get extensionsCustomSearch => 'Custom search';
@@ -1882,8 +1812,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1895,12 +1824,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote =>
'Actual quality depends on track availability from the service';
String get qualityNote => 'Actual quality depends on track availability from the service';
@override
String get downloadAskBeforeDownload => 'Ask Before Download';
@@ -1990,15 +1917,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get queueClearAll => 'Clear All';
@override
String get queueClearAllMessage =>
'Are you sure you want to clear all downloads?';
String get queueClearAllMessage => 'Are you sure you want to clear all downloads?';
@override
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -2010,8 +1935,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2023,170 +1947,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
@@ -2222,8 +1983,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get albumFolderArtistYearAlbum => 'Artist / [Year] Album';
@override
String get albumFolderArtistYearAlbumSubtitle =>
'Albums/Artist Name/[2005] Album Name/';
String get albumFolderArtistYearAlbumSubtitle => 'Albums/Artist Name/[2005] Album Name/';
@override
String get albumFolderAlbumOnly => 'Album Only';
@@ -2241,8 +2001,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => 'Delete Selected';
@@ -2352,8 +2111,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get discographySelectAlbums => 'Select Albums...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'Fetching tracks...';
@@ -2400,16 +2158,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2430,8 +2185,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2443,8 +2197,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2462,8 +2215,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2475,15 +2227,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2521,8 +2271,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2613,81 +2362,4 @@ class AppLocalizationsHi extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
File diff suppressed because it is too large Load Diff
+75 -389
View File
@@ -12,8 +12,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get appName => 'SpotiFLAC';
@override
String get appDescription =>
'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。';
String get appDescription => 'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。';
@override
String get navHome => 'ホーム';
@@ -102,15 +101,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get historyNoAlbums => 'アルバムのダウンロードはありません';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'シングルのダウンロードはありません';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => '検索履歴...';
@@ -158,8 +155,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get downloadAskQuality => 'ダウンロード前に品質を確認する';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'ファイル名の形式';
@@ -171,8 +167,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get downloadSeparateSingles => 'シングルを分割';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'おすすめ';
@@ -229,8 +224,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsPrimaryProvider => 'プライマリーのプロバイダー';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsJa extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => '拡張のプロバイダーを使用する';
@@ -281,8 +273,7 @@ class AppLocalizationsJa extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => '拡張ストア';
@@ -294,8 +285,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsCheckUpdates => '更新を確認';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => '更新チャンネル';
@@ -307,8 +297,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsUpdateChannelPreview => 'プレビューリリースを入手';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'ダウンロード履歴を消去';
@@ -337,8 +326,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => '必須 - タップで設定';
@override
String get optionsSpotifyWarning =>
'Spotify は独自の API 認証情報が必要です。developer.spotify.com から取得できます。';
String get optionsSpotifyWarning => 'Spotify は独自の API 認証情報が必要です。developer.spotify.com から取得できます。';
@override
String get extensionsTitle => '拡張';
@@ -462,34 +450,28 @@ class AppLocalizationsJa extends AppLocalizations {
String get aboutVersion => 'バージョン';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。';
String get aboutAppDescription => 'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。';
@override
String get albumTitle => 'アルバム';
@@ -621,19 +603,16 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupStorageAccessRequired => 'ストレージアクセスが必要です';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => '設定を開く';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -652,8 +631,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupUseDefaultFolder => 'デフォルトのフォルダを使用しますか?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'デフォルトを使用する';
@@ -662,15 +640,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupDownloadLocationTitle => 'ダウンロード先';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'アプリのドキュメントフォルダ';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'ファイルから選択';
@@ -679,12 +655,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'iCloud またはその他の場所を選択';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Spotify のトラックを FLAC でダウンロード';
@@ -711,8 +685,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupStorageRequired => 'ストレージの権限が必要です';
@override
String get setupStorageDescription =>
'SpotiFLAC はダウンロードした音楽ファイルを保存するためにストレージの権限が必要です。';
String get setupStorageDescription => 'SpotiFLAC はダウンロードした音楽ファイルを保存するためにストレージの権限が必要です。';
@override
String get setupNotificationGranted => '通知の権限が許可されました!';
@@ -721,8 +694,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupNotificationEnable => '通知を有効化する';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'ダウンロードフォルダが選択済みです!';
@@ -731,8 +703,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupFolderChoose => 'ダウンロードフォルダを選択';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'フォルダを変更';
@@ -744,8 +715,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (任意)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Spotify API を使用する';
@@ -763,8 +733,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupEnterClientSecret => 'Spotify クライアントシークレットを入力';
@override
String get setupGetFreeCredentials =>
'Spotify 開発者ダッシュボードから無料の API 認証情報を取得します。';
String get setupGetFreeCredentials => 'Spotify 開発者ダッシュボードから無料の API 認証情報を取得します。';
@override
String get setupEnableNotifications => '通知を有効化する';
@@ -773,12 +742,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => '今はスキップ';
@@ -796,12 +763,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get setupSkipAndStart => 'スキップと開始';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'developer.spotify.com から認証情報を取得します';
String get setupGetCredentialsFromSpotify => 'developer.spotify.com から認証情報を取得します';
@override
String get dialogCancel => 'キャンセル';
@@ -852,8 +817,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get dialogDiscardChanges => '変更を破棄しますか?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'ダウンロードに失敗しました';
@@ -871,8 +835,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get dialogClearAll => 'すべて消去';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'デバイスから削除しますか?';
@@ -881,8 +844,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get dialogRemoveExtension => '拡張を削除';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => '拡張をアンインストールしますか?';
@@ -896,8 +858,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get dialogClearHistoryTitle => '履歴を消去';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => '選択済みを削除';
@@ -1014,8 +975,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get errorRateLimited => 'レート制限';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,16 +1148,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get folderOrganizationNoneSubtitle => 'ダウンロードフォルダ内のすべてのファイル';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => '更新が利用可能です';
@@ -1256,12 +1213,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get providerPriorityTitle => 'プロバイダーの優先度';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => '内蔵';
@@ -1273,19 +1228,16 @@ class AppLocalizationsJa extends AppLocalizations {
String get metadataProviderPriority => 'メタデータプロバイダーの優先度';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'メタデータの優先度';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'レート制限はありません';
@@ -1357,19 +1309,16 @@ class AppLocalizationsJa extends AppLocalizations {
String get logIssueSummary => '問題の概要';
@override
String get logIspBlockingDescription =>
'ISP がダウンロードサービスのアクセスをブロックしている可能性があります';
String get logIspBlockingDescription => 'ISP がダウンロードサービスのアクセスをブロックしている可能性があります';
@override
String get logIspBlockingSuggestion =>
'VPN を使用するか DNS を 1.1.1.1 または 8.8.8.8 に変更をお試しください';
String get logIspBlockingSuggestion => 'VPN を使用するか DNS を 1.1.1.1 または 8.8.8.8 に変更をお試しください';
@override
String get logRateLimitedDescription => 'サービスへのリクエストが多すぎます';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => '接続の問題が検出されました';
@@ -1378,12 +1327,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get logNetworkErrorSuggestion => 'インターネット接続を確認してください';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1409,8 +1356,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get credentialsTitle => 'Spotify の認証情報';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'クライアント ID';
@@ -1464,8 +1410,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get lyricsMode => '歌詞モード';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1477,8 +1422,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get lyricsModeExternal => '外部 .lrc ファイル';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => '両方';
@@ -1638,8 +1582,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get trackDeleteConfirmTitle => 'デバイスから削除しますか?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1791,15 +1734,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get extensionsNoExtensions => '拡張はインストールされていません';
@override
String get extensionsNoExtensionsSubtitle =>
'新しいプロバイダーを追加するには .spotiflac-ext ファイルをインストールします';
String get extensionsNoExtensionsSubtitle => '新しいプロバイダーを追加するには .spotiflac-ext ファイルをインストールします';
@override
String get extensionsInstallButton => '拡張をインストール';
@override
String get extensionsInfoTip =>
'拡張は新しいメタデータとダウンロードプロバイダーを追加することがあります。信頼できるソースからの拡張のみをインストールしてください。';
String get extensionsInfoTip => '拡張は新しいメタデータとダウンロードプロバイダーを追加することがあります。信頼できるソースからの拡張のみをインストールしてください。';
@override
String get extensionsInstalledSuccess => '拡張のインストールが成功しました';
@@ -1871,8 +1812,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1884,8 +1824,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote => '実際の品質はサービスからのトラックの可用性に依存します';
@@ -1984,8 +1923,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -1997,8 +1935,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2010,170 +1947,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'キューにダウンロードがありません';
@@ -2227,8 +2001,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => '選択済みを削除';
@@ -2338,8 +2111,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get discographySelectAlbums => 'アルバムを選択...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'トラックを取得中です...';
@@ -2386,16 +2158,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2416,8 +2185,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2429,8 +2197,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2448,8 +2215,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2461,15 +2227,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2507,8 +2271,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2599,81 +2362,4 @@ class AppLocalizationsJa extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
+89 -417
View File
@@ -12,8 +12,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get appName => 'SpotiFLAC';
@override
String get appDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get appDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get navHome => 'Home';
@@ -102,15 +101,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get historyNoAlbums => 'No album downloads';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'No single downloads';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => 'Search history...';
@@ -158,8 +155,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get downloadAskQuality => 'Ask Quality Before Download';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'Filename Format';
@@ -171,8 +167,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get downloadSeparateSingles => 'Separate Singles';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'Best Available';
@@ -229,8 +224,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsPrimaryProvider => 'Primary Provider';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsKo extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => 'Use Extension Providers';
@@ -261,15 +253,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsEmbedLyrics => 'Embed Lyrics';
@override
String get optionsEmbedLyricsSubtitle =>
'Embed synced lyrics into FLAC files';
String get optionsEmbedLyricsSubtitle => 'Embed synced lyrics into FLAC files';
@override
String get optionsMaxQualityCover => 'Max Quality Cover';
@override
String get optionsMaxQualityCoverSubtitle =>
'Download highest resolution cover art';
String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@@ -283,8 +273,7 @@ class AppLocalizationsKo extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -296,8 +285,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsCheckUpdates => 'Check for Updates';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => 'Update Channel';
@@ -309,15 +297,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsUpdateChannelPreview => 'Get preview releases';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'Clear Download History';
@override
String get optionsClearHistorySubtitle =>
'Remove all downloaded tracks from history';
String get optionsClearHistorySubtitle => 'Remove all downloaded tracks from history';
@override
String get optionsDetailedLogging => 'Detailed Logging';
@@ -340,8 +326,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => 'Required - tap to configure';
@override
String get optionsSpotifyWarning =>
'Spotify requires your own API credentials. Get them free from developer.spotify.com';
String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com';
@override
String get extensionsTitle => 'Extensions';
@@ -405,8 +390,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
@override
String get aboutLogoArtist =>
'The talented artist who created our beautiful app logo!';
String get aboutLogoArtist => 'The talented artist who created our beautiful app logo!';
@override
String get aboutTranslators => 'Translators';
@@ -466,34 +450,28 @@ class AppLocalizationsKo extends AppLocalizations {
String get aboutVersion => 'Version';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get albumTitle => 'Album';
@@ -598,8 +576,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupStoragePermission => 'Storage Permission';
@override
String get setupStoragePermissionSubtitle =>
'Required to save downloaded files';
String get setupStoragePermissionSubtitle => 'Required to save downloaded files';
@override
String get setupStoragePermissionGranted => 'Permission granted';
@@ -626,19 +603,16 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupStorageAccessRequired => 'Storage Access Required';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => 'Open Settings';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -657,8 +631,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupUseDefaultFolder => 'Use Default Folder?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'Use Default';
@@ -667,15 +640,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupDownloadLocationTitle => 'Download Location';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'App Documents Folder';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'Choose from Files';
@@ -684,12 +655,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'Select iCloud or other location';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
@@ -716,8 +685,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupStorageRequired => 'Storage Permission Required';
@override
String get setupStorageDescription =>
'SpotiFLAC needs storage permission to save your downloaded music files.';
String get setupStorageDescription => 'SpotiFLAC needs storage permission to save your downloaded music files.';
@override
String get setupNotificationGranted => 'Notification Permission Granted!';
@@ -726,8 +694,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupNotificationEnable => 'Enable Notifications';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'Download Folder Selected!';
@@ -736,8 +703,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupFolderChoose => 'Choose Download Folder';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'Change Folder';
@@ -749,8 +715,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (Optional)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Use Spotify API';
@@ -768,8 +733,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupEnterClientSecret => 'Enter Spotify Client Secret';
@override
String get setupGetFreeCredentials =>
'Get your free API credentials from the Spotify Developer Dashboard.';
String get setupGetFreeCredentials => 'Get your free API credentials from the Spotify Developer Dashboard.';
@override
String get setupEnableNotifications => 'Enable Notifications';
@@ -778,12 +742,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => 'Skip for now';
@@ -801,12 +763,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get setupSkipAndStart => 'Skip & Start';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'Get credentials from developer.spotify.com';
String get setupGetCredentialsFromSpotify => 'Get credentials from developer.spotify.com';
@override
String get dialogCancel => 'Cancel';
@@ -857,8 +817,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get dialogDiscardChanges => 'Discard Changes?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'Download Failed';
@@ -876,8 +835,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get dialogClearAll => 'Clear All';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'Remove from device?';
@@ -886,8 +844,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get dialogRemoveExtension => 'Remove Extension';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => 'Uninstall Extension?';
@@ -901,8 +858,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get dialogClearHistoryTitle => 'Clear History';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => 'Delete Selected';
@@ -997,8 +953,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get snackbarProviderPrioritySaved => 'Provider priority saved';
@override
String get snackbarMetadataProviderSaved =>
'Metadata provider priority saved';
String get snackbarMetadataProviderSaved => 'Metadata provider priority saved';
@override
String snackbarExtensionInstalled(String extensionName) {
@@ -1020,8 +975,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get errorRateLimited => 'Rate Limited';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,23 +1142,19 @@ class AppLocalizationsKo extends AppLocalizations {
String get folderOrganizationByArtistAlbum => 'Artist/Album';
@override
String get folderOrganizationDescription =>
'Organize downloaded files into folders';
String get folderOrganizationDescription => 'Organize downloaded files into folders';
@override
String get folderOrganizationNoneSubtitle => 'All files in download folder';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => 'Update Available';
@@ -1263,12 +1213,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get providerPriorityTitle => 'Provider Priority';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => 'Built-in';
@@ -1280,19 +1228,16 @@ class AppLocalizationsKo extends AppLocalizations {
String get metadataProviderPriority => 'Metadata Provider Priority';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'Metadata Priority';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'No rate limits';
@@ -1364,19 +1309,16 @@ class AppLocalizationsKo extends AppLocalizations {
String get logIssueSummary => 'Issue Summary';
@override
String get logIspBlockingDescription =>
'Your ISP may be blocking access to download services';
String get logIspBlockingDescription => 'Your ISP may be blocking access to download services';
@override
String get logIspBlockingSuggestion =>
'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
String get logIspBlockingSuggestion => 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
@override
String get logRateLimitedDescription => 'Too many requests to the service';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => 'Connection issues detected';
@@ -1385,12 +1327,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get logNetworkErrorSuggestion => 'Check your internet connection';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1416,8 +1356,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get credentialsTitle => 'Spotify Credentials';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'Client ID';
@@ -1471,8 +1410,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get lyricsMode => 'Lyrics Mode';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1484,8 +1422,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get lyricsModeExternal => 'External .lrc file';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => 'Both';
@@ -1645,8 +1582,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get trackDeleteConfirmTitle => 'Remove from device?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1798,15 +1734,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get extensionsNoExtensions => 'No extensions installed';
@override
String get extensionsNoExtensionsSubtitle =>
'Install .spotiflac-ext files to add new providers';
String get extensionsNoExtensionsSubtitle => 'Install .spotiflac-ext files to add new providers';
@override
String get extensionsInstallButton => 'Install Extension';
@override
String get extensionsInfoTip =>
'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
String get extensionsInfoTip => 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
@override
String get extensionsInstalledSuccess => 'Extension installed successfully';
@@ -1818,19 +1752,16 @@ class AppLocalizationsKo extends AppLocalizations {
String get extensionsDownloadPrioritySubtitle => 'Set download service order';
@override
String get extensionsNoDownloadProvider =>
'No extensions with download provider';
String get extensionsNoDownloadProvider => 'No extensions with download provider';
@override
String get extensionsMetadataPriority => 'Metadata Priority';
@override
String get extensionsMetadataPrioritySubtitle =>
'Set search & metadata source order';
String get extensionsMetadataPrioritySubtitle => 'Set search & metadata source order';
@override
String get extensionsNoMetadataProvider =>
'No extensions with metadata provider';
String get extensionsNoMetadataProvider => 'No extensions with metadata provider';
@override
String get extensionsSearchProvider => 'Search Provider';
@@ -1839,8 +1770,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get extensionsNoCustomSearch => 'No extensions with custom search';
@override
String get extensionsSearchProviderDescription =>
'Choose which service to use for searching tracks';
String get extensionsSearchProviderDescription => 'Choose which service to use for searching tracks';
@override
String get extensionsCustomSearch => 'Custom search';
@@ -1882,8 +1812,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1895,12 +1824,10 @@ class AppLocalizationsKo extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote =>
'Actual quality depends on track availability from the service';
String get qualityNote => 'Actual quality depends on track availability from the service';
@override
String get downloadAskBeforeDownload => 'Ask Before Download';
@@ -1990,15 +1917,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get queueClearAll => 'Clear All';
@override
String get queueClearAllMessage =>
'Are you sure you want to clear all downloads?';
String get queueClearAllMessage => 'Are you sure you want to clear all downloads?';
@override
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -2010,8 +1935,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2023,170 +1947,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
@@ -2222,8 +1983,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get albumFolderArtistYearAlbum => 'Artist / [Year] Album';
@override
String get albumFolderArtistYearAlbumSubtitle =>
'Albums/Artist Name/[2005] Album Name/';
String get albumFolderArtistYearAlbumSubtitle => 'Albums/Artist Name/[2005] Album Name/';
@override
String get albumFolderAlbumOnly => 'Album Only';
@@ -2241,8 +2001,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => 'Delete Selected';
@@ -2352,8 +2111,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get discographySelectAlbums => 'Select Albums...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'Fetching tracks...';
@@ -2400,16 +2158,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2430,8 +2185,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2443,8 +2197,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2462,8 +2215,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2475,15 +2227,13 @@ class AppLocalizationsKo extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2521,8 +2271,7 @@ class AppLocalizationsKo extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2613,81 +2362,4 @@ class AppLocalizationsKo extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
+89 -417
View File
@@ -12,8 +12,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get appName => 'SpotiFLAC';
@override
String get appDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get appDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get navHome => 'Home';
@@ -102,15 +101,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get historyNoAlbums => 'No album downloads';
@override
String get historyNoAlbumsSubtitle =>
'Download multiple tracks from an album to see them here';
String get historyNoAlbumsSubtitle => 'Download multiple tracks from an album to see them here';
@override
String get historyNoSingles => 'No single downloads';
@override
String get historyNoSinglesSubtitle =>
'Single track downloads will appear here';
String get historyNoSinglesSubtitle => 'Single track downloads will appear here';
@override
String get historySearchHint => 'Search history...';
@@ -158,8 +155,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get downloadAskQuality => 'Ask Quality Before Download';
@override
String get downloadAskQualitySubtitle =>
'Show quality picker for each download';
String get downloadAskQualitySubtitle => 'Show quality picker for each download';
@override
String get downloadFilenameFormat => 'Filename Format';
@@ -171,8 +167,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get downloadSeparateSingles => 'Separate Singles';
@override
String get downloadSeparateSinglesSubtitle =>
'Put single tracks in a separate folder';
String get downloadSeparateSinglesSubtitle => 'Put single tracks in a separate folder';
@override
String get qualityBest => 'Best Available';
@@ -229,8 +224,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsPrimaryProvider => 'Primary Provider';
@override
String get optionsPrimaryProviderSubtitle =>
'Service used when searching by track name.';
String get optionsPrimaryProviderSubtitle => 'Service used when searching by track name.';
@override
String optionsUsingExtension(String extensionName) {
@@ -238,15 +232,13 @@ class AppLocalizationsNl extends AppLocalizations {
}
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => 'Tap Deezer or Spotify to switch back from extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@override
String get optionsAutoFallbackSubtitle =>
'Try other services if download fails';
String get optionsAutoFallbackSubtitle => 'Try other services if download fails';
@override
String get optionsUseExtensionProviders => 'Use Extension Providers';
@@ -261,15 +253,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsEmbedLyrics => 'Embed Lyrics';
@override
String get optionsEmbedLyricsSubtitle =>
'Embed synced lyrics into FLAC files';
String get optionsEmbedLyricsSubtitle => 'Embed synced lyrics into FLAC files';
@override
String get optionsMaxQualityCover => 'Max Quality Cover';
@override
String get optionsMaxQualityCoverSubtitle =>
'Download highest resolution cover art';
String get optionsMaxQualityCoverSubtitle => 'Download highest resolution cover art';
@override
String get optionsConcurrentDownloads => 'Concurrent Downloads';
@@ -283,8 +273,7 @@ class AppLocalizationsNl extends AppLocalizations {
}
@override
String get optionsConcurrentWarning =>
'Parallel downloads may trigger rate limiting';
String get optionsConcurrentWarning => 'Parallel downloads may trigger rate limiting';
@override
String get optionsExtensionStore => 'Extension Store';
@@ -296,8 +285,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsCheckUpdates => 'Check for Updates';
@override
String get optionsCheckUpdatesSubtitle =>
'Notify when new version is available';
String get optionsCheckUpdatesSubtitle => 'Notify when new version is available';
@override
String get optionsUpdateChannel => 'Update Channel';
@@ -309,15 +297,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsUpdateChannelPreview => 'Get preview releases';
@override
String get optionsUpdateChannelWarning =>
'Preview may contain bugs or incomplete features';
String get optionsUpdateChannelWarning => 'Preview may contain bugs or incomplete features';
@override
String get optionsClearHistory => 'Clear Download History';
@override
String get optionsClearHistorySubtitle =>
'Remove all downloaded tracks from history';
String get optionsClearHistorySubtitle => 'Remove all downloaded tracks from history';
@override
String get optionsDetailedLogging => 'Detailed Logging';
@@ -340,8 +326,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get optionsSpotifyCredentialsRequired => 'Required - tap to configure';
@override
String get optionsSpotifyWarning =>
'Spotify requires your own API credentials. Get them free from developer.spotify.com';
String get optionsSpotifyWarning => 'Spotify requires your own API credentials. Get them free from developer.spotify.com';
@override
String get extensionsTitle => 'Extensions';
@@ -405,8 +390,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
@override
String get aboutLogoArtist =>
'The talented artist who created our beautiful app logo!';
String get aboutLogoArtist => 'The talented artist who created our beautiful app logo!';
@override
String get aboutTranslators => 'Translators';
@@ -466,34 +450,28 @@ class AppLocalizationsNl extends AppLocalizations {
String get aboutVersion => 'Version';
@override
String get aboutBinimumDesc =>
'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
String get aboutBinimumDesc => 'The creator of QQDL & HiFi API. Without this API, Tidal downloads wouldn\'t exist!';
@override
String get aboutSachinsenalDesc =>
'The original HiFi project creator. The foundation of Tidal integration!';
String get aboutSachinsenalDesc => 'The original HiFi project creator. The foundation of Tidal integration!';
@override
String get aboutSjdonadoDesc =>
'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
String get aboutSjdonadoDesc => 'Creator of I Don\'t Have Spotify (IDHS). The fallback link resolver that saves the day!';
@override
String get aboutDoubleDouble => 'DoubleDouble';
@override
String get aboutDoubleDoubleDesc =>
'Amazing API for Amazon Music downloads. Thank you for making it free!';
String get aboutDoubleDoubleDesc => 'Amazing API for Amazon Music downloads. Thank you for making it free!';
@override
String get aboutDabMusic => 'DAB Music';
@override
String get aboutDabMusicDesc =>
'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
String get aboutDabMusicDesc => 'The best Qobuz streaming API. Hi-Res downloads wouldn\'t be possible without this!';
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
String get aboutAppDescription => 'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
@override
String get albumTitle => 'Album';
@@ -598,8 +576,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupStoragePermission => 'Storage Permission';
@override
String get setupStoragePermissionSubtitle =>
'Required to save downloaded files';
String get setupStoragePermissionSubtitle => 'Required to save downloaded files';
@override
String get setupStoragePermissionGranted => 'Permission granted';
@@ -626,19 +603,16 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupStorageAccessRequired => 'Storage Access Required';
@override
String get setupStorageAccessMessage =>
'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
String get setupStorageAccessMessage => 'SpotiFLAC needs \"All files access\" permission to save music files to your chosen folder.';
@override
String get setupStorageAccessMessageAndroid11 =>
'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
String get setupStorageAccessMessageAndroid11 => 'Android 11+ requires \"All files access\" permission to save files to your chosen download folder.';
@override
String get setupOpenSettings => 'Open Settings';
@override
String get setupPermissionDeniedMessage =>
'Permission denied. Please grant all permissions to continue.';
String get setupPermissionDeniedMessage => 'Permission denied. Please grant all permissions to continue.';
@override
String setupPermissionRequired(String permissionType) {
@@ -657,8 +631,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupUseDefaultFolder => 'Use Default Folder?';
@override
String get setupNoFolderSelected =>
'No folder selected. Would you like to use the default Music folder?';
String get setupNoFolderSelected => 'No folder selected. Would you like to use the default Music folder?';
@override
String get setupUseDefault => 'Use Default';
@@ -667,15 +640,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupDownloadLocationTitle => 'Download Location';
@override
String get setupDownloadLocationIosMessage =>
'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
String get setupDownloadLocationIosMessage => 'On iOS, downloads are saved to the app\'s Documents folder. You can access them via the Files app.';
@override
String get setupAppDocumentsFolder => 'App Documents Folder';
@override
String get setupAppDocumentsFolderSubtitle =>
'Recommended - accessible via Files app';
String get setupAppDocumentsFolderSubtitle => 'Recommended - accessible via Files app';
@override
String get setupChooseFromFiles => 'Choose from Files';
@@ -684,12 +655,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupChooseFromFilesSubtitle => 'Select iCloud or other location';
@override
String get setupIosEmptyFolderWarning =>
'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
String get setupIosEmptyFolderWarning => 'iOS limitation: Empty folders cannot be selected. Choose a folder with at least one file.';
@override
String get setupIcloudNotSupported =>
'iCloud Drive is not supported. Please use the app Documents folder.';
String get setupIcloudNotSupported => 'iCloud Drive is not supported. Please use the app Documents folder.';
@override
String get setupDownloadInFlac => 'Download Spotify tracks in FLAC';
@@ -716,8 +685,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupStorageRequired => 'Storage Permission Required';
@override
String get setupStorageDescription =>
'SpotiFLAC needs storage permission to save your downloaded music files.';
String get setupStorageDescription => 'SpotiFLAC needs storage permission to save your downloaded music files.';
@override
String get setupNotificationGranted => 'Notification Permission Granted!';
@@ -726,8 +694,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupNotificationEnable => 'Enable Notifications';
@override
String get setupNotificationDescription =>
'Get notified when downloads complete or require attention.';
String get setupNotificationDescription => 'Get notified when downloads complete or require attention.';
@override
String get setupFolderSelected => 'Download Folder Selected!';
@@ -736,8 +703,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupFolderChoose => 'Choose Download Folder';
@override
String get setupFolderDescription =>
'Select a folder where your downloaded music will be saved.';
String get setupFolderDescription => 'Select a folder where your downloaded music will be saved.';
@override
String get setupChangeFolder => 'Change Folder';
@@ -749,8 +715,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupSpotifyApiOptional => 'Spotify API (Optional)';
@override
String get setupSpotifyApiDescription =>
'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
String get setupSpotifyApiDescription => 'Add your Spotify API credentials for better search results and access to Spotify-exclusive content.';
@override
String get setupUseSpotifyApi => 'Use Spotify API';
@@ -768,8 +733,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupEnterClientSecret => 'Enter Spotify Client Secret';
@override
String get setupGetFreeCredentials =>
'Get your free API credentials from the Spotify Developer Dashboard.';
String get setupGetFreeCredentials => 'Get your free API credentials from the Spotify Developer Dashboard.';
@override
String get setupEnableNotifications => 'Enable Notifications';
@@ -778,12 +742,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupProceedToNextStep => 'You can now proceed to the next step.';
@override
String get setupNotificationProgressDescription =>
'You will receive download progress notifications.';
String get setupNotificationProgressDescription => 'You will receive download progress notifications.';
@override
String get setupNotificationBackgroundDescription =>
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
String get setupNotificationBackgroundDescription => 'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
@override
String get setupSkipForNow => 'Skip for now';
@@ -801,12 +763,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get setupSkipAndStart => 'Skip & Start';
@override
String get setupAllowAccessToManageFiles =>
'Please enable \"Allow access to manage all files\" in the next screen.';
String get setupAllowAccessToManageFiles => 'Please enable \"Allow access to manage all files\" in the next screen.';
@override
String get setupGetCredentialsFromSpotify =>
'Get credentials from developer.spotify.com';
String get setupGetCredentialsFromSpotify => 'Get credentials from developer.spotify.com';
@override
String get dialogCancel => 'Cancel';
@@ -857,8 +817,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get dialogDiscardChanges => 'Discard Changes?';
@override
String get dialogUnsavedChanges =>
'You have unsaved changes. Do you want to discard them?';
String get dialogUnsavedChanges => 'You have unsaved changes. Do you want to discard them?';
@override
String get dialogDownloadFailed => 'Download Failed';
@@ -876,8 +835,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get dialogClearAll => 'Clear All';
@override
String get dialogClearAllDownloads =>
'Are you sure you want to clear all downloads?';
String get dialogClearAllDownloads => 'Are you sure you want to clear all downloads?';
@override
String get dialogRemoveFromDevice => 'Remove from device?';
@@ -886,8 +844,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get dialogRemoveExtension => 'Remove Extension';
@override
String get dialogRemoveExtensionMessage =>
'Are you sure you want to remove this extension? This cannot be undone.';
String get dialogRemoveExtensionMessage => 'Are you sure you want to remove this extension? This cannot be undone.';
@override
String get dialogUninstallExtension => 'Uninstall Extension?';
@@ -901,8 +858,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get dialogClearHistoryTitle => 'Clear History';
@override
String get dialogClearHistoryMessage =>
'Are you sure you want to clear all download history? This cannot be undone.';
String get dialogClearHistoryMessage => 'Are you sure you want to clear all download history? This cannot be undone.';
@override
String get dialogDeleteSelectedTitle => 'Delete Selected';
@@ -997,8 +953,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get snackbarProviderPrioritySaved => 'Provider priority saved';
@override
String get snackbarMetadataProviderSaved =>
'Metadata provider priority saved';
String get snackbarMetadataProviderSaved => 'Metadata provider priority saved';
@override
String snackbarExtensionInstalled(String extensionName) {
@@ -1020,8 +975,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get errorRateLimited => 'Rate Limited';
@override
String get errorRateLimitedMessage =>
'Too many requests. Please wait a moment before searching again.';
String get errorRateLimitedMessage => 'Too many requests. Please wait a moment before searching again.';
@override
String errorFailedToLoad(String item) {
@@ -1188,23 +1142,19 @@ class AppLocalizationsNl extends AppLocalizations {
String get folderOrganizationByArtistAlbum => 'Artist/Album';
@override
String get folderOrganizationDescription =>
'Organize downloaded files into folders';
String get folderOrganizationDescription => 'Organize downloaded files into folders';
@override
String get folderOrganizationNoneSubtitle => 'All files in download folder';
@override
String get folderOrganizationByArtistSubtitle =>
'Separate folder for each artist';
String get folderOrganizationByArtistSubtitle => 'Separate folder for each artist';
@override
String get folderOrganizationByAlbumSubtitle =>
'Separate folder for each album';
String get folderOrganizationByAlbumSubtitle => 'Separate folder for each album';
@override
String get folderOrganizationByArtistAlbumSubtitle =>
'Nested folders for artist and album';
String get folderOrganizationByArtistAlbumSubtitle => 'Nested folders for artist and album';
@override
String get updateAvailable => 'Update Available';
@@ -1263,12 +1213,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get providerPriorityTitle => 'Provider Priority';
@override
String get providerPriorityDescription =>
'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
String get providerPriorityDescription => 'Drag to reorder download providers. The app will try providers from top to bottom when downloading tracks.';
@override
String get providerPriorityInfo =>
'If a track is not available on the first provider, the app will automatically try the next one.';
String get providerPriorityInfo => 'If a track is not available on the first provider, the app will automatically try the next one.';
@override
String get providerBuiltIn => 'Built-in';
@@ -1280,19 +1228,16 @@ class AppLocalizationsNl extends AppLocalizations {
String get metadataProviderPriority => 'Metadata Provider Priority';
@override
String get metadataProviderPrioritySubtitle =>
'Order used when fetching track metadata';
String get metadataProviderPrioritySubtitle => 'Order used when fetching track metadata';
@override
String get metadataProviderPriorityTitle => 'Metadata Priority';
@override
String get metadataProviderPriorityDescription =>
'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
String get metadataProviderPriorityDescription => 'Drag to reorder metadata providers. The app will try providers from top to bottom when searching for tracks and fetching metadata.';
@override
String get metadataProviderPriorityInfo =>
'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
String get metadataProviderPriorityInfo => 'Deezer has no rate limits and is recommended as primary. Spotify may rate limit after many requests.';
@override
String get metadataNoRateLimits => 'No rate limits';
@@ -1364,19 +1309,16 @@ class AppLocalizationsNl extends AppLocalizations {
String get logIssueSummary => 'Issue Summary';
@override
String get logIspBlockingDescription =>
'Your ISP may be blocking access to download services';
String get logIspBlockingDescription => 'Your ISP may be blocking access to download services';
@override
String get logIspBlockingSuggestion =>
'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
String get logIspBlockingSuggestion => 'Try using a VPN or change DNS to 1.1.1.1 or 8.8.8.8';
@override
String get logRateLimitedDescription => 'Too many requests to the service';
@override
String get logRateLimitedSuggestion =>
'Wait a few minutes before trying again';
String get logRateLimitedSuggestion => 'Wait a few minutes before trying again';
@override
String get logNetworkErrorDescription => 'Connection issues detected';
@@ -1385,12 +1327,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get logNetworkErrorSuggestion => 'Check your internet connection';
@override
String get logTrackNotFoundDescription =>
'Some tracks could not be found on download services';
String get logTrackNotFoundDescription => 'Some tracks could not be found on download services';
@override
String get logTrackNotFoundSuggestion =>
'The track may not be available in lossless quality';
String get logTrackNotFoundSuggestion => 'The track may not be available in lossless quality';
@override
String logTotalErrors(int count) {
@@ -1416,8 +1356,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get credentialsTitle => 'Spotify Credentials';
@override
String get credentialsDescription =>
'Enter your Client ID and Secret to use your own Spotify application quota.';
String get credentialsDescription => 'Enter your Client ID and Secret to use your own Spotify application quota.';
@override
String get credentialsClientId => 'Client ID';
@@ -1471,8 +1410,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get lyricsMode => 'Lyrics Mode';
@override
String get lyricsModeDescription =>
'Choose how lyrics are saved with your downloads';
String get lyricsModeDescription => 'Choose how lyrics are saved with your downloads';
@override
String get lyricsModeEmbed => 'Embed in file';
@@ -1484,8 +1422,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get lyricsModeExternal => 'External .lrc file';
@override
String get lyricsModeExternalSubtitle =>
'Separate .lrc file for players like Samsung Music';
String get lyricsModeExternalSubtitle => 'Separate .lrc file for players like Samsung Music';
@override
String get lyricsModeBoth => 'Both';
@@ -1645,8 +1582,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get trackDeleteConfirmTitle => 'Remove from device?';
@override
String get trackDeleteConfirmMessage =>
'This will permanently delete the downloaded file and remove it from your history.';
String get trackDeleteConfirmMessage => 'This will permanently delete the downloaded file and remove it from your history.';
@override
String trackCannotOpen(String message) {
@@ -1798,15 +1734,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get extensionsNoExtensions => 'No extensions installed';
@override
String get extensionsNoExtensionsSubtitle =>
'Install .spotiflac-ext files to add new providers';
String get extensionsNoExtensionsSubtitle => 'Install .spotiflac-ext files to add new providers';
@override
String get extensionsInstallButton => 'Install Extension';
@override
String get extensionsInfoTip =>
'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
String get extensionsInfoTip => 'Extensions can add new metadata and download providers. Only install extensions from trusted sources.';
@override
String get extensionsInstalledSuccess => 'Extension installed successfully';
@@ -1818,19 +1752,16 @@ class AppLocalizationsNl extends AppLocalizations {
String get extensionsDownloadPrioritySubtitle => 'Set download service order';
@override
String get extensionsNoDownloadProvider =>
'No extensions with download provider';
String get extensionsNoDownloadProvider => 'No extensions with download provider';
@override
String get extensionsMetadataPriority => 'Metadata Priority';
@override
String get extensionsMetadataPrioritySubtitle =>
'Set search & metadata source order';
String get extensionsMetadataPrioritySubtitle => 'Set search & metadata source order';
@override
String get extensionsNoMetadataProvider =>
'No extensions with metadata provider';
String get extensionsNoMetadataProvider => 'No extensions with metadata provider';
@override
String get extensionsSearchProvider => 'Search Provider';
@@ -1839,8 +1770,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get extensionsNoCustomSearch => 'No extensions with custom search';
@override
String get extensionsSearchProviderDescription =>
'Choose which service to use for searching tracks';
String get extensionsSearchProviderDescription => 'Choose which service to use for searching tracks';
@override
String get extensionsCustomSearch => 'Custom search';
@@ -1882,8 +1812,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
@override
String get enableLossyOptionSubtitleOff =>
'Downloads FLAC then converts to lossy format';
String get enableLossyOptionSubtitleOff => 'Downloads FLAC then converts to lossy format';
@override
String get lossyFormat => 'Lossy Format';
@@ -1895,12 +1824,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
@override
String get lossyFormatOpusSubtitle =>
'128kbps, better quality at smaller size';
String get lossyFormatOpusSubtitle => '128kbps, better quality at smaller size';
@override
String get qualityNote =>
'Actual quality depends on track availability from the service';
String get qualityNote => 'Actual quality depends on track availability from the service';
@override
String get downloadAskBeforeDownload => 'Ask Before Download';
@@ -1990,15 +1917,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get queueClearAll => 'Clear All';
@override
String get queueClearAllMessage =>
'Are you sure you want to clear all downloads?';
String get queueClearAllMessage => 'Are you sure you want to clear all downloads?';
@override
String get queueExportFailed => 'Export';
@override
String get queueExportFailedSuccess =>
'Failed downloads exported to TXT file';
String get queueExportFailedSuccess => 'Failed downloads exported to TXT file';
@override
String get queueExportFailedClear => 'Clear Failed';
@@ -2010,8 +1935,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get settingsAutoExportFailed => 'Auto-export failed downloads';
@override
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
String get settingsAutoExportFailedSubtitle => 'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@@ -2023,170 +1947,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get settingsCloudSave => 'Cloud Save';
@override
String get settingsCloudSaveSubtitle => 'Auto-upload to NAS or cloud storage';
@override
String get cloudSettingsTitle => 'Cloud Save';
@override
String get cloudSettingsSectionGeneral => 'General';
@override
String get cloudSettingsEnable => 'Enable Cloud Upload';
@override
String get cloudSettingsEnableSubtitle =>
'Automatically upload files after download completes';
@override
String get cloudSettingsSectionProvider => 'Cloud Provider';
@override
String get cloudSettingsProvider => 'Provider';
@override
String get cloudSettingsProviderDescription =>
'Select where to upload your downloaded files';
@override
String get cloudSettingsSectionServer => 'Server Configuration';
@override
String get cloudSettingsServerUrl => 'Server URL';
@override
String get cloudSettingsUsername => 'Username';
@override
String get cloudSettingsPassword => 'Password';
@override
String get cloudSettingsRemotePath => 'Remote Folder Path';
@override
String get cloudSettingsTestConnection => 'Test Connection';
@override
String get cloudSettingsInfo =>
'Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.';
@override
String get cloudSettingsUploadQueue => 'Upload Queue';
@override
String get cloudSettingsRetryFailed => 'Retry Failed';
@override
String get cloudSettingsClearDone => 'Clear Done';
@override
String get cloudSettingsRecentUploads => 'Recent Uploads';
@override
String get cloudSettingsResetSftpHostKey => 'Reset SFTP Host Key';
@override
String get cloudSettingsResetAllSftpHostKeys => 'Reset All SFTP Host Keys';
@override
String get cloudSettingsResetSftpHostKeyMessage =>
'This will forget the saved host key for this server. The next connection will save a new key.';
@override
String get cloudSettingsResetAllSftpHostKeysMessage =>
'This will forget all saved SFTP host keys. Next connections will save new keys.';
@override
String get cloudSettingsResetConfirm => 'Reset';
@override
String get cloudSettingsResetAllConfirm => 'Reset All';
@override
String get cloudSettingsServerUrlRequired => 'Server URL is required';
@override
String get cloudSettingsResetSftpHostKeySuccess =>
'SFTP host key reset. Connect again to save a new key.';
@override
String get cloudSettingsResetSftpHostKeyNotFound =>
'No stored host key found for this server.';
@override
String cloudSettingsResetAllSftpHostKeysCleared(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Cleared $count SFTP host keys.',
one: 'Cleared 1 SFTP host key.',
);
return '$_temp0';
}
@override
String get cloudSettingsResetAllSftpHostKeysNone =>
'No stored SFTP host keys found.';
@override
String get cloudSettingsAllowHttpTitle => 'Allow HTTP (Insecure)';
@override
String get cloudSettingsAllowHttpSubtitle =>
'Sends credentials without TLS. Not recommended.';
@override
String get cloudSettingsAllowHttpMessage =>
'HTTP does not encrypt your credentials. Only enable if you trust the network.';
@override
String get cloudSettingsAllowHttpConfirm => 'Allow HTTP';
@override
String get webdavErrorInvalidScheme => 'Invalid URL: scheme is required';
@override
String get webdavErrorHttpsRequired => 'WebDAV URL must use https';
@override
String get webdavErrorInvalidHost => 'Invalid URL: hostname is required';
@override
String get webdavErrorAuthFailed =>
'Authentication failed. Check username and password.';
@override
String get webdavErrorForbidden =>
'Access denied. Check permissions on the server.';
@override
String get webdavErrorNotFound => 'Server path not found. Check the URL.';
@override
String get webdavErrorConnectionFailed =>
'Cannot connect to server. Check URL and network.';
@override
String get webdavErrorTlsError =>
'SSL/TLS error. Server certificate may be invalid.';
@override
String get webdavErrorTimeout =>
'Connection timed out. Server may be unreachable.';
@override
String get webdavErrorInsufficientStorage =>
'Insufficient storage on server.';
@override
String get webdavErrorUnknown => 'Upload failed. Please try again.';
String get settingsDownloadNetworkSubtitle => 'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
@@ -2222,8 +1983,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get albumFolderArtistYearAlbum => 'Artist / [Year] Album';
@override
String get albumFolderArtistYearAlbumSubtitle =>
'Albums/Artist Name/[2005] Album Name/';
String get albumFolderArtistYearAlbumSubtitle => 'Albums/Artist Name/[2005] Album Name/';
@override
String get albumFolderAlbumOnly => 'Album Only';
@@ -2241,8 +2001,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get albumFolderArtistAlbumSingles => 'Artist / Album + Singles';
@override
String get albumFolderArtistAlbumSinglesSubtitle =>
'Artist/Album/ and Artist/Singles/';
String get albumFolderArtistAlbumSinglesSubtitle => 'Artist/Album/ and Artist/Singles/';
@override
String get downloadedAlbumDeleteSelected => 'Delete Selected';
@@ -2352,8 +2111,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get discographySelectAlbums => 'Select Albums...';
@override
String get discographySelectAlbumsSubtitle =>
'Choose specific albums or singles';
String get discographySelectAlbumsSubtitle => 'Choose specific albums or singles';
@override
String get discographyFetchingTracks => 'Fetching tracks...';
@@ -2400,16 +2158,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get allFilesAccessDisabledSubtitle => 'Limited to media folders only';
@override
String get allFilesAccessDescription =>
'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
String get allFilesAccessDescription => 'Enable this if you encounter write errors when saving to custom folders. Android 13+ restricts access to certain directories by default.';
@override
String get allFilesAccessDeniedMessage =>
'Permission was denied. Please enable \'All files access\' manually in system settings.';
String get allFilesAccessDeniedMessage => 'Permission was denied. Please enable \'All files access\' manually in system settings.';
@override
String get allFilesAccessDisabledMessage =>
'All Files Access disabled. The app will use limited storage access.';
String get allFilesAccessDisabledMessage => 'All Files Access disabled. The app will use limited storage access.';
@override
String get settingsLocalLibrary => 'Local Library';
@@ -2430,8 +2185,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get libraryEnableLocalLibrary => 'Enable Local Library';
@override
String get libraryEnableLocalLibrarySubtitle =>
'Scan and track your existing music';
String get libraryEnableLocalLibrarySubtitle => 'Scan and track your existing music';
@override
String get libraryFolder => 'Library Folder';
@@ -2443,8 +2197,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get libraryShowDuplicateIndicator => 'Show Duplicate Indicator';
@override
String get libraryShowDuplicateIndicatorSubtitle =>
'Show when searching for existing tracks';
String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks';
@override
String get libraryActions => 'Actions';
@@ -2462,8 +2215,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get libraryCleanupMissingFiles => 'Cleanup Missing Files';
@override
String get libraryCleanupMissingFilesSubtitle =>
'Remove entries for files that no longer exist';
String get libraryCleanupMissingFilesSubtitle => 'Remove entries for files that no longer exist';
@override
String get libraryClear => 'Clear Library';
@@ -2475,15 +2227,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get libraryClearConfirmTitle => 'Clear Library';
@override
String get libraryClearConfirmMessage =>
'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
String get libraryClearConfirmMessage => 'This will remove all scanned tracks from your library. Your actual music files will not be deleted.';
@override
String get libraryAbout => 'About Local Library';
@override
String get libraryAboutDescription =>
'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
String get libraryAboutDescription => 'Scans your existing music collection to detect duplicates when downloading. Supports FLAC, M4A, MP3, Opus, and OGG formats. Metadata is read from file tags when available.';
@override
String libraryTracksCount(int count) {
@@ -2521,8 +2271,7 @@ class AppLocalizationsNl extends AppLocalizations {
String get libraryStorageAccessRequired => 'Storage Access Required';
@override
String get libraryStorageAccessMessage =>
'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
String get libraryStorageAccessMessage => 'SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.';
@override
String get libraryFolderNotExist => 'Selected folder does not exist';
@@ -2613,81 +2362,4 @@ class AppLocalizationsNl extends AppLocalizations {
);
return '$_temp0';
}
@override
String get cloudProviderWebdav => 'WebDAV (Synology, Nextcloud, QNAP)';
@override
String get cloudProviderSftp => 'SFTP (SSH File Transfer)';
@override
String get cloudProviderNotConfigured => 'Not Configured';
@override
String get cloudProviderWebdavTitle => 'WebDAV';
@override
String get cloudProviderWebdavSubtitle =>
'Synology, Nextcloud, QNAP, ownCloud';
@override
String get cloudProviderSftpTitle => 'SFTP';
@override
String get cloudProviderSftpSubtitle => 'SSH File Transfer Protocol';
@override
String get cloudTestErrorServerUrlRequired => 'Server URL is required';
@override
String get cloudTestErrorCredentialsRequired =>
'Username and password are required';
@override
String get cloudTestSuccessWebdav => 'Connected to WebDAV server';
@override
String get cloudTestSuccessSftp => 'Connected to SFTP server';
@override
String get cloudTestErrorNoProvider => 'No provider selected';
@override
String connectionTestSuccess(String message) {
return 'Success: $message';
}
@override
String get uploadStatusPending => 'Pending';
@override
String get uploadStatusUploading => 'Uploading';
@override
String get uploadStatusDone => 'Done';
@override
String get uploadStatusFailed => 'Failed';
@override
String get cloudStatusDisabled => 'Cloud Save Off';
@override
String get cloudStatusDisabledSubtitle => 'Enable to auto-upload tracks';
@override
String get cloudStatusNoProvider => 'Select Provider';
@override
String get cloudStatusNoProviderSubtitle => 'Choose a cloud service';
@override
String get cloudStatusNotConfigured => 'Setup Required';
@override
String get cloudStatusNotConfiguredSubtitle =>
'Configure your server details';
@override
String get cloudStatusActive => 'Connected';
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -153
View File
@@ -1488,100 +1488,6 @@
"settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.",
"@settingsDownloadNetworkSubtitle": {"description": "Subtitle explaining network preference"},
"settingsCloudSave": "Cloud Save",
"@settingsCloudSave": {"description": "Settings menu item for cloud upload"},
"settingsCloudSaveSubtitle": "Auto-upload to NAS or cloud storage",
"@settingsCloudSaveSubtitle": {"description": "Subtitle for cloud save menu item"},
"cloudSettingsTitle": "Cloud Save",
"@cloudSettingsTitle": {"description": "Cloud settings page title"},
"cloudSettingsSectionGeneral": "General",
"@cloudSettingsSectionGeneral": {"description": "General section header"},
"cloudSettingsEnable": "Enable Cloud Upload",
"@cloudSettingsEnable": {"description": "Toggle to enable cloud upload"},
"cloudSettingsEnableSubtitle": "Automatically upload files after download completes",
"@cloudSettingsEnableSubtitle": {"description": "Subtitle for enable toggle"},
"cloudSettingsSectionProvider": "Cloud Provider",
"@cloudSettingsSectionProvider": {"description": "Provider section header"},
"cloudSettingsProvider": "Provider",
"@cloudSettingsProvider": {"description": "Provider selection setting"},
"cloudSettingsProviderDescription": "Select where to upload your downloaded files",
"@cloudSettingsProviderDescription": {"description": "Provider picker description"},
"cloudSettingsSectionServer": "Server Configuration",
"@cloudSettingsSectionServer": {"description": "Server config section header"},
"cloudSettingsServerUrl": "Server URL",
"@cloudSettingsServerUrl": {"description": "Server URL field label"},
"cloudSettingsUsername": "Username",
"@cloudSettingsUsername": {"description": "Username field label"},
"cloudSettingsPassword": "Password",
"@cloudSettingsPassword": {"description": "Password field label"},
"cloudSettingsRemotePath": "Remote Folder Path",
"@cloudSettingsRemotePath": {"description": "Remote path field label"},
"cloudSettingsTestConnection": "Test Connection",
"@cloudSettingsTestConnection": {"description": "Test connection button"},
"cloudSettingsInfo": "Downloaded files will be automatically uploaded to your cloud storage after download completes. Original files are kept on your device.",
"@cloudSettingsInfo": {"description": "Info card explaining the feature"},
"cloudSettingsUploadQueue": "Upload Queue",
"@cloudSettingsUploadQueue": {"description": "Section header for upload queue status"},
"cloudSettingsRetryFailed": "Retry Failed",
"@cloudSettingsRetryFailed": {"description": "Button to retry failed uploads"},
"cloudSettingsClearDone": "Clear Done",
"@cloudSettingsClearDone": {"description": "Button to clear completed uploads"},
"cloudSettingsRecentUploads": "Recent Uploads",
"@cloudSettingsRecentUploads": {"description": "Section header for recent uploads list"},
"cloudSettingsResetSftpHostKey": "Reset SFTP Host Key",
"@cloudSettingsResetSftpHostKey": {"description": "Button/title to reset SFTP host key for current server"},
"cloudSettingsResetAllSftpHostKeys": "Reset All SFTP Host Keys",
"@cloudSettingsResetAllSftpHostKeys": {"description": "Button/title to reset all saved SFTP host keys"},
"cloudSettingsResetSftpHostKeyMessage": "This will forget the saved host key for this server. The next connection will save a new key.",
"@cloudSettingsResetSftpHostKeyMessage": {"description": "Dialog message for resetting SFTP host key"},
"cloudSettingsResetAllSftpHostKeysMessage": "This will forget all saved SFTP host keys. Next connections will save new keys.",
"@cloudSettingsResetAllSftpHostKeysMessage": {"description": "Dialog message for resetting all SFTP host keys"},
"cloudSettingsResetConfirm": "Reset",
"@cloudSettingsResetConfirm": {"description": "Dialog confirm button for reset action"},
"cloudSettingsResetAllConfirm": "Reset All",
"@cloudSettingsResetAllConfirm": {"description": "Dialog confirm button for reset all action"},
"cloudSettingsServerUrlRequired": "Server URL is required",
"@cloudSettingsServerUrlRequired": {"description": "Validation message when server URL is missing"},
"cloudSettingsResetSftpHostKeySuccess": "SFTP host key reset. Connect again to save a new key.",
"@cloudSettingsResetSftpHostKeySuccess": {"description": "Snackbar after resetting SFTP host key"},
"cloudSettingsResetSftpHostKeyNotFound": "No stored host key found for this server.",
"@cloudSettingsResetSftpHostKeyNotFound": {"description": "Snackbar when no host key exists for the current server"},
"cloudSettingsResetAllSftpHostKeysCleared": "{count, plural, =1{Cleared 1 SFTP host key.} other{Cleared {count} SFTP host keys.}}",
"@cloudSettingsResetAllSftpHostKeysCleared": {"description": "Snackbar after clearing all SFTP host keys", "placeholders": {"count": {}}},
"cloudSettingsResetAllSftpHostKeysNone": "No stored SFTP host keys found.",
"@cloudSettingsResetAllSftpHostKeysNone": {"description": "Snackbar when no SFTP host keys exist"},
"cloudSettingsAllowHttpTitle": "Allow HTTP (Insecure)",
"@cloudSettingsAllowHttpTitle": {"description": "Toggle/title for allowing insecure HTTP WebDAV connections"},
"cloudSettingsAllowHttpSubtitle": "Sends credentials without TLS. Not recommended.",
"@cloudSettingsAllowHttpSubtitle": {"description": "Subtitle warning for allowing insecure HTTP"},
"cloudSettingsAllowHttpMessage": "HTTP does not encrypt your credentials. Only enable if you trust the network.",
"@cloudSettingsAllowHttpMessage": {"description": "Dialog warning message for enabling insecure HTTP"},
"cloudSettingsAllowHttpConfirm": "Allow HTTP",
"@cloudSettingsAllowHttpConfirm": {"description": "Dialog confirm button for enabling insecure HTTP"},
"webdavErrorInvalidScheme": "Invalid URL: scheme is required",
"@webdavErrorInvalidScheme": {"description": "WebDAV error when URL scheme is missing"},
"webdavErrorHttpsRequired": "WebDAV URL must use https",
"@webdavErrorHttpsRequired": {"description": "WebDAV error when HTTPS is required"},
"webdavErrorInvalidHost": "Invalid URL: hostname is required",
"@webdavErrorInvalidHost": {"description": "WebDAV error when hostname is missing"},
"webdavErrorAuthFailed": "Authentication failed. Check username and password.",
"@webdavErrorAuthFailed": {"description": "WebDAV error when authentication fails"},
"webdavErrorForbidden": "Access denied. Check permissions on the server.",
"@webdavErrorForbidden": {"description": "WebDAV error when access is forbidden"},
"webdavErrorNotFound": "Server path not found. Check the URL.",
"@webdavErrorNotFound": {"description": "WebDAV error when path is not found"},
"webdavErrorConnectionFailed": "Cannot connect to server. Check URL and network.",
"@webdavErrorConnectionFailed": {"description": "WebDAV error when connection fails"},
"webdavErrorTlsError": "SSL/TLS error. Server certificate may be invalid.",
"@webdavErrorTlsError": {"description": "WebDAV error for TLS issues"},
"webdavErrorTimeout": "Connection timed out. Server may be unreachable.",
"@webdavErrorTimeout": {"description": "WebDAV error when connection times out"},
"webdavErrorInsufficientStorage": "Insufficient storage on server.",
"@webdavErrorInsufficientStorage": {"description": "WebDAV error when server storage is full"},
"webdavErrorUnknown": "Upload failed. Please try again.",
"@webdavErrorUnknown": {"description": "WebDAV fallback error message"},
"queueEmpty": "No downloads in queue",
"@queueEmpty": {"description": "Empty queue state title"},
"queueEmptySubtitle": "Add tracks from the home screen",
@@ -1934,63 +1840,5 @@
"placeholders": {
"count": {"type": "int"}
}
},
"cloudProviderWebdav": "WebDAV (Synology, Nextcloud, QNAP)",
"@cloudProviderWebdav": {"description": "WebDAV provider option with examples"},
"cloudProviderSftp": "SFTP (SSH File Transfer)",
"@cloudProviderSftp": {"description": "SFTP provider option"},
"cloudProviderNotConfigured": "Not Configured",
"@cloudProviderNotConfigured": {"description": "No cloud provider selected"},
"cloudProviderWebdavTitle": "WebDAV",
"@cloudProviderWebdavTitle": {"description": "WebDAV provider title in picker"},
"cloudProviderWebdavSubtitle": "Synology, Nextcloud, QNAP, ownCloud",
"@cloudProviderWebdavSubtitle": {"description": "WebDAV provider subtitle in picker"},
"cloudProviderSftpTitle": "SFTP",
"@cloudProviderSftpTitle": {"description": "SFTP provider title in picker"},
"cloudProviderSftpSubtitle": "SSH File Transfer Protocol",
"@cloudProviderSftpSubtitle": {"description": "SFTP provider subtitle in picker"},
"cloudTestErrorServerUrlRequired": "Server URL is required",
"@cloudTestErrorServerUrlRequired": {"description": "Error when testing connection without server URL"},
"cloudTestErrorCredentialsRequired": "Username and password are required",
"@cloudTestErrorCredentialsRequired": {"description": "Error when testing connection without credentials"},
"cloudTestSuccessWebdav": "Connected to WebDAV server",
"@cloudTestSuccessWebdav": {"description": "Success message for WebDAV connection test"},
"cloudTestSuccessSftp": "Connected to SFTP server",
"@cloudTestSuccessSftp": {"description": "Success message for SFTP connection test"},
"cloudTestErrorNoProvider": "No provider selected",
"@cloudTestErrorNoProvider": {"description": "Error when testing connection without provider"},
"connectionTestSuccess": "Success: {message}",
"@connectionTestSuccess": {
"description": "Connection test success message",
"placeholders": {
"message": {"type": "String"}
}
},
"uploadStatusPending": "Pending",
"@uploadStatusPending": {"description": "Upload queue status - waiting"},
"uploadStatusUploading": "Uploading",
"@uploadStatusUploading": {"description": "Upload queue status - in progress"},
"uploadStatusDone": "Done",
"@uploadStatusDone": {"description": "Upload queue status - completed"},
"uploadStatusFailed": "Failed",
"@uploadStatusFailed": {"description": "Upload queue status - error"},
"cloudStatusDisabled": "Cloud Save Off",
"@cloudStatusDisabled": {"description": "Status when cloud save is disabled"},
"cloudStatusDisabledSubtitle": "Enable to auto-upload tracks",
"@cloudStatusDisabledSubtitle": {"description": "Subtitle when cloud save is disabled"},
"cloudStatusNoProvider": "Select Provider",
"@cloudStatusNoProvider": {"description": "Status when no provider selected"},
"cloudStatusNoProviderSubtitle": "Choose a cloud service",
"@cloudStatusNoProviderSubtitle": {"description": "Subtitle when no provider selected"},
"cloudStatusNotConfigured": "Setup Required",
"@cloudStatusNotConfigured": {"description": "Status when settings missing"},
"cloudStatusNotConfiguredSubtitle": "Configure your server details",
"@cloudStatusNotConfiguredSubtitle": {"description": "Subtitle when settings missing"},
"cloudStatusActive": "Connected",
"@cloudStatusActive": {"description": "Status when cloud save is active"}
}
}
-34
View File
@@ -37,15 +37,6 @@ class AppSettings {
final bool autoExportFailedDownloads; // Auto export failed downloads to TXT file
final String downloadNetworkMode; // 'any' = WiFi + Mobile, 'wifi_only' = WiFi only
// Cloud Upload Settings
final bool cloudUploadEnabled; // Enable auto-upload after download
final String cloudProvider; // 'none', 'webdav', 'sftp', 'gdrive'
final String cloudServerUrl; // WebDAV/SFTP server URL
final String cloudUsername; // Server username
final String cloudPassword; // Server password (stored securely)
final String cloudRemotePath; // Remote folder path (e.g. /Music/SpotiFLAC)
final bool cloudAllowInsecureHttp; // Allow HTTP for WebDAV (insecure)
// Local Library Settings
final bool localLibraryEnabled; // Enable local library scanning
final String localLibraryPath; // Path to scan for audio files
@@ -84,14 +75,6 @@ class AppSettings {
this.useAllFilesAccess = false,
this.autoExportFailedDownloads = false,
this.downloadNetworkMode = 'any',
// Cloud Upload defaults
this.cloudUploadEnabled = false,
this.cloudProvider = 'none',
this.cloudServerUrl = '',
this.cloudUsername = '',
this.cloudPassword = '',
this.cloudRemotePath = '/Music/SpotiFLAC',
this.cloudAllowInsecureHttp = false,
// Local Library defaults
this.localLibraryEnabled = false,
this.localLibraryPath = '',
@@ -132,14 +115,6 @@ class AppSettings {
bool? useAllFilesAccess,
bool? autoExportFailedDownloads,
String? downloadNetworkMode,
// Cloud Upload
bool? cloudUploadEnabled,
String? cloudProvider,
String? cloudServerUrl,
String? cloudUsername,
String? cloudPassword,
String? cloudRemotePath,
bool? cloudAllowInsecureHttp,
// Local Library
bool? localLibraryEnabled,
String? localLibraryPath,
@@ -178,15 +153,6 @@ class AppSettings {
useAllFilesAccess: useAllFilesAccess ?? this.useAllFilesAccess,
autoExportFailedDownloads: autoExportFailedDownloads ?? this.autoExportFailedDownloads,
downloadNetworkMode: downloadNetworkMode ?? this.downloadNetworkMode,
// Cloud Upload
cloudUploadEnabled: cloudUploadEnabled ?? this.cloudUploadEnabled,
cloudProvider: cloudProvider ?? this.cloudProvider,
cloudServerUrl: cloudServerUrl ?? this.cloudServerUrl,
cloudUsername: cloudUsername ?? this.cloudUsername,
cloudPassword: cloudPassword ?? this.cloudPassword,
cloudRemotePath: cloudRemotePath ?? this.cloudRemotePath,
cloudAllowInsecureHttp:
cloudAllowInsecureHttp ?? this.cloudAllowInsecureHttp,
// Local Library
localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled,
localLibraryPath: localLibraryPath ?? this.localLibraryPath,
-15
View File
@@ -42,14 +42,6 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
autoExportFailedDownloads:
json['autoExportFailedDownloads'] as bool? ?? false,
downloadNetworkMode: json['downloadNetworkMode'] as String? ?? 'any',
cloudUploadEnabled: json['cloudUploadEnabled'] as bool? ?? false,
cloudProvider: json['cloudProvider'] as String? ?? 'none',
cloudServerUrl: json['cloudServerUrl'] as String? ?? '',
cloudUsername: json['cloudUsername'] as String? ?? '',
cloudPassword: json['cloudPassword'] as String? ?? '',
cloudRemotePath: json['cloudRemotePath'] as String? ?? '/Music/SpotiFLAC',
cloudAllowInsecureHttp:
json['cloudAllowInsecureHttp'] as bool? ?? false,
localLibraryEnabled: json['localLibraryEnabled'] as bool? ?? false,
localLibraryPath: json['localLibraryPath'] as String? ?? '',
localLibraryShowDuplicates:
@@ -90,13 +82,6 @@ Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
'useAllFilesAccess': instance.useAllFilesAccess,
'autoExportFailedDownloads': instance.autoExportFailedDownloads,
'downloadNetworkMode': instance.downloadNetworkMode,
'cloudUploadEnabled': instance.cloudUploadEnabled,
'cloudProvider': instance.cloudProvider,
'cloudServerUrl': instance.cloudServerUrl,
'cloudUsername': instance.cloudUsername,
'cloudPassword': instance.cloudPassword,
'cloudRemotePath': instance.cloudRemotePath,
'cloudAllowInsecureHttp': instance.cloudAllowInsecureHttp,
'localLibraryEnabled': instance.localLibraryEnabled,
'localLibraryPath': instance.localLibraryPath,
'localLibraryShowDuplicates': instance.localLibraryShowDuplicates,
@@ -15,7 +15,6 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/services/ffmpeg_service.dart';
import 'package:spotiflac_android/services/notification_service.dart';
import 'package:spotiflac_android/services/history_database.dart';
import 'package:spotiflac_android/providers/upload_queue_provider.dart';
import 'package:spotiflac_android/utils/logger.dart';
final _log = AppLogger('DownloadQueue');
@@ -1020,35 +1019,6 @@ void removeItem(String id) {
_saveQueueToStorage();
}
/// Trigger cloud upload for a completed download
void _triggerCloudUpload({
required String filePath,
required String trackName,
required String artistName,
}) {
final settings = ref.read(settingsProvider);
// Check if cloud upload is enabled
if (!settings.cloudUploadEnabled || settings.cloudProvider == 'none') {
return;
}
// Check if server is configured
if (settings.cloudServerUrl.isEmpty) {
_log.w('Cloud upload enabled but server URL not configured');
return;
}
// Add to upload queue
ref.read(uploadQueueProvider.notifier).addToQueue(
localPath: filePath,
trackName: trackName,
artistName: artistName,
);
_log.d('Added to cloud upload queue: $trackName - $artistName');
}
/// Export failed downloads to a TXT file
/// Returns the file path if successful, null otherwise
/// Uses daily files: same day = append to existing file, new day = new file
@@ -2459,13 +2429,6 @@ result = await PlatformBridge.downloadWithExtensions(
);
removeItem(item.id);
// Trigger cloud upload if enabled
_triggerCloudUpload(
filePath: filePath,
trackName: trackToDownload.name,
artistName: trackToDownload.artistName,
);
}
} else {
final itemAfterFailure = state.items.firstWhere(
-96
View File
@@ -9,7 +9,6 @@ import 'package:spotiflac_android/utils/logger.dart';
const _settingsKey = 'app_settings';
const _migrationVersionKey = 'settings_migration_version';
const _currentMigrationVersion = 1;
const _cloudPasswordKey = 'cloud_password';
const _spotifyClientSecretKey = 'spotify_client_secret';
class SettingsNotifier extends Notifier<AppSettings> {
@@ -31,7 +30,6 @@ class SettingsNotifier extends Notifier<AppSettings> {
await _runMigrations(prefs);
}
await _loadCloudPassword(prefs);
await _loadSpotifyClientSecret(prefs);
_applySpotifyCredentials();
@@ -57,42 +55,11 @@ class SettingsNotifier extends Notifier<AppSettings> {
Future<void> _saveSettings() async {
final prefs = await _prefs;
final settingsToSave = state.copyWith(
cloudPassword: '',
spotifyClientSecret: '',
);
await prefs.setString(_settingsKey, jsonEncode(settingsToSave.toJson()));
}
Future<void> _loadCloudPassword(SharedPreferences prefs) async {
final storedPassword = await _secureStorage.read(key: _cloudPasswordKey);
final prefsPassword = state.cloudPassword;
if ((storedPassword == null || storedPassword.isEmpty) &&
prefsPassword.isNotEmpty) {
await _secureStorage.write(key: _cloudPasswordKey, value: prefsPassword);
}
final effectivePassword = (storedPassword != null && storedPassword.isNotEmpty)
? storedPassword
: (prefsPassword.isNotEmpty ? prefsPassword : '');
if (effectivePassword != state.cloudPassword) {
state = state.copyWith(cloudPassword: effectivePassword);
}
if (prefsPassword.isNotEmpty) {
await _saveSettings();
}
}
Future<void> _storeCloudPassword(String password) async {
if (password.isEmpty) {
await _secureStorage.delete(key: _cloudPasswordKey);
} else {
await _secureStorage.write(key: _cloudPasswordKey, value: password);
}
}
Future<void> _loadSpotifyClientSecret(SharedPreferences prefs) async {
final storedSecret = await _secureStorage.read(key: _spotifyClientSecretKey);
final prefsSecret = state.spotifyClientSecret;
@@ -325,69 +292,6 @@ void setUseAllFilesAccess(bool enabled) {
_saveSettings();
}
// Cloud Upload Settings
void setCloudUploadEnabled(bool enabled) {
state = state.copyWith(cloudUploadEnabled: enabled);
_saveSettings();
}
void setCloudProvider(String provider) {
state = state.copyWith(cloudProvider: provider);
_saveSettings();
}
void setCloudServerUrl(String url) {
state = state.copyWith(cloudServerUrl: url);
_saveSettings();
}
void setCloudUsername(String username) {
state = state.copyWith(cloudUsername: username);
_saveSettings();
}
Future<void> setCloudPassword(String password) async {
state = state.copyWith(cloudPassword: password);
await _storeCloudPassword(password);
_saveSettings();
}
void setCloudRemotePath(String path) {
state = state.copyWith(cloudRemotePath: path);
_saveSettings();
}
void setCloudAllowInsecureHttp(bool allowed) {
state = state.copyWith(cloudAllowInsecureHttp: allowed);
_saveSettings();
}
Future<void> setCloudSettings({
bool? enabled,
String? provider,
String? serverUrl,
String? username,
String? password,
String? remotePath,
bool? allowInsecureHttp,
}) async {
final nextPassword = password ?? state.cloudPassword;
state = state.copyWith(
cloudUploadEnabled: enabled ?? state.cloudUploadEnabled,
cloudProvider: provider ?? state.cloudProvider,
cloudServerUrl: serverUrl ?? state.cloudServerUrl,
cloudUsername: username ?? state.cloudUsername,
cloudPassword: nextPassword,
cloudRemotePath: remotePath ?? state.cloudRemotePath,
cloudAllowInsecureHttp:
allowInsecureHttp ?? state.cloudAllowInsecureHttp,
);
if (password != null) {
await _storeCloudPassword(nextPassword);
}
_saveSettings();
}
// Local Library Settings
void setLocalLibraryEnabled(bool enabled) {
state = state.copyWith(localLibraryEnabled: enabled);
-302
View File
@@ -1,302 +0,0 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/services/cloud_upload_service.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
/// Status of an upload item
enum UploadStatus {
pending,
uploading,
completed,
failed,
}
/// An item in the upload queue
class UploadQueueItem {
final String id;
final String localPath;
final String remotePath;
final String trackName;
final String artistName;
final UploadStatus status;
final double progress;
final String? error;
final DateTime queuedAt;
final DateTime? completedAt;
const UploadQueueItem({
required this.id,
required this.localPath,
required this.remotePath,
required this.trackName,
required this.artistName,
this.status = UploadStatus.pending,
this.progress = 0.0,
this.error,
required this.queuedAt,
this.completedAt,
});
UploadQueueItem copyWith({
UploadStatus? status,
double? progress,
String? error,
DateTime? completedAt,
}) {
return UploadQueueItem(
id: id,
localPath: localPath,
remotePath: remotePath,
trackName: trackName,
artistName: artistName,
status: status ?? this.status,
progress: progress ?? this.progress,
error: error ?? this.error,
queuedAt: queuedAt,
completedAt: completedAt ?? this.completedAt,
);
}
}
/// State of the upload queue
class UploadQueueState {
final List<UploadQueueItem> items;
final bool isProcessing;
final int completedCount;
final int failedCount;
const UploadQueueState({
this.items = const [],
this.isProcessing = false,
this.completedCount = 0,
this.failedCount = 0,
});
UploadQueueState copyWith({
List<UploadQueueItem>? items,
bool? isProcessing,
int? completedCount,
int? failedCount,
}) {
return UploadQueueState(
items: items ?? this.items,
isProcessing: isProcessing ?? this.isProcessing,
completedCount: completedCount ?? this.completedCount,
failedCount: failedCount ?? this.failedCount,
);
}
int get pendingCount => items.where((i) => i.status == UploadStatus.pending).length;
int get uploadingCount => items.where((i) => i.status == UploadStatus.uploading).length;
}
/// Provider for managing the cloud upload queue
class UploadQueueNotifier extends Notifier<UploadQueueState> {
final CloudUploadService _uploadService = CloudUploadService.instance;
bool _isProcessing = false;
@override
UploadQueueState build() {
return const UploadQueueState();
}
/// Add a file to the upload queue
void addToQueue({
required String localPath,
required String trackName,
required String artistName,
}) {
final settings = ref.read(settingsProvider);
// Don't add if cloud upload is disabled
if (!settings.cloudUploadEnabled || settings.cloudProvider == 'none') {
return;
}
final remotePath = _uploadService.getRemotePath(
localFilePath: localPath,
baseRemotePath: settings.cloudRemotePath,
downloadDirectory: settings.downloadDirectory,
);
final item = UploadQueueItem(
id: '${DateTime.now().millisecondsSinceEpoch}_${localPath.hashCode}',
localPath: localPath,
remotePath: remotePath,
trackName: trackName,
artistName: artistName,
queuedAt: DateTime.now(),
);
state = state.copyWith(
items: [...state.items, item],
);
// Start processing if not already
_processQueue();
}
/// Process the upload queue
Future<void> _processQueue() async {
if (_isProcessing) return;
_isProcessing = true;
state = state.copyWith(isProcessing: true);
while (true) {
final settings = ref.read(settingsProvider);
// Stop processing if cloud upload is disabled or provider is unset
if (!settings.cloudUploadEnabled || settings.cloudProvider == 'none') {
break;
}
// Find next pending item
final pendingIndex = state.items.indexWhere(
(i) => i.status == UploadStatus.pending,
);
if (pendingIndex == -1) break;
// Update status to uploading
final item = state.items[pendingIndex];
_updateItem(pendingIndex, item.copyWith(status: UploadStatus.uploading));
// Perform upload based on provider
CloudUploadResult result;
if (settings.cloudProvider == 'webdav') {
result = await _uploadService.uploadFileWebDAV(
localPath: item.localPath,
remotePath: item.remotePath,
serverUrl: settings.cloudServerUrl,
username: settings.cloudUsername,
password: settings.cloudPassword,
allowInsecureHttp: settings.cloudAllowInsecureHttp,
onProgress: (sent, total) {
if (total > 0) {
final progress = sent / total;
_updateItem(pendingIndex, item.copyWith(
status: UploadStatus.uploading,
progress: progress,
));
}
},
);
} else if (settings.cloudProvider == 'sftp') {
result = await _uploadService.uploadFileSFTP(
localPath: item.localPath,
remotePath: item.remotePath,
serverUrl: settings.cloudServerUrl,
username: settings.cloudUsername,
password: settings.cloudPassword,
onProgress: (sent, total) {
if (total > 0) {
final progress = sent / total;
_updateItem(pendingIndex, item.copyWith(
status: UploadStatus.uploading,
progress: progress,
));
}
},
);
} else {
result = CloudUploadResult.failure('Unknown cloud provider: ${settings.cloudProvider}');
}
// Update status based on result
if (result.success) {
_updateItem(pendingIndex, item.copyWith(
status: UploadStatus.completed,
progress: 1.0,
completedAt: DateTime.now(),
));
state = state.copyWith(completedCount: state.completedCount + 1);
} else {
_updateItem(pendingIndex, item.copyWith(
status: UploadStatus.failed,
error: result.error,
));
state = state.copyWith(failedCount: state.failedCount + 1);
}
// Small delay between uploads to prevent overwhelming the server
await Future.delayed(const Duration(milliseconds: 500));
}
_isProcessing = false;
state = state.copyWith(isProcessing: false);
}
void _updateItem(int index, UploadQueueItem item) {
if (index < 0 || index >= state.items.length) return;
final items = [...state.items];
items[index] = item;
state = state.copyWith(items: items);
}
/// Retry a failed upload
void retryFailed(String id) {
final index = state.items.indexWhere((i) => i.id == id);
if (index == -1) return;
final item = state.items[index];
if (item.status != UploadStatus.failed) return;
_updateItem(index, item.copyWith(
status: UploadStatus.pending,
progress: 0.0,
error: null,
));
state = state.copyWith(failedCount: state.failedCount - 1);
_processQueue();
}
/// Retry all failed uploads
void retryAllFailed() {
final items = state.items.map((item) {
if (item.status == UploadStatus.failed) {
return item.copyWith(
status: UploadStatus.pending,
progress: 0.0,
error: null,
);
}
return item;
}).toList();
state = state.copyWith(
items: items,
failedCount: 0,
);
_processQueue();
}
/// Remove completed items from queue
void clearCompleted() {
final items = state.items.where(
(i) => i.status != UploadStatus.completed,
).toList();
state = state.copyWith(
items: items,
completedCount: 0,
);
}
/// Remove a specific item from queue
void removeItem(String id) {
final items = state.items.where((i) => i.id != id).toList();
state = state.copyWith(items: items);
}
/// Clear all items from queue
void clearAll() {
state = const UploadQueueState();
}
}
final uploadQueueProvider = NotifierProvider<UploadQueueNotifier, UploadQueueState>(
UploadQueueNotifier.new,
);
@@ -1,833 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/upload_queue_provider.dart';
import 'package:spotiflac_android/services/cloud_upload_service.dart';
import 'package:spotiflac_android/widgets/settings_group.dart';
class CloudSettingsPage extends ConsumerStatefulWidget {
const CloudSettingsPage({super.key});
@override
ConsumerState<CloudSettingsPage> createState() => _CloudSettingsPageState();
}
class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
late TextEditingController _serverUrlController;
late TextEditingController _usernameController;
late TextEditingController _passwordController;
late TextEditingController _remotePathController;
bool _isTestingConnection = false;
bool _isResettingHostKey = false;
bool _isResettingAllHostKeys = false;
String? _connectionTestResult;
@override
void initState() {
super.initState();
final settings = ref.read(settingsProvider);
_serverUrlController = TextEditingController(text: settings.cloudServerUrl);
_usernameController = TextEditingController(text: settings.cloudUsername);
_passwordController = TextEditingController(text: settings.cloudPassword);
_remotePathController = TextEditingController(text: settings.cloudRemotePath);
}
@override
void dispose() {
_serverUrlController.dispose();
_usernameController.dispose();
_passwordController.dispose();
_remotePathController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final settings = ref.watch(settingsProvider);
final colorScheme = Theme.of(context).colorScheme;
final topPadding = MediaQuery.of(context).padding.top;
return PopScope(
canPop: true,
child: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 120 + topPadding,
collapsedHeight: kToolbarHeight,
floating: false,
pinned: true,
backgroundColor: colorScheme.surface,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
flexibleSpace: LayoutBuilder(
builder: (context, constraints) {
final maxHeight = 120 + topPadding;
final minHeight = kToolbarHeight + topPadding;
final expandRatio =
((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
titlePadding: EdgeInsets.only(
left: leftPadding,
bottom: 16,
),
title: Text(
context.l10n.cloudSettingsTitle,
style: TextStyle(
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
);
},
),
),
// Enable Cloud Upload
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.cloudSettingsSectionGeneral),
),
SliverToBoxAdapter(
child: SettingsGroup(
children: [
SettingsSwitchItem(
icon: Icons.cloud_upload_outlined,
title: context.l10n.cloudSettingsEnable,
subtitle: context.l10n.cloudSettingsEnableSubtitle,
value: settings.cloudUploadEnabled,
onChanged: (value) {
ref.read(settingsProvider.notifier).setCloudUploadEnabled(value);
},
showDivider: false,
),
],
),
),
// Provider Selection
if (settings.cloudUploadEnabled) ...[
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.cloudSettingsSectionProvider),
),
SliverToBoxAdapter(
child: SettingsGroup(
children: [
SettingsItem(
icon: Icons.dns_outlined,
title: context.l10n.cloudSettingsProvider,
subtitle: _getProviderName(settings.cloudProvider),
onTap: () => _showProviderPicker(context, settings.cloudProvider),
showDivider: false,
),
],
),
),
// Server Configuration (for WebDAV/SFTP)
if (settings.cloudProvider != 'none' && settings.cloudProvider != 'gdrive') ...[
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.cloudSettingsSectionServer),
),
SliverToBoxAdapter(
child: SettingsGroup(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: TextField(
controller: _serverUrlController,
decoration: InputDecoration(
labelText: context.l10n.cloudSettingsServerUrl,
hintText: settings.cloudProvider == 'webdav'
? 'https://your-server.com/webdav'
: 'sftp://your-server.com:22',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.link),
),
onChanged: (value) {
ref.read(settingsProvider.notifier).setCloudServerUrl(value);
},
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: TextField(
controller: _usernameController,
decoration: InputDecoration(
labelText: context.l10n.cloudSettingsUsername,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.person_outline),
),
onChanged: (value) {
ref.read(settingsProvider.notifier).setCloudUsername(value);
},
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: TextField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: context.l10n.cloudSettingsPassword,
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.lock_outline),
),
onChanged: (value) {
ref.read(settingsProvider.notifier).setCloudPassword(value);
},
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: TextField(
controller: _remotePathController,
decoration: InputDecoration(
labelText: context.l10n.cloudSettingsRemotePath,
hintText: '/Music/SpotiFLAC',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.folder_outlined),
),
onChanged: (value) {
ref.read(settingsProvider.notifier).setCloudRemotePath(value);
},
),
),
],
),
),
if (settings.cloudProvider == 'webdav')
SliverToBoxAdapter(
child: SettingsGroup(
children: [
SettingsSwitchItem(
icon: Icons.warning_amber_outlined,
title: context.l10n.cloudSettingsAllowHttpTitle,
subtitle: context.l10n.cloudSettingsAllowHttpSubtitle,
value: settings.cloudAllowInsecureHttp,
onChanged: _handleAllowHttpChanged,
showDivider: false,
),
],
),
),
// Test Connection Button
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: FilledButton.icon(
onPressed: _isTestingConnection ? null : _testConnection,
icon: _isTestingConnection
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.sync),
label: Text(context.l10n.cloudSettingsTestConnection),
),
),
),
if (settings.cloudProvider == 'sftp')
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: OutlinedButton.icon(
onPressed: (_isResettingHostKey || settings.cloudServerUrl.isEmpty)
? null
: _resetSftpHostKey,
icon: _isResettingHostKey
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.security),
label: Text(context.l10n.cloudSettingsResetSftpHostKey),
),
),
),
if (settings.cloudProvider == 'sftp')
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: OutlinedButton.icon(
onPressed: _isResettingAllHostKeys ? null : _resetAllSftpHostKeys,
icon: _isResettingAllHostKeys
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.delete_sweep),
label: Text(context.l10n.cloudSettingsResetAllSftpHostKeys),
),
),
),
// Connection Test Result
if (_connectionTestResult != null)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Card(
color: _connectionTestResult!.startsWith('Success')
? colorScheme.primaryContainer
: colorScheme.errorContainer,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(
_connectionTestResult!.startsWith('Success')
? Icons.check_circle
: Icons.error,
color: _connectionTestResult!.startsWith('Success')
? colorScheme.onPrimaryContainer
: colorScheme.onErrorContainer,
),
const SizedBox(width: 12),
Expanded(
child: Text(
_connectionTestResult!,
style: TextStyle(
color: _connectionTestResult!.startsWith('Success')
? colorScheme.onPrimaryContainer
: colorScheme.onErrorContainer,
),
),
),
],
),
),
),
),
),
],
// Info about the feature
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Card(
color: colorScheme.tertiaryContainer.withValues(alpha: 0.3),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.info_outline,
size: 20,
color: colorScheme.tertiary,
),
const SizedBox(width: 12),
Expanded(
child: Text(
context.l10n.cloudSettingsInfo,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onTertiaryContainer,
),
),
),
],
),
),
),
),
),
// Upload Queue Section
SliverToBoxAdapter(
child: _buildUploadQueueSection(context),
),
],
const SliverToBoxAdapter(child: SizedBox(height: 32)),
],
),
),
);
}
String _getProviderName(String provider) {
switch (provider) {
case 'webdav':
return context.l10n.cloudProviderWebdav;
case 'sftp':
return context.l10n.cloudProviderSftp;
default:
return context.l10n.cloudProviderNotConfigured;
}
}
void _showProviderPicker(BuildContext context, String current) {
final colorScheme = Theme.of(context).colorScheme;
showModalBottomSheet(
context: context,
backgroundColor: colorScheme.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
builder: (context) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Text(
context.l10n.cloudSettingsProvider,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
child: Text(
context.l10n.cloudSettingsProviderDescription,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
ListTile(
leading: const Icon(Icons.web),
title: Text(context.l10n.cloudProviderWebdavTitle),
subtitle: Text(context.l10n.cloudProviderWebdavSubtitle),
trailing: current == 'webdav' ? Icon(Icons.check, color: colorScheme.primary) : null,
onTap: () {
ref.read(settingsProvider.notifier).setCloudProvider('webdav');
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.terminal),
title: Text(context.l10n.cloudProviderSftpTitle),
subtitle: Text(context.l10n.cloudProviderSftpSubtitle),
trailing: current == 'sftp' ? Icon(Icons.check, color: colorScheme.primary) : null,
onTap: () {
ref.read(settingsProvider.notifier).setCloudProvider('sftp');
Navigator.pop(context);
},
),
const SizedBox(height: 16),
],
),
),
);
}
Future<void> _testConnection() async {
setState(() {
_isTestingConnection = true;
_connectionTestResult = null;
});
final settings = ref.read(settingsProvider);
if (settings.cloudServerUrl.isEmpty) {
setState(() {
_isTestingConnection = false;
_connectionTestResult = context.l10n.errorGeneric(context.l10n.cloudTestErrorServerUrlRequired);
});
return;
}
if (settings.cloudUsername.isEmpty || settings.cloudPassword.isEmpty) {
setState(() {
_isTestingConnection = false;
_connectionTestResult = context.l10n.errorGeneric(context.l10n.cloudTestErrorCredentialsRequired);
});
return;
}
if (settings.cloudProvider == 'webdav') {
final result = await CloudUploadService.instance.testWebDAVConnection(
serverUrl: settings.cloudServerUrl,
username: settings.cloudUsername,
password: settings.cloudPassword,
allowInsecureHttp: settings.cloudAllowInsecureHttp,
);
setState(() {
_isTestingConnection = false;
_connectionTestResult = result.success
? context.l10n.connectionTestSuccess(context.l10n.cloudTestSuccessWebdav)
: context.l10n.errorGeneric(_localizeWebDavError(context, result));
});
} else if (settings.cloudProvider == 'sftp') {
final result = await CloudUploadService.instance.testSFTPConnection(
serverUrl: settings.cloudServerUrl,
username: settings.cloudUsername,
password: settings.cloudPassword,
);
setState(() {
_isTestingConnection = false;
_connectionTestResult = result.success
? context.l10n.connectionTestSuccess(context.l10n.cloudTestSuccessSftp)
: context.l10n.errorGeneric(result.error ?? '');
});
} else {
setState(() {
_isTestingConnection = false;
_connectionTestResult = context.l10n.errorGeneric(context.l10n.cloudTestErrorNoProvider);
});
}
}
Future<void> _handleAllowHttpChanged(bool value) async {
if (!value) {
ref.read(settingsProvider.notifier).setCloudAllowInsecureHttp(false);
return;
}
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.cloudSettingsAllowHttpTitle),
content: Text(context.l10n.cloudSettingsAllowHttpMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(context.l10n.dialogCancel),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.l10n.cloudSettingsAllowHttpConfirm),
),
],
);
},
);
if (confirmed == true) {
ref.read(settingsProvider.notifier).setCloudAllowInsecureHttp(true);
}
}
String _localizeWebDavError(
BuildContext context,
CloudUploadResult result,
) {
switch (result.errorCode) {
case 'webdav_invalid_scheme':
return context.l10n.webdavErrorInvalidScheme;
case 'webdav_https_required':
return context.l10n.webdavErrorHttpsRequired;
case 'webdav_invalid_host':
return context.l10n.webdavErrorInvalidHost;
case 'webdav_auth_failed':
return context.l10n.webdavErrorAuthFailed;
case 'webdav_forbidden':
return context.l10n.webdavErrorForbidden;
case 'webdav_not_found':
return context.l10n.webdavErrorNotFound;
case 'webdav_connection_failed':
return context.l10n.webdavErrorConnectionFailed;
case 'webdav_tls_error':
return context.l10n.webdavErrorTlsError;
case 'webdav_timeout':
return context.l10n.webdavErrorTimeout;
case 'webdav_insufficient_storage':
return context.l10n.webdavErrorInsufficientStorage;
case 'webdav_unknown':
return result.error ?? context.l10n.webdavErrorUnknown;
default:
return result.error ?? context.l10n.webdavErrorUnknown;
}
}
Future<void> _resetSftpHostKey() async {
final settings = ref.read(settingsProvider);
if (settings.cloudServerUrl.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.cloudSettingsServerUrlRequired)),
);
return;
}
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.cloudSettingsResetSftpHostKey),
content: Text(context.l10n.cloudSettingsResetSftpHostKeyMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(context.l10n.dialogCancel),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.l10n.cloudSettingsResetConfirm),
),
],
);
},
);
if (confirmed != true) return;
setState(() {
_isResettingHostKey = true;
});
final cleared = await CloudUploadService.instance.clearSftpHostKey(
serverUrl: settings.cloudServerUrl,
);
if (!mounted) return;
setState(() {
_isResettingHostKey = false;
});
final message = cleared
? context.l10n.cloudSettingsResetSftpHostKeySuccess
: context.l10n.cloudSettingsResetSftpHostKeyNotFound;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Future<void> _resetAllSftpHostKeys() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(context.l10n.cloudSettingsResetAllSftpHostKeys),
content: Text(context.l10n.cloudSettingsResetAllSftpHostKeysMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(context.l10n.dialogCancel),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.l10n.cloudSettingsResetAllConfirm),
),
],
);
},
);
if (confirmed != true) return;
setState(() {
_isResettingAllHostKeys = true;
});
final clearedCount = await CloudUploadService.instance.clearAllSftpHostKeys();
if (!mounted) return;
setState(() {
_isResettingAllHostKeys = false;
});
final message = clearedCount > 0
? context.l10n.cloudSettingsResetAllSftpHostKeysCleared(clearedCount)
: context.l10n.cloudSettingsResetAllSftpHostKeysNone;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Widget _buildUploadQueueSection(BuildContext context) {
final uploadState = ref.watch(uploadQueueProvider);
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSectionHeader(title: context.l10n.cloudSettingsUploadQueue),
SettingsGroup(
children: [
// Stats row
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem(
context,
Icons.hourglass_empty,
uploadState.pendingCount.toString(),
context.l10n.uploadStatusPending,
colorScheme.tertiary,
),
_buildStatItem(
context,
Icons.cloud_upload,
uploadState.uploadingCount.toString(),
context.l10n.uploadStatusUploading,
colorScheme.primary,
),
_buildStatItem(
context,
Icons.check_circle,
uploadState.completedCount.toString(),
context.l10n.uploadStatusDone,
Colors.green,
),
_buildStatItem(
context,
Icons.error,
uploadState.failedCount.toString(),
context.l10n.uploadStatusFailed,
colorScheme.error,
),
],
),
),
// Action buttons
if (uploadState.failedCount > 0 || uploadState.completedCount > 0)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Row(
children: [
if (uploadState.failedCount > 0)
Expanded(
child: OutlinedButton.icon(
onPressed: () {
ref.read(uploadQueueProvider.notifier).retryAllFailed();
},
icon: const Icon(Icons.refresh, size: 18),
label: Text(context.l10n.cloudSettingsRetryFailed),
),
),
if (uploadState.failedCount > 0 && uploadState.completedCount > 0)
const SizedBox(width: 12),
if (uploadState.completedCount > 0)
Expanded(
child: OutlinedButton.icon(
onPressed: () {
ref.read(uploadQueueProvider.notifier).clearCompleted();
},
icon: const Icon(Icons.clear_all, size: 18),
label: Text(context.l10n.cloudSettingsClearDone),
),
),
],
),
),
],
),
// Recent uploads list (show last 5)
if (uploadState.items.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
context.l10n.cloudSettingsRecentUploads,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
...uploadState.items.reversed.take(5).map((item) => _buildUploadItem(context, item)),
],
],
);
}
Widget _buildStatItem(
BuildContext context,
IconData icon,
String value,
String label,
Color color,
) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 4),
Text(
value,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
);
}
Widget _buildUploadItem(BuildContext context, UploadQueueItem item) {
final colorScheme = Theme.of(context).colorScheme;
IconData icon;
Color iconColor;
Widget? trailing;
switch (item.status) {
case UploadStatus.pending:
icon = Icons.hourglass_empty;
iconColor = colorScheme.tertiary;
break;
case UploadStatus.uploading:
icon = Icons.cloud_upload;
iconColor = colorScheme.primary;
trailing = SizedBox(
width: 40,
child: Text(
'${(item.progress * 100).toInt()}%',
style: TextStyle(color: colorScheme.primary),
),
);
break;
case UploadStatus.completed:
icon = Icons.check_circle;
iconColor = Colors.green;
break;
case UploadStatus.failed:
icon = Icons.error;
iconColor = colorScheme.error;
trailing = IconButton(
icon: Icon(Icons.refresh, color: colorScheme.primary),
onPressed: () {
ref.read(uploadQueueProvider.notifier).retryFailed(item.id);
},
tooltip: context.l10n.dialogRetry,
);
break;
}
return ListTile(
leading: Icon(icon, color: iconColor),
title: Text(
item.trackName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
item.artistName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
trailing: trailing,
dense: true,
);
}
}
+212 -107
View File
@@ -13,7 +13,8 @@ class LibrarySettingsPage extends ConsumerStatefulWidget {
const LibrarySettingsPage({super.key});
@override
ConsumerState<LibrarySettingsPage> createState() => _LibrarySettingsPageState();
ConsumerState<LibrarySettingsPage> createState() =>
_LibrarySettingsPageState();
}
class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
@@ -31,7 +32,7 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
final sdkVersion = androidInfo.version.sdkInt;
// Check appropriate storage permission based on Android version
bool hasPermission;
if (sdkVersion >= 30) {
@@ -39,7 +40,7 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
} else {
hasPermission = await Permission.storage.isGranted;
}
if (mounted) {
setState(() {
_androidSdkVersion = sdkVersion;
@@ -54,14 +55,14 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
Future<bool> _requestStoragePermission() async {
if (Platform.isIOS) return true;
PermissionStatus status;
if (_androidSdkVersion >= 30) {
status = await Permission.manageExternalStorage.request();
} else {
status = await Permission.storage.request();
}
if (status.isGranted) {
setState(() => _hasStoragePermission = true);
return true;
@@ -108,7 +109,7 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
Future<void> _startScan() async {
final settings = ref.read(settingsProvider);
final libraryPath = settings.localLibraryPath;
if (libraryPath.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.libraryScanSelectFolderFirst)),
@@ -158,18 +159,22 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
if (confirmed == true) {
await ref.read(localLibraryProvider.notifier).clearLibrary();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.libraryCleared)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(context.l10n.libraryCleared)));
}
}
}
Future<void> _cleanupMissingFiles() async {
final removed = await ref.read(localLibraryProvider.notifier).cleanupMissingFiles();
final removed = await ref
.read(localLibraryProvider.notifier)
.cleanupMissingFiles();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.libraryRemovedMissingFiles(removed))),
SnackBar(
content: Text(context.l10n.libraryRemovedMissingFiles(removed)),
),
);
}
}
@@ -199,9 +204,10 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
builder: (context, constraints) {
final maxHeight = 120 + topPadding;
final minHeight = kToolbarHeight + topPadding;
final expandRatio = ((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final expandRatio =
((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
@@ -219,28 +225,22 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
),
),
// Library Status Section
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.libraryStatus),
),
SliverToBoxAdapter(
child: SettingsGroup(
children: [
_LibraryStatusCard(
itemCount: libraryState.items.length,
isScanning: libraryState.isScanning,
scanProgress: libraryState.scanProgress,
scanCurrentFile: libraryState.scanCurrentFile,
scanTotalFiles: libraryState.scanTotalFiles,
lastScannedAt: libraryState.lastScannedAt,
),
],
child: _LibraryHeroCard(
itemCount: libraryState.items.length,
isScanning: libraryState.isScanning,
scanProgress: libraryState.scanProgress,
scanCurrentFile: libraryState.scanCurrentFile,
scanTotalFiles: libraryState.scanTotalFiles,
lastScannedAt: libraryState.lastScannedAt,
),
),
// Scan Settings Section
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.libraryScanSettings),
child: SettingsSectionHeader(
title: context.l10n.libraryScanSettings,
),
),
SliverToBoxAdapter(
child: SettingsGroup(
@@ -264,7 +264,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
subtitle: settings.localLibraryPath.isEmpty
? context.l10n.libraryFolderHint
: settings.localLibraryPath,
onTap: settings.localLibraryEnabled ? _pickLibraryFolder : null,
onTap: settings.localLibraryEnabled
? _pickLibraryFolder
: null,
),
),
SettingsSwitchItem(
@@ -308,7 +310,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
subtitle: settings.localLibraryPath.isEmpty
? context.l10n.libraryScanSelectFolderFirst
: context.l10n.libraryScanSubtitle,
onTap: settings.localLibraryPath.isNotEmpty ? _startScan : null,
onTap: settings.localLibraryPath.isNotEmpty
? _startScan
: null,
),
),
Opacity(
@@ -317,7 +321,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
icon: Icons.cleaning_services_outlined,
title: context.l10n.libraryCleanupMissingFiles,
subtitle: context.l10n.libraryCleanupMissingFilesSubtitle,
onTap: libraryState.items.isNotEmpty ? _cleanupMissingFiles : null,
onTap: libraryState.items.isNotEmpty
? _cleanupMissingFiles
: null,
),
),
Opacity(
@@ -326,7 +332,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
icon: Icons.delete_outline,
title: context.l10n.libraryClear,
subtitle: context.l10n.libraryClearSubtitle,
onTap: libraryState.items.isNotEmpty ? _clearLibrary : null,
onTap: libraryState.items.isNotEmpty
? _clearLibrary
: null,
showDivider: false,
),
),
@@ -358,20 +366,23 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.libraryAbout,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onPrimaryContainer,
Text(
context.l10n.libraryAbout,
style: Theme.of(context).textTheme.titleSmall
?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onPrimaryContainer,
),
),
),
const SizedBox(height: 4),
Text(
context.l10n.libraryAboutDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.8),
const SizedBox(height: 4),
Text(
context.l10n.libraryAboutDescription,
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: colorScheme.onPrimaryContainer
.withValues(alpha: 0.8),
),
),
),
],
),
),
@@ -388,7 +399,7 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
}
}
class _LibraryStatusCard extends StatelessWidget {
class _LibraryHeroCard extends StatelessWidget {
final int itemCount;
final bool isScanning;
final double scanProgress;
@@ -396,7 +407,7 @@ class _LibraryStatusCard extends StatelessWidget {
final int scanTotalFiles;
final DateTime? lastScannedAt;
const _LibraryStatusCard({
const _LibraryHeroCard({
required this.itemCount,
required this.isScanning,
required this.scanProgress,
@@ -409,87 +420,178 @@ class _LibraryStatusCard extends StatelessWidget {
if (lastScannedAt == null) return context.l10n.libraryLastScannedNever;
final now = DateTime.now();
final diff = now.difference(lastScannedAt!);
if (diff.inMinutes < 1) return context.l10n.timeJustNow;
if (diff.inHours < 1) return context.l10n.timeMinutesAgo(diff.inMinutes);
if (diff.inDays < 1) return context.l10n.timeHoursAgo(diff.inHours);
if (diff.inDays < 7) return context.l10n.dateDaysAgo(diff.inDays);
return '${lastScannedAt!.day}/${lastScannedAt!.month}/${lastScannedAt!.year}';
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
height: 220,
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(28),
boxShadow: [
if (!isDark)
BoxShadow(
color: colorScheme.shadow.withOpacity(0.05),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.library_music,
color: colorScheme.onPrimaryContainer,
size: 28,
),
// Background decorative elements
Positioned(
right: -20,
top: -20,
child: Icon(
Icons.library_music,
size: 200,
color: colorScheme.primary.withOpacity(0.05),
),
),
Positioned(
left: -40,
bottom: -40,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.secondaryContainer.withOpacity(0.3),
),
const SizedBox(width: 16),
Expanded(
child: Column(
),
),
// Content
Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.libraryTracksCount(itemCount),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Icon(
isScanning ? Icons.sync : Icons.music_note,
color: colorScheme.onPrimaryContainer,
size: 32,
),
),
Text(
context.l10n.libraryLastScanned(_formatLastScanned(context)),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
const Spacer(),
if (isScanning)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
color: colorScheme.onPrimary,
),
),
const SizedBox(width: 8),
Text(
'Scanning...',
style: TextStyle(
color: colorScheme.onPrimary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
),
if (isScanning)
SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
value: scanProgress / 100,
color: colorScheme.primary,
const Spacer(),
FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
itemCount.toString(),
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
height: 1.0,
letterSpacing: -2,
),
),
),
],
const SizedBox(height: 4),
Text(
context.l10n
.libraryTracksCount(itemCount)
.replaceAll(itemCount.toString(), '')
.trim(), // Getting just the label part if possible, or just use the full string if not
style: TextStyle(
fontSize: 16,
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
if (isScanning && scanCurrentFile != null) ...[
const SizedBox(height: 16),
LinearProgressIndicator(
value: scanProgress / 100,
backgroundColor: colorScheme.surfaceContainerHighest,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(4),
),
] else ...[
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.history,
size: 14,
color: colorScheme.onSurfaceVariant.withOpacity(0.7),
),
const SizedBox(width: 6),
Text(
context.l10n.libraryLastScanned(
_formatLastScanned(context),
),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant.withOpacity(0.7),
),
),
],
),
],
],
),
),
if (isScanning && scanCurrentFile != null) ...[
const SizedBox(height: 12),
LinearProgressIndicator(
value: scanProgress / 100,
backgroundColor: colorScheme.surfaceContainerHighest,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(4),
),
const SizedBox(height: 8),
Text(
scanCurrentFile!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
),
);
@@ -533,7 +635,10 @@ class _ScanProgressTile extends StatelessWidget {
),
),
Text(
context.l10n.libraryScanProgress(progress.toStringAsFixed(0), totalFiles),
context.l10n.libraryScanProgress(
progress.toStringAsFixed(0),
totalFiles,
),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
-7
View File
@@ -7,7 +7,6 @@ import 'package:spotiflac_android/screens/settings/download_settings_page.dart';
import 'package:spotiflac_android/screens/settings/extensions_page.dart';
import 'package:spotiflac_android/screens/settings/library_settings_page.dart';
import 'package:spotiflac_android/screens/settings/options_settings_page.dart';
import 'package:spotiflac_android/screens/settings/cloud_settings_page.dart';
import 'package:spotiflac_android/screens/settings/about_page.dart';
import 'package:spotiflac_android/screens/settings/log_screen.dart';
import 'package:spotiflac_android/widgets/settings_group.dart';
@@ -75,12 +74,6 @@ class SettingsTab extends ConsumerWidget {
subtitle: l10n.settingsDownloadSubtitle,
onTap: () => _navigateTo(context, const DownloadSettingsPage()),
),
SettingsItem(
icon: Icons.cloud_upload_outlined,
title: l10n.settingsCloudSave,
subtitle: l10n.settingsCloudSaveSubtitle,
onTap: () => _navigateTo(context, const CloudSettingsPage()),
),
SettingsItem(
icon: Icons.library_music_outlined,
title: l10n.settingsLocalLibrary,
-718
View File
@@ -1,718 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:webdav_client/webdav_client.dart' as webdav;
import 'package:dartssh2/dartssh2.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/utils/logger.dart';
/// Result of a cloud upload operation
class CloudUploadResult {
final bool success;
final String? error;
final String? errorCode;
final String? remotePath;
const CloudUploadResult({
required this.success,
this.error,
this.errorCode,
this.remotePath,
});
factory CloudUploadResult.success(String remotePath) => CloudUploadResult(
success: true,
remotePath: remotePath,
);
factory CloudUploadResult.failure(String error, {String? errorCode}) =>
CloudUploadResult(
success: false,
error: error,
errorCode: errorCode,
);
}
/// Parsed SFTP server URL
class SftpServerInfo {
final String host;
final int port;
const SftpServerInfo({required this.host, required this.port});
}
class _WebDavError {
final String code;
final String message;
const _WebDavError({required this.code, required this.message});
}
/// Service for uploading files to cloud storage (WebDAV, SFTP)
class CloudUploadService {
static CloudUploadService? _instance;
static CloudUploadService get instance => _instance ??= CloudUploadService._();
CloudUploadService._();
final LogBuffer _log = LogBuffer();
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
webdav.Client? _webdavClient;
String? _currentServerUrl;
String? _currentUsername;
String? _currentPassword;
bool? _currentAllowInsecureHttp;
static const _sftpHostKeysKey = 'sftp_known_host_keys';
Map<String, Map<String, String>>? _knownHostKeys;
void _logInfo(String tag, String message) {
_log.add(LogEntry(
timestamp: DateTime.now(),
level: 'INFO',
tag: tag,
message: message,
));
}
void _logError(String tag, String message, [String? error]) {
_log.add(LogEntry(
timestamp: DateTime.now(),
level: 'ERROR',
tag: tag,
message: message,
error: error,
));
}
// ============================================================
// WebDAV Methods
// ============================================================
_WebDavError? _validateWebDavUrl(
String url, {
required bool allowInsecureHttp,
}) {
final uri = Uri.tryParse(url.trim());
if (uri == null || uri.scheme.isEmpty) {
return const _WebDavError(
code: 'webdav_invalid_scheme',
message: 'Invalid URL: scheme is required',
);
}
final scheme = uri.scheme.toLowerCase();
if (scheme != 'https') {
if (scheme == 'http' && allowInsecureHttp) {
// Explicitly allowed by user
} else {
return const _WebDavError(
code: 'webdav_https_required',
message: 'WebDAV URL must use https',
);
}
}
if (uri.host.isEmpty) {
return const _WebDavError(
code: 'webdav_invalid_host',
message: 'Invalid URL: hostname is required',
);
}
return null;
}
/// Initialize WebDAV client with server credentials
Future<void> initializeWebDAV({
required String serverUrl,
required String username,
required String password,
bool allowInsecureHttp = false,
}) async {
final urlError = _validateWebDavUrl(
serverUrl,
allowInsecureHttp: allowInsecureHttp,
);
if (urlError != null) {
throw ArgumentError(urlError.message);
}
// Reuse existing client if credentials haven't changed
if (_webdavClient != null &&
_currentServerUrl == serverUrl &&
_currentUsername == username &&
_currentPassword == password &&
_currentAllowInsecureHttp == allowInsecureHttp) {
return;
}
_webdavClient = webdav.newClient(
serverUrl,
user: username,
password: password,
debug: false,
);
_currentServerUrl = serverUrl;
_currentUsername = username;
_currentPassword = password;
_currentAllowInsecureHttp = allowInsecureHttp;
_logInfo('CloudUpload', 'WebDAV client initialized for $serverUrl');
}
/// Test connection to WebDAV server
Future<CloudUploadResult> testWebDAVConnection({
required String serverUrl,
required String username,
required String password,
bool allowInsecureHttp = false,
}) async {
final urlError = _validateWebDavUrl(
serverUrl,
allowInsecureHttp: allowInsecureHttp,
);
if (urlError != null) {
return CloudUploadResult.failure(
urlError.message,
errorCode: urlError.code,
);
}
try {
final client = webdav.newClient(
serverUrl,
user: username,
password: password,
debug: false,
);
// Try to ping/read root directory
await client.ping();
_logInfo('CloudUpload', 'WebDAV connection test successful: $serverUrl');
return CloudUploadResult.success('/');
} catch (e) {
_logError('CloudUpload', 'WebDAV connection test failed', e.toString());
final parsed = _parseWebDAVError(e);
return CloudUploadResult.failure(
parsed.message,
errorCode: parsed.code,
);
}
}
/// Upload a file to WebDAV server
Future<CloudUploadResult> uploadFileWebDAV({
required String localPath,
required String remotePath,
required String serverUrl,
required String username,
required String password,
void Function(int sent, int total)? onProgress,
bool allowInsecureHttp = false,
}) async {
final urlError = _validateWebDavUrl(
serverUrl,
allowInsecureHttp: allowInsecureHttp,
);
if (urlError != null) {
return CloudUploadResult.failure(
urlError.message,
errorCode: urlError.code,
);
}
try {
// Initialize client if needed
await initializeWebDAV(
serverUrl: serverUrl,
username: username,
password: password,
allowInsecureHttp: allowInsecureHttp,
);
final client = _webdavClient!;
final file = File(localPath);
if (!await file.exists()) {
return CloudUploadResult.failure('File not found: $localPath');
}
// Extract directory path and ensure it exists
final remoteDir = remotePath.substring(0, remotePath.lastIndexOf('/'));
if (remoteDir.isNotEmpty) {
await _ensureWebDAVDirectoryExists(client, remoteDir);
}
// Upload the file
_logInfo('CloudUpload', 'WebDAV uploading: $localPath -> $remotePath');
await client.writeFromFile(
localPath,
remotePath,
onProgress: onProgress,
);
_logInfo('CloudUpload', 'WebDAV upload complete: $remotePath');
return CloudUploadResult.success(remotePath);
} catch (e) {
_logError('CloudUpload', 'WebDAV upload failed', e.toString());
final parsed = _parseWebDAVError(e);
return CloudUploadResult.failure(
parsed.message,
errorCode: parsed.code,
);
}
}
/// Ensure a directory exists on the WebDAV server, creating it if necessary
Future<void> _ensureWebDAVDirectoryExists(webdav.Client client, String path) async {
final parts = path.split('/').where((p) => p.isNotEmpty).toList();
var currentPath = '';
for (final part in parts) {
currentPath += '/$part';
try {
await client.mkdir(currentPath);
} catch (e) {
// Directory might already exist, ignore error
}
}
}
/// Parse WebDAV error to user-friendly message
_WebDavError _parseWebDAVError(dynamic error) {
final errorStr = error.toString().toLowerCase();
if (errorStr.contains('401') || errorStr.contains('unauthorized')) {
return const _WebDavError(
code: 'webdav_auth_failed',
message: 'Authentication failed. Check username and password.',
);
}
if (errorStr.contains('403') || errorStr.contains('forbidden')) {
return const _WebDavError(
code: 'webdav_forbidden',
message: 'Access denied. Check permissions on the server.',
);
}
if (errorStr.contains('404') || errorStr.contains('not found')) {
return const _WebDavError(
code: 'webdav_not_found',
message: 'Server path not found. Check the URL.',
);
}
if (errorStr.contains('connection refused') || errorStr.contains('socket')) {
return const _WebDavError(
code: 'webdav_connection_failed',
message: 'Cannot connect to server. Check URL and network.',
);
}
if (errorStr.contains('certificate') || errorStr.contains('ssl') || errorStr.contains('tls')) {
return const _WebDavError(
code: 'webdav_tls_error',
message: 'SSL/TLS error. Server certificate may be invalid.',
);
}
if (errorStr.contains('timeout')) {
return const _WebDavError(
code: 'webdav_timeout',
message: 'Connection timed out. Server may be unreachable.',
);
}
if (errorStr.contains('507') || errorStr.contains('insufficient storage')) {
return const _WebDavError(
code: 'webdav_insufficient_storage',
message: 'Insufficient storage on server.',
);
}
return _WebDavError(
code: 'webdav_unknown',
message: 'Upload failed: ${error.toString()}',
);
}
// ============================================================
// SFTP Methods
// ============================================================
/// Parse SFTP server URL to extract host and port
/// Supports formats:
/// - sftp://hostname:port
/// - sftp://hostname
/// - hostname:port
/// - hostname
SftpServerInfo _parseSftpUrl(String serverUrl) {
var url = serverUrl.trim();
// Remove sftp:// prefix if present
if (url.toLowerCase().startsWith('sftp://')) {
url = url.substring(7);
}
// Check for port
final colonIndex = url.lastIndexOf(':');
if (colonIndex > 0) {
final host = url.substring(0, colonIndex);
final portStr = url.substring(colonIndex + 1);
final port = int.tryParse(portStr) ?? 22;
return SftpServerInfo(host: host, port: port);
}
return SftpServerInfo(host: url, port: 22);
}
/// Test connection to SFTP server
Future<CloudUploadResult> testSFTPConnection({
required String serverUrl,
required String username,
required String password,
}) async {
SSHClient? client;
try {
final serverInfo = _parseSftpUrl(serverUrl);
_logInfo('CloudUpload', 'SFTP connecting to ${serverInfo.host}:${serverInfo.port}');
// Connect to SSH server
final socket = await SSHSocket.connect(
serverInfo.host,
serverInfo.port,
timeout: const Duration(seconds: 10),
);
client = SSHClient(
socket,
username: username,
onPasswordRequest: () => password,
onVerifyHostKey: (type, fingerprint) => _verifySftpHostKey(
host: serverInfo.host,
port: serverInfo.port,
type: type,
fingerprint: fingerprint,
),
);
// Wait for authentication
await client.authenticated;
// Test SFTP subsystem
final sftp = await client.sftp();
await sftp.listdir('.');
sftp.close();
_logInfo('CloudUpload', 'SFTP connection test successful: ${serverInfo.host}');
return CloudUploadResult.success('/');
} catch (e) {
_logError('CloudUpload', 'SFTP connection test failed', e.toString());
return CloudUploadResult.failure(_parseSFTPError(e));
} finally {
client?.close();
}
}
/// Upload a file to SFTP server
Future<CloudUploadResult> uploadFileSFTP({
required String localPath,
required String remotePath,
required String serverUrl,
required String username,
required String password,
void Function(int sent, int total)? onProgress,
}) async {
SSHClient? client;
try {
final file = File(localPath);
if (!await file.exists()) {
return CloudUploadResult.failure('File not found: $localPath');
}
final fileSize = await file.length();
final serverInfo = _parseSftpUrl(serverUrl);
_logInfo('CloudUpload', 'SFTP connecting to ${serverInfo.host}:${serverInfo.port}');
// Connect to SSH server
final socket = await SSHSocket.connect(
serverInfo.host,
serverInfo.port,
timeout: const Duration(seconds: 30),
);
client = SSHClient(
socket,
username: username,
onPasswordRequest: () => password,
onVerifyHostKey: (type, fingerprint) => _verifySftpHostKey(
host: serverInfo.host,
port: serverInfo.port,
type: type,
fingerprint: fingerprint,
),
);
// Wait for authentication
await client.authenticated;
// Open SFTP session
final sftp = await client.sftp();
// Ensure remote directory exists
final remoteDir = remotePath.substring(0, remotePath.lastIndexOf('/'));
if (remoteDir.isNotEmpty) {
await _ensureSFTPDirectoryExists(sftp, remoteDir);
}
_logInfo('CloudUpload', 'SFTP uploading: $localPath -> $remotePath');
// Open remote file for writing
final remoteFile = await sftp.open(
remotePath,
mode: SftpFileOpenMode.create |
SftpFileOpenMode.write |
SftpFileOpenMode.truncate,
);
// Read local file and write to remote with progress
final localFileStream = file.openRead();
int bytesUploaded = 0;
await for (final chunk in localFileStream) {
await remoteFile.write(Stream.value(Uint8List.fromList(chunk)));
bytesUploaded += chunk.length;
onProgress?.call(bytesUploaded, fileSize);
}
await remoteFile.close();
sftp.close();
_logInfo('CloudUpload', 'SFTP upload complete: $remotePath');
return CloudUploadResult.success(remotePath);
} catch (e) {
_logError('CloudUpload', 'SFTP upload failed', e.toString());
return CloudUploadResult.failure(_parseSFTPError(e));
} finally {
client?.close();
}
}
/// Ensure a directory exists on the SFTP server, creating it if necessary
Future<void> _ensureSFTPDirectoryExists(SftpClient sftp, String path) async {
final parts = path.split('/').where((p) => p.isNotEmpty).toList();
if (parts.isEmpty) return;
final isAbsolute = path.startsWith('/');
var currentPath = '';
for (final part in parts) {
if (currentPath.isEmpty) {
currentPath = isAbsolute ? '/$part' : part;
} else {
currentPath += '/$part';
}
try {
await sftp.mkdir(currentPath);
} catch (e) {
// Directory might already exist, ignore error
// SFTP throws exception if directory exists
}
}
}
/// Parse SFTP error to user-friendly message
String _parseSFTPError(dynamic error) {
final errorStr = error.toString().toLowerCase();
if (errorStr.contains('authentication') ||
errorStr.contains('permission denied') ||
errorStr.contains('auth fail')) {
return 'Authentication failed. Check username and password.';
}
if (errorStr.contains('connection refused')) {
return 'Connection refused. Check server address and port.';
}
if (errorStr.contains('no route to host') ||
errorStr.contains('network is unreachable')) {
return 'Cannot reach server. Check network connection.';
}
if (errorStr.contains('connection timed out') ||
errorStr.contains('timeout')) {
return 'Connection timed out. Server may be unreachable.';
}
if (errorStr.contains('host key') ||
errorStr.contains('fingerprint')) {
return 'Host key verification failed.';
}
if (errorStr.contains('no such file') ||
errorStr.contains('not found')) {
return 'Remote path not found.';
}
if (errorStr.contains('permission') ||
errorStr.contains('access denied')) {
return 'Permission denied. Check folder permissions.';
}
if (errorStr.contains('disk full') ||
errorStr.contains('no space')) {
return 'Insufficient storage on server.';
}
if (errorStr.contains('socket') ||
errorStr.contains('broken pipe')) {
return 'Connection lost. Try again.';
}
return 'SFTP error: ${error.toString()}';
}
// ============================================================
// Common Methods
// ============================================================
/// Get the remote path for a downloaded file
String getRemotePath({
required String localFilePath,
required String baseRemotePath,
required String downloadDirectory,
}) {
// Extract relative path from download directory
String relativePath;
if (localFilePath.startsWith(downloadDirectory)) {
relativePath = localFilePath.substring(downloadDirectory.length);
if (relativePath.startsWith('/') || relativePath.startsWith('\\')) {
relativePath = relativePath.substring(1);
}
} else {
// Just use the filename
relativePath = localFilePath.split(Platform.pathSeparator).last;
}
// Normalize path separators
relativePath = relativePath.replaceAll('\\', '/');
// Combine with base remote path
var remotePath = baseRemotePath;
if (!remotePath.endsWith('/')) {
remotePath += '/';
}
remotePath += relativePath;
return remotePath;
}
/// Dispose resources
void dispose() {
_webdavClient = null;
_currentServerUrl = null;
_currentUsername = null;
_currentPassword = null;
_currentAllowInsecureHttp = null;
}
Future<bool> clearSftpHostKey({required String serverUrl}) async {
final serverInfo = _parseSftpUrl(serverUrl);
final knownHostKeys = await _loadKnownHostKeys();
final keyId = '${serverInfo.host}:${serverInfo.port}';
final removed = knownHostKeys.remove(keyId) != null;
if (removed) {
await _saveKnownHostKeys();
_logInfo('CloudUpload', 'Cleared SFTP host key for $keyId');
}
return removed;
}
Future<int> clearAllSftpHostKeys() async {
final knownHostKeys = await _loadKnownHostKeys();
final count = knownHostKeys.length;
if (count == 0) {
return 0;
}
knownHostKeys.clear();
await _saveKnownHostKeys();
_logInfo('CloudUpload', 'Cleared all SFTP host keys');
return count;
}
Future<Map<String, Map<String, String>>> _loadKnownHostKeys() async {
if (_knownHostKeys != null) {
return _knownHostKeys!;
}
final prefs = await _prefs;
final raw = prefs.getString(_sftpHostKeysKey);
if (raw == null || raw.isEmpty) {
_knownHostKeys = <String, Map<String, String>>{};
return _knownHostKeys!;
}
try {
final decoded = jsonDecode(raw);
if (decoded is Map<String, dynamic>) {
final map = <String, Map<String, String>>{};
decoded.forEach((key, value) {
if (value is Map) {
final type = value['type'];
final fingerprint = value['fingerprint'];
if (type is String && fingerprint is String) {
map[key] = {'type': type, 'fingerprint': fingerprint};
}
}
});
_knownHostKeys = map;
return map;
}
} catch (e) {
_logError('CloudUpload', 'Failed to parse known host keys', e.toString());
}
_knownHostKeys = <String, Map<String, String>>{};
return _knownHostKeys!;
}
Future<void> _saveKnownHostKeys() async {
if (_knownHostKeys == null) return;
final prefs = await _prefs;
await prefs.setString(_sftpHostKeysKey, jsonEncode(_knownHostKeys));
}
String _formatFingerprint(Uint8List fingerprint) {
final buffer = StringBuffer();
for (var i = 0; i < fingerprint.length; i++) {
if (i > 0) buffer.write(':');
buffer.write(fingerprint[i].toRadixString(16).padLeft(2, '0'));
}
return buffer.toString();
}
Future<bool> _verifySftpHostKey({
required String host,
required int port,
required String type,
required Uint8List fingerprint,
}) async {
final knownHostKeys = await _loadKnownHostKeys();
final keyId = '$host:$port';
final fingerprintHex = _formatFingerprint(fingerprint);
final existing = knownHostKeys[keyId];
if (existing == null) {
knownHostKeys[keyId] = {
'type': type,
'fingerprint': fingerprintHex,
};
await _saveKnownHostKeys();
_logInfo('CloudUpload', 'Saved new SFTP host key for $keyId');
return true;
}
final existingFingerprint = existing['fingerprint'];
if (existingFingerprint == fingerprintHex) {
return true;
}
_logError(
'CloudUpload',
'SFTP host key mismatch for $keyId',
'expected=$existingFingerprint got=$fingerprintHex',
);
return false;
}
}
-40
View File
@@ -41,14 +41,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.7.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
url: "https://pub.dev"
source: hosted
version: "1.6.5"
async:
dependency: transitive
description:
@@ -257,14 +249,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.3"
dartssh2:
dependency: "direct main"
description:
name: dartssh2
sha256: bb242396c1d90f05c261b5eb8338cc67167e803c0948b0207029339cc568a66c
url: "https://pub.dev"
source: hosted
version: "2.13.0"
dbus:
dependency: transitive
description:
@@ -885,14 +869,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
pinenacl:
dependency: transitive
description:
name: pinenacl
sha256: "57e907beaacbc3c024a098910b6240758e899674de07d6949a67b52fd984cbdf"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
platform:
dependency: transitive
description:
@@ -909,14 +885,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
pool:
dependency: transitive
description:
@@ -1426,14 +1394,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
webdav_client:
dependency: "direct main"
description:
name: webdav_client
sha256: "682fffc50b61dc0e8f46717171db03bf9caaa17347be41c0c91e297553bf86b2"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
webkit_inspection_protocol:
dependency: transitive
description:
-2
View File
@@ -33,8 +33,6 @@ dependencies:
http: ^1.6.0
dio: ^5.8.0
connectivity_plus: ^6.0.3
webdav_client: ^1.2.2
dartssh2: ^2.13.0
# UI Components
cupertino_icons: ^1.0.8