diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9871253e..08fa7c13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -388,8 +388,6 @@ jobs: ### Installation **Android**: Enable "Install from unknown sources" and install the APK **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA - - ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) FOOTER echo "Release body:" @@ -399,7 +397,7 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.get-version.outputs.version }} - name: SpotiFLAC ${{ needs.get-version.outputs.version }} + name: SpotiFLAC-Mobile ${{ needs.get-version.outputs.version }} body_path: /tmp/release_body.txt files: ./release/* draft: false @@ -565,7 +563,7 @@ jobs: curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ -F chat_id="${TELEGRAM_CHANNEL_ID}" \ -F document=@"${ARM64_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" + -F caption="SpotiFLAC Mobile ${VERSION} - arm64 (recommended)" fi # Upload arm32 APK to channel @@ -574,7 +572,7 @@ jobs: curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ -F chat_id="${TELEGRAM_CHANNEL_ID}" \ -F document=@"${ARM32_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm32" + -F caption="SpotiFLAC Mobile ${VERSION} - arm32" fi # Upload iOS IPA to channel @@ -584,7 +582,7 @@ jobs: curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ -F chat_id="${TELEGRAM_CHANNEL_ID}" \ -F document=@"${IOS_IPA}" \ - -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" + -F caption="SpotiFLAC Mobile ${VERSION} - iOS (unsigned, sideload required)" fi echo "Telegram notification sent!" diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 688d4baa..a7fd32da 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -3200,6 +3200,68 @@ class _QueueTabState extends ConsumerState { } } + Future _showDownloadErrorDialog( + BuildContext context, + DownloadItem item, + ) async { + final colorScheme = Theme.of(context).colorScheme; + final isRateLimit = item.errorType == DownloadErrorType.rateLimit; + final title = isRateLimit + ? context.l10n.queueRateLimitTitle + : context.l10n.updateDownloadFailed; + final message = isRateLimit + ? context.l10n.queueRateLimitMessage + : (item.errorMessage.trim().isNotEmpty + ? item.errorMessage + : context.l10n.updateDownloadFailed); + final action = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(title), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + item.track.name, + style: Theme.of( + ctx, + ).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 10), + SelectableText( + message, + style: Theme.of(ctx).textTheme.bodyMedium, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop('remove'), + style: TextButton.styleFrom(foregroundColor: colorScheme.error), + child: Text(context.l10n.dialogRemove), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: Text(context.l10n.dialogCancel), + ), + FilledButton( + onPressed: () => Navigator.of(ctx).pop('retry'), + child: Text(context.l10n.dialogRetry), + ), + ], + ), + ); + if (!mounted) return; + if (action == 'retry') { + ref.read(downloadQueueProvider.notifier).retryItem(item.id); + } else if (action == 'remove') { + ref.read(downloadQueueProvider.notifier).removeItem(item.id); + } + } + Widget _buildDownloadGridItem( BuildContext context, DownloadItem item, @@ -3227,7 +3289,9 @@ class _QueueTabState extends ConsumerState { child: Icon(Icons.music_note, color: colorScheme.onSurfaceVariant), ); - final onTap = isFailed || item.status == DownloadStatus.skipped + final onTap = isFailed + ? () => _showDownloadErrorDialog(context, item) + : item.status == DownloadStatus.skipped ? () => ref.read(downloadQueueProvider.notifier).removeItem(item.id) : () => _confirmCancelDownload(context, item); @@ -6068,7 +6132,11 @@ class _QueueTabState extends ConsumerState { margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), clipBehavior: Clip.antiAlias, child: InkWell( - onTap: isCompleted ? () => _navigateToMetadataScreen(item) : null, + onTap: isCompleted + ? () => _navigateToMetadataScreen(item) + : item.status == DownloadStatus.failed + ? () => _showDownloadErrorDialog(context, item) + : null, borderRadius: BorderRadius.circular(12), child: Stack( children: [