151 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:gap/gap.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/models/account.dart';
 | 
						|
import 'package:island/pods/network.dart';
 | 
						|
import 'package:material_symbols_icons/material_symbols_icons.dart';
 | 
						|
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
						|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
						|
import 'package:styled_widget/styled_widget.dart';
 | 
						|
 | 
						|
part 'credits.g.dart';
 | 
						|
 | 
						|
@riverpod
 | 
						|
Future<double> socialCredits(Ref ref) async {
 | 
						|
  final client = ref.watch(apiClientProvider);
 | 
						|
  final response = await client.get('/pass/accounts/me/credits');
 | 
						|
  if (response.statusCode != 200) {
 | 
						|
    throw Exception('Failed to load social credits');
 | 
						|
  }
 | 
						|
  return response.data?.toDouble() ?? 0.0;
 | 
						|
}
 | 
						|
 | 
						|
@riverpod
 | 
						|
class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier
 | 
						|
    with CursorPagingNotifierMixin<SnSocialCreditRecord> {
 | 
						|
  static const int _pageSize = 20;
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<CursorPagingData<SnSocialCreditRecord>> build() => fetch(cursor: null);
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<CursorPagingData<SnSocialCreditRecord>> fetch({
 | 
						|
    required String? cursor,
 | 
						|
  }) async {
 | 
						|
    final client = ref.read(apiClientProvider);
 | 
						|
    final offset = cursor == null ? 0 : int.parse(cursor);
 | 
						|
 | 
						|
    final queryParams = {'offset': offset, 'take': _pageSize};
 | 
						|
 | 
						|
    final response = await client.get(
 | 
						|
      '/pass/accounts/me/credits/history',
 | 
						|
      queryParameters: queryParams,
 | 
						|
    );
 | 
						|
    final total = int.parse(response.headers.value('X-Total') ?? '0');
 | 
						|
    final List<dynamic> data = response.data;
 | 
						|
    final records =
 | 
						|
        data.map((json) => SnSocialCreditRecord.fromJson(json)).toList();
 | 
						|
 | 
						|
    final hasMore = offset + records.length < total;
 | 
						|
    final nextCursor = hasMore ? (offset + records.length).toString() : null;
 | 
						|
 | 
						|
    return CursorPagingData(
 | 
						|
      items: records,
 | 
						|
      hasMore: hasMore,
 | 
						|
      nextCursor: nextCursor,
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class SocialCreditsTab extends HookConsumerWidget {
 | 
						|
  const SocialCreditsTab({super.key});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final socialCredits = ref.watch(socialCreditsProvider);
 | 
						|
    return Column(
 | 
						|
      children: [
 | 
						|
        const Gap(8),
 | 
						|
        Card(
 | 
						|
          margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
 | 
						|
          child: socialCredits
 | 
						|
              .when(
 | 
						|
                data:
 | 
						|
                    (credits) => Stack(
 | 
						|
                      children: [
 | 
						|
                        Column(
 | 
						|
                          crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                          children: [
 | 
						|
                            Text(
 | 
						|
                              credits < 100
 | 
						|
                                  ? 'socialCreditsLevelPoor'.tr()
 | 
						|
                                  : credits < 150
 | 
						|
                                  ? 'socialCreditsLevelNormal'.tr()
 | 
						|
                                  : credits < 200
 | 
						|
                                  ? 'socialCreditsLevelGood'.tr()
 | 
						|
                                  : 'socialCreditsLevelExcellent'.tr(),
 | 
						|
                            ).tr().bold().fontSize(20),
 | 
						|
                            Text(
 | 
						|
                              '${credits.toStringAsFixed(2)} pts',
 | 
						|
                            ).fontSize(14),
 | 
						|
                            const Gap(8),
 | 
						|
                            LinearProgressIndicator(value: credits / 200),
 | 
						|
                          ],
 | 
						|
                        ),
 | 
						|
                        Positioned(
 | 
						|
                          right: 0,
 | 
						|
                          top: 0,
 | 
						|
                          child: IconButton(
 | 
						|
                            onPressed: () {},
 | 
						|
                            icon: const Icon(Symbols.info),
 | 
						|
                            tooltip: 'socialCreditsDescription'.tr(),
 | 
						|
                          ),
 | 
						|
                        ),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                error: (_, _) => Text('Error loading credits'),
 | 
						|
                loading: () => const LinearProgressIndicator(),
 | 
						|
              )
 | 
						|
              .padding(horizontal: 20, vertical: 16),
 | 
						|
        ),
 | 
						|
        Expanded(
 | 
						|
          child: PagingHelperView(
 | 
						|
            provider: socialCreditHistoryNotifierProvider,
 | 
						|
            futureRefreshable: socialCreditHistoryNotifierProvider.future,
 | 
						|
            notifierRefreshable: socialCreditHistoryNotifierProvider.notifier,
 | 
						|
            contentBuilder:
 | 
						|
                (data, widgetCount, endItemView) => ListView.builder(
 | 
						|
                  padding: EdgeInsets.zero,
 | 
						|
                  itemCount: widgetCount,
 | 
						|
                  itemBuilder: (context, index) {
 | 
						|
                    if (index == widgetCount - 1) {
 | 
						|
                      return endItemView;
 | 
						|
                    }
 | 
						|
                    final record = data.items[index];
 | 
						|
                    return ListTile(
 | 
						|
                      contentPadding: const EdgeInsets.symmetric(
 | 
						|
                        horizontal: 24,
 | 
						|
                      ),
 | 
						|
                      title: Text(record.reason),
 | 
						|
                      subtitle: Text(
 | 
						|
                        DateFormat.yMMMd().format(record.createdAt),
 | 
						|
                      ),
 | 
						|
                      trailing: Text(
 | 
						|
                        record.delta > 0
 | 
						|
                            ? '+${record.delta}'
 | 
						|
                            : '${record.delta}',
 | 
						|
                        style: TextStyle(
 | 
						|
                          color: record.delta > 0 ? Colors.green : Colors.red,
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    );
 | 
						|
                  },
 | 
						|
                ),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |