231 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
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/alert.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<List<SnSticker>> 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<bool> 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<void> 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;
 | 
						|
      showSnackBar('stickerPackAdded'.tr());
 | 
						|
    }
 | 
						|
 | 
						|
    // Remove ownership of the pack
 | 
						|
    Future<void> 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;
 | 
						|
      showSnackBar('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.image.id,
 | 
						|
                                      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 + 16),
 | 
						|
            ],
 | 
						|
          );
 | 
						|
        },
 | 
						|
        error:
 | 
						|
            (err, _) =>
 | 
						|
                Text('Error: $err').textAlignment(TextAlign.center).center(),
 | 
						|
        loading: () => const CircularProgressIndicator().center(),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |