@@ -24,6 +24,7 @@ import '../models/music_link.dart';
2424import '../models/music_platform.dart' ;
2525import '../pages/home_page.dart' ;
2626import '../services/music_converter_service.dart' ;
27+ import '../services/odesli_service.dart' ;
2728import '../services/rate_limiter_service.dart' ;
2829import '../utils/ui_helpers.dart' ;
2930import '../widgets/platform_card.dart' ;
@@ -40,7 +41,11 @@ class ConversionPage extends StatefulWidget {
4041
4142class _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