mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-06 12:48:03 +02:00
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:
+1
-21
@@ -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
@@ -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.'
|
||||
);
|
||||
}
|
||||
|
||||
+104
-447
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
}
|
||||
|
||||
+190
-618
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
+102
-443
File diff suppressed because it is too large
Load Diff
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
+187
-612
File diff suppressed because it is too large
Load Diff
+115
-469
File diff suppressed because it is too large
Load Diff
+104
-447
File diff suppressed because it is too large
Load Diff
+237
-711
File diff suppressed because it is too large
Load Diff
+1
-153
@@ -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"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user