Skip to content

Commit 5e4a22a

Browse files
feat: customizable download location, UI changes, queue blur, use http if https fails, ios app name
1 parent da9eb38 commit 5e4a22a

39 files changed

Lines changed: 1319 additions & 196 deletions

ios/Podfile.lock

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,42 @@ PODS:
88
- Flutter
99
- connectivity_plus (0.0.1):
1010
- Flutter
11+
- disk_space_plus (0.0.1):
12+
- Flutter
13+
- DKImagePickerController/Core (4.3.9):
14+
- DKImagePickerController/ImageDataManager
15+
- DKImagePickerController/Resource
16+
- DKImagePickerController/ImageDataManager (4.3.9)
17+
- DKImagePickerController/PhotoGallery (4.3.9):
18+
- DKImagePickerController/Core
19+
- DKPhotoGallery
20+
- DKImagePickerController/Resource (4.3.9)
21+
- DKPhotoGallery (0.0.19):
22+
- DKPhotoGallery/Core (= 0.0.19)
23+
- DKPhotoGallery/Model (= 0.0.19)
24+
- DKPhotoGallery/Preview (= 0.0.19)
25+
- DKPhotoGallery/Resource (= 0.0.19)
26+
- SDWebImage
27+
- SwiftyGif
28+
- DKPhotoGallery/Core (0.0.19):
29+
- DKPhotoGallery/Model
30+
- DKPhotoGallery/Preview
31+
- SDWebImage
32+
- SwiftyGif
33+
- DKPhotoGallery/Model (0.0.19):
34+
- SDWebImage
35+
- SwiftyGif
36+
- DKPhotoGallery/Preview (0.0.19):
37+
- DKPhotoGallery/Model
38+
- DKPhotoGallery/Resource
39+
- SDWebImage
40+
- SwiftyGif
41+
- DKPhotoGallery/Resource (0.0.19):
42+
- SDWebImage
43+
- SwiftyGif
44+
- file_picker (0.0.1):
45+
- DKImagePickerController/PhotoGallery
46+
- Flutter
1147
- Flutter (1.0.0)
1248
- flutter_discord_rpc (0.0.1):
1349
- Flutter
@@ -20,6 +56,9 @@ PODS:
2056
- path_provider_foundation (0.0.1):
2157
- Flutter
2258
- FlutterMacOS
59+
- SDWebImage (5.21.3):
60+
- SDWebImage/Core (= 5.21.3)
61+
- SDWebImage/Core (5.21.3)
2362
- shared_preferences_foundation (0.0.1):
2463
- Flutter
2564
- FlutterMacOS
@@ -51,6 +90,7 @@ PODS:
5190
- sqlite3/perf-threadsafe
5291
- sqlite3/rtree
5392
- sqlite3/session
93+
- SwiftyGif (5.4.5)
5494
- url_launcher_ios (0.0.1):
5595
- Flutter
5696

@@ -59,6 +99,8 @@ DEPENDENCIES:
5999
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
60100
- audio_session (from `.symlinks/plugins/audio_session/ios`)
61101
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
102+
- disk_space_plus (from `.symlinks/plugins/disk_space_plus/ios`)
103+
- file_picker (from `.symlinks/plugins/file_picker/ios`)
62104
- Flutter (from `Flutter`)
63105
- flutter_discord_rpc (from `.symlinks/plugins/flutter_discord_rpc/ios`)
64106
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
@@ -72,7 +114,11 @@ DEPENDENCIES:
72114

73115
SPEC REPOS:
74116
trunk:
117+
- DKImagePickerController
118+
- DKPhotoGallery
119+
- SDWebImage
75120
- sqlite3
121+
- SwiftyGif
76122

77123
EXTERNAL SOURCES:
78124
app_links:
@@ -83,6 +129,10 @@ EXTERNAL SOURCES:
83129
:path: ".symlinks/plugins/audio_session/ios"
84130
connectivity_plus:
85131
:path: ".symlinks/plugins/connectivity_plus/ios"
132+
disk_space_plus:
133+
:path: ".symlinks/plugins/disk_space_plus/ios"
134+
file_picker:
135+
:path: ".symlinks/plugins/file_picker/ios"
86136
Flutter:
87137
:path: Flutter
88138
flutter_discord_rpc:
@@ -109,16 +159,22 @@ SPEC CHECKSUMS:
109159
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
110160
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
111161
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
162+
disk_space_plus: a36391fb5f732dbdd29628b0bac5a1acbd43aaef
163+
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
164+
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
165+
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
112166
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
113167
flutter_discord_rpc: 0572e8227ea730c5afe5876a37c08c728ce95f3a
114168
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
115169
flutter_soloud: 272cc604453b6ca135f274045fb259e7c6426122
116170
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
117171
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
172+
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
118173
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
119174
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
120175
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
121176
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
177+
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
122178
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
123179

124180
PODFILE CHECKSUM: fd33f5f1975d674cca43343bc22b2937f97c0906

lib/api/auth_api.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:dio/dio.dart';
22
import 'package:distributeapp/model/user.dart';
3+
import 'package:distributeapp/model/dto/login_response.dart';
34

45
class AuthAPI {
56
final Dio client;
@@ -16,16 +17,18 @@ class AuthAPI {
1617
data: {'username': username, 'password': password},
1718
);
1819

19-
final rootFolderId = response.data['user']['root_folder_id'] as String?;
20+
final loginResponse = LoginResponse.fromJson(response.data);
21+
final userData = loginResponse.user;
2022

2123
final user = LoggedInUser(
22-
username: response.data['username'] as String,
23-
token: response.data['token'] as String,
24-
id: response.data['id'] as String,
25-
rootFolderId: rootFolderId,
24+
username: loginResponse.username,
25+
token: loginResponse.token,
26+
id: loginResponse.id,
27+
rootFolderId: userData.rootFolderId,
28+
isAdmin: userData.isAdmin,
2629
);
2730

28-
return (user: user, rootFolderId: rootFolderId);
31+
return (user: user, rootFolderId: userData.rootFolderId);
2932
} on DioException catch (e) {
3033
if (e.response?.statusCode == 401) {
3134
throw Exception('Invalid username or password');

lib/api/download_api.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import 'dart:io';
22

3+
import 'package:distributeapp/core/preferences/settings_repository.dart';
4+
35
import 'package:dio/dio.dart';
46
import 'package:distributeapp/model/song.dart';
57

68
class DownloadApi {
79
final Dio client;
8-
final String appDataPath;
10+
final SettingsRepository settings;
11+
12+
DownloadApi({required this.client, required this.settings});
913

10-
DownloadApi({required this.client, required this.appDataPath});
14+
String get _rootPath => settings.rootPath;
1115

1216
Future<void> downloadFile(
1317
Song song,
@@ -16,7 +20,7 @@ class DownloadApi {
1620
final fileID = song.fileId;
1721
final url = '/api/songs/download/$fileID';
1822

19-
final tempFile = File('${song.localPath(appDataPath)}.tmp');
23+
final tempFile = File('${song.localPath(_rootPath)}.tmp');
2024
await tempFile.parent.create(recursive: true);
2125

2226
await client.download(
@@ -25,12 +29,12 @@ class DownloadApi {
2529
onReceiveProgress: onReceiveProgress,
2630
);
2731
if (await tempFile.exists()) {
28-
await tempFile.rename(song.localPath(appDataPath));
32+
await tempFile.rename(song.localPath(_rootPath));
2933
}
3034
}
3135

3236
Future<void> deleteFile(Song song) async {
33-
final fileObj = File(song.localPath(appDataPath));
37+
final fileObj = File(song.localPath(_rootPath));
3438
if (await fileObj.exists()) {
3539
await fileObj.delete();
3640
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'dart:io';
2+
import 'package:path/path.dart' as p;
3+
import 'package:distributeapp/core/preferences/settings_cubit.dart';
4+
import 'package:distributeapp/repositories/storage_repository.dart';
5+
import 'package:distributeapp/core/artwork/artwork_repository.dart';
6+
import 'package:distributeapp/repositories/audio/music_player_controller.dart';
7+
import 'package:flutter_bloc/flutter_bloc.dart';
8+
import 'package:freezed_annotation/freezed_annotation.dart';
9+
10+
part 'storage_state.dart';
11+
part 'storage_cubit.freezed.dart';
12+
13+
class StorageCubit extends Cubit<StorageState> {
14+
final StorageRepository _storageRepository;
15+
final SettingsCubit _settingsCubit;
16+
final ArtworkRepository _artworkRepository;
17+
final MusicPlayerController _musicPlayerController;
18+
19+
StorageCubit({
20+
required StorageRepository storageRepository,
21+
required SettingsCubit settingsCubit,
22+
required ArtworkRepository artworkRepository,
23+
required MusicPlayerController musicPlayerController,
24+
}) : _storageRepository = storageRepository,
25+
_settingsCubit = settingsCubit,
26+
_artworkRepository = artworkRepository,
27+
_musicPlayerController = musicPlayerController,
28+
super(const StorageState.initial());
29+
30+
Future<void> requestMove(String selectedPath) async {
31+
String newPath = selectedPath;
32+
33+
if (newPath.endsWith(Platform.pathSeparator)) {
34+
newPath = newPath.substring(0, newPath.length - 1);
35+
}
36+
37+
final String paramName = newPath.split(Platform.pathSeparator).last;
38+
if (paramName != 'Distribute') {
39+
newPath = '$newPath${Platform.pathSeparator}Distribute';
40+
}
41+
42+
emit(const StorageState.checking());
43+
44+
try {
45+
final oldRootPath = _settingsCubit.state.rootPath;
46+
47+
if (p.equals(oldRootPath, newPath)) {
48+
emit(
49+
const StorageState.error(
50+
"Selected location is the same as the current one.",
51+
),
52+
);
53+
return;
54+
}
55+
56+
if (p.isWithin(oldRootPath, newPath)) {
57+
emit(
58+
const StorageState.error(
59+
"New location cannot be a subdirectory of the current one as it would cause issues during file transfer.",
60+
),
61+
);
62+
return;
63+
}
64+
65+
final freeSpace = await _storageRepository.getFreeSpaceInBytes(newPath);
66+
67+
final oldDir = Directory(oldRootPath);
68+
final requiredBytes = await _storageRepository.calculateDirectorySize(
69+
oldDir,
70+
);
71+
72+
if (freeSpace < requiredBytes) {
73+
emit(
74+
StorageState.insufficientSpace(
75+
requiredBytes: requiredBytes,
76+
availableBytes: freeSpace,
77+
pendingPath: newPath,
78+
),
79+
);
80+
} else {
81+
await _startTransfer(oldRootPath, newPath);
82+
}
83+
} catch (e) {
84+
emit(StorageState.error(e.toString()));
85+
}
86+
}
87+
88+
Future<void> confirmMoveWithoutTransfer(String newPath) async {
89+
await _settingsCubit.setCustomDownloadPath(newPath);
90+
_artworkRepository.clearCache();
91+
_musicPlayerController.clearCache();
92+
emit(const StorageState.success());
93+
}
94+
95+
Future<void> startTransfer(String newPath) async {
96+
final oldRootPath = _settingsCubit.state.rootPath;
97+
await _startTransfer(oldRootPath, newPath);
98+
}
99+
100+
Future<void> _startTransfer(String oldPath, String newPath) async {
101+
emit(const StorageState.transferring(0.0));
102+
103+
try {
104+
if (oldPath == newPath) {
105+
emit(const StorageState.success());
106+
return;
107+
}
108+
109+
await for (final progress in _storageRepository.moveFiles(
110+
oldPath: oldPath,
111+
newPath: newPath,
112+
)) {
113+
emit(StorageState.transferring(progress));
114+
}
115+
116+
await _settingsCubit.setCustomDownloadPath(newPath);
117+
_artworkRepository.clearCache();
118+
_musicPlayerController.clearCache();
119+
emit(const StorageState.success());
120+
} catch (e) {
121+
emit(StorageState.error(e.toString()));
122+
}
123+
}
124+
125+
void reset() {
126+
emit(const StorageState.initial());
127+
}
128+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
part of 'storage_cubit.dart';
2+
3+
@freezed
4+
class StorageState with _$StorageState {
5+
const factory StorageState.initial() = _Initial;
6+
const factory StorageState.checking() = _Checking;
7+
const factory StorageState.insufficientSpace({
8+
required int requiredBytes,
9+
required int availableBytes,
10+
required String pendingPath,
11+
}) = _InsufficientSpace;
12+
const factory StorageState.transferring(double progress) = _Transferring;
13+
const factory StorageState.success() = _Success;
14+
const factory StorageState.error(String message) = _Error;
15+
}

lib/components/song_list_tile.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class SongListTile extends StatelessWidget {
3636
),
3737
onTap: onAdd ?? onTap,
3838
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
39+
dense: true,
3940
);
4041
}
4142
}

lib/core/artwork/artwork_repository.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'dart:io';
2+
3+
import 'package:distributeapp/core/preferences/settings_repository.dart';
24
import 'package:dio/dio.dart';
35
import 'package:flutter/material.dart';
46
import 'dart:convert';
@@ -38,9 +40,14 @@ enum ArtQuality { hq, lq }
3840
class ArtworkRepository {
3941
final Map<String, ArtworkData> _memoryCache = {};
4042
final Dio client;
41-
final String appDataPath;
43+
final SettingsRepository settings;
44+
45+
ArtworkRepository(this.client, {required this.settings});
4246

43-
ArtworkRepository(this.client, this.appDataPath);
47+
void clearCache() {
48+
_memoryCache.clear();
49+
_pendingDownloads.clear();
50+
}
4451

4552
final Map<String, Future<File>> _pendingDownloads = {};
4653

@@ -155,6 +162,7 @@ class ArtworkRepository {
155162
}
156163

157164
String getAbsolutePath(String albumId, ArtQuality quality) {
158-
return '$appDataPath/${getRelativePath(albumId, quality)}';
165+
final root = settings.rootPath;
166+
return '$root/${getRelativePath(albumId, quality)}';
159167
}
160168
}

0 commit comments

Comments
 (0)