Skip to content

Commit b2d6a64

Browse files
committed
feat: Add file picker support and implement radio station functionality
- Updated GeneratedPluginRegistrant.swift to include file_picker plugin. - Modified pubspec.lock to add dependencies for file_picker and cross_file. - Updated pubspec.yaml to specify file_picker version. - Created RadioStation model to represent internet radio stations. - Implemented LibrarySearchDelegate for searching through playlists. - Developed RadioScreen to display and manage internet radio stations. - Added PlayerUiSettingsService for managing player UI settings. - Implemented ReplayGainService for volume normalization based on ReplayGain metadata. - Created NavigationHelper for nested navigation support in the app.
1 parent aa7f5ef commit b2d6a64

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4134
-549
lines changed

lib/models/models.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export 'song.dart';
44
export 'playlist.dart';
55
export 'server_config.dart';
66
export 'music_folder.dart';
7+
export 'radio_station.dart';

lib/models/radio_station.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// Represents an internet radio station from the Subsonic/Navidrome server.
2+
class RadioStation {
3+
final String id;
4+
final String name;
5+
final String streamUrl;
6+
final String? homePageUrl;
7+
8+
RadioStation({
9+
required this.id,
10+
required this.name,
11+
required this.streamUrl,
12+
this.homePageUrl,
13+
});
14+
15+
factory RadioStation.fromJson(Map<String, dynamic> json) {
16+
return RadioStation(
17+
id: json['id']?.toString() ?? '',
18+
name: json['name'] ?? 'Unknown Station',
19+
streamUrl: json['streamUrl'] ?? '',
20+
homePageUrl: json['homePageUrl']?.toString(),
21+
);
22+
}
23+
24+
Map<String, dynamic> toJson() {
25+
return {
26+
'id': id,
27+
'name': name,
28+
'streamUrl': streamUrl,
29+
'homePageUrl': homePageUrl,
30+
};
31+
}
32+
33+
@override
34+
bool operator ==(Object other) =>
35+
identical(this, other) ||
36+
other is RadioStation &&
37+
runtimeType == other.runtimeType &&
38+
id == other.id;
39+
40+
@override
41+
int get hashCode => id.hashCode;
42+
}

lib/models/server_config.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@ class ServerConfig {
33
final String username;
44
final String password;
55
final bool useLegacyAuth;
6+
final bool allowSelfSignedCertificates;
67
final List<String> selectedMusicFolderIds;
78
final String? serverType;
89
final String? serverVersion;
10+
final String? customCertificatePath; // Path to custom certificate file
911

1012
ServerConfig({
1113
required this.serverUrl,
1214
required this.username,
1315
required this.password,
1416
this.useLegacyAuth = false,
17+
this.allowSelfSignedCertificates = false,
1518
this.selectedMusicFolderIds = const [],
1619
this.serverType,
1720
this.serverVersion,
21+
this.customCertificatePath,
1822
});
1923

2024
factory ServerConfig.fromJson(Map<String, dynamic> json) {
@@ -23,13 +27,15 @@ class ServerConfig {
2327
username: json['username'] ?? '',
2428
password: json['password'] ?? '',
2529
useLegacyAuth: json['useLegacyAuth'] ?? false,
30+
allowSelfSignedCertificates: json['allowSelfSignedCertificates'] ?? false,
2631
selectedMusicFolderIds:
2732
(json['selectedMusicFolderIds'] as List<dynamic>?)
2833
?.map((e) => e.toString())
2934
.toList() ??
3035
[],
3136
serverType: json['serverType'],
3237
serverVersion: json['serverVersion'],
38+
customCertificatePath: json['customCertificatePath'],
3339
);
3440
}
3541

@@ -39,9 +45,11 @@ class ServerConfig {
3945
'username': username,
4046
'password': password,
4147
'useLegacyAuth': useLegacyAuth,
48+
'allowSelfSignedCertificates': allowSelfSignedCertificates,
4249
'selectedMusicFolderIds': selectedMusicFolderIds,
4350
'serverType': serverType,
4451
'serverVersion': serverVersion,
52+
'customCertificatePath': customCertificatePath,
4553
};
4654
}
4755

@@ -50,19 +58,25 @@ class ServerConfig {
5058
String? username,
5159
String? password,
5260
bool? useLegacyAuth,
61+
bool? allowSelfSignedCertificates,
5362
List<String>? selectedMusicFolderIds,
5463
String? serverType,
5564
String? serverVersion,
65+
String? customCertificatePath,
5666
}) {
5767
return ServerConfig(
5868
serverUrl: serverUrl ?? this.serverUrl,
5969
username: username ?? this.username,
6070
password: password ?? this.password,
6171
useLegacyAuth: useLegacyAuth ?? this.useLegacyAuth,
72+
allowSelfSignedCertificates:
73+
allowSelfSignedCertificates ?? this.allowSelfSignedCertificates,
6274
selectedMusicFolderIds:
6375
selectedMusicFolderIds ?? this.selectedMusicFolderIds,
6476
serverType: serverType ?? this.serverType,
6577
serverVersion: serverVersion ?? this.serverVersion,
78+
customCertificatePath:
79+
customCertificatePath ?? this.customCertificatePath,
6680
);
6781
}
6882

lib/models/song.dart

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class Song {
1616
final int? size;
1717
final String? path;
1818
final bool? starred;
19+
final double? replayGainTrackGain;
20+
final double? replayGainAlbumGain;
21+
final double? replayGainTrackPeak;
22+
final double? replayGainAlbumPeak;
1923

2024
Song({
2125
required this.id,
@@ -35,9 +39,16 @@ class Song {
3539
this.size,
3640
this.path,
3741
this.starred,
42+
this.replayGainTrackGain,
43+
this.replayGainAlbumGain,
44+
this.replayGainTrackPeak,
45+
this.replayGainAlbumPeak,
3846
});
3947

4048
factory Song.fromJson(Map<String, dynamic> json) {
49+
// Parse ReplayGain data - Subsonic API provides it in a nested 'replayGain' object
50+
final replayGain = json['replayGain'] as Map<String, dynamic>?;
51+
4152
return Song(
4253
id: json['id']?.toString() ?? '',
4354
title: json['title'] ?? 'Unknown Title',
@@ -56,6 +67,10 @@ class Song {
5667
size: json['size'] as int?,
5768
path: json['path']?.toString(),
5869
starred: json['starred'] != null ? true : false,
70+
replayGainTrackGain: (replayGain?['trackGain'] as num?)?.toDouble(),
71+
replayGainAlbumGain: (replayGain?['albumGain'] as num?)?.toDouble(),
72+
replayGainTrackPeak: (replayGain?['trackPeak'] as num?)?.toDouble(),
73+
replayGainAlbumPeak: (replayGain?['albumPeak'] as num?)?.toDouble(),
5974
);
6075
}
6176

@@ -77,6 +92,12 @@ class Song {
7792
'contentType': contentType,
7893
'size': size,
7994
'path': path,
95+
'replayGain': {
96+
if (replayGainTrackGain != null) 'trackGain': replayGainTrackGain,
97+
if (replayGainAlbumGain != null) 'albumGain': replayGainAlbumGain,
98+
if (replayGainTrackPeak != null) 'trackPeak': replayGainTrackPeak,
99+
if (replayGainAlbumPeak != null) 'albumPeak': replayGainAlbumPeak,
100+
},
80101
};
81102
}
82103

@@ -105,6 +126,10 @@ class Song {
105126
int? size,
106127
String? path,
107128
bool? starred,
129+
double? replayGainTrackGain,
130+
double? replayGainAlbumGain,
131+
double? replayGainTrackPeak,
132+
double? replayGainAlbumPeak,
108133
}) {
109134
return Song(
110135
id: id ?? this.id,
@@ -124,6 +149,10 @@ class Song {
124149
size: size ?? this.size,
125150
path: path ?? this.path,
126151
starred: starred ?? this.starred,
152+
replayGainTrackGain: replayGainTrackGain ?? this.replayGainTrackGain,
153+
replayGainAlbumGain: replayGainAlbumGain ?? this.replayGainAlbumGain,
154+
replayGainTrackPeak: replayGainTrackPeak ?? this.replayGainTrackPeak,
155+
replayGainAlbumPeak: replayGainAlbumPeak ?? this.replayGainAlbumPeak,
127156
);
128157
}
129-
}
158+
}

lib/providers/auth_provider.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ class AuthProvider extends ChangeNotifier {
6969
required String username,
7070
required String password,
7171
bool useLegacyAuth = false,
72+
bool allowSelfSignedCertificates = false,
73+
String? customCertificatePath,
7274
}) async {
7375
_state = AuthState.authenticating;
7476
_error = null;
@@ -79,6 +81,8 @@ class AuthProvider extends ChangeNotifier {
7981
username: username,
8082
password: password,
8183
useLegacyAuth: useLegacyAuth,
84+
allowSelfSignedCertificates: allowSelfSignedCertificates,
85+
customCertificatePath: customCertificatePath,
8286
);
8387

8488
_subsonicService.configure(config);
@@ -116,7 +120,7 @@ class AuthProvider extends ChangeNotifier {
116120
return 'Cannot connect to server. Check the URL and your internet connection.';
117121
} else if (errorStr.contains('HandshakeException') ||
118122
errorStr.contains('CERTIFICATE_VERIFY_FAILED')) {
119-
return 'SSL certificate error. Try using http:// instead of https://';
123+
return 'SSL certificate error. Enable "Allow Self-Signed Certificates" for custom CA servers.';
120124
} else if (errorStr.contains('TimeoutException')) {
121125
return 'Connection timed out. Check your server URL.';
122126
} else if (errorStr.contains('FormatException')) {

0 commit comments

Comments
 (0)