120 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:flutter/material.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/models/post.dart';
 | 
						|
import 'package:island/pods/network.dart';
 | 
						|
import 'package:island/widgets/post/post_item.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 'post_replies.g.dart';
 | 
						|
 | 
						|
@riverpod
 | 
						|
class PostRepliesNotifier extends _$PostRepliesNotifier
 | 
						|
    with CursorPagingNotifierMixin<SnPost> {
 | 
						|
  static const int _pageSize = 20;
 | 
						|
 | 
						|
  PostRepliesNotifier();
 | 
						|
 | 
						|
  String? _postId;
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<CursorPagingData<SnPost>> build(String postId) {
 | 
						|
    _postId = postId;
 | 
						|
    return fetch(cursor: null);
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<CursorPagingData<SnPost>> fetch({required String? cursor}) async {
 | 
						|
    if (_postId == null) {
 | 
						|
      throw StateError('PostRepliesNotifier must be initialized with postId');
 | 
						|
    }
 | 
						|
 | 
						|
    final client = ref.read(apiClientProvider);
 | 
						|
    final offset = cursor == null ? 0 : int.parse(cursor);
 | 
						|
 | 
						|
    final response = await client.get(
 | 
						|
      '/sphere/posts/$_postId/replies',
 | 
						|
      queryParameters: {'offset': offset, 'take': _pageSize},
 | 
						|
    );
 | 
						|
 | 
						|
    final total = int.parse(response.headers.value('X-Total') ?? '0');
 | 
						|
    final List<dynamic> data = response.data;
 | 
						|
    final posts = data.map((json) => SnPost.fromJson(json)).toList();
 | 
						|
 | 
						|
    final hasMore = offset + posts.length < total;
 | 
						|
    final nextCursor = hasMore ? (offset + posts.length).toString() : null;
 | 
						|
 | 
						|
    return CursorPagingData(
 | 
						|
      items: posts,
 | 
						|
      hasMore: hasMore,
 | 
						|
      nextCursor: nextCursor,
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class PostRepliesList extends HookConsumerWidget {
 | 
						|
  final String postId;
 | 
						|
  final double? maxWidth;
 | 
						|
  final VoidCallback? onOpen;
 | 
						|
  const PostRepliesList({
 | 
						|
    super.key,
 | 
						|
    required this.postId,
 | 
						|
    this.maxWidth,
 | 
						|
    this.onOpen,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    return PagingHelperSliverView(
 | 
						|
      provider: postRepliesNotifierProvider(postId),
 | 
						|
      futureRefreshable: postRepliesNotifierProvider(postId).future,
 | 
						|
      notifierRefreshable: postRepliesNotifierProvider(postId).notifier,
 | 
						|
      contentBuilder: (data, widgetCount, endItemView) {
 | 
						|
        if (data.items.isEmpty) {
 | 
						|
          return SliverToBoxAdapter(
 | 
						|
            child: Column(
 | 
						|
              children: [
 | 
						|
                Text(
 | 
						|
                  'No replies',
 | 
						|
                  textAlign: TextAlign.center,
 | 
						|
                ).fontSize(18).bold(),
 | 
						|
                const Text('Why not start a discussion?'),
 | 
						|
              ],
 | 
						|
            ).padding(vertical: 16),
 | 
						|
          );
 | 
						|
        }
 | 
						|
 | 
						|
        return SliverList.builder(
 | 
						|
          itemCount: widgetCount,
 | 
						|
          itemBuilder: (context, index) {
 | 
						|
            if (index == widgetCount - 1) {
 | 
						|
              return endItemView;
 | 
						|
            }
 | 
						|
 | 
						|
            final contentWidget = Card(
 | 
						|
              margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
 | 
						|
              child: PostActionableItem(
 | 
						|
                borderRadius: 8,
 | 
						|
                item: data.items[index],
 | 
						|
                isShowReference: false,
 | 
						|
                isEmbedOpenable: true,
 | 
						|
                onOpen: onOpen,
 | 
						|
              ),
 | 
						|
            );
 | 
						|
 | 
						|
            if (maxWidth == null) return contentWidget;
 | 
						|
 | 
						|
            return Center(
 | 
						|
              child: ConstrainedBox(
 | 
						|
                constraints: BoxConstraints(maxWidth: maxWidth!),
 | 
						|
                child: contentWidget,
 | 
						|
              ),
 | 
						|
            );
 | 
						|
          },
 | 
						|
        );
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |