Skip to content

Commit 8a309ca

Browse files
committed
feat(ux): change link conversion flow
now instead of selecting a destination platform, an may be get a not found error it fetches all the links for each supported platforms ans disables the platforms where the link was not found
1 parent ec3c7ea commit 8a309ca

File tree

7 files changed

+171
-139
lines changed

7 files changed

+171
-139
lines changed

lib/models/music_platform.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ enum MusicPlatform {
2626
}
2727

2828
extension MusicPlatformExtension on MusicPlatform {
29+
String get platformKey {
30+
switch (this) {
31+
case MusicPlatform.spotify:
32+
return 'spotify';
33+
case MusicPlatform.deezer:
34+
return 'deezer';
35+
case MusicPlatform.appleMusic:
36+
return 'appleMusic';
37+
case MusicPlatform.youtubeMusic:
38+
return 'youtubeMusic';
39+
case MusicPlatform.tidal:
40+
return 'tidal';
41+
case MusicPlatform.soundCloud:
42+
return 'soundCloud';
43+
case MusicPlatform.unknown:
44+
return '';
45+
}
46+
}
47+
2948
String get displayName {
3049
switch (this) {
3150
case MusicPlatform.spotify:

lib/pages/about_page.dart

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,22 +189,17 @@ class _AboutPageState extends State<AboutPage> {
189189
}
190190

191191
Widget _buildPlatformChips() {
192-
List<MusicPlatform> platforms = MusicPlatform
193-
.values
194-
.where((mp) => mp != MusicPlatform.unknown)
195-
.toList();
192+
List<MusicPlatform> platforms = MusicPlatform.values
193+
.where((mp) => mp != MusicPlatform.unknown)
194+
.toList();
196195

197196
return Wrap(
198197
spacing: 8.0,
199198
runSpacing: 8.0,
200199
children: platforms.map((platform) {
201200
return Chip(
202201
label: Text(platform.displayName),
203-
avatar: Image.asset(
204-
platform.logo,
205-
width: 16,
206-
height: 16,
207-
),
202+
avatar: Image.asset(platform.logo, width: 16, height: 16),
208203
);
209204
}).toList(),
210205
);

lib/pages/conversion_page.dart

Lines changed: 115 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import '../models/music_link.dart';
2424
import '../models/music_platform.dart';
2525
import '../pages/home_page.dart';
2626
import '../services/music_converter_service.dart';
27+
import '../services/odesli_service.dart';
2728
import '../services/rate_limiter_service.dart';
2829
import '../utils/ui_helpers.dart';
2930
import '../widgets/platform_card.dart';
@@ -40,7 +41,11 @@ class ConversionPage extends StatefulWidget {
4041

4142
class _ConversionPageState extends State<ConversionPage> {
4243
final MusicConverterService _converterService = MusicConverterService();
43-
bool _isConverting = false;
44+
45+
bool _isConverting = true;
46+
47+
OdesliResult? _conversionResult;
48+
String? _error;
4449

4550
List<MusicPlatform> get availablePlatforms {
4651
return MusicPlatform.values
@@ -52,44 +57,33 @@ class _ConversionPageState extends State<ConversionPage> {
5257
.toList();
5358
}
5459

55-
Future<void> _convertToPlatform(
56-
BuildContext context,
57-
MusicPlatform targetPlatform,
58-
) async {
59-
if (_isConverting) return;
60+
bool _isPlatformAvailable(MusicPlatform platform) {
61+
if (_conversionResult == null) return false;
6062

61-
setState(() {
62-
_isConverting = true;
63-
});
63+
return _conversionResult!.platformLinks[platform.platformKey] != null;
64+
}
6465

65-
final scaffoldMessenger = ScaffoldMessenger.of(context);
66+
String? _getPlatformUrl(MusicPlatform platform) {
67+
if (_conversionResult == null) return null;
6668

69+
return _conversionResult!.platformLinks[platform.platformKey];
70+
}
71+
72+
Future<void> _loadConversions() async {
6773
try {
68-
final result = await _converterService.convert(
69-
widget.musicLink,
70-
targetPlatform,
71-
);
74+
final result = await _converterService.convert(widget.musicLink);
7275

7376
if (!mounted) return;
7477

7578
setState(() {
79+
_conversionResult = result;
7680
_isConverting = false;
7781
});
78-
79-
if (result.isSuccess) {
80-
_showConversionSuccessDialog(result, targetPlatform);
81-
} else {
82-
scaffoldMessenger.showSnackBar(
83-
SnackBar(
84-
content: Text(result.error ?? 'Conversion failed'),
85-
backgroundColor: Colors.red,
86-
),
87-
);
88-
}
8982
} on RateLimitException catch (e) {
9083
if (!mounted) return;
9184

9285
setState(() {
86+
_error = 'Rate limit exceeded. Please wait.';
9387
_isConverting = false;
9488
});
9589

@@ -98,7 +92,7 @@ class _ConversionPageState extends State<ConversionPage> {
9892
? 'Rate limit reached. Please wait $seconds seconds.'
9993
: 'Rate limit reached. Please wait ${(seconds / 60).ceil()} minute(s).';
10094

101-
scaffoldMessenger.showSnackBar(
95+
ScaffoldMessenger.of(context).showSnackBar(
10296
SnackBar(
10397
content: Column(
10498
mainAxisSize: MainAxisSize.min,
@@ -123,15 +117,43 @@ class _ConversionPageState extends State<ConversionPage> {
123117
if (!mounted) return;
124118

125119
setState(() {
120+
_error = e.toString();
126121
_isConverting = false;
127122
});
128123

129-
scaffoldMessenger.showSnackBar(
124+
ScaffoldMessenger.of(context).showSnackBar(
130125
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
131126
);
132127
}
133128
}
134129

130+
void _handlePlatformTap(MusicPlatform platform) {
131+
if (_conversionResult == null) return;
132+
133+
final url = _getPlatformUrl(platform);
134+
135+
if (url == null) {
136+
ScaffoldMessenger.of(context).showSnackBar(
137+
SnackBar(
138+
content: Text('Content not available on ${platform.displayName}'),
139+
backgroundColor: Colors.orange,
140+
),
141+
);
142+
return;
143+
}
144+
145+
_showConversionSuccessDialog(url, platform);
146+
}
147+
148+
@override
149+
void initState() {
150+
super.initState();
151+
152+
WidgetsBinding.instance.addPostFrameCallback((_) {
153+
_loadConversions();
154+
});
155+
}
156+
135157
@override
136158
Widget build(BuildContext context) {
137159
return Scaffold(
@@ -227,26 +249,63 @@ class _ConversionPageState extends State<ConversionPage> {
227249
const SizedBox(height: 20),
228250

229251
Expanded(
230-
child: AbsorbPointer(
231-
absorbing: _isConverting,
232-
child: Opacity(
233-
opacity: _isConverting ? 0.5 : 1.0,
234-
child: ListView.builder(
235-
itemCount: availablePlatforms.length,
236-
itemBuilder: (context, index) {
237-
final platform = availablePlatforms[index];
238-
return Padding(
239-
padding: const EdgeInsets.only(bottom: 12.0),
240-
child: PlatformCard(
241-
platform: platform,
242-
onTap: () =>
243-
_convertToPlatform(context, platform),
252+
child: _error != null
253+
? Center(
254+
child: Column(
255+
mainAxisSize: MainAxisSize.min,
256+
children: [
257+
const Icon(
258+
Icons.error_outline,
259+
size: 48,
260+
color: Colors.red,
261+
),
262+
const SizedBox(height: 16),
263+
Text(
264+
'Failed to load conversions',
265+
style: const TextStyle(
266+
fontSize: 16,
267+
fontWeight: FontWeight.w500,
268+
),
269+
),
270+
const SizedBox(height: 8),
271+
Text(
272+
_error!,
273+
style: const TextStyle(
274+
fontSize: 12,
275+
color: Colors.grey,
276+
),
277+
textAlign: TextAlign.center,
278+
),
279+
],
280+
),
281+
)
282+
: AbsorbPointer(
283+
absorbing: _isConverting,
284+
child: Opacity(
285+
opacity: _isConverting ? 0.5 : 1.0,
286+
child: ListView.builder(
287+
itemCount: availablePlatforms.length,
288+
itemBuilder: (context, index) {
289+
final platform = availablePlatforms[index];
290+
final isAvailable = _isPlatformAvailable(
291+
platform,
292+
);
293+
return Padding(
294+
padding: const EdgeInsets.only(bottom: 12.0),
295+
child: Opacity(
296+
opacity: _isConverting || !isAvailable
297+
? 0.5
298+
: 1.0,
299+
child: PlatformCard(
300+
platform: platform,
301+
onTap: () => _handlePlatformTap(platform),
302+
),
303+
),
304+
);
305+
},
244306
),
245-
);
246-
},
247-
),
248-
),
249-
),
307+
),
308+
),
250309
),
251310
],
252311
),
@@ -277,10 +336,7 @@ class _ConversionPageState extends State<ConversionPage> {
277336
);
278337
}
279338

280-
void _showConversionSuccessDialog(
281-
ConversionResult result,
282-
MusicPlatform targetPlatform,
283-
) {
339+
void _showConversionSuccessDialog(String url, MusicPlatform targetPlatform) {
284340
showDialog(
285341
context: context,
286342
builder: (dialogContext) => AlertDialog(
@@ -289,21 +345,21 @@ class _ConversionPageState extends State<ConversionPage> {
289345
mainAxisSize: MainAxisSize.min,
290346
crossAxisAlignment: CrossAxisAlignment.start,
291347
children: [
292-
if (result.metadata != null) ...[
348+
if (_conversionResult?.metadata != null) ...[
293349
Text(
294-
result.metadata!.title,
350+
_conversionResult!.metadata!.title,
295351
style: const TextStyle(
296352
fontWeight: FontWeight.bold,
297353
fontSize: 16,
298354
),
299355
),
300-
Text(result.metadata!.artist),
356+
Text(_conversionResult!.metadata!.artist),
301357
const SizedBox(height: 16),
302358
],
303359
Text('Converted to ${targetPlatform.displayName}'),
304360
const SizedBox(height: 8),
305361
SelectableText(
306-
result.url!,
362+
url,
307363
style: const TextStyle(fontSize: 12, color: Colors.grey),
308364
),
309365
],
@@ -318,9 +374,9 @@ class _ConversionPageState extends State<ConversionPage> {
318374
Navigator.pop(dialogContext);
319375

320376
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
321-
await _shareLink(result);
377+
await _shareLink(url);
322378
} else {
323-
await _copyLink(result);
379+
await _copyLink(url);
324380
}
325381
},
326382
icon: Icon(
@@ -339,11 +395,9 @@ class _ConversionPageState extends State<ConversionPage> {
339395
);
340396
}
341397

342-
Future<void> _shareLink(ConversionResult result) async {
343-
if (result.url == null) return;
344-
398+
Future<void> _shareLink(String url) async {
345399
try {
346-
await SharePlus.instance.share(ShareParams(text: result.url));
400+
await SharePlus.instance.share(ShareParams(text: url));
347401

348402
if (!mounted) return;
349403

@@ -363,11 +417,7 @@ class _ConversionPageState extends State<ConversionPage> {
363417
}
364418
}
365419

366-
Future<void> _copyLink(ConversionResult result) async {
367-
final url = result.url;
368-
369-
if (url == null) return;
370-
420+
Future<void> _copyLink(String url) async {
371421
try {
372422
await Clipboard.setData(ClipboardData(text: url));
373423

0 commit comments

Comments
 (0)