import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/sticker.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/creators/stickers/stickers.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner /// Marketplace version of sticker pack detail page (no publisher dependency). /// Shows all stickers in the pack and provides a button to add the sticker. /// API interactions are intentionally left blank per request. @riverpod Future> marketplaceStickerPackContent( Ref ref, { required String packId, }) async { final apiClient = ref.watch(apiClientProvider); final resp = await apiClient.get('/sphere/stickers/$packId/content'); return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList(); } @riverpod Future marketplaceStickerPackOwnership( Ref ref, { required String packId, }) async { final api = ref.watch(apiClientProvider); try { await api.get('/sphere/stickers/$packId/own'); // If not 404, consider owned return true; } on Object catch (e) { // Dio error handling agnostic: treat 404 as not-owned, rethrow others final msg = e.toString(); if (msg.contains('404')) return false; rethrow; } } class MarketplaceStickerPackDetailScreen extends HookConsumerWidget { final String id; const MarketplaceStickerPackDetailScreen({super.key, required this.id}); @override Widget build(BuildContext context, WidgetRef ref) { // Pack metadata provider exists globally in creators file; reuse it. final pack = ref.watch(stickerPackProvider(id)); final packContent = ref.watch( marketplaceStickerPackContentProvider(packId: id), ); final owned = ref.watch( marketplaceStickerPackOwnershipProvider(packId: id), ); // Add entire pack to user's collection Future addPackToMyCollection() async { final apiClient = ref.watch(apiClientProvider); await apiClient.post('/sphere/stickers/$id/own'); HapticFeedback.selectionClick(); ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id)); if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('stickerPackAdded').tr())); } // Remove ownership of the pack Future removePackFromMyCollection() async { final apiClient = ref.watch(apiClientProvider); await apiClient.delete('/sphere/stickers/$id/own'); HapticFeedback.selectionClick(); ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id)); if (!context.mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('stickerPackRemoved').tr())); } return AppScaffold( appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())), body: pack.when( data: (p) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Pack meta Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text(p?.description ?? ''), Row( spacing: 4, children: [ const Icon(Symbols.folder, size: 16), Text( '${packContent.value?.length ?? 0}/24', style: GoogleFonts.robotoMono(), ), ], ).opacity(0.85), Row( spacing: 4, children: [ const Icon(Symbols.sell, size: 16), Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()), ], ).opacity(0.85), Row( spacing: 4, children: [ const Icon(Symbols.tag, size: 16), SelectableText( p?.id ?? id, style: GoogleFonts.robotoMono(), ), ], ).opacity(0.85), ], ).padding(horizontal: 24, vertical: 24), const Divider(height: 1), // Stickers grid Expanded( child: packContent.when( data: (stickers) => RefreshIndicator( onRefresh: () => ref.refresh( marketplaceStickerPackContentProvider( packId: id, ).future, ), child: GridView.builder( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 20, ), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 96, mainAxisSpacing: 12, crossAxisSpacing: 12, ), itemCount: stickers.length, itemBuilder: (context, index) { final sticker = stickers[index]; return Tooltip( message: ':${p?.prefix ?? ''}${sticker.slug}:', child: ClipRRect( borderRadius: const BorderRadius.all( Radius.circular(8), ), child: Container( decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.surfaceContainer, borderRadius: const BorderRadius.all( Radius.circular(8), ), ), child: AspectRatio( aspectRatio: 1, child: CloudImageWidget( fileId: sticker.imageId, fit: BoxFit.contain, ), ), ), ), ); }, ), ), error: (err, _) => Text( 'Error: $err', ).textAlignment(TextAlign.center).center(), loading: () => const CircularProgressIndicator().center(), ), ), Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8), child: owned.when( data: (isOwned) => FilledButton.icon( onPressed: isOwned ? removePackFromMyCollection : addPackToMyCollection, icon: Icon( isOwned ? Symbols.remove_circle : Symbols.add_circle, ), label: Text( isOwned ? 'removePack'.tr() : 'addPack'.tr(), ), ), loading: () => const SizedBox( height: 32, width: 32, child: CircularProgressIndicator(strokeWidth: 2), ), error: (_, _) => OutlinedButton.icon( onPressed: addPackToMyCollection, icon: const Icon(Symbols.add_circle), label: Text('addPack').tr(), ), ), ), Gap(MediaQuery.of(context).padding.bottom), ], ); }, error: (err, _) => Text('Error: $err').textAlignment(TextAlign.center).center(), loading: () => const CircularProgressIndicator().center(), ), ); } }