Post details

This commit is contained in:
2025-04-26 17:42:03 +08:00
parent bdb602c8c6
commit 7646a51cd9
8 changed files with 666 additions and 58 deletions

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:auto_route/auto_route.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/network.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/widgets/alert.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: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()
class PostComposeScreen extends HookConsumerWidget {
const PostComposeScreen({super.key});
final SnPost? originalPost;
const PostComposeScreen({super.key, this.originalPost});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -37,10 +64,29 @@ class PostComposeScreen extends HookConsumerWidget {
}, [publishers]);
// Contains the XFile, ByteData, or SnCloudFile
final attachments = useState<List<UniversalFile>>([]);
final contentController = useTextEditingController();
final titleController = useTextEditingController();
final descriptionController = useTextEditingController();
final attachments = useState<List<UniversalFile>>(
originalPost?.attachments
.map(
(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);
@ -149,6 +195,7 @@ class PostComposeScreen extends HookConsumerWidget {
.map((e) => e.data.id)
.toList(),
},
options: Options(headers: {'X-Pub': currentPublisher.value?.name}),
);
if (context.mounted) {
context.maybePop(true);

View 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'),
),
);
}
}

View 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