✨ Post details
This commit is contained in:
parent
bdb602c8c6
commit
7646a51cd9
@ -28,5 +28,7 @@ class AppRouter extends RootStackRouter {
|
|||||||
path: '/account/me/publishers/:id',
|
path: '/account/me/publishers/:id',
|
||||||
),
|
),
|
||||||
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
|
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
|
||||||
|
AutoRoute(page: PostDetailRoute.page, path: '/posts/:id'),
|
||||||
|
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -9,27 +9,29 @@
|
|||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:auto_route/auto_route.dart' as _i10;
|
import 'package:auto_route/auto_route.dart' as _i11;
|
||||||
import 'package:flutter/material.dart' as _i11;
|
import 'package:flutter/material.dart' as _i12;
|
||||||
|
import 'package:island/models/post.dart' as _i13;
|
||||||
import 'package:island/screens/account.dart' as _i1;
|
import 'package:island/screens/account.dart' as _i1;
|
||||||
import 'package:island/screens/account/me.dart' as _i6;
|
import 'package:island/screens/account/me.dart' as _i6;
|
||||||
import 'package:island/screens/account/me/publishers.dart' as _i3;
|
import 'package:island/screens/account/me/publishers.dart' as _i3;
|
||||||
import 'package:island/screens/account/me/update.dart' as _i9;
|
import 'package:island/screens/account/me/update.dart' as _i10;
|
||||||
import 'package:island/screens/auth/create_account.dart' as _i2;
|
import 'package:island/screens/auth/create_account.dart' as _i2;
|
||||||
import 'package:island/screens/auth/login.dart' as _i5;
|
import 'package:island/screens/auth/login.dart' as _i5;
|
||||||
import 'package:island/screens/auth/tabs.dart' as _i8;
|
import 'package:island/screens/auth/tabs.dart' as _i9;
|
||||||
import 'package:island/screens/explore.dart' as _i4;
|
import 'package:island/screens/explore.dart' as _i4;
|
||||||
import 'package:island/screens/posts/compose.dart' as _i7;
|
import 'package:island/screens/posts/compose.dart' as _i7;
|
||||||
|
import 'package:island/screens/posts/detail.dart' as _i8;
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i1.AccountScreen]
|
/// [_i1.AccountScreen]
|
||||||
class AccountRoute extends _i10.PageRouteInfo<void> {
|
class AccountRoute extends _i11.PageRouteInfo<void> {
|
||||||
const AccountRoute({List<_i10.PageRouteInfo>? children})
|
const AccountRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(AccountRoute.name, initialChildren: children);
|
: super(AccountRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'AccountRoute';
|
static const String name = 'AccountRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i1.AccountScreen();
|
return const _i1.AccountScreen();
|
||||||
@ -39,13 +41,13 @@ class AccountRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i2.CreateAccountScreen]
|
/// [_i2.CreateAccountScreen]
|
||||||
class CreateAccountRoute extends _i10.PageRouteInfo<void> {
|
class CreateAccountRoute extends _i11.PageRouteInfo<void> {
|
||||||
const CreateAccountRoute({List<_i10.PageRouteInfo>? children})
|
const CreateAccountRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(CreateAccountRoute.name, initialChildren: children);
|
: super(CreateAccountRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'CreateAccountRoute';
|
static const String name = 'CreateAccountRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i2.CreateAccountScreen();
|
return const _i2.CreateAccountScreen();
|
||||||
@ -55,11 +57,11 @@ class CreateAccountRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i3.EditPublisherScreen]
|
/// [_i3.EditPublisherScreen]
|
||||||
class EditPublisherRoute extends _i10.PageRouteInfo<EditPublisherRouteArgs> {
|
class EditPublisherRoute extends _i11.PageRouteInfo<EditPublisherRouteArgs> {
|
||||||
EditPublisherRoute({
|
EditPublisherRoute({
|
||||||
_i11.Key? key,
|
_i12.Key? key,
|
||||||
String? name,
|
String? name,
|
||||||
List<_i10.PageRouteInfo>? children,
|
List<_i11.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
EditPublisherRoute.name,
|
EditPublisherRoute.name,
|
||||||
args: EditPublisherRouteArgs(key: key, name: name),
|
args: EditPublisherRouteArgs(key: key, name: name),
|
||||||
@ -69,7 +71,7 @@ class EditPublisherRoute extends _i10.PageRouteInfo<EditPublisherRouteArgs> {
|
|||||||
|
|
||||||
static const String name = 'EditPublisherRoute';
|
static const String name = 'EditPublisherRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
final pathParams = data.inheritedPathParams;
|
final pathParams = data.inheritedPathParams;
|
||||||
@ -84,7 +86,7 @@ class EditPublisherRoute extends _i10.PageRouteInfo<EditPublisherRouteArgs> {
|
|||||||
class EditPublisherRouteArgs {
|
class EditPublisherRouteArgs {
|
||||||
const EditPublisherRouteArgs({this.key, this.name});
|
const EditPublisherRouteArgs({this.key, this.name});
|
||||||
|
|
||||||
final _i11.Key? key;
|
final _i12.Key? key;
|
||||||
|
|
||||||
final String? name;
|
final String? name;
|
||||||
|
|
||||||
@ -96,13 +98,13 @@ class EditPublisherRouteArgs {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i4.ExploreScreen]
|
/// [_i4.ExploreScreen]
|
||||||
class ExploreRoute extends _i10.PageRouteInfo<void> {
|
class ExploreRoute extends _i11.PageRouteInfo<void> {
|
||||||
const ExploreRoute({List<_i10.PageRouteInfo>? children})
|
const ExploreRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(ExploreRoute.name, initialChildren: children);
|
: super(ExploreRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'ExploreRoute';
|
static const String name = 'ExploreRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i4.ExploreScreen();
|
return const _i4.ExploreScreen();
|
||||||
@ -112,13 +114,13 @@ class ExploreRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i5.LoginScreen]
|
/// [_i5.LoginScreen]
|
||||||
class LoginRoute extends _i10.PageRouteInfo<void> {
|
class LoginRoute extends _i11.PageRouteInfo<void> {
|
||||||
const LoginRoute({List<_i10.PageRouteInfo>? children})
|
const LoginRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(LoginRoute.name, initialChildren: children);
|
: super(LoginRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'LoginRoute';
|
static const String name = 'LoginRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i5.LoginScreen();
|
return const _i5.LoginScreen();
|
||||||
@ -128,13 +130,13 @@ class LoginRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i3.ManagedPublisherScreen]
|
/// [_i3.ManagedPublisherScreen]
|
||||||
class ManagedPublisherRoute extends _i10.PageRouteInfo<void> {
|
class ManagedPublisherRoute extends _i11.PageRouteInfo<void> {
|
||||||
const ManagedPublisherRoute({List<_i10.PageRouteInfo>? children})
|
const ManagedPublisherRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(ManagedPublisherRoute.name, initialChildren: children);
|
: super(ManagedPublisherRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'ManagedPublisherRoute';
|
static const String name = 'ManagedPublisherRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i3.ManagedPublisherScreen();
|
return const _i3.ManagedPublisherScreen();
|
||||||
@ -144,13 +146,13 @@ class ManagedPublisherRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i6.MyselfProfileScreen]
|
/// [_i6.MyselfProfileScreen]
|
||||||
class MyselfProfileRoute extends _i10.PageRouteInfo<void> {
|
class MyselfProfileRoute extends _i11.PageRouteInfo<void> {
|
||||||
const MyselfProfileRoute({List<_i10.PageRouteInfo>? children})
|
const MyselfProfileRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(MyselfProfileRoute.name, initialChildren: children);
|
: super(MyselfProfileRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'MyselfProfileRoute';
|
static const String name = 'MyselfProfileRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i6.MyselfProfileScreen();
|
return const _i6.MyselfProfileScreen();
|
||||||
@ -160,13 +162,13 @@ class MyselfProfileRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i3.NewPublisherScreen]
|
/// [_i3.NewPublisherScreen]
|
||||||
class NewPublisherRoute extends _i10.PageRouteInfo<void> {
|
class NewPublisherRoute extends _i11.PageRouteInfo<void> {
|
||||||
const NewPublisherRoute({List<_i10.PageRouteInfo>? children})
|
const NewPublisherRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(NewPublisherRoute.name, initialChildren: children);
|
: super(NewPublisherRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'NewPublisherRoute';
|
static const String name = 'NewPublisherRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i3.NewPublisherScreen();
|
return const _i3.NewPublisherScreen();
|
||||||
@ -176,48 +178,156 @@ class NewPublisherRoute extends _i10.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i7.PostComposeScreen]
|
/// [_i7.PostComposeScreen]
|
||||||
class PostComposeRoute extends _i10.PageRouteInfo<void> {
|
class PostComposeRoute extends _i11.PageRouteInfo<PostComposeRouteArgs> {
|
||||||
const PostComposeRoute({List<_i10.PageRouteInfo>? children})
|
PostComposeRoute({
|
||||||
: super(PostComposeRoute.name, initialChildren: children);
|
_i12.Key? key,
|
||||||
|
_i13.SnPost? originalPost,
|
||||||
|
List<_i11.PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
PostComposeRoute.name,
|
||||||
|
args: PostComposeRouteArgs(key: key, originalPost: originalPost),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
static const String name = 'PostComposeRoute';
|
static const String name = 'PostComposeRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i7.PostComposeScreen();
|
final args = data.argsAs<PostComposeRouteArgs>(
|
||||||
|
orElse: () => const PostComposeRouteArgs(),
|
||||||
|
);
|
||||||
|
return _i7.PostComposeScreen(
|
||||||
|
key: args.key,
|
||||||
|
originalPost: args.originalPost,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PostComposeRouteArgs {
|
||||||
|
const PostComposeRouteArgs({this.key, this.originalPost});
|
||||||
|
|
||||||
|
final _i12.Key? key;
|
||||||
|
|
||||||
|
final _i13.SnPost? originalPost;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostComposeRouteArgs{key: $key, originalPost: $originalPost}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i8.TabsScreen]
|
/// [_i8.PostDetailScreen]
|
||||||
class TabsRoute extends _i10.PageRouteInfo<void> {
|
class PostDetailRoute extends _i11.PageRouteInfo<PostDetailRouteArgs> {
|
||||||
const TabsRoute({List<_i10.PageRouteInfo>? children})
|
PostDetailRoute({
|
||||||
|
_i12.Key? key,
|
||||||
|
required int id,
|
||||||
|
List<_i11.PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
PostDetailRoute.name,
|
||||||
|
args: PostDetailRouteArgs(key: key, id: id),
|
||||||
|
rawPathParams: {'id': id},
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'PostDetailRoute';
|
||||||
|
|
||||||
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
final pathParams = data.inheritedPathParams;
|
||||||
|
final args = data.argsAs<PostDetailRouteArgs>(
|
||||||
|
orElse: () => PostDetailRouteArgs(id: pathParams.getInt('id')),
|
||||||
|
);
|
||||||
|
return _i8.PostDetailScreen(key: args.key, id: args.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostDetailRouteArgs {
|
||||||
|
const PostDetailRouteArgs({this.key, required this.id});
|
||||||
|
|
||||||
|
final _i12.Key? key;
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostDetailRouteArgs{key: $key, id: $id}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [_i7.PostEditScreen]
|
||||||
|
class PostEditRoute extends _i11.PageRouteInfo<PostEditRouteArgs> {
|
||||||
|
PostEditRoute({
|
||||||
|
_i12.Key? key,
|
||||||
|
required int id,
|
||||||
|
List<_i11.PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
|
PostEditRoute.name,
|
||||||
|
args: PostEditRouteArgs(key: key, id: id),
|
||||||
|
rawPathParams: {'id': id},
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'PostEditRoute';
|
||||||
|
|
||||||
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
final pathParams = data.inheritedPathParams;
|
||||||
|
final args = data.argsAs<PostEditRouteArgs>(
|
||||||
|
orElse: () => PostEditRouteArgs(id: pathParams.getInt('id')),
|
||||||
|
);
|
||||||
|
return _i7.PostEditScreen(key: args.key, id: args.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostEditRouteArgs {
|
||||||
|
const PostEditRouteArgs({this.key, required this.id});
|
||||||
|
|
||||||
|
final _i12.Key? key;
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostEditRouteArgs{key: $key, id: $id}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [_i9.TabsScreen]
|
||||||
|
class TabsRoute extends _i11.PageRouteInfo<void> {
|
||||||
|
const TabsRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(TabsRoute.name, initialChildren: children);
|
: super(TabsRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'TabsRoute';
|
static const String name = 'TabsRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i8.TabsScreen();
|
return const _i9.TabsScreen();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i9.UpdateProfileScreen]
|
/// [_i10.UpdateProfileScreen]
|
||||||
class UpdateProfileRoute extends _i10.PageRouteInfo<void> {
|
class UpdateProfileRoute extends _i11.PageRouteInfo<void> {
|
||||||
const UpdateProfileRoute({List<_i10.PageRouteInfo>? children})
|
const UpdateProfileRoute({List<_i11.PageRouteInfo>? children})
|
||||||
: super(UpdateProfileRoute.name, initialChildren: children);
|
: super(UpdateProfileRoute.name, initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'UpdateProfileRoute';
|
static const String name = 'UpdateProfileRoute';
|
||||||
|
|
||||||
static _i10.PageInfo page = _i10.PageInfo(
|
static _i11.PageInfo page = _i11.PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const _i9.UpdateProfileScreen();
|
return const _i10.UpdateProfileScreen();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@ -12,6 +13,7 @@ import 'package:island/models/post.dart';
|
|||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/account/me/publishers.dart';
|
import 'package:island/screens/account/me/publishers.dart';
|
||||||
|
import 'package:island/screens/posts/detail.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@ -19,9 +21,34 @@ import 'package:island/widgets/content/cloud_files.dart';
|
|||||||
import 'package:lucide_icons/lucide_icons.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class PostEditScreen extends HookConsumerWidget {
|
||||||
|
final int id;
|
||||||
|
const PostEditScreen({super.key, @PathParam('id') required this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final post = ref.watch(postProvider(id));
|
||||||
|
return post.when(
|
||||||
|
data: (post) => PostComposeScreen(originalPost: post),
|
||||||
|
loading:
|
||||||
|
() => AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(e, _) => AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: Text('Error: $e', textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PostComposeScreen extends HookConsumerWidget {
|
class PostComposeScreen extends HookConsumerWidget {
|
||||||
const PostComposeScreen({super.key});
|
final SnPost? originalPost;
|
||||||
|
const PostComposeScreen({super.key, this.originalPost});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -37,10 +64,29 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
}, [publishers]);
|
}, [publishers]);
|
||||||
|
|
||||||
// Contains the XFile, ByteData, or SnCloudFile
|
// Contains the XFile, ByteData, or SnCloudFile
|
||||||
final attachments = useState<List<UniversalFile>>([]);
|
final attachments = useState<List<UniversalFile>>(
|
||||||
final contentController = useTextEditingController();
|
originalPost?.attachments
|
||||||
final titleController = useTextEditingController();
|
.map(
|
||||||
final descriptionController = useTextEditingController();
|
(e) => UniversalFile(
|
||||||
|
data: e,
|
||||||
|
type: switch (e.mimeType?.split('/').firstOrNull) {
|
||||||
|
'image' => UniversalFileType.image,
|
||||||
|
'video' => UniversalFileType.video,
|
||||||
|
'audio' => UniversalFileType.audio,
|
||||||
|
_ => UniversalFileType.file,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
final contentController = useTextEditingController(
|
||||||
|
text: originalPost?.content,
|
||||||
|
);
|
||||||
|
final titleController = useTextEditingController(text: originalPost?.title);
|
||||||
|
final descriptionController = useTextEditingController(
|
||||||
|
text: originalPost?.description,
|
||||||
|
);
|
||||||
|
|
||||||
final submitting = useState(false);
|
final submitting = useState(false);
|
||||||
|
|
||||||
@ -149,6 +195,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
.map((e) => e.data.id)
|
.map((e) => e.data.id)
|
||||||
.toList(),
|
.toList(),
|
||||||
},
|
},
|
||||||
|
options: Options(headers: {'X-Pub': currentPublisher.value?.name}),
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.maybePop(true);
|
context.maybePop(true);
|
||||||
|
67
lib/screens/posts/detail.dart
Normal file
67
lib/screens/posts/detail.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
|
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||||
|
import 'package:island/widgets/post/post_replies.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'detail.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<SnPost?> post(Ref ref, int id) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final resp = await client.get('/posts/$id');
|
||||||
|
return SnPost.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class PostDetailScreen extends HookConsumerWidget {
|
||||||
|
final int id;
|
||||||
|
const PostDetailScreen({super.key, @PathParam('id') required this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final post = ref.watch(postProvider(id));
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: const Text('Post')),
|
||||||
|
body: post.when(
|
||||||
|
data:
|
||||||
|
(post) => Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
PostItem(item: post!),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(child: PostRepliesList(postId: id)),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Material(
|
||||||
|
elevation: 2,
|
||||||
|
child: PostQuickReply(parent: post).padding(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom,
|
||||||
|
top: 16,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (e, _) => Text('Error: $e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
144
lib/screens/posts/detail.g.dart
Normal file
144
lib/screens/posts/detail.g.dart
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'detail.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$postHash() => r'58de03954e284b5c04544b61ccb9cadfc45e9422';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [post].
|
||||||
|
@ProviderFor(post)
|
||||||
|
const postProvider = PostFamily();
|
||||||
|
|
||||||
|
/// See also [post].
|
||||||
|
class PostFamily extends Family<AsyncValue<SnPost?>> {
|
||||||
|
/// See also [post].
|
||||||
|
const PostFamily();
|
||||||
|
|
||||||
|
/// See also [post].
|
||||||
|
PostProvider call(int id) {
|
||||||
|
return PostProvider(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PostProvider getProviderOverride(covariant PostProvider provider) {
|
||||||
|
return call(provider.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'postProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [post].
|
||||||
|
class PostProvider extends AutoDisposeFutureProvider<SnPost?> {
|
||||||
|
/// See also [post].
|
||||||
|
PostProvider(int id)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => post(ref as PostRef, id),
|
||||||
|
from: postProvider,
|
||||||
|
name: r'postProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$postHash,
|
||||||
|
dependencies: PostFamily._dependencies,
|
||||||
|
allTransitiveDependencies: PostFamily._allTransitiveDependencies,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
PostProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.id,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(FutureOr<SnPost?> Function(PostRef provider) create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PostProvider._internal(
|
||||||
|
(ref) => create(ref as PostRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<SnPost?> createElement() {
|
||||||
|
return _PostProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PostProvider && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, id.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PostRef on AutoDisposeFutureProviderRef<SnPost?> {
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
int get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostProviderElement extends AutoDisposeFutureProviderElement<SnPost?>
|
||||||
|
with PostRef {
|
||||||
|
_PostProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id => (origin as PostProvider).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
@ -8,7 +10,13 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
class PostItem extends StatelessWidget {
|
class PostItem extends StatelessWidget {
|
||||||
final SnPost item;
|
final SnPost item;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
const PostItem({super.key, required this.item, this.padding});
|
final bool isOpenable;
|
||||||
|
const PostItem({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
this.padding,
|
||||||
|
this.isOpenable = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -26,13 +34,20 @@ class PostItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(item: item.publisher.picture),
|
ProfilePictureWidget(item: item.publisher.picture),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: GestureDetector(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(item.publisher.nick).bold(),
|
children: [
|
||||||
if (item.content.isNotEmpty)
|
Text(item.publisher.nick).bold(),
|
||||||
MarkdownTextContent(content: item.content),
|
if (item.content.isNotEmpty)
|
||||||
],
|
MarkdownTextContent(content: item.content),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (isOpenable) {
|
||||||
|
context.router.push(PostDetailRoute(id: item.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
107
lib/widgets/post/post_quick_reply.dart
Normal file
107
lib/widgets/post/post_quick_reply.dart
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/account/me/publishers.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class PostQuickReply extends HookConsumerWidget {
|
||||||
|
final SnPost parent;
|
||||||
|
final Function? onPosted;
|
||||||
|
const PostQuickReply({super.key, required this.parent, this.onPosted});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final publishers = ref.watch(publishersManagedProvider);
|
||||||
|
|
||||||
|
final currentPublisher = useState<SnPublisher?>(null);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (publishers.value?.isNotEmpty ?? false) {
|
||||||
|
currentPublisher.value = publishers.value!.first;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [publishers]);
|
||||||
|
|
||||||
|
final submitting = useState(false);
|
||||||
|
|
||||||
|
final contentController = useTextEditingController();
|
||||||
|
|
||||||
|
Future<void> performAction() async {
|
||||||
|
if (!contentController.text.isNotEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
await client.post(
|
||||||
|
'/posts',
|
||||||
|
data: {
|
||||||
|
'content': contentController.text,
|
||||||
|
'replied_post_id': parent.id,
|
||||||
|
},
|
||||||
|
options: Options(headers: {'X-Pub': currentPublisher.value?.name}),
|
||||||
|
);
|
||||||
|
contentController.clear();
|
||||||
|
onPosted?.call();
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return publishers.when(
|
||||||
|
data:
|
||||||
|
(data) => Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
ProfilePictureWidget(
|
||||||
|
item: currentPublisher.value?.picture,
|
||||||
|
radius: 16,
|
||||||
|
).padding(right: 4),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: contentController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Post your reply',
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
maxLines: null,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
icon:
|
||||||
|
submitting.value
|
||||||
|
? SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 3),
|
||||||
|
)
|
||||||
|
: Icon(LucideIcons.send, size: 20),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
onPressed: submitting.value ? null : performAction,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (e, _) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
116
lib/widgets/post/post_replies.dart
Normal file
116
lib/widgets/post/post_replies.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import 'package:dio/dio.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:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
class PostRepliesList extends HookConsumerWidget {
|
||||||
|
final int postId;
|
||||||
|
const PostRepliesList({super.key, required this.postId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final postAsync = ref.watch(postRepliesProvider(postId));
|
||||||
|
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh:
|
||||||
|
() => Future.sync((() {
|
||||||
|
ref.invalidate(postRepliesProvider(postId));
|
||||||
|
})),
|
||||||
|
child: postAsync.when(
|
||||||
|
data:
|
||||||
|
(controller) => RefreshIndicator(
|
||||||
|
onRefresh:
|
||||||
|
() => Future.sync((() {
|
||||||
|
ref.invalidate(postRepliesProvider(postId));
|
||||||
|
})),
|
||||||
|
child: InfiniteList(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom,
|
||||||
|
),
|
||||||
|
itemCount: controller.posts.length,
|
||||||
|
isLoading: controller.isLoading,
|
||||||
|
hasReachedMax: controller.hasReachedMax,
|
||||||
|
onFetchData: controller.fetchMore,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final post = controller.posts[index];
|
||||||
|
return PostItem(item: post);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
|
emptyBuilder: (context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'No replies',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).fontSize(18).bold(),
|
||||||
|
Text('Why not start a discussion?'),
|
||||||
|
],
|
||||||
|
).padding(vertical: 16);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(e, _) => GestureDetector(
|
||||||
|
child: Center(
|
||||||
|
child: Text('Error: $e', textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
ref.invalidate(postRepliesProvider(postId));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final postRepliesProvider = FutureProviderFamily<_PostRepliesController, int>((
|
||||||
|
ref,
|
||||||
|
postId,
|
||||||
|
) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final controller = _PostRepliesController(client, postId);
|
||||||
|
await controller.fetchMore();
|
||||||
|
return controller;
|
||||||
|
});
|
||||||
|
|
||||||
|
class _PostRepliesController {
|
||||||
|
_PostRepliesController(this._dio, this.parentId);
|
||||||
|
|
||||||
|
final Dio _dio;
|
||||||
|
final int parentId;
|
||||||
|
final List<SnPost> posts = [];
|
||||||
|
bool isLoading = false;
|
||||||
|
bool hasReachedMax = false;
|
||||||
|
int offset = 0;
|
||||||
|
final int take = 20;
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
Future<void> fetchMore() async {
|
||||||
|
if (isLoading || hasReachedMax) return;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
final response = await _dio.get(
|
||||||
|
'/posts/$parentId/replies',
|
||||||
|
queryParameters: {'offset': offset, 'take': take},
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<SnPost> fetched =
|
||||||
|
(response.data as List)
|
||||||
|
.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final headerTotal = int.tryParse(response.headers['x-total']?.first ?? '');
|
||||||
|
if (headerTotal != null) total = headerTotal;
|
||||||
|
|
||||||
|
posts.addAll(fetched);
|
||||||
|
offset += fetched.length;
|
||||||
|
if (posts.length >= total) hasReachedMax = true;
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user