mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-12 10:47:31 +02:00
feat: rename History tab to Library and show local library items
- Rename bottom navigation 'History' to 'Library' - Add Local Library section showing scanned tracks below downloaded tracks - Add source badge to each item (Downloaded/Local) for clear identification - Add new localization strings for Library tab and source badges - Local library items can be played directly from the library tab
This commit is contained in:
1 parent
22f001a735
commit
9c22f41a3e
29 files changed
+2969
-80
No files matched your search
@@ -14,6 +14,10 @@
|
|||||||
- **Duplicate Detection in Search Results**: "In Library" badge shows on tracks that exist in your local library
|
- **Duplicate Detection in Search Results**: "In Library" badge shows on tracks that exist in your local library
|
||||||
- Matches by ISRC (exact match) or track name + artist (fuzzy match)
|
- Matches by ISRC (exact match) or track name + artist (fuzzy match)
|
||||||
- Toggle indicator visibility in Settings > Local Library
|
- Toggle indicator visibility in Settings > Local Library
|
||||||
|
- **Unified Library Tab**: History tab renamed to Library, now shows both Downloaded and Local Library tracks
|
||||||
|
- Source badge on each item (Downloaded/Local) to identify the source
|
||||||
|
- Local Library items shown in a separate section when enabled
|
||||||
|
- Play button to open local library tracks directly
|
||||||
- **Cloud Upload with WebDAV & SFTP**: Automatically upload downloaded files to your NAS or cloud storage
|
- **Cloud Upload with WebDAV & SFTP**: Automatically upload downloaded files to your NAS or cloud storage
|
||||||
- Full WebDAV support (Synology DSM, Nextcloud, QNAP, ownCloud)
|
- Full WebDAV support (Synology DSM, Nextcloud, QNAP, ownCloud)
|
||||||
- Full SFTP support (any SSH server with SFTP enabled)
|
- Full SFTP support (any SSH server with SFTP enabled)
|
||||||
@@ -33,6 +37,11 @@
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Cloud upload passwords are now stored in secure storage instead of SharedPreferences
|
- 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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package gobackend
|
package gobackend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -104,7 +107,16 @@ func NewExtensionRuntime(ext *LoadedExtension) *ExtensionRuntime {
|
|||||||
Jar: jar,
|
Jar: jar,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
// Validate redirect target domain against allowed domains
|
// Validate redirect target domain against allowed domains
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
GoLog("[Extension:%s] Redirect blocked: non-https scheme '%s'\n", ext.ID, req.URL.Scheme)
|
||||||
|
return fmt.Errorf("redirect blocked: only https is allowed")
|
||||||
|
}
|
||||||
|
|
||||||
domain := req.URL.Hostname()
|
domain := req.URL.Hostname()
|
||||||
|
if domain == "" {
|
||||||
|
GoLog("[Extension:%s] Redirect blocked: missing hostname\n", ext.ID)
|
||||||
|
return fmt.Errorf("redirect blocked: hostname is required")
|
||||||
|
}
|
||||||
if !ext.Manifest.IsDomainAllowed(domain) {
|
if !ext.Manifest.IsDomainAllowed(domain) {
|
||||||
GoLog("[Extension:%s] Redirect blocked: domain '%s' not in allowed list\n", ext.ID, domain)
|
GoLog("[Extension:%s] Redirect blocked: domain '%s' not in allowed list\n", ext.ID, domain)
|
||||||
return &RedirectBlockedError{Domain: domain}
|
return &RedirectBlockedError{Domain: domain}
|
||||||
@@ -139,35 +151,48 @@ func (e *RedirectBlockedError) Error() string {
|
|||||||
|
|
||||||
// isPrivateIP checks if a hostname resolves to a private/local IP address
|
// isPrivateIP checks if a hostname resolves to a private/local IP address
|
||||||
func isPrivateIP(host string) bool {
|
func isPrivateIP(host string) bool {
|
||||||
// Block common private network patterns
|
hostLower := strings.ToLower(strings.TrimSpace(host))
|
||||||
// This is a simple check - for production, consider DNS resolution
|
if hostLower == "" {
|
||||||
privatePatterns := []string{
|
return false
|
||||||
"localhost",
|
|
||||||
"127.",
|
|
||||||
"10.",
|
|
||||||
"172.16.", "172.17.", "172.18.", "172.19.",
|
|
||||||
"172.20.", "172.21.", "172.22.", "172.23.",
|
|
||||||
"172.24.", "172.25.", "172.26.", "172.27.",
|
|
||||||
"172.28.", "172.29.", "172.30.", "172.31.",
|
|
||||||
"192.168.",
|
|
||||||
"169.254.",
|
|
||||||
"::1",
|
|
||||||
"fc00:",
|
|
||||||
"fe80:",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostLower := host
|
if hostLower == "localhost" || strings.HasSuffix(hostLower, ".local") {
|
||||||
for _, pattern := range privatePatterns {
|
return true
|
||||||
if hostLower == pattern || len(hostLower) > len(pattern) && hostLower[:len(pattern)] == pattern {
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(hostLower); ip != nil {
|
||||||
|
return isPrivateIPAddr(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := net.LookupIP(hostLower)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if isPrivateIPAddr(ip) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also block .local domains
|
return false
|
||||||
if len(host) > 6 && host[len(host)-6:] == ".local" {
|
}
|
||||||
|
|
||||||
|
func isPrivateIPAddr(ip net.IP) bool {
|
||||||
|
if ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ip.IsLoopback() ||
|
||||||
|
ip.IsPrivate() ||
|
||||||
|
ip.IsLinkLocalUnicast() ||
|
||||||
|
ip.IsLinkLocalMulticast() ||
|
||||||
|
ip.IsMulticast() ||
|
||||||
|
ip.IsUnspecified() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !ip.IsGlobalUnicast() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,39 @@ func isPathInAllowedDirs(absPath string) bool {
|
|||||||
defer allowedDownloadDirsMu.RUnlock()
|
defer allowedDownloadDirsMu.RUnlock()
|
||||||
|
|
||||||
for _, allowedDir := range allowedDownloadDirs {
|
for _, allowedDir := range allowedDownloadDirs {
|
||||||
if strings.HasPrefix(absPath, allowedDir) {
|
if isPathWithinBase(allowedDir, absPath) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPathWithinBase(baseDir, targetPath string) bool {
|
||||||
|
baseAbs, err := filepath.Abs(baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
targetAbs, err := filepath.Abs(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, err := filepath.Rel(baseAbs, targetAbs)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rel = filepath.Clean(rel)
|
||||||
|
if rel == "." {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := ".." + string(filepath.Separator)
|
||||||
|
if rel == ".." || strings.HasPrefix(rel, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ExtensionRuntime) validatePath(path string) (string, error) {
|
func (r *ExtensionRuntime) validatePath(path string) (string, error) {
|
||||||
// Check if extension has file permission
|
// Check if extension has file permission
|
||||||
if !r.manifest.Permissions.File {
|
if !r.manifest.Permissions.File {
|
||||||
@@ -77,7 +103,7 @@ func (r *ExtensionRuntime) validatePath(path string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absDataDir, _ := filepath.Abs(r.dataDir)
|
absDataDir, _ := filepath.Abs(r.dataDir)
|
||||||
if !strings.HasPrefix(absPath, absDataDir) {
|
if !isPathWithinBase(absDataDir, absPath) {
|
||||||
return "", fmt.Errorf("file access denied: path '%s' is outside sandbox", path)
|
return "", fmt.Errorf("file access denied: path '%s' is outside sandbox", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ func (r *ExtensionRuntime) validateDomain(urlStr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
domain := parsed.Hostname()
|
domain := parsed.Hostname()
|
||||||
|
if domain == "" {
|
||||||
|
return fmt.Errorf("invalid URL: hostname is required")
|
||||||
|
}
|
||||||
|
|
||||||
// Block private/local network access (SSRF protection)
|
// Block private/local network access (SSRF protection)
|
||||||
if isPrivateIP(domain) {
|
if isPrivateIP(domain) {
|
||||||
|
|||||||
@@ -142,7 +142,13 @@ abstract class AppLocalizations {
|
|||||||
/// **'Home'**
|
/// **'Home'**
|
||||||
String get navHome;
|
String get navHome;
|
||||||
|
|
||||||
/// Bottom navigation - History tab
|
/// Bottom navigation - Library tab
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Library'**
|
||||||
|
String get navLibrary;
|
||||||
|
|
||||||
|
/// Bottom navigation - History tab (legacy)
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'History'**
|
/// **'History'**
|
||||||
@@ -3898,6 +3904,96 @@ abstract class AppLocalizations {
|
|||||||
/// **'No stored SFTP host keys found.'**
|
/// **'No stored SFTP host keys found.'**
|
||||||
String get cloudSettingsResetAllSftpHostKeysNone;
|
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
|
/// Empty queue state title
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -4455,6 +4551,156 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Selected folder does not exist'**
|
/// **'Selected folder does not exist'**
|
||||||
String get libraryFolderNotExist;
|
String get libraryFolderNotExist;
|
||||||
|
|
||||||
|
/// Badge for tracks downloaded via SpotiFLAC
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Downloaded'**
|
||||||
|
String get librarySourceDownloaded;
|
||||||
|
|
||||||
|
/// Badge for tracks from local library scan
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Local'**
|
||||||
|
String get librarySourceLocal;
|
||||||
|
|
||||||
|
/// Filter chip - show all library items
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'All'**
|
||||||
|
String get libraryFilterAll;
|
||||||
|
|
||||||
|
/// Filter chip - show only downloaded items
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Downloaded'**
|
||||||
|
String get libraryFilterDownloaded;
|
||||||
|
|
||||||
|
/// Filter chip - show only local library items
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Local'**
|
||||||
|
String get libraryFilterLocal;
|
||||||
|
|
||||||
|
/// Relative time - less than a minute ago
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Just now'**
|
||||||
|
String get timeJustNow;
|
||||||
|
|
||||||
|
/// Relative time - minutes ago
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count, plural, =1{1 minute ago} other{{count} minutes ago}}'**
|
||||||
|
String timeMinutesAgo(int count);
|
||||||
|
|
||||||
|
/// Relative time - hours ago
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Startseite';
|
String get navHome => 'Startseite';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'Verlauf';
|
String get navHistory => 'Verlauf';
|
||||||
|
|
||||||
@@ -2147,6 +2150,59 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2485,4 +2541,99 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,4 +2526,99 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,6 +2526,101 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Spanish Castilian, as used in Spain (`es_ES`).
|
/// The translations for Spanish Castilian, as used in Spain (`es_ES`).
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,4 +2526,99 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'होम';
|
String get navHome => 'होम';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'इतिहास';
|
String get navHistory => 'इतिहास';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,4 +2526,99 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Beranda';
|
String get navHome => 'Beranda';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'Riwayat';
|
String get navHistory => 'Riwayat';
|
||||||
|
|
||||||
@@ -2145,6 +2148,59 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'Tidak ada unduhan dalam antrian';
|
String get queueEmpty => 'Tidak ada unduhan dalam antrian';
|
||||||
|
|
||||||
@@ -2483,4 +2539,99 @@ class AppLocalizationsId extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'ホーム';
|
String get navHome => 'ホーム';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => '履歴';
|
String get navHistory => '履歴';
|
||||||
|
|
||||||
@@ -2119,6 +2122,59 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'キューにダウンロードがありません';
|
String get queueEmpty => 'キューにダウンロードがありません';
|
||||||
|
|
||||||
@@ -2456,4 +2512,99 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,4 +2526,99 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,4 +2526,99 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,6 +2526,101 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Portuguese, as used in Portugal (`pt_PT`).
|
/// The translations for Portuguese, as used in Portugal (`pt_PT`).
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Главная';
|
String get navHome => 'Главная';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'История';
|
String get navHistory => 'История';
|
||||||
|
|
||||||
@@ -2171,6 +2174,59 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'Нет загрузок в очереди';
|
String get queueEmpty => 'Нет загрузок в очереди';
|
||||||
|
|
||||||
@@ -2516,4 +2572,99 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Ara';
|
String get navHome => 'Ara';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'Geçmiş';
|
String get navHistory => 'Geçmiş';
|
||||||
|
|
||||||
@@ -2147,6 +2150,59 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2485,4 +2541,99 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get navHome => 'Home';
|
String get navHome => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get navLibrary => 'Library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get navHistory => 'History';
|
String get navHistory => 'History';
|
||||||
|
|
||||||
@@ -2132,6 +2135,59 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get cloudSettingsResetAllSftpHostKeysNone =>
|
String get cloudSettingsResetAllSftpHostKeysNone =>
|
||||||
'No stored SFTP host keys found.';
|
'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.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get queueEmpty => 'No downloads in queue';
|
String get queueEmpty => 'No downloads in queue';
|
||||||
|
|
||||||
@@ -2470,6 +2526,101 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryFolderNotExist => 'Selected folder does not exist';
|
String get libraryFolderNotExist => 'Selected folder does not exist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get librarySourceLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterAll => 'All';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterDownloaded => 'Downloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryFilterLocal => 'Local';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get timeJustNow => 'Just now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeMinutesAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count minutes ago',
|
||||||
|
one: '1 minute ago',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String timeHoursAgo(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count hours ago',
|
||||||
|
one: '1 hour ago',
|
||||||
|
);
|
||||||
|
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Chinese, as used in China (`zh_CN`).
|
/// The translations for Chinese, as used in China (`zh_CN`).
|
||||||
|
|||||||
+104
-2
@@ -9,8 +9,10 @@
|
|||||||
|
|
||||||
"navHome": "Home",
|
"navHome": "Home",
|
||||||
"@navHome": {"description": "Bottom navigation - Home tab"},
|
"@navHome": {"description": "Bottom navigation - Home tab"},
|
||||||
|
"navLibrary": "Library",
|
||||||
|
"@navLibrary": {"description": "Bottom navigation - Library tab"},
|
||||||
"navHistory": "History",
|
"navHistory": "History",
|
||||||
"@navHistory": {"description": "Bottom navigation - History tab"},
|
"@navHistory": {"description": "Bottom navigation - History tab (legacy)"},
|
||||||
"navSettings": "Settings",
|
"navSettings": "Settings",
|
||||||
"@navSettings": {"description": "Bottom navigation - Settings tab"},
|
"@navSettings": {"description": "Bottom navigation - Settings tab"},
|
||||||
"navStore": "Store",
|
"navStore": "Store",
|
||||||
@@ -1549,6 +1551,36 @@
|
|||||||
"@cloudSettingsResetAllSftpHostKeysCleared": {"description": "Snackbar after clearing all SFTP host keys", "placeholders": {"count": {}}},
|
"@cloudSettingsResetAllSftpHostKeysCleared": {"description": "Snackbar after clearing all SFTP host keys", "placeholders": {"count": {}}},
|
||||||
"cloudSettingsResetAllSftpHostKeysNone": "No stored SFTP host keys found.",
|
"cloudSettingsResetAllSftpHostKeysNone": "No stored SFTP host keys found.",
|
||||||
"@cloudSettingsResetAllSftpHostKeysNone": {"description": "Snackbar when no SFTP host keys exist"},
|
"@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": "No downloads in queue",
|
||||||
"@queueEmpty": {"description": "Empty queue state title"},
|
"@queueEmpty": {"description": "Empty queue state title"},
|
||||||
@@ -1839,5 +1871,75 @@
|
|||||||
"libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.",
|
"libraryStorageAccessMessage": "SpotiFLAC needs storage access to scan your music library. Please grant permission in settings.",
|
||||||
"@libraryStorageAccessMessage": {"description": "Dialog message for storage permission"},
|
"@libraryStorageAccessMessage": {"description": "Dialog message for storage permission"},
|
||||||
"libraryFolderNotExist": "Selected folder does not exist",
|
"libraryFolderNotExist": "Selected folder does not exist",
|
||||||
"@libraryFolderNotExist": {"description": "Error when folder doesn't exist"}
|
"@libraryFolderNotExist": {"description": "Error when folder doesn't exist"},
|
||||||
|
"librarySourceDownloaded": "Downloaded",
|
||||||
|
"@librarySourceDownloaded": {"description": "Badge for tracks downloaded via SpotiFLAC"},
|
||||||
|
"librarySourceLocal": "Local",
|
||||||
|
"@librarySourceLocal": {"description": "Badge for tracks from local library scan"},
|
||||||
|
"libraryFilterAll": "All",
|
||||||
|
"@libraryFilterAll": {"description": "Filter chip - show all library items"},
|
||||||
|
"libraryFilterDownloaded": "Downloaded",
|
||||||
|
"@libraryFilterDownloaded": {"description": "Filter chip - show only downloaded items"},
|
||||||
|
"libraryFilterLocal": "Local",
|
||||||
|
"@libraryFilterLocal": {"description": "Filter chip - show only local library items"},
|
||||||
|
|
||||||
|
"timeJustNow": "Just now",
|
||||||
|
"@timeJustNow": {"description": "Relative time - less than a minute ago"},
|
||||||
|
"timeMinutesAgo": "{count, plural, =1{1 minute ago} other{{count} minutes ago}}",
|
||||||
|
"@timeMinutesAgo": {
|
||||||
|
"description": "Relative time - minutes ago",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {"type": "int"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeHoursAgo": "{count, plural, =1{1 hour ago} other{{count} hours ago}}",
|
||||||
|
"@timeHoursAgo": {
|
||||||
|
"description": "Relative time - hours ago",
|
||||||
|
"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"}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ class AppSettings {
|
|||||||
final String cloudUsername; // Server username
|
final String cloudUsername; // Server username
|
||||||
final String cloudPassword; // Server password (stored securely)
|
final String cloudPassword; // Server password (stored securely)
|
||||||
final String cloudRemotePath; // Remote folder path (e.g. /Music/SpotiFLAC)
|
final String cloudRemotePath; // Remote folder path (e.g. /Music/SpotiFLAC)
|
||||||
|
final bool cloudAllowInsecureHttp; // Allow HTTP for WebDAV (insecure)
|
||||||
|
|
||||||
// Local Library Settings
|
// Local Library Settings
|
||||||
final bool localLibraryEnabled; // Enable local library scanning
|
final bool localLibraryEnabled; // Enable local library scanning
|
||||||
@@ -90,6 +91,7 @@ class AppSettings {
|
|||||||
this.cloudUsername = '',
|
this.cloudUsername = '',
|
||||||
this.cloudPassword = '',
|
this.cloudPassword = '',
|
||||||
this.cloudRemotePath = '/Music/SpotiFLAC',
|
this.cloudRemotePath = '/Music/SpotiFLAC',
|
||||||
|
this.cloudAllowInsecureHttp = false,
|
||||||
// Local Library defaults
|
// Local Library defaults
|
||||||
this.localLibraryEnabled = false,
|
this.localLibraryEnabled = false,
|
||||||
this.localLibraryPath = '',
|
this.localLibraryPath = '',
|
||||||
@@ -137,6 +139,7 @@ class AppSettings {
|
|||||||
String? cloudUsername,
|
String? cloudUsername,
|
||||||
String? cloudPassword,
|
String? cloudPassword,
|
||||||
String? cloudRemotePath,
|
String? cloudRemotePath,
|
||||||
|
bool? cloudAllowInsecureHttp,
|
||||||
// Local Library
|
// Local Library
|
||||||
bool? localLibraryEnabled,
|
bool? localLibraryEnabled,
|
||||||
String? localLibraryPath,
|
String? localLibraryPath,
|
||||||
@@ -182,6 +185,8 @@ class AppSettings {
|
|||||||
cloudUsername: cloudUsername ?? this.cloudUsername,
|
cloudUsername: cloudUsername ?? this.cloudUsername,
|
||||||
cloudPassword: cloudPassword ?? this.cloudPassword,
|
cloudPassword: cloudPassword ?? this.cloudPassword,
|
||||||
cloudRemotePath: cloudRemotePath ?? this.cloudRemotePath,
|
cloudRemotePath: cloudRemotePath ?? this.cloudRemotePath,
|
||||||
|
cloudAllowInsecureHttp:
|
||||||
|
cloudAllowInsecureHttp ?? this.cloudAllowInsecureHttp,
|
||||||
// Local Library
|
// Local Library
|
||||||
localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled,
|
localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled,
|
||||||
localLibraryPath: localLibraryPath ?? this.localLibraryPath,
|
localLibraryPath: localLibraryPath ?? this.localLibraryPath,
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
|||||||
cloudUsername: json['cloudUsername'] as String? ?? '',
|
cloudUsername: json['cloudUsername'] as String? ?? '',
|
||||||
cloudPassword: json['cloudPassword'] as String? ?? '',
|
cloudPassword: json['cloudPassword'] as String? ?? '',
|
||||||
cloudRemotePath: json['cloudRemotePath'] as String? ?? '/Music/SpotiFLAC',
|
cloudRemotePath: json['cloudRemotePath'] as String? ?? '/Music/SpotiFLAC',
|
||||||
|
cloudAllowInsecureHttp:
|
||||||
|
json['cloudAllowInsecureHttp'] as bool? ?? false,
|
||||||
localLibraryEnabled: json['localLibraryEnabled'] as bool? ?? false,
|
localLibraryEnabled: json['localLibraryEnabled'] as bool? ?? false,
|
||||||
localLibraryPath: json['localLibraryPath'] as String? ?? '',
|
localLibraryPath: json['localLibraryPath'] as String? ?? '',
|
||||||
localLibraryShowDuplicates:
|
localLibraryShowDuplicates:
|
||||||
@@ -94,6 +96,7 @@ Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
|
|||||||
'cloudUsername': instance.cloudUsername,
|
'cloudUsername': instance.cloudUsername,
|
||||||
'cloudPassword': instance.cloudPassword,
|
'cloudPassword': instance.cloudPassword,
|
||||||
'cloudRemotePath': instance.cloudRemotePath,
|
'cloudRemotePath': instance.cloudRemotePath,
|
||||||
|
'cloudAllowInsecureHttp': instance.cloudAllowInsecureHttp,
|
||||||
'localLibraryEnabled': instance.localLibraryEnabled,
|
'localLibraryEnabled': instance.localLibraryEnabled,
|
||||||
'localLibraryPath': instance.localLibraryPath,
|
'localLibraryPath': instance.localLibraryPath,
|
||||||
'localLibraryShowDuplicates': instance.localLibraryShowDuplicates,
|
'localLibraryShowDuplicates': instance.localLibraryShowDuplicates,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const _settingsKey = 'app_settings';
|
|||||||
const _migrationVersionKey = 'settings_migration_version';
|
const _migrationVersionKey = 'settings_migration_version';
|
||||||
const _currentMigrationVersion = 1;
|
const _currentMigrationVersion = 1;
|
||||||
const _cloudPasswordKey = 'cloud_password';
|
const _cloudPasswordKey = 'cloud_password';
|
||||||
|
const _spotifyClientSecretKey = 'spotify_client_secret';
|
||||||
|
|
||||||
class SettingsNotifier extends Notifier<AppSettings> {
|
class SettingsNotifier extends Notifier<AppSettings> {
|
||||||
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
||||||
@@ -31,6 +32,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _loadCloudPassword(prefs);
|
await _loadCloudPassword(prefs);
|
||||||
|
await _loadSpotifyClientSecret(prefs);
|
||||||
|
|
||||||
_applySpotifyCredentials();
|
_applySpotifyCredentials();
|
||||||
|
|
||||||
@@ -54,7 +56,10 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
|||||||
|
|
||||||
Future<void> _saveSettings() async {
|
Future<void> _saveSettings() async {
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
final settingsToSave = state.copyWith(cloudPassword: '');
|
final settingsToSave = state.copyWith(
|
||||||
|
cloudPassword: '',
|
||||||
|
spotifyClientSecret: '',
|
||||||
|
);
|
||||||
await prefs.setString(_settingsKey, jsonEncode(settingsToSave.toJson()));
|
await prefs.setString(_settingsKey, jsonEncode(settingsToSave.toJson()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +93,36 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadSpotifyClientSecret(SharedPreferences prefs) async {
|
||||||
|
final storedSecret = await _secureStorage.read(key: _spotifyClientSecretKey);
|
||||||
|
final prefsSecret = state.spotifyClientSecret;
|
||||||
|
|
||||||
|
if ((storedSecret == null || storedSecret.isEmpty) &&
|
||||||
|
prefsSecret.isNotEmpty) {
|
||||||
|
await _secureStorage.write(key: _spotifyClientSecretKey, value: prefsSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
final effectiveSecret = (storedSecret != null && storedSecret.isNotEmpty)
|
||||||
|
? storedSecret
|
||||||
|
: (prefsSecret.isNotEmpty ? prefsSecret : '');
|
||||||
|
|
||||||
|
if (effectiveSecret != state.spotifyClientSecret) {
|
||||||
|
state = state.copyWith(spotifyClientSecret: effectiveSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefsSecret.isNotEmpty) {
|
||||||
|
await _saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _storeSpotifyClientSecret(String secret) async {
|
||||||
|
if (secret.isEmpty) {
|
||||||
|
await _secureStorage.delete(key: _spotifyClientSecretKey);
|
||||||
|
} else {
|
||||||
|
await _secureStorage.write(key: _spotifyClientSecretKey, value: secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _applySpotifyCredentials() async {
|
Future<void> _applySpotifyCredentials() async {
|
||||||
if (state.spotifyClientId.isNotEmpty &&
|
if (state.spotifyClientId.isNotEmpty &&
|
||||||
state.spotifyClientSecret.isNotEmpty) {
|
state.spotifyClientSecret.isNotEmpty) {
|
||||||
@@ -193,25 +228,28 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
|||||||
_saveSettings();
|
_saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSpotifyClientSecret(String clientSecret) {
|
Future<void> setSpotifyClientSecret(String clientSecret) async {
|
||||||
state = state.copyWith(spotifyClientSecret: clientSecret);
|
state = state.copyWith(spotifyClientSecret: clientSecret);
|
||||||
|
await _storeSpotifyClientSecret(clientSecret);
|
||||||
_saveSettings();
|
_saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSpotifyCredentials(String clientId, String clientSecret) {
|
Future<void> setSpotifyCredentials(String clientId, String clientSecret) async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
spotifyClientId: clientId,
|
spotifyClientId: clientId,
|
||||||
spotifyClientSecret: clientSecret,
|
spotifyClientSecret: clientSecret,
|
||||||
);
|
);
|
||||||
|
await _storeSpotifyClientSecret(clientSecret);
|
||||||
_saveSettings();
|
_saveSettings();
|
||||||
_applySpotifyCredentials();
|
_applySpotifyCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearSpotifyCredentials() {
|
Future<void> clearSpotifyCredentials() async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
spotifyClientId: '',
|
spotifyClientId: '',
|
||||||
spotifyClientSecret: '',
|
spotifyClientSecret: '',
|
||||||
);
|
);
|
||||||
|
await _storeSpotifyClientSecret('');
|
||||||
_saveSettings();
|
_saveSettings();
|
||||||
_applySpotifyCredentials();
|
_applySpotifyCredentials();
|
||||||
}
|
}
|
||||||
@@ -319,6 +357,11 @@ void setUseAllFilesAccess(bool enabled) {
|
|||||||
_saveSettings();
|
_saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCloudAllowInsecureHttp(bool allowed) {
|
||||||
|
state = state.copyWith(cloudAllowInsecureHttp: allowed);
|
||||||
|
_saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setCloudSettings({
|
Future<void> setCloudSettings({
|
||||||
bool? enabled,
|
bool? enabled,
|
||||||
String? provider,
|
String? provider,
|
||||||
@@ -326,6 +369,7 @@ void setUseAllFilesAccess(bool enabled) {
|
|||||||
String? username,
|
String? username,
|
||||||
String? password,
|
String? password,
|
||||||
String? remotePath,
|
String? remotePath,
|
||||||
|
bool? allowInsecureHttp,
|
||||||
}) async {
|
}) async {
|
||||||
final nextPassword = password ?? state.cloudPassword;
|
final nextPassword = password ?? state.cloudPassword;
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@@ -335,6 +379,8 @@ void setUseAllFilesAccess(bool enabled) {
|
|||||||
cloudUsername: username ?? state.cloudUsername,
|
cloudUsername: username ?? state.cloudUsername,
|
||||||
cloudPassword: nextPassword,
|
cloudPassword: nextPassword,
|
||||||
cloudRemotePath: remotePath ?? state.cloudRemotePath,
|
cloudRemotePath: remotePath ?? state.cloudRemotePath,
|
||||||
|
cloudAllowInsecureHttp:
|
||||||
|
allowInsecureHttp ?? state.cloudAllowInsecureHttp,
|
||||||
);
|
);
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
await _storeCloudPassword(nextPassword);
|
await _storeCloudPassword(nextPassword);
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ class UploadQueueNotifier extends Notifier<UploadQueueState> {
|
|||||||
serverUrl: settings.cloudServerUrl,
|
serverUrl: settings.cloudServerUrl,
|
||||||
username: settings.cloudUsername,
|
username: settings.cloudUsername,
|
||||||
password: settings.cloudPassword,
|
password: settings.cloudPassword,
|
||||||
|
allowInsecureHttp: settings.cloudAllowInsecureHttp,
|
||||||
onProgress: (sent, total) {
|
onProgress: (sent, total) {
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
final progress = sent / total;
|
final progress = sent / total;
|
||||||
|
|||||||
@@ -202,14 +202,14 @@ class _MainShellState extends ConsumerState<MainShell> {
|
|||||||
icon: Badge(
|
icon: Badge(
|
||||||
isLabelVisible: queueState > 0,
|
isLabelVisible: queueState > 0,
|
||||||
label: Text('$queueState'),
|
label: Text('$queueState'),
|
||||||
child: const Icon(Icons.history_outlined),
|
child: const Icon(Icons.library_music_outlined),
|
||||||
),
|
),
|
||||||
selectedIcon: Badge(
|
selectedIcon: Badge(
|
||||||
isLabelVisible: queueState > 0,
|
isLabelVisible: queueState > 0,
|
||||||
label: Text('$queueState'),
|
label: Text('$queueState'),
|
||||||
child: const Icon(Icons.history),
|
child: const Icon(Icons.library_music),
|
||||||
),
|
),
|
||||||
label: l10n.navHistory,
|
label: l10n.navLibrary,
|
||||||
),
|
),
|
||||||
if (showStore)
|
if (showStore)
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
|
|||||||
+288
-2
@@ -12,9 +12,82 @@ import 'package:spotiflac_android/utils/mime_utils.dart';
|
|||||||
import 'package:spotiflac_android/models/download_item.dart';
|
import 'package:spotiflac_android/models/download_item.dart';
|
||||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||||
|
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||||
|
import 'package:spotiflac_android/services/library_database.dart';
|
||||||
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
||||||
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
|
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
|
||||||
|
|
||||||
|
/// Represents the source of a library item
|
||||||
|
enum LibraryItemSource { downloaded, local }
|
||||||
|
|
||||||
|
/// Unified library item that can come from download history or local library
|
||||||
|
class UnifiedLibraryItem {
|
||||||
|
final String id;
|
||||||
|
final String trackName;
|
||||||
|
final String artistName;
|
||||||
|
final String albumName;
|
||||||
|
final String? coverUrl;
|
||||||
|
final String filePath;
|
||||||
|
final String? quality;
|
||||||
|
final DateTime addedAt;
|
||||||
|
final LibraryItemSource source;
|
||||||
|
|
||||||
|
// Original items for navigation
|
||||||
|
final DownloadHistoryItem? historyItem;
|
||||||
|
final LocalLibraryItem? localItem;
|
||||||
|
|
||||||
|
UnifiedLibraryItem({
|
||||||
|
required this.id,
|
||||||
|
required this.trackName,
|
||||||
|
required this.artistName,
|
||||||
|
required this.albumName,
|
||||||
|
this.coverUrl,
|
||||||
|
required this.filePath,
|
||||||
|
this.quality,
|
||||||
|
required this.addedAt,
|
||||||
|
required this.source,
|
||||||
|
this.historyItem,
|
||||||
|
this.localItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UnifiedLibraryItem.fromDownloadHistory(DownloadHistoryItem item) {
|
||||||
|
return UnifiedLibraryItem(
|
||||||
|
id: 'dl_${item.id}',
|
||||||
|
trackName: item.trackName,
|
||||||
|
artistName: item.artistName,
|
||||||
|
albumName: item.albumName,
|
||||||
|
coverUrl: item.coverUrl,
|
||||||
|
filePath: item.filePath,
|
||||||
|
quality: item.quality,
|
||||||
|
addedAt: item.downloadedAt,
|
||||||
|
source: LibraryItemSource.downloaded,
|
||||||
|
historyItem: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory UnifiedLibraryItem.fromLocalLibrary(LocalLibraryItem item) {
|
||||||
|
String? quality;
|
||||||
|
if (item.bitDepth != null && item.sampleRate != null) {
|
||||||
|
quality = '${item.bitDepth}bit/${(item.sampleRate! / 1000).toStringAsFixed(1)}kHz';
|
||||||
|
}
|
||||||
|
return UnifiedLibraryItem(
|
||||||
|
id: 'local_${item.id}',
|
||||||
|
trackName: item.trackName,
|
||||||
|
artistName: item.artistName,
|
||||||
|
albumName: item.albumName,
|
||||||
|
coverUrl: null, // Local library doesn't have cover URLs
|
||||||
|
filePath: item.filePath,
|
||||||
|
quality: quality,
|
||||||
|
addedAt: item.scannedAt,
|
||||||
|
source: LibraryItemSource.local,
|
||||||
|
localItem: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get searchKey => '${trackName.toLowerCase()}|${artistName.toLowerCase()}|${albumName.toLowerCase()}';
|
||||||
|
String get albumKey => '$albumName|$artistName';
|
||||||
|
}
|
||||||
|
|
||||||
class _GroupedAlbum {
|
class _GroupedAlbum {
|
||||||
final String albumName;
|
final String albumName;
|
||||||
final String artistName;
|
final String artistName;
|
||||||
@@ -664,6 +737,12 @@ final queueItems = ref.watch(downloadQueueProvider.select((s) => s.items));
|
|||||||
final allHistoryItems = ref.watch(
|
final allHistoryItems = ref.watch(
|
||||||
downloadHistoryProvider.select((s) => s.items),
|
downloadHistoryProvider.select((s) => s.items),
|
||||||
);
|
);
|
||||||
|
// Watch local library items
|
||||||
|
final localLibraryEnabled = ref.watch(settingsProvider.select((s) => s.localLibraryEnabled));
|
||||||
|
final localLibraryItems = localLibraryEnabled
|
||||||
|
? ref.watch(localLibraryProvider.select((s) => s.items))
|
||||||
|
: <LocalLibraryItem>[];
|
||||||
|
|
||||||
_ensureHistoryCaches(allHistoryItems);
|
_ensureHistoryCaches(allHistoryItems);
|
||||||
final historyViewMode = ref.watch(
|
final historyViewMode = ref.watch(
|
||||||
settingsProvider.select((s) => s.historyViewMode),
|
settingsProvider.select((s) => s.historyViewMode),
|
||||||
@@ -720,7 +799,7 @@ final queueItems = ref.watch(downloadQueueProvider.select((s) => s.items));
|
|||||||
expandedTitleScale: 1.0,
|
expandedTitleScale: 1.0,
|
||||||
titlePadding: const EdgeInsets.only(left: 24, bottom: 16),
|
titlePadding: const EdgeInsets.only(left: 24, bottom: 16),
|
||||||
title: Text(
|
title: Text(
|
||||||
context.l10n.historyTitle,
|
context.l10n.navLibrary,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20 + (14 * expandRatio),
|
fontSize: 20 + (14 * expandRatio),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -945,6 +1024,7 @@ const Spacer(),
|
|||||||
queueItems: queueItems,
|
queueItems: queueItems,
|
||||||
groupedAlbums: groupedAlbums,
|
groupedAlbums: groupedAlbums,
|
||||||
albumCounts: historyStats.albumCounts,
|
albumCounts: historyStats.albumCounts,
|
||||||
|
localLibraryItems: localLibraryItems,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -982,7 +1062,8 @@ child: _buildSelectionBottomBar(
|
|||||||
required String historyViewMode,
|
required String historyViewMode,
|
||||||
required List<DownloadItem> queueItems,
|
required List<DownloadItem> queueItems,
|
||||||
required List<_GroupedAlbum> groupedAlbums,
|
required List<_GroupedAlbum> groupedAlbums,
|
||||||
required Map<String, int> albumCounts,
|
required Map<String, int> albumCounts,
|
||||||
|
required List<LocalLibraryItem> localLibraryItems,
|
||||||
}) {
|
}) {
|
||||||
final historyItems = _resolveHistoryItems(
|
final historyItems = _resolveHistoryItems(
|
||||||
filterMode: filterMode,
|
filterMode: filterMode,
|
||||||
@@ -1151,8 +1232,57 @@ if (filterMode == 'albums' && filteredGroupedAlbums.isNotEmpty)
|
|||||||
}, childCount: historyItems.length ),
|
}, childCount: historyItems.length ),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Local Library Section
|
||||||
|
if (localLibraryItems.isNotEmpty && filterMode == 'all')
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.l10n.libraryFilterLocal,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.secondaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${localLibraryItems.length}',
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color: colorScheme.onSecondaryContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (localLibraryItems.isNotEmpty && filterMode == 'all')
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
final item = localLibraryItems[index];
|
||||||
|
return KeyedSubtree(
|
||||||
|
key: ValueKey('local_${item.id}'),
|
||||||
|
child: _buildLocalLibraryItem(
|
||||||
|
context,
|
||||||
|
item,
|
||||||
|
colorScheme,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, childCount: localLibraryItems.length),
|
||||||
|
),
|
||||||
|
|
||||||
if (queueItems.isEmpty &&
|
if (queueItems.isEmpty &&
|
||||||
historyItems.isEmpty &&
|
historyItems.isEmpty &&
|
||||||
|
localLibraryItems.isEmpty &&
|
||||||
(filterMode != 'albums' || filteredGroupedAlbums.isEmpty) &&
|
(filterMode != 'albums' || filteredGroupedAlbums.isEmpty) &&
|
||||||
!showFilteringIndicator)
|
!showFilteringIndicator)
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
@@ -2084,6 +2214,27 @@ child: CachedNetworkImage(
|
|||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
// Source badge
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.l10n.librarySourceDownloaded,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
|
?.copyWith(
|
||||||
|
color: colorScheme.onPrimaryContainer,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
dateStr,
|
dateStr,
|
||||||
style: Theme.of(context).textTheme.labelSmall
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
@@ -2158,6 +2309,141 @@ child: CachedNetworkImage(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildLocalLibraryItem(
|
||||||
|
BuildContext context,
|
||||||
|
LocalLibraryItem item,
|
||||||
|
ColorScheme colorScheme,
|
||||||
|
) {
|
||||||
|
final fileExists = _checkFileExists(item.filePath);
|
||||||
|
|
||||||
|
// Format quality info
|
||||||
|
String? qualityStr;
|
||||||
|
if (item.bitDepth != null && item.sampleRate != null) {
|
||||||
|
qualityStr = '${item.bitDepth}bit/${(item.sampleRate! / 1000).toStringAsFixed(1)}kHz';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _openFile(item.filePath),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Placeholder for cover (local library doesn't have cover URLs)
|
||||||
|
Container(
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.secondaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.folder_outlined,
|
||||||
|
color: colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.trackName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
item.artistName,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// Source badge
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.secondaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.l10n.librarySourceLocal,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
|
?.copyWith(
|
||||||
|
color: colorScheme.onSecondaryContainer,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (qualityStr != null) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: qualityStr.startsWith('24')
|
||||||
|
? colorScheme.tertiaryContainer
|
||||||
|
: colorScheme.surfaceContainerHighest,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
qualityStr,
|
||||||
|
style: Theme.of(context).textTheme.labelSmall
|
||||||
|
?.copyWith(
|
||||||
|
color: qualityStr.startsWith('24')
|
||||||
|
? colorScheme.onTertiaryContainer
|
||||||
|
: colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
|
if (fileExists)
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _openFile(item.filePath),
|
||||||
|
icon: Icon(
|
||||||
|
Icons.play_arrow,
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
tooltip: context.l10n.tooltipPlay,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
color: colorScheme.error,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FilterChip extends StatelessWidget {
|
class _FilterChip extends StatelessWidget {
|
||||||
|
|||||||
@@ -204,6 +204,21 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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
|
// Test Connection Button
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@@ -347,11 +362,11 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
String _getProviderName(String provider) {
|
String _getProviderName(String provider) {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'webdav':
|
case 'webdav':
|
||||||
return 'WebDAV (Synology, Nextcloud, QNAP)';
|
return context.l10n.cloudProviderWebdav;
|
||||||
case 'sftp':
|
case 'sftp':
|
||||||
return 'SFTP (SSH File Transfer)';
|
return context.l10n.cloudProviderSftp;
|
||||||
default:
|
default:
|
||||||
return 'Not Configured';
|
return context.l10n.cloudProviderNotConfigured;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,8 +403,8 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.web),
|
leading: const Icon(Icons.web),
|
||||||
title: const Text('WebDAV'),
|
title: Text(context.l10n.cloudProviderWebdavTitle),
|
||||||
subtitle: const Text('Synology, Nextcloud, QNAP, ownCloud'),
|
subtitle: Text(context.l10n.cloudProviderWebdavSubtitle),
|
||||||
trailing: current == 'webdav' ? Icon(Icons.check, color: colorScheme.primary) : null,
|
trailing: current == 'webdav' ? Icon(Icons.check, color: colorScheme.primary) : null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ref.read(settingsProvider.notifier).setCloudProvider('webdav');
|
ref.read(settingsProvider.notifier).setCloudProvider('webdav');
|
||||||
@@ -398,8 +413,8 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.terminal),
|
leading: const Icon(Icons.terminal),
|
||||||
title: const Text('SFTP'),
|
title: Text(context.l10n.cloudProviderSftpTitle),
|
||||||
subtitle: const Text('SSH File Transfer Protocol'),
|
subtitle: Text(context.l10n.cloudProviderSftpSubtitle),
|
||||||
trailing: current == 'sftp' ? Icon(Icons.check, color: colorScheme.primary) : null,
|
trailing: current == 'sftp' ? Icon(Icons.check, color: colorScheme.primary) : null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ref.read(settingsProvider.notifier).setCloudProvider('sftp');
|
ref.read(settingsProvider.notifier).setCloudProvider('sftp');
|
||||||
@@ -424,7 +439,7 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
if (settings.cloudServerUrl.isEmpty) {
|
if (settings.cloudServerUrl.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTestingConnection = false;
|
_isTestingConnection = false;
|
||||||
_connectionTestResult = 'Error: Server URL is required';
|
_connectionTestResult = context.l10n.errorGeneric(context.l10n.cloudTestErrorServerUrlRequired);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -432,7 +447,7 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
if (settings.cloudUsername.isEmpty || settings.cloudPassword.isEmpty) {
|
if (settings.cloudUsername.isEmpty || settings.cloudPassword.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTestingConnection = false;
|
_isTestingConnection = false;
|
||||||
_connectionTestResult = 'Error: Username and password are required';
|
_connectionTestResult = context.l10n.errorGeneric(context.l10n.cloudTestErrorCredentialsRequired);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -442,13 +457,14 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
serverUrl: settings.cloudServerUrl,
|
serverUrl: settings.cloudServerUrl,
|
||||||
username: settings.cloudUsername,
|
username: settings.cloudUsername,
|
||||||
password: settings.cloudPassword,
|
password: settings.cloudPassword,
|
||||||
|
allowInsecureHttp: settings.cloudAllowInsecureHttp,
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTestingConnection = false;
|
_isTestingConnection = false;
|
||||||
_connectionTestResult = result.success
|
_connectionTestResult = result.success
|
||||||
? 'Success: Connected to WebDAV server'
|
? context.l10n.connectionTestSuccess(context.l10n.cloudTestSuccessWebdav)
|
||||||
: 'Error: ${result.error}';
|
: context.l10n.errorGeneric(_localizeWebDavError(context, result));
|
||||||
});
|
});
|
||||||
} else if (settings.cloudProvider == 'sftp') {
|
} else if (settings.cloudProvider == 'sftp') {
|
||||||
final result = await CloudUploadService.instance.testSFTPConnection(
|
final result = await CloudUploadService.instance.testSFTPConnection(
|
||||||
@@ -460,17 +476,80 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isTestingConnection = false;
|
_isTestingConnection = false;
|
||||||
_connectionTestResult = result.success
|
_connectionTestResult = result.success
|
||||||
? 'Success: Connected to SFTP server'
|
? context.l10n.connectionTestSuccess(context.l10n.cloudTestSuccessSftp)
|
||||||
: 'Error: ${result.error}';
|
: context.l10n.errorGeneric(result.error ?? '');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTestingConnection = false;
|
_isTestingConnection = false;
|
||||||
_connectionTestResult = 'Error: No provider selected';
|
_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 {
|
Future<void> _resetSftpHostKey() async {
|
||||||
final settings = ref.read(settingsProvider);
|
final settings = ref.read(settingsProvider);
|
||||||
if (settings.cloudServerUrl.isEmpty) {
|
if (settings.cloudServerUrl.isEmpty) {
|
||||||
@@ -586,28 +665,28 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
context,
|
context,
|
||||||
Icons.hourglass_empty,
|
Icons.hourglass_empty,
|
||||||
uploadState.pendingCount.toString(),
|
uploadState.pendingCount.toString(),
|
||||||
'Pending',
|
context.l10n.uploadStatusPending,
|
||||||
colorScheme.tertiary,
|
colorScheme.tertiary,
|
||||||
),
|
),
|
||||||
_buildStatItem(
|
_buildStatItem(
|
||||||
context,
|
context,
|
||||||
Icons.cloud_upload,
|
Icons.cloud_upload,
|
||||||
uploadState.uploadingCount.toString(),
|
uploadState.uploadingCount.toString(),
|
||||||
'Uploading',
|
context.l10n.uploadStatusUploading,
|
||||||
colorScheme.primary,
|
colorScheme.primary,
|
||||||
),
|
),
|
||||||
_buildStatItem(
|
_buildStatItem(
|
||||||
context,
|
context,
|
||||||
Icons.check_circle,
|
Icons.check_circle,
|
||||||
uploadState.completedCount.toString(),
|
uploadState.completedCount.toString(),
|
||||||
'Done',
|
context.l10n.uploadStatusDone,
|
||||||
Colors.green,
|
Colors.green,
|
||||||
),
|
),
|
||||||
_buildStatItem(
|
_buildStatItem(
|
||||||
context,
|
context,
|
||||||
Icons.error,
|
Icons.error,
|
||||||
uploadState.failedCount.toString(),
|
uploadState.failedCount.toString(),
|
||||||
'Failed',
|
context.l10n.uploadStatusFailed,
|
||||||
colorScheme.error,
|
colorScheme.error,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -729,7 +808,7 @@ class _CloudSettingsPageState extends ConsumerState<CloudSettingsPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.read(uploadQueueProvider.notifier).retryFailed(item.id);
|
ref.read(uploadQueueProvider.notifier).retryFailed(item.id);
|
||||||
},
|
},
|
||||||
tooltip: 'Retry',
|
tooltip: context.l10n.dialogRetry,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,9 +410,9 @@ class _LibraryStatusCard extends StatelessWidget {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final diff = now.difference(lastScannedAt!);
|
final diff = now.difference(lastScannedAt!);
|
||||||
|
|
||||||
if (diff.inMinutes < 1) return 'Just now';
|
if (diff.inMinutes < 1) return context.l10n.timeJustNow;
|
||||||
if (diff.inHours < 1) return '${diff.inMinutes} minutes ago';
|
if (diff.inHours < 1) return context.l10n.timeMinutesAgo(diff.inMinutes);
|
||||||
if (diff.inDays < 1) return '${diff.inHours} hours ago';
|
if (diff.inDays < 1) return context.l10n.timeHoursAgo(diff.inHours);
|
||||||
if (diff.inDays < 7) return context.l10n.dateDaysAgo(diff.inDays);
|
if (diff.inDays < 7) return context.l10n.dateDaysAgo(diff.inDays);
|
||||||
|
|
||||||
return '${lastScannedAt!.day}/${lastScannedAt!.month}/${lastScannedAt!.year}';
|
return '${lastScannedAt!.day}/${lastScannedAt!.month}/${lastScannedAt!.year}';
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import 'package:spotiflac_android/utils/logger.dart';
|
|||||||
class CloudUploadResult {
|
class CloudUploadResult {
|
||||||
final bool success;
|
final bool success;
|
||||||
final String? error;
|
final String? error;
|
||||||
|
final String? errorCode;
|
||||||
final String? remotePath;
|
final String? remotePath;
|
||||||
|
|
||||||
const CloudUploadResult({
|
const CloudUploadResult({
|
||||||
required this.success,
|
required this.success,
|
||||||
this.error,
|
this.error,
|
||||||
|
this.errorCode,
|
||||||
this.remotePath,
|
this.remotePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,9 +25,11 @@ class CloudUploadResult {
|
|||||||
remotePath: remotePath,
|
remotePath: remotePath,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory CloudUploadResult.failure(String error) => CloudUploadResult(
|
factory CloudUploadResult.failure(String error, {String? errorCode}) =>
|
||||||
|
CloudUploadResult(
|
||||||
success: false,
|
success: false,
|
||||||
error: error,
|
error: error,
|
||||||
|
errorCode: errorCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +41,13 @@ class SftpServerInfo {
|
|||||||
const SftpServerInfo({required this.host, required this.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)
|
/// Service for uploading files to cloud storage (WebDAV, SFTP)
|
||||||
class CloudUploadService {
|
class CloudUploadService {
|
||||||
static CloudUploadService? _instance;
|
static CloudUploadService? _instance;
|
||||||
@@ -51,6 +62,7 @@ class CloudUploadService {
|
|||||||
String? _currentServerUrl;
|
String? _currentServerUrl;
|
||||||
String? _currentUsername;
|
String? _currentUsername;
|
||||||
String? _currentPassword;
|
String? _currentPassword;
|
||||||
|
bool? _currentAllowInsecureHttp;
|
||||||
|
|
||||||
static const _sftpHostKeysKey = 'sftp_known_host_keys';
|
static const _sftpHostKeysKey = 'sftp_known_host_keys';
|
||||||
Map<String, Map<String, String>>? _knownHostKeys;
|
Map<String, Map<String, String>>? _knownHostKeys;
|
||||||
@@ -78,9 +90,35 @@ class CloudUploadService {
|
|||||||
// WebDAV Methods
|
// WebDAV Methods
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool _isHttpsUrl(String url) {
|
_WebDavError? _validateWebDavUrl(
|
||||||
final uri = Uri.tryParse(url);
|
String url, {
|
||||||
return uri != null && uri.scheme == 'https';
|
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
|
/// Initialize WebDAV client with server credentials
|
||||||
@@ -88,16 +126,22 @@ class CloudUploadService {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
|
bool allowInsecureHttp = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isHttpsUrl(serverUrl)) {
|
final urlError = _validateWebDavUrl(
|
||||||
throw ArgumentError('WebDAV URL must use https');
|
serverUrl,
|
||||||
|
allowInsecureHttp: allowInsecureHttp,
|
||||||
|
);
|
||||||
|
if (urlError != null) {
|
||||||
|
throw ArgumentError(urlError.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse existing client if credentials haven't changed
|
// Reuse existing client if credentials haven't changed
|
||||||
if (_webdavClient != null &&
|
if (_webdavClient != null &&
|
||||||
_currentServerUrl == serverUrl &&
|
_currentServerUrl == serverUrl &&
|
||||||
_currentUsername == username &&
|
_currentUsername == username &&
|
||||||
_currentPassword == password) {
|
_currentPassword == password &&
|
||||||
|
_currentAllowInsecureHttp == allowInsecureHttp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +155,7 @@ class CloudUploadService {
|
|||||||
_currentServerUrl = serverUrl;
|
_currentServerUrl = serverUrl;
|
||||||
_currentUsername = username;
|
_currentUsername = username;
|
||||||
_currentPassword = password;
|
_currentPassword = password;
|
||||||
|
_currentAllowInsecureHttp = allowInsecureHttp;
|
||||||
|
|
||||||
_logInfo('CloudUpload', 'WebDAV client initialized for $serverUrl');
|
_logInfo('CloudUpload', 'WebDAV client initialized for $serverUrl');
|
||||||
}
|
}
|
||||||
@@ -120,9 +165,17 @@ class CloudUploadService {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
|
bool allowInsecureHttp = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isHttpsUrl(serverUrl)) {
|
final urlError = _validateWebDavUrl(
|
||||||
return CloudUploadResult.failure('WebDAV URL must use https.');
|
serverUrl,
|
||||||
|
allowInsecureHttp: allowInsecureHttp,
|
||||||
|
);
|
||||||
|
if (urlError != null) {
|
||||||
|
return CloudUploadResult.failure(
|
||||||
|
urlError.message,
|
||||||
|
errorCode: urlError.code,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final client = webdav.newClient(
|
final client = webdav.newClient(
|
||||||
@@ -139,7 +192,11 @@ class CloudUploadService {
|
|||||||
return CloudUploadResult.success('/');
|
return CloudUploadResult.success('/');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logError('CloudUpload', 'WebDAV connection test failed', e.toString());
|
_logError('CloudUpload', 'WebDAV connection test failed', e.toString());
|
||||||
return CloudUploadResult.failure(_parseWebDAVError(e));
|
final parsed = _parseWebDAVError(e);
|
||||||
|
return CloudUploadResult.failure(
|
||||||
|
parsed.message,
|
||||||
|
errorCode: parsed.code,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +208,17 @@ class CloudUploadService {
|
|||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
void Function(int sent, int total)? onProgress,
|
void Function(int sent, int total)? onProgress,
|
||||||
|
bool allowInsecureHttp = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isHttpsUrl(serverUrl)) {
|
final urlError = _validateWebDavUrl(
|
||||||
return CloudUploadResult.failure('WebDAV URL must use https.');
|
serverUrl,
|
||||||
|
allowInsecureHttp: allowInsecureHttp,
|
||||||
|
);
|
||||||
|
if (urlError != null) {
|
||||||
|
return CloudUploadResult.failure(
|
||||||
|
urlError.message,
|
||||||
|
errorCode: urlError.code,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Initialize client if needed
|
// Initialize client if needed
|
||||||
@@ -161,6 +226,7 @@ class CloudUploadService {
|
|||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
|
allowInsecureHttp: allowInsecureHttp,
|
||||||
);
|
);
|
||||||
|
|
||||||
final client = _webdavClient!;
|
final client = _webdavClient!;
|
||||||
@@ -189,7 +255,11 @@ class CloudUploadService {
|
|||||||
return CloudUploadResult.success(remotePath);
|
return CloudUploadResult.success(remotePath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logError('CloudUpload', 'WebDAV upload failed', e.toString());
|
_logError('CloudUpload', 'WebDAV upload failed', e.toString());
|
||||||
return CloudUploadResult.failure(_parseWebDAVError(e));
|
final parsed = _parseWebDAVError(e);
|
||||||
|
return CloudUploadResult.failure(
|
||||||
|
parsed.message,
|
||||||
|
errorCode: parsed.code,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,32 +279,56 @@ class CloudUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse WebDAV error to user-friendly message
|
/// Parse WebDAV error to user-friendly message
|
||||||
String _parseWebDAVError(dynamic error) {
|
_WebDavError _parseWebDAVError(dynamic error) {
|
||||||
final errorStr = error.toString().toLowerCase();
|
final errorStr = error.toString().toLowerCase();
|
||||||
|
|
||||||
if (errorStr.contains('401') || errorStr.contains('unauthorized')) {
|
if (errorStr.contains('401') || errorStr.contains('unauthorized')) {
|
||||||
return 'Authentication failed. Check username and password.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_auth_failed',
|
||||||
|
message: 'Authentication failed. Check username and password.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('403') || errorStr.contains('forbidden')) {
|
if (errorStr.contains('403') || errorStr.contains('forbidden')) {
|
||||||
return 'Access denied. Check permissions on the server.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_forbidden',
|
||||||
|
message: 'Access denied. Check permissions on the server.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('404') || errorStr.contains('not found')) {
|
if (errorStr.contains('404') || errorStr.contains('not found')) {
|
||||||
return 'Server path not found. Check the URL.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_not_found',
|
||||||
|
message: 'Server path not found. Check the URL.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('connection refused') || errorStr.contains('socket')) {
|
if (errorStr.contains('connection refused') || errorStr.contains('socket')) {
|
||||||
return 'Cannot connect to server. Check URL and network.';
|
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')) {
|
if (errorStr.contains('certificate') || errorStr.contains('ssl') || errorStr.contains('tls')) {
|
||||||
return 'SSL/TLS error. Server certificate may be invalid.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_tls_error',
|
||||||
|
message: 'SSL/TLS error. Server certificate may be invalid.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('timeout')) {
|
if (errorStr.contains('timeout')) {
|
||||||
return 'Connection timed out. Server may be unreachable.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_timeout',
|
||||||
|
message: 'Connection timed out. Server may be unreachable.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('507') || errorStr.contains('insufficient storage')) {
|
if (errorStr.contains('507') || errorStr.contains('insufficient storage')) {
|
||||||
return 'Insufficient storage on server.';
|
return const _WebDavError(
|
||||||
|
code: 'webdav_insufficient_storage',
|
||||||
|
message: 'Insufficient storage on server.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Upload failed: ${error.toString()}';
|
return _WebDavError(
|
||||||
|
code: 'webdav_unknown',
|
||||||
|
message: 'Upload failed: ${error.toString()}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -508,6 +602,7 @@ class CloudUploadService {
|
|||||||
_currentServerUrl = null;
|
_currentServerUrl = null;
|
||||||
_currentUsername = null;
|
_currentUsername = null;
|
||||||
_currentPassword = null;
|
_currentPassword = null;
|
||||||
|
_currentAllowInsecureHttp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> clearSftpHostKey({required String serverUrl}) async {
|
Future<bool> clearSftpHostKey({required String serverUrl}) async {
|
||||||
|
|||||||
Reference in new issue
Block a user