feat: WiFi-only download mode

- Add network mode setting (any/wifi_only) in settings model
- Add connectivity check in download queue processor
- Downloads pause automatically when on mobile data (if wifi_only enabled)
- Add UI toggle in Download Settings page
- Add localization strings for network mode setting
- Bump version to 3.3.6+71
This commit is contained in:
zarzet
2026-02-02 07:13:03 +07:00
parent 107d9ca007
commit 5e19178bc0
24 changed files with 377 additions and 5 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## [3.3.6] - 2026-02-02
### Added
- **WiFi-Only Download Mode**: Option to pause downloads when not connected to WiFi
- New setting in Settings > Download > Download Network
- Choose between "WiFi + Mobile Data" or "WiFi Only"
- Downloads automatically pause on mobile data and resume on WiFi
### Dependencies
- Added `connectivity_plus: ^6.0.3` for network state detection
---
## [3.3.5] - 2026-02-01
Same as 3.3.1 but fixes crash issues caused by FFmpeg.
+2 -2
View File
@@ -1,8 +1,8 @@
/// App version and info constants
/// Update version here only - all other files will reference this
class AppInfo {
static const String version = '3.3.5';
static const String buildNumber = '70';
static const String version = '3.3.6';
static const String buildNumber = '71';
static const String fullVersion = '$version+$buildNumber';
+24
View File
@@ -3688,6 +3688,30 @@ abstract class AppLocalizations {
/// **'Save failed downloads to TXT file automatically'**
String get settingsAutoExportFailedSubtitle;
/// Setting for network type preference
///
/// In en, this message translates to:
/// **'Download Network'**
String get settingsDownloadNetwork;
/// Network option - use any connection
///
/// In en, this message translates to:
/// **'WiFi + Mobile Data'**
String get settingsDownloadNetworkAny;
/// Network option - only use WiFi
///
/// In en, this message translates to:
/// **'WiFi Only'**
String get settingsDownloadNetworkWifiOnly;
/// Subtitle explaining network preference
///
/// In en, this message translates to:
/// **'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.'**
String get settingsDownloadNetworkSubtitle;
/// Empty queue state title
///
/// In en, this message translates to:
+13
View File
@@ -2025,6 +2025,19 @@ class AppLocalizationsDe extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsEs extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsFr extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsHi extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2023,6 +2023,19 @@ class AppLocalizationsId extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'Tidak ada unduhan dalam antrian';
+13
View File
@@ -1997,6 +1997,19 @@ class AppLocalizationsJa extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'キューにダウンロードがありません';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsKo extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsNl extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsPt extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2049,6 +2049,19 @@ class AppLocalizationsRu extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'Нет загрузок в очереди';
+13
View File
@@ -2025,6 +2025,19 @@ class AppLocalizationsTr extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+13
View File
@@ -2010,6 +2010,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get settingsAutoExportFailedSubtitle =>
'Save failed downloads to TXT file automatically';
@override
String get settingsDownloadNetwork => 'Download Network';
@override
String get settingsDownloadNetworkAny => 'WiFi + Mobile Data';
@override
String get settingsDownloadNetworkWifiOnly => 'WiFi Only';
@override
String get settingsDownloadNetworkSubtitle =>
'Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.';
@override
String get queueEmpty => 'No downloads in queue';
+10
View File
@@ -1476,6 +1476,16 @@
"@settingsAutoExportFailed": {"description": "Setting toggle for auto-export"},
"settingsAutoExportFailedSubtitle": "Save failed downloads to TXT file automatically",
"@settingsAutoExportFailedSubtitle": {"description": "Subtitle for auto-export setting"},
"settingsDownloadNetwork": "Download Network",
"@settingsDownloadNetwork": {"description": "Setting for network type preference"},
"settingsDownloadNetworkAny": "WiFi + Mobile Data",
"@settingsDownloadNetworkAny": {"description": "Network option - use any connection"},
"settingsDownloadNetworkWifiOnly": "WiFi Only",
"@settingsDownloadNetworkWifiOnly": {"description": "Network option - only use WiFi"},
"settingsDownloadNetworkSubtitle": "Choose which network to use for downloads. When set to WiFi Only, downloads will pause on mobile data.",
"@settingsDownloadNetworkSubtitle": {"description": "Subtitle explaining network preference"},
"queueEmpty": "No downloads in queue",
"@queueEmpty": {"description": "Empty queue state title"},
"queueEmptySubtitle": "Add tracks from the home screen",
+4
View File
@@ -35,6 +35,7 @@ class AppSettings {
final String tidalHighFormat; // Format for Tidal HIGH quality: 'mp3_320' or 'opus_128'
final bool useAllFilesAccess; // Android 13+ only: enable MANAGE_EXTERNAL_STORAGE
final bool autoExportFailedDownloads; // Auto export failed downloads to TXT file
final String downloadNetworkMode; // 'any' = WiFi + Mobile, 'wifi_only' = WiFi only
const AppSettings({
this.defaultService = 'tidal',
@@ -68,6 +69,7 @@ class AppSettings {
this.tidalHighFormat = 'mp3_320',
this.useAllFilesAccess = false,
this.autoExportFailedDownloads = false,
this.downloadNetworkMode = 'any',
});
AppSettings copyWith({
@@ -103,6 +105,7 @@ class AppSettings {
String? tidalHighFormat,
bool? useAllFilesAccess,
bool? autoExportFailedDownloads,
String? downloadNetworkMode,
}) {
return AppSettings(
defaultService: defaultService ?? this.defaultService,
@@ -136,6 +139,7 @@ class AppSettings {
tidalHighFormat: tidalHighFormat ?? this.tidalHighFormat,
useAllFilesAccess: useAllFilesAccess ?? this.useAllFilesAccess,
autoExportFailedDownloads: autoExportFailedDownloads ?? this.autoExportFailedDownloads,
downloadNetworkMode: downloadNetworkMode ?? this.downloadNetworkMode,
);
}
+2
View File
@@ -41,6 +41,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
useAllFilesAccess: json['useAllFilesAccess'] as bool? ?? false,
autoExportFailedDownloads:
json['autoExportFailedDownloads'] as bool? ?? false,
downloadNetworkMode: json['downloadNetworkMode'] as String? ?? 'any',
);
Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
@@ -76,4 +77,5 @@ Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
'tidalHighFormat': instance.tidalHighFormat,
'useAllFilesAccess': instance.useAllFilesAccess,
'autoExportFailedDownloads': instance.autoExportFailedDownloads,
'downloadNetworkMode': instance.downloadNetworkMode,
};
+14 -1
View File
@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:spotiflac_android/models/download_item.dart';
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/models/track.dart';
@@ -1597,9 +1598,21 @@ void removeItem(String id) {
}
}
Future<void> _processQueue() async {
Future<void> _processQueue() async {
if (state.isProcessing) return;
// Check network connectivity before starting
final settings = ref.read(settingsProvider);
if (settings.downloadNetworkMode == 'wifi_only') {
final connectivityResult = await Connectivity().checkConnectivity();
final hasWifi = connectivityResult.contains(ConnectivityResult.wifi);
if (!hasWifi) {
_log.w('WiFi-only mode enabled but no WiFi connection. Queue paused.');
state = state.copyWith(isProcessing: false, isPaused: true);
return;
}
}
state = state.copyWith(isProcessing: true);
_log.i('Starting queue processing...');
+5
View File
@@ -245,6 +245,11 @@ void setUseAllFilesAccess(bool enabled) {
state = state.copyWith(autoExportFailedDownloads: enabled);
_saveSettings();
}
void setDownloadNetworkMode(String mode) {
state = state.copyWith(downloadNetworkMode: mode);
_saveSettings();
}
}
final settingsProvider = NotifierProvider<SettingsNotifier, AppSettings>(
@@ -407,6 +407,49 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
),
),
const SliverToBoxAdapter(child: SizedBox(height: 16)),
// Download Network Mode
SliverToBoxAdapter(
child: SettingsGroup(
children: [
SettingsItem(
icon: Icons.wifi,
title: context.l10n.settingsDownloadNetwork,
subtitle: settings.downloadNetworkMode == 'wifi_only'
? context.l10n.settingsDownloadNetworkWifiOnly
: context.l10n.settingsDownloadNetworkAny,
onTap: () => _showNetworkModePicker(context, ref, settings.downloadNetworkMode),
showDivider: false,
),
],
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.info_outline,
size: 16,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Expanded(
child: Text(
context.l10n.settingsDownloadNetworkSubtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 32)),
],
),
@@ -959,6 +1002,68 @@ ListTile(
);
}
void _showNetworkModePicker(
BuildContext context,
WidgetRef ref,
String current,
) {
final colorScheme = Theme.of(context).colorScheme;
showModalBottomSheet(
context: context,
backgroundColor: colorScheme.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
builder: (context) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Text(
context.l10n.settingsDownloadNetwork,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
child: Text(
context.l10n.settingsDownloadNetworkSubtitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
ListTile(
leading: const Icon(Icons.signal_cellular_alt),
title: Text(context.l10n.settingsDownloadNetworkAny),
subtitle: const Text('WiFi + Mobile Data'),
trailing: current == 'any' ? Icon(Icons.check, color: colorScheme.primary) : null,
onTap: () {
ref.read(settingsProvider.notifier).setDownloadNetworkMode('any');
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.wifi),
title: Text(context.l10n.settingsDownloadNetworkWifiOnly),
subtitle: const Text('Pause downloads on mobile data'),
trailing: current == 'wifi_only' ? Icon(Icons.check, color: colorScheme.primary) : null,
onTap: () {
ref.read(settingsProvider.notifier).setDownloadNetworkMode('wifi_only');
Navigator.pop(context);
},
),
const SizedBox(height: 16),
],
),
),
);
}
void _showFolderOrganizationPicker(
BuildContext context,
WidgetRef ref,
+24
View File
@@ -185,6 +185,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
url: "https://pub.dev"
source: hosted
version: "6.1.5"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert:
dependency: transitive
description:
@@ -637,6 +653,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.6.1"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble:
dependency: transitive
description:
+3 -2
View File
@@ -1,7 +1,7 @@
name: spotiflac_android
description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music
publish_to: "none"
version: 3.3.5+70
version: 3.3.6+71
environment:
sdk: ^3.10.0
@@ -28,9 +28,10 @@ dependencies:
path: ^1.9.0
sqflite: ^2.4.1
# HTTP & Network
# HTTP & Network
http: ^1.6.0
dio: ^5.8.0
connectivity_plus: ^6.0.3
# UI Components
cupertino_icons: ^1.0.8