Basic large screen support

This commit is contained in:
2024-05-02 00:49:38 +08:00
parent fceb3edbc6
commit b39c8c770e
41 changed files with 716 additions and 607 deletions

View File

@ -97,7 +97,7 @@ class NameCard extends StatelessWidget {
Future<Widget> renderAvatar(BuildContext context) async {
final auth = context.read<AuthProvider>();
final profiles = await auth.getProfiles();
return AccountAvatar(source: profiles["picture"], direct: true);
return AccountAvatar(source: profiles['picture'], direct: true);
}
Future<Column> renderLabel(BuildContext context) async {
@ -107,13 +107,13 @@ class NameCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
profiles["nick"],
profiles['nick'],
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(profiles["email"])
Text(profiles['email'])
],
);
}

View File

@ -157,8 +157,9 @@ class _FriendScreenState extends State<FriendScreen> {
DismissDirection getDismissDirection(Friendship relation) {
if (relation.status == 2) return DismissDirection.endToStart;
if (relation.status == 1) return DismissDirection.startToEnd;
if (relation.status == 0 && relation.relatedId != _selfId)
if (relation.status == 0 && relation.relatedId != _selfId) {
return DismissDirection.startToEnd;
}
return DismissDirection.horizontal;
}

View File

@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class AuthorizationScreen extends StatelessWidget {
final Uri authorizationUrl;
const AuthorizationScreen(this.authorizationUrl, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.signIn),
),
body: Stack(children: [
WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.white)
..setNavigationDelegate(NavigationDelegate(
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('solian')) {
Navigator.of(context).pop(request.url);
WebViewCookieManager().clearCookies();
return NavigationDecision.prevent;
} else if (request.url.contains("sign-up")) {
launchUrl(Uri.parse(request.url));
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
))
..loadRequest(authorizationUrl)
..clearCache(),
),
]),
);
}
}

View File

@ -24,14 +24,14 @@ class _ChatCallState extends State<ChatCall> {
late ChatProvider _chat;
ChatCallInstance get _call => _chat.call!;
ChatCallInstance get _call => _chat.currentCall!;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_chat.setShown(true);
_chat.setCallShown(true);
});
}
@ -40,13 +40,13 @@ class _ChatCallState extends State<ChatCall> {
_chat = context.watch<ChatProvider>();
if (!_isHandled) {
_isHandled = true;
if (_chat.handleCall(widget.call, widget.call.channel)) {
_chat.call?.init();
if (_chat.handleCallJoin(widget.call, widget.call.channel)) {
_chat.currentCall?.init();
}
}
Widget content;
if (_chat.call == null) {
if (_chat.currentCall == null) {
content = const Center(
child: CircularProgressIndicator(),
);
@ -136,7 +136,7 @@ class _ChatCallState extends State<ChatCall> {
@override
void deactivate() {
WidgetsBinding.instance.addPostFrameCallback((_) => _chat.setShown(false));
WidgetsBinding.instance.addPostFrameCallback((_) => _chat.setCallShown(false));
super.deactivate();
}
}

View File

@ -42,7 +42,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
? getRequestUri('messaging', '/api/channels')
: getRequestUri('messaging', '/api/channels/${widget.editing!.id}');
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
req.headers['Content-Type'] = 'application/json';
req.body = jsonEncode(<String, dynamic>{
'alias': _aliasController.value.text.toLowerCase(),

View File

@ -4,69 +4,52 @@ import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.dart';
import 'package:solian/models/call.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/message.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/chat.dart';
import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/chat/channel_action.dart';
import 'package:solian/widgets/chat/maintainer.dart';
import 'package:solian/widgets/chat/message.dart';
import 'package:solian/widgets/chat/message_action.dart';
import 'package:solian/widgets/chat/message_editor.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart' as http;
class ChatScreen extends StatefulWidget {
class ChatScreen extends StatelessWidget {
final String alias;
const ChatScreen({super.key, required this.alias});
@override
State<ChatScreen> createState() => _ChatScreenState();
Widget build(BuildContext context) {
return IndentWrapper(
title: AppLocalizations.of(context)!.post,
noSafeArea: true,
hideDrawer: true,
child: ChatScreenWidget(
alias: alias,
),
);
}
}
class _ChatScreenState extends State<ChatScreen> {
Call? _ongoingCall;
Channel? _channelMeta;
class ChatScreenWidget extends StatefulWidget {
final String alias;
const ChatScreenWidget({super.key, required this.alias});
@override
State<ChatScreenWidget> createState() => _ChatScreenWidgetState();
}
class _ChatScreenWidgetState extends State<ChatScreenWidget> {
bool _isReady = false;
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
final http.Client _client = http.Client();
Future<Channel> fetchMetadata() async {
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}');
var res = await _client.get(uri);
if (res.statusCode == 200) {
final result = jsonDecode(utf8.decode(res.bodyBytes));
setState(() => _channelMeta = Channel.fromJson(result));
return _channelMeta!;
} else {
var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message);
throw Exception(message);
}
}
Future<Call?> fetchCall() async {
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}/calls/ongoing');
var res = await _client.get(uri);
if (res.statusCode == 200) {
final result = jsonDecode(utf8.decode(res.bodyBytes));
setState(() => _ongoingCall = Call.fromJson(result));
return _ongoingCall;
} else if (res.statusCode != 404) {
var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message);
throw Exception(message);
} else {
return null;
}
}
late final ChatProvider _chat;
Future<void> fetchMessages(int pageKey, BuildContext context) async {
final auth = context.read<AuthProvider>();
@ -142,11 +125,6 @@ class _ChatScreenState extends State<ChatScreen> {
@override
void initState() {
Future.delayed(Duration.zero, () {
fetchMetadata();
fetchCall();
});
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
super.initState();
@ -180,6 +158,11 @@ class _ChatScreenState extends State<ChatScreen> {
);
}
if (!_isReady) {
_isReady = true;
_chat = context.watch<ChatProvider>();
}
final callBanner = MaterialBanner(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.call_received),
@ -192,7 +175,7 @@ class _ChatScreenState extends State<ChatScreen> {
onPressed: () {
router.pushNamed(
'chat.channel.call',
extra: _ongoingCall,
extra: _chat.ongoingCall,
pathParameters: {'channel': widget.alias},
);
},
@ -200,69 +183,52 @@ class _ChatScreenState extends State<ChatScreen> {
],
);
return IndentWrapper(
hideDrawer: true,
title: _channelMeta?.name ?? 'Loading...',
appBarActions: _channelMeta != null
? [
ChannelCallAction(
call: _ongoingCall,
channel: _channelMeta!,
onUpdate: () => fetchMetadata(),
),
ChannelManageAction(
channel: _channelMeta!,
onUpdate: () => fetchMetadata(),
),
]
: [],
child: FutureBuilder(
future: fetchMetadata(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const Center(child: CircularProgressIndicator());
}
return FutureBuilder(
future: _chat.fetchChannel(widget.alias),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const Center(child: CircularProgressIndicator());
}
return ChatMaintainer(
channel: snapshot.data!,
child: Stack(
children: [
Column(
children: [
Expanded(
child: PagedListView<int, Message>(
reverse: true,
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Message>(
animateTransitions: true,
transitionDuration: 500.ms,
itemBuilder: chatHistoryBuilder,
noItemsFoundIndicatorBuilder: (_) => Container(),
),
return ChatMaintainer(
channel: snapshot.data!,
child: Stack(
children: [
Column(
children: [
Expanded(
child: PagedListView<int, Message>(
reverse: true,
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Message>(
animateTransitions: true,
transitionDuration: 500.ms,
itemBuilder: chatHistoryBuilder,
noItemsFoundIndicatorBuilder: (_) => Container(),
),
),
ChatMessageEditor(
channel: widget.alias,
editing: _editingItem,
replying: _replyingItem,
onReset: () => setState(() {
_editingItem = null;
_replyingItem = null;
}),
),
],
),
_ongoingCall != null ? callBanner.animate().slideY() : Container(),
],
),
onInsertMessage: (message) => addMessage(message),
onUpdateMessage: (message) => updateMessage(message),
onDeleteMessage: (message) => deleteMessage(message),
onCallStarted: (call) => setState(() => _ongoingCall = call),
onCallEnded: () => setState(() => _ongoingCall = null),
);
},
),
),
ChatMessageEditor(
channel: widget.alias,
editing: _editingItem,
replying: _replyingItem,
onReset: () => setState(() {
_editingItem = null;
_replyingItem = null;
}),
),
],
),
_chat.ongoingCall != null ? callBanner.animate().slideY() : Container(),
],
),
onInsertMessage: (message) => addMessage(message),
onUpdateMessage: (message) => updateMessage(message),
onDeleteMessage: (message) => deleteMessage(message),
onCallStarted: (call) => _chat.setOngoingCall(call),
onCallEnded: () => _chat.setOngoingCall(null),
);
},
);
}
}

View File

@ -5,8 +5,10 @@ import 'package:provider/provider.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/chat/chat.dart';
import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/chat/chat_new.dart';
import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -21,6 +23,68 @@ class ChatIndexScreen extends StatefulWidget {
}
class _ChatIndexScreenState extends State<ChatIndexScreen> {
Channel? _selectedChannel;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
return IndentWrapper(
title: AppLocalizations.of(context)!.chat,
appBarActions: const [NotificationButton()],
fixedAppBarColor: isLargeScreen,
child: isLargeScreen
? Row(
children: [
Flexible(
flex: 2,
child: ChatIndexScreenWidget(
onSelect: (item) {
setState(() => _selectedChannel = item);
},
),
),
const VerticalDivider(thickness: 0.3, width: 0.3),
Flexible(
flex: 4,
child: _selectedChannel == null
? const SelectionEmptyWidget()
: ChatScreenWidget(
key: Key('c${_selectedChannel!.id}'),
alias: _selectedChannel!.alias,
),
),
],
)
: ChatIndexScreenWidget(
onSelect: (item) async {
final result = await router.pushNamed(
'chat.channel',
pathParameters: {
'channel': item.alias,
},
);
switch (result) {
case 'refresh':
// fetchChannels();
}
},
),
);
}
}
class ChatIndexScreenWidget extends StatefulWidget {
final Function(Channel item) onSelect;
const ChatIndexScreenWidget({super.key, required this.onSelect});
@override
State<ChatIndexScreenWidget> createState() => _ChatIndexScreenWidgetState();
}
class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> {
List<Channel> _channels = List.empty();
Future<void> fetchChannels() async {
@ -61,9 +125,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
Widget build(BuildContext context) {
final auth = context.read<AuthProvider>();
return IndentWrapper(
title: AppLocalizations.of(context)!.chat,
appBarActions: const [NotificationButton()],
return Scaffold(
floatingActionButton: FutureBuilder(
future: auth.isAuthorized(),
builder: (context, snapshot) {
@ -78,43 +140,33 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
}
},
),
child: FutureBuilder(
future: auth.isAuthorized(),
builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data!) {
return const SignInRequiredScreen();
}
body: FutureBuilder(
future: auth.isAuthorized(),
builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data!) {
return const SignInRequiredScreen();
}
return RefreshIndicator(
onRefresh: () => fetchChannels(),
child: ListView.builder(
itemCount: _channels.length,
itemBuilder: (context, index) {
final element = _channels[index];
return ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.indigo,
child: Icon(Icons.tag, color: Colors.white),
),
title: Text(element.name),
subtitle: Text(element.description),
onTap: () async {
final result = await router.pushNamed(
'chat.channel',
pathParameters: {
'channel': element.alias,
},
);
switch (result) {
case 'refresh':
fetchChannels();
}
},
);
},
),
);
}),
return RefreshIndicator(
onRefresh: () => fetchChannels(),
child: ListView.builder(
itemCount: _channels.length,
itemBuilder: (context, index) {
final element = _channels[index];
return ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.indigo,
child: Icon(Icons.tag, color: Colors.white),
),
title: Text(element.name),
subtitle: Text(element.description),
onTap: () => widget.onSelect(element),
);
},
),
);
},
),
);
}
}

View File

@ -6,10 +6,12 @@ import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/posts/screen.dart';
import 'package:solian/utils/service_url.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart' as http;
import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/indent_wrapper.dart';
import 'package:solian/widgets/notification_notifier.dart';
import 'package:solian/widgets/posts/item.dart';
@ -22,8 +24,68 @@ class ExploreScreen extends StatefulWidget {
}
class _ExploreScreenState extends State<ExploreScreen> {
final PagingController<int, Post> _pagingController =
PagingController(firstPageKey: 0);
Post? _selectedPost;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isLargeScreen = screenWidth >= 600;
return IndentWrapper(
noSafeArea: true,
fixedAppBarColor: isLargeScreen,
appBarActions: const [NotificationButton()],
title: AppLocalizations.of(context)!.explore,
child: isLargeScreen
? Row(
children: [
Flexible(
flex: 2,
child: ExploreScreenWidget(
onSelect: (item) {
setState(() => _selectedPost = item);
},
),
),
const VerticalDivider(thickness: 0.3, width: 0.3),
Flexible(
flex: 4,
child: _selectedPost == null
? const SelectionEmptyWidget()
: PostScreenWidget(
key: Key('p${_selectedPost!.id}'),
dataset: _selectedPost!.dataset,
alias: _selectedPost!.alias,
),
),
],
)
: ExploreScreenWidget(
onSelect: (item) {
router.pushNamed(
'posts.screen',
pathParameters: {
'alias': item.alias,
'dataset': item.dataset,
},
);
},
),
);
}
}
class ExploreScreenWidget extends StatefulWidget {
final Function(Post item) onSelect;
const ExploreScreenWidget({super.key, required this.onSelect});
@override
State<ExploreScreenWidget> createState() => _ExploreScreenWidgetState();
}
class _ExploreScreenWidgetState extends State<ExploreScreenWidget> {
final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0);
final http.Client _client = http.Client();
@ -31,15 +93,12 @@ class _ExploreScreenState extends State<ExploreScreen> {
final offset = pageKey;
const take = 5;
var uri =
getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
var uri = getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
var res = await _client.get(uri);
if (res.statusCode == 200) {
final result =
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
final items =
result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
final items = result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
final isLastPage = (result.count - pageKey) < take;
if (isLastPage || result.data == null) {
_pagingController.appendLastPage(items);
@ -63,8 +122,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
Widget build(BuildContext context) {
final auth = context.read<AuthProvider>();
return IndentWrapper(
noSafeArea: true,
return Scaffold(
floatingActionButton: FutureBuilder(
future: auth.isAuthorized(),
builder: (context, snapshot) {
@ -72,7 +130,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
return FloatingActionButton(
child: const Icon(Icons.edit),
onPressed: () async {
final did = await router.pushNamed("posts.moments.editor");
final did = await router.pushNamed('posts.moments.editor');
if (did == true) _pagingController.refresh();
},
);
@ -81,9 +139,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
}
},
),
appBarActions: const [NotificationButton()],
title: AppLocalizations.of(context)!.explore,
child: RefreshIndicator(
body: RefreshIndicator(
onRefresh: () => Future.sync(
() => _pagingController.refresh(),
),
@ -93,15 +149,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
itemBuilder: (context, item, index) => PostItem(
item: item,
onUpdate: () => _pagingController.refresh(),
onTap: () {
router.pushNamed(
'posts.screen',
pathParameters: {
'alias': item.alias,
'dataset': '${item.modelType}s',
},
);
},
onTap: () => widget.onSelect(item),
),
),
),

View File

@ -97,7 +97,7 @@ class NotificationItem extends StatelessWidget {
padding: const EdgeInsets.only(
left: 16, right: 16, top: 34, bottom: 12),
child: Text(
"Links",
'Links',
style: Theme.of(context).textTheme.headlineSmall,
),
),
@ -151,7 +151,7 @@ class NotificationItem extends StatelessWidget {
text: item.subject,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const TextSpan(text: " is marked as read")
const TextSpan(text: ' is marked as read')
],
),
),

View File

@ -65,7 +65,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
? getRequestUri('interactive', '/api/p/$relatedDataset/$alias/comments')
: getRequestUri('interactive', '/api/p/comments/${widget.editing!.id}');
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
req.headers['Content-Type'] = 'application/json';
req.body = jsonEncode(<String, dynamic>{
'alias': _alias,
@ -140,12 +140,12 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
if (snapshot.hasData) {
var userinfo = snapshot.data;
return ListTile(
title: Text(userinfo["nick"]),
title: Text(userinfo['nick']),
subtitle: Text(
AppLocalizations.of(context)!.postIdentityNotify,
),
leading: AccountAvatar(
source: userinfo["picture"],
source: userinfo['picture'],
direct: true,
),
);

View File

@ -55,7 +55,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
? getRequestUri('interactive', '/api/p/moments')
: getRequestUri('interactive', '/api/p/moments/${widget.editing!.id}');
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
req.headers['Content-Type'] = 'application/json';
req.body = jsonEncode(<String, dynamic>{
'alias': _alias,
@ -130,12 +130,12 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
if (snapshot.hasData) {
var userinfo = snapshot.data;
return ListTile(
title: Text(userinfo["nick"]),
title: Text(userinfo['nick']),
subtitle: Text(
AppLocalizations.of(context)!.postIdentityNotify,
),
leading: AccountAvatar(
source: userinfo["picture"],
source: userinfo['picture'],
direct: true,
),
);

View File

@ -11,25 +11,43 @@ import 'package:solian/widgets/posts/comment_list.dart';
import 'package:solian/widgets/posts/item.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class PostScreen extends StatefulWidget {
class PostScreen extends StatelessWidget {
final String dataset;
final String alias;
const PostScreen({super.key, required this.alias, required this.dataset});
@override
State<PostScreen> createState() => _PostScreenState();
Widget build(BuildContext context) {
return IndentWrapper(
title: AppLocalizations.of(context)!.post,
noSafeArea: true,
hideDrawer: true,
child: PostScreenWidget(
dataset: dataset,
alias: alias,
),
);
}
}
class _PostScreenState extends State<PostScreen> {
class PostScreenWidget extends StatefulWidget {
final String dataset;
final String alias;
const PostScreenWidget({super.key, required this.dataset, required this.alias});
@override
State<PostScreenWidget> createState() => _PostScreenWidgetState();
}
class _PostScreenWidgetState extends State<PostScreenWidget> {
final _client = http.Client();
final PagingController<int, Post> _commentPagingController =
PagingController(firstPageKey: 0);
final PagingController<int, Post> _commentPagingController = PagingController(firstPageKey: 0);
Future<Post?> fetchPost(BuildContext context) async {
final uri = getRequestUri(
'interactive', '/api/p/${widget.dataset}/${widget.alias}');
final uri = getRequestUri('interactive', '/api/p/${widget.dataset}/${widget.alias}');
final res = await _client.get(uri);
if (res.statusCode != 200) {
final err = utf8.decode(res.bodyBytes);
@ -42,43 +60,38 @@ class _PostScreenState extends State<PostScreen> {
@override
Widget build(BuildContext context) {
return IndentWrapper(
noSafeArea: true,
hideDrawer: true,
title: AppLocalizations.of(context)!.post,
child: FutureBuilder(
future: fetchPost(context),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: PostItem(
item: snapshot.data!,
brief: false,
ripple: false,
),
return FutureBuilder(
future: fetchPost(context),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: PostItem(
item: snapshot.data!,
brief: false,
ripple: false,
),
SliverToBoxAdapter(
child: CommentListHeader(
related: snapshot.data!,
paging: _commentPagingController,
),
),
CommentList(
),
SliverToBoxAdapter(
child: CommentListHeader(
related: snapshot.data!,
dataset: widget.dataset,
paging: _commentPagingController,
),
],
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
),
CommentList(
related: snapshot.data!,
dataset: widget.dataset,
paging: _commentPagingController,
),
],
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
);
}