🚚 Update files layout of pods
This commit is contained in:
@@ -18,14 +18,12 @@ _PostComposeInitialState _$PostComposeInitialStateFromJson(
|
||||
.toList() ??
|
||||
const [],
|
||||
visibility: (json['visibility'] as num?)?.toInt(),
|
||||
replyingTo:
|
||||
json['replying_to'] == null
|
||||
? null
|
||||
: SnPost.fromJson(json['replying_to'] as Map<String, dynamic>),
|
||||
forwardingTo:
|
||||
json['forwarding_to'] == null
|
||||
? null
|
||||
: SnPost.fromJson(json['forwarding_to'] as Map<String, dynamic>),
|
||||
replyingTo: json['replying_to'] == null
|
||||
? null
|
||||
: SnPost.fromJson(json['replying_to'] as Map<String, dynamic>),
|
||||
forwardingTo: json['forwarding_to'] == null
|
||||
? null
|
||||
: SnPost.fromJson(json['forwarding_to'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PostComposeInitialStateToJson(
|
||||
|
||||
@@ -7,9 +7,12 @@ 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/posts/post_filter.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:island/pods/paging.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/paging/pagination_list.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
final postSearchProvider = AsyncNotifierProvider.autoDispose(
|
||||
@@ -36,6 +39,14 @@ class PostSearchNotifier extends AsyncNotifier<List<SnPost>>
|
||||
return [];
|
||||
}
|
||||
|
||||
bool? _includeReplies;
|
||||
bool _mediaOnly = false;
|
||||
String? _queryTerm;
|
||||
String? _order;
|
||||
bool _orderDesc = true;
|
||||
int? _periodStart;
|
||||
int? _periodEnd;
|
||||
|
||||
Future<void> search(
|
||||
String query, {
|
||||
String? pubName,
|
||||
@@ -45,6 +56,13 @@ class PostSearchNotifier extends AsyncNotifier<List<SnPost>>
|
||||
List<String>? tags,
|
||||
bool shuffle = false,
|
||||
bool? pinned,
|
||||
bool? includeReplies,
|
||||
bool mediaOnly = false,
|
||||
String? queryTerm,
|
||||
String? order,
|
||||
bool orderDesc = true,
|
||||
int? periodStart,
|
||||
int? periodEnd,
|
||||
}) async {
|
||||
_currentQuery = query.trim();
|
||||
_pubName = pubName;
|
||||
@@ -54,6 +72,13 @@ class PostSearchNotifier extends AsyncNotifier<List<SnPost>>
|
||||
_tags = tags;
|
||||
_shuffle = shuffle;
|
||||
_pinned = pinned;
|
||||
_includeReplies = includeReplies;
|
||||
_mediaOnly = mediaOnly;
|
||||
_queryTerm = queryTerm;
|
||||
_order = order;
|
||||
_orderDesc = orderDesc;
|
||||
_periodStart = periodStart;
|
||||
_periodEnd = periodEnd;
|
||||
|
||||
final hasFilters =
|
||||
pubName != null ||
|
||||
@@ -62,7 +87,13 @@ class PostSearchNotifier extends AsyncNotifier<List<SnPost>>
|
||||
categories != null ||
|
||||
tags != null ||
|
||||
shuffle ||
|
||||
pinned != null;
|
||||
pinned != null ||
|
||||
includeReplies != null ||
|
||||
mediaOnly ||
|
||||
queryTerm != null ||
|
||||
order != null ||
|
||||
periodStart != null ||
|
||||
periodEnd != null;
|
||||
|
||||
if (_currentQuery.isEmpty && !hasFilters) {
|
||||
state = const AsyncData([]);
|
||||
@@ -91,6 +122,13 @@ class PostSearchNotifier extends AsyncNotifier<List<SnPost>>
|
||||
if (_categories != null) 'categories': _categories,
|
||||
if (_shuffle) 'shuffle': true,
|
||||
if (_pinned != null) 'pinned': _pinned,
|
||||
if (_includeReplies != null) 'includeReplies': _includeReplies,
|
||||
if (_mediaOnly) 'mediaOnly': true,
|
||||
if (_queryTerm != null) 'queryTerm': _queryTerm,
|
||||
if (_order != null) 'order': _order,
|
||||
if (_orderDesc) 'orderDesc': true,
|
||||
if (_periodStart != null) 'periodStart': _periodStart,
|
||||
if (_periodEnd != null) 'periodEnd': _periodEnd,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -117,6 +155,17 @@ class PostSearchScreen extends HookConsumerWidget {
|
||||
final shuffleValue = useState(false);
|
||||
final pinnedValue = useState<bool?>(null);
|
||||
|
||||
// State variables for PostFilterWidget
|
||||
final categoryTabController = useTabController(initialLength: 3);
|
||||
final includeReplies = useState<bool?>(null);
|
||||
final mediaOnly = useState(false);
|
||||
final queryTerm = useState<String?>(null);
|
||||
final order = useState<String?>('date');
|
||||
final orderDesc = useState(true);
|
||||
final periodStart = useState<int?>(null);
|
||||
final periodEnd = useState<int?>(null);
|
||||
final showAdvancedFilters = useState(false);
|
||||
|
||||
useEffect(() {
|
||||
return () {
|
||||
searchController.dispose();
|
||||
@@ -130,7 +179,21 @@ class PostSearchScreen extends HookConsumerWidget {
|
||||
if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel();
|
||||
|
||||
debounceTimer.value = Timer(debounce, () {
|
||||
ref.read(postSearchProvider.notifier).search(query);
|
||||
ref
|
||||
.read(postSearchProvider.notifier)
|
||||
.search(
|
||||
query,
|
||||
type: categoryTabController.index == 1
|
||||
? 0
|
||||
: (categoryTabController.index == 2 ? 1 : null),
|
||||
includeReplies: includeReplies.value,
|
||||
mediaOnly: mediaOnly.value,
|
||||
queryTerm: queryTerm.value,
|
||||
order: order.value,
|
||||
orderDesc: orderDesc.value,
|
||||
periodStart: periodStart.value,
|
||||
periodEnd: periodEnd.value,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,20 +205,28 @@ class PostSearchScreen extends HookConsumerWidget {
|
||||
.read(postSearchProvider.notifier)
|
||||
.search(
|
||||
query,
|
||||
pubName:
|
||||
pubNameController.text.isNotEmpty
|
||||
? pubNameController.text
|
||||
: null,
|
||||
realm:
|
||||
realmController.text.isNotEmpty ? realmController.text : null,
|
||||
type: typeValue.value,
|
||||
categories:
|
||||
selectedCategories.value.isNotEmpty
|
||||
? selectedCategories.value
|
||||
: null,
|
||||
pubName: pubNameController.text.isNotEmpty
|
||||
? pubNameController.text
|
||||
: null,
|
||||
realm: realmController.text.isNotEmpty
|
||||
? realmController.text
|
||||
: null,
|
||||
type: categoryTabController.index == 1
|
||||
? 0
|
||||
: (categoryTabController.index == 2 ? 1 : null),
|
||||
categories: selectedCategories.value.isNotEmpty
|
||||
? selectedCategories.value
|
||||
: null,
|
||||
tags: selectedTags.value.isNotEmpty ? selectedTags.value : null,
|
||||
shuffle: shuffleValue.value,
|
||||
pinned: pinnedValue.value,
|
||||
includeReplies: includeReplies.value,
|
||||
mediaOnly: mediaOnly.value,
|
||||
queryTerm: queryTerm.value,
|
||||
order: order.value,
|
||||
orderDesc: orderDesc.value,
|
||||
periodStart: periodStart.value,
|
||||
periodEnd: periodEnd.value,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -164,187 +235,213 @@ class PostSearchScreen extends HookConsumerWidget {
|
||||
showFilters.value = !showFilters.value;
|
||||
}
|
||||
|
||||
void applyFilters() {
|
||||
onSearchWithFilters(searchController.text);
|
||||
}
|
||||
|
||||
void clearFilters() {
|
||||
pubNameController.clear();
|
||||
realmController.clear();
|
||||
typeValue.value = null;
|
||||
selectedCategories.value = [];
|
||||
selectedTags.value = [];
|
||||
shuffleValue.value = false;
|
||||
pinnedValue.value = null;
|
||||
onSearchChanged(searchController.text);
|
||||
}
|
||||
|
||||
Widget buildFilterPanel() {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'filters'.tr(),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).padding(left: 4),
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: applyFilters,
|
||||
child: Text('apply'.tr()),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: clearFilters,
|
||||
child: Text('clear'.tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: pubNameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'pubName'.tr(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
onChanged:
|
||||
(value) => onSearchWithFilters(searchController.text),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: realmController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'realm'.tr(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
onChanged:
|
||||
(value) => onSearchWithFilters(searchController.text),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: shuffleValue.value,
|
||||
onChanged: (value) {
|
||||
shuffleValue.value = value ?? false;
|
||||
onSearchWithFilters(searchController.text);
|
||||
},
|
||||
),
|
||||
Text('shuffle'.tr()),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: pinnedValue.value ?? false,
|
||||
onChanged: (value) {
|
||||
pinnedValue.value = value;
|
||||
onSearchWithFilters(searchController.text);
|
||||
},
|
||||
),
|
||||
Text('pinned'.tr()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return PostFilterWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
includeReplies: includeReplies,
|
||||
mediaOnly: mediaOnly,
|
||||
queryTerm: queryTerm,
|
||||
order: order,
|
||||
orderDesc: orderDesc,
|
||||
periodStart: periodStart,
|
||||
periodEnd: periodEnd,
|
||||
showAdvancedFilters: showAdvancedFilters,
|
||||
hideSearch: true,
|
||||
);
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'search'.tr(),
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
appBar: isWideScreen(context)
|
||||
? null
|
||||
: AppBar(
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'search'.tr(),
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
onChanged: onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
onSearchWithFilters(value);
|
||||
},
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||
),
|
||||
onChanged: onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
onSearchWithFilters(value);
|
||||
},
|
||||
autofocus: true,
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
showFilters.value
|
||||
? Icons.filter_alt
|
||||
: Icons.filter_alt_outlined,
|
||||
),
|
||||
onPressed: toggleFilters,
|
||||
tooltip: 'toggleFilters'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
showFilters.value
|
||||
? Icons.filter_alt
|
||||
: Icons.filter_alt_outlined,
|
||||
),
|
||||
onPressed: toggleFilters,
|
||||
tooltip: 'toggleFilters'.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final searchState = ref.watch(postSearchProvider);
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
if (showFilters.value)
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 600),
|
||||
child: buildFilterPanel(),
|
||||
return isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(16),
|
||||
SliverToBoxAdapter(
|
||||
child: SearchBar(
|
||||
elevation: WidgetStateProperty.all(4),
|
||||
controller: searchController,
|
||||
hintText: 'search'.tr(),
|
||||
leading: const Icon(Icons.search),
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.symmetric(horizontal: 24),
|
||||
),
|
||||
onChanged: onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
onSearchWithFilters(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SliverGap(16),
|
||||
if (showFilters.value && !isWideScreen(context))
|
||||
SliverToBoxAdapter(child: buildFilterPanel()),
|
||||
// Use PaginationList with isSliver=true
|
||||
PaginationList(
|
||||
provider: postSearchProvider,
|
||||
notifier: postSearchProvider.notifier,
|
||||
isSliver: true,
|
||||
isRefreshable: false,
|
||||
itemBuilder: (context, index, post) {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: PostActionableItem(
|
||||
item: post,
|
||||
borderRadius: 8,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (searchState.value?.isEmpty == true &&
|
||||
searchController.text.isNotEmpty &&
|
||||
!searchState.isLoading)
|
||||
SliverFillRemaining(
|
||||
child: Center(child: Text('noResultsFound'.tr())),
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
).padding(left: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Use PaginationList with isSliver=true
|
||||
PaginationList(
|
||||
provider: postSearchProvider,
|
||||
notifier: postSearchProvider.notifier,
|
||||
isSliver: true,
|
||||
isRefreshable:
|
||||
false, // CustomScrollView handles refreshing usually, but here we don't have PullToRefresh
|
||||
itemBuilder: (context, index, post) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Gap(16),
|
||||
Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Symbols.tune,
|
||||
).padding(horizontal: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'filters'.tr(),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Symbols.filter_alt,
|
||||
fill: showFilters.value ? 1 : null,
|
||||
),
|
||||
onPressed: toggleFilters,
|
||||
tooltip: 'toggleFilters'.tr(),
|
||||
),
|
||||
const Gap(4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
if (showFilters.value) buildFilterPanel(),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: PostActionableItem(item: post, borderRadius: 8),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (searchState.value?.isEmpty == true &&
|
||||
searchController.text.isNotEmpty &&
|
||||
!searchState.isLoading)
|
||||
SliverFillRemaining(
|
||||
child: Center(child: Text('noResultsFound'.tr())),
|
||||
),
|
||||
],
|
||||
);
|
||||
],
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
if (showFilters.value)
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 600),
|
||||
child: buildFilterPanel(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Use PaginationList with isSliver=true
|
||||
PaginationList(
|
||||
provider: postSearchProvider,
|
||||
notifier: postSearchProvider.notifier,
|
||||
isSliver: true,
|
||||
isRefreshable: false,
|
||||
itemBuilder: (context, index, post) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 600),
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: PostActionableItem(
|
||||
item: post,
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (searchState.value?.isEmpty == true &&
|
||||
searchController.text.isNotEmpty &&
|
||||
!searchState.isLoading)
|
||||
SliverFillRemaining(
|
||||
child: Center(child: Text('noResultsFound'.tr())),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:island/widgets/activity_heatmap.dart';
|
||||
import 'package:island/widgets/posts/post_filter.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:island/services/color_extraction.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -82,8 +83,9 @@ class _PublisherBasisWidget extends StatelessWidget {
|
||||
size: 12,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(
|
||||
file: data.picture,
|
||||
@@ -121,8 +123,9 @@ class _PublisherBasisWidget extends StatelessWidget {
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary,
|
||||
offset: Offset(0, 48),
|
||||
child: ProfilePictureWidget(
|
||||
file: data.picture,
|
||||
@@ -201,45 +204,41 @@ class _PublisherBasisWidget extends StatelessWidget {
|
||||
),
|
||||
subStatus
|
||||
.when(
|
||||
data:
|
||||
(status) => FilledButton.icon(
|
||||
onPressed:
|
||||
subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label:
|
||||
Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
data: (status) => FilledButton.icon(
|
||||
onPressed: subscribing.value
|
||||
? null
|
||||
: (status.isSubscribed
|
||||
? unsubscribe
|
||||
: subscribe),
|
||||
icon: Icon(
|
||||
status.isSubscribed
|
||||
? Symbols.remove_circle
|
||||
: Symbols.add_circle,
|
||||
),
|
||||
label: Text(
|
||||
status.isSubscribed
|
||||
? 'unsubscribe'
|
||||
: 'subscribe',
|
||||
).tr(),
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
),
|
||||
error: (_, _) => const SizedBox(),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
loading: () => const SizedBox(
|
||||
height: 36,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(vertical: 12),
|
||||
],
|
||||
@@ -271,10 +270,10 @@ class _PublisherBadgesWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return (badges.value?.isNotEmpty ?? false)
|
||||
? Card(
|
||||
child: BadgeList(
|
||||
badges: badges.value!,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4)
|
||||
child: BadgeList(
|
||||
badges: badges.value!,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@@ -288,9 +287,9 @@ class _PublisherVerificationWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return (data.verification != null)
|
||||
? Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VerificationStatusCard(mark: data.verification!),
|
||||
)
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VerificationStatusCard(mark: data.verification!),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@@ -333,14 +332,12 @@ class _PublisherHeatmapWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return heatmap.when(
|
||||
data:
|
||||
(data) =>
|
||||
data != null
|
||||
? ActivityHeatmapWidget(
|
||||
heatmap: data,
|
||||
forceDense: forceDense,
|
||||
).padding(horizontal: 8)
|
||||
: const SizedBox.shrink(),
|
||||
data: (data) => data != null
|
||||
? ActivityHeatmapWidget(
|
||||
heatmap: data,
|
||||
forceDense: forceDense,
|
||||
).padding(horizontal: 8)
|
||||
: const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
);
|
||||
@@ -372,242 +369,16 @@ class _PublisherCategoryTabWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
children: [
|
||||
TabBar(
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [
|
||||
Tab(text: 'all'.tr()),
|
||||
Tab(text: 'postTypePost'.tr()),
|
||||
Tab(text: 'postArticle'.tr()),
|
||||
],
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
title: Text('reply'.tr()),
|
||||
value: includeReplies.value,
|
||||
tristate: true,
|
||||
onChanged: (value) {
|
||||
// Cycle through: null -> false -> true -> null
|
||||
if (includeReplies.value == null) {
|
||||
includeReplies.value = false;
|
||||
} else if (includeReplies.value == false) {
|
||||
includeReplies.value = true;
|
||||
} else {
|
||||
includeReplies.value = null;
|
||||
}
|
||||
},
|
||||
dense: true,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
secondary: const Icon(Symbols.reply),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
title: Text('attachments'.tr()),
|
||||
value: mediaOnly.value,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
mediaOnly.value = value;
|
||||
}
|
||||
},
|
||||
dense: true,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
secondary: const Icon(Symbols.attachment),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: Text('descendingOrder'.tr()),
|
||||
value: orderDesc.value,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
orderDesc.value = value;
|
||||
}
|
||||
},
|
||||
dense: true,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
secondary: const Icon(Symbols.sort),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
title: Text('advancedFilters'.tr()),
|
||||
leading: const Icon(Symbols.filter_list),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(const Radius.circular(8)),
|
||||
),
|
||||
trailing: Icon(
|
||||
showAdvancedFilters.value
|
||||
? Symbols.expand_less
|
||||
: Symbols.expand_more,
|
||||
),
|
||||
onTap: () {
|
||||
showAdvancedFilters.value = !showAdvancedFilters.value;
|
||||
},
|
||||
),
|
||||
if (showAdvancedFilters.value) ...[
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'search'.tr(),
|
||||
hintText: 'searchPosts'.tr(),
|
||||
prefixIcon: const Icon(Symbols.search),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
queryTerm.value = value.isEmpty ? null : value;
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'sortBy'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
value: order.value,
|
||||
items: [
|
||||
DropdownMenuItem(value: 'date', child: Text('date'.tr())),
|
||||
DropdownMenuItem(
|
||||
value: 'popularity',
|
||||
child: Text('popularity'.tr()),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
order.value = value;
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate:
|
||||
periodStart.value != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(
|
||||
periodStart.value! * 1000,
|
||||
)
|
||||
: DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime.now().add(
|
||||
const Duration(days: 365),
|
||||
),
|
||||
);
|
||||
if (pickedDate != null) {
|
||||
periodStart.value =
|
||||
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'fromDate'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
suffixIcon: const Icon(Symbols.calendar_today),
|
||||
),
|
||||
child: Text(
|
||||
periodStart.value != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(
|
||||
periodStart.value! * 1000,
|
||||
).toString().split(' ')[0]
|
||||
: 'selectDate'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate:
|
||||
periodEnd.value != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(
|
||||
periodEnd.value! * 1000,
|
||||
)
|
||||
: DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime.now().add(
|
||||
const Duration(days: 365),
|
||||
),
|
||||
);
|
||||
if (pickedDate != null) {
|
||||
periodEnd.value =
|
||||
pickedDate.millisecondsSinceEpoch ~/ 1000;
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'toDate'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
suffixIcon: const Icon(Symbols.calendar_today),
|
||||
),
|
||||
child: Text(
|
||||
periodEnd.value != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(
|
||||
periodEnd.value! * 1000,
|
||||
).toString().split(' ')[0]
|
||||
: 'selectDate'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
return PostFilterWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
includeReplies: includeReplies,
|
||||
mediaOnly: mediaOnly,
|
||||
queryTerm: queryTerm,
|
||||
order: order,
|
||||
orderDesc: orderDesc,
|
||||
periodStart: periodStart,
|
||||
periodEnd: periodEnd,
|
||||
showAdvancedFilters: showAdvancedFilters,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -739,205 +510,34 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return publisher.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar:
|
||||
isWideScreen(context)
|
||||
? AppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body:
|
||||
isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(16),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text('pinnedPosts'.tr()),
|
||||
leading: const Icon(Symbols.push_pin),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
isPinnedExpanded.value
|
||||
? Symbols.expand_less
|
||||
: Symbols.expand_more,
|
||||
),
|
||||
onTap:
|
||||
() =>
|
||||
isPinnedExpanded.value =
|
||||
!isPinnedExpanded.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
...[
|
||||
if (isPinnedExpanded.value)
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherCategoryTabWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
includeReplies: includeReplies,
|
||||
mediaOnly: mediaOnly,
|
||||
queryTerm: queryTerm,
|
||||
order: order,
|
||||
orderDesc: orderDesc,
|
||||
periodStart: periodStart,
|
||||
periodEnd: periodEnd,
|
||||
showAdvancedFilters: showAdvancedFilters,
|
||||
),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(
|
||||
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
|
||||
),
|
||||
pubName: name,
|
||||
pinned: false,
|
||||
type:
|
||||
categoryTab.value == 1
|
||||
? 0
|
||||
: (categoryTab.value == 2 ? 1 : null),
|
||||
includeReplies: includeReplies.value,
|
||||
mediaOnly: mediaOnly.value,
|
||||
queryTerm: queryTerm.value,
|
||||
order: order.value,
|
||||
orderDesc: orderDesc.value,
|
||||
periodStart: periodStart.value,
|
||||
periodEnd: periodEnd.value,
|
||||
),
|
||||
SliverGap(
|
||||
MediaQuery.of(context).padding.bottom + 16,
|
||||
),
|
||||
],
|
||||
).padding(left: 8),
|
||||
),
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(horizontal: 4, top: 20),
|
||||
_PublisherBadgesWidget(
|
||||
data: data,
|
||||
badges: badges,
|
||||
),
|
||||
_PublisherVerificationWidget(data: data),
|
||||
_PublisherBioWidget(data: data),
|
||||
_PublisherHeatmapWidget(
|
||||
heatmap: heatmap,
|
||||
forceDense: true,
|
||||
).padding(vertical: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: CustomScrollView(
|
||||
data: (data) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: isWideScreen(context)
|
||||
? AppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child:
|
||||
data.background?.id != null
|
||||
? CloudImageWidget(
|
||||
file: data.background,
|
||||
)
|
||||
: Container(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(
|
||||
context,
|
||||
).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(horizontal: 4, top: 8),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBadgesWidget(
|
||||
data: data,
|
||||
badges: badges,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherVerificationWidget(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBioWidget(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherHeatmapWidget(
|
||||
heatmap: heatmap,
|
||||
).padding(vertical: 4),
|
||||
),
|
||||
SliverGap(16),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
@@ -946,15 +546,19 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text('pinnedPosts'.tr()),
|
||||
leading: const Icon(Symbols.push_pin),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
isPinnedExpanded.value
|
||||
? Symbols.expand_less
|
||||
: Symbols.expand_more,
|
||||
),
|
||||
onTap:
|
||||
() =>
|
||||
isPinnedExpanded.value =
|
||||
!isPinnedExpanded.value,
|
||||
onTap: () => isPinnedExpanded.value =
|
||||
!isPinnedExpanded.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -981,10 +585,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
pubName: name,
|
||||
pinned: false,
|
||||
type:
|
||||
categoryTab.value == 1
|
||||
? 0
|
||||
: (categoryTab.value == 2 ? 1 : null),
|
||||
type: categoryTab.value == 1
|
||||
? 0
|
||||
: (categoryTab.value == 2 ? 1 : null),
|
||||
includeReplies: includeReplies.value,
|
||||
mediaOnly: mediaOnly.value,
|
||||
queryTerm: queryTerm.value,
|
||||
@@ -995,20 +598,158 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
).padding(left: 8),
|
||||
),
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(horizontal: 4, top: 20),
|
||||
_PublisherBadgesWidget(data: data, badges: badges),
|
||||
_PublisherVerificationWidget(data: data),
|
||||
_PublisherBioWidget(data: data),
|
||||
_PublisherHeatmapWidget(
|
||||
heatmap: heatmap,
|
||||
forceDense: true,
|
||||
).padding(vertical: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
error:
|
||||
(error, stackTrace) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: Text(error.toString())),
|
||||
),
|
||||
loading:
|
||||
() => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: data.background?.id != null
|
||||
? CloudImageWidget(file: data.background)
|
||||
: Container(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
data.nick,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [appbarShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBasisWidget(
|
||||
data: data,
|
||||
subStatus: subStatus,
|
||||
subscribing: subscribing,
|
||||
subscribe: subscribe,
|
||||
unsubscribe: unsubscribe,
|
||||
).padding(horizontal: 4, top: 8),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherBadgesWidget(data: data, badges: badges),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherVerificationWidget(data: data),
|
||||
),
|
||||
SliverToBoxAdapter(child: _PublisherBioWidget(data: data)),
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherHeatmapWidget(
|
||||
heatmap: heatmap,
|
||||
).padding(vertical: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: ListTile(
|
||||
title: Text('pinnedPosts'.tr()),
|
||||
trailing: Icon(
|
||||
isPinnedExpanded.value
|
||||
? Symbols.expand_less
|
||||
: Symbols.expand_more,
|
||||
),
|
||||
onTap: () =>
|
||||
isPinnedExpanded.value = !isPinnedExpanded.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
...[
|
||||
if (isPinnedExpanded.value)
|
||||
SliverPostList(pubName: name, pinned: true),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: _PublisherCategoryTabWidget(
|
||||
categoryTabController: categoryTabController,
|
||||
includeReplies: includeReplies,
|
||||
mediaOnly: mediaOnly,
|
||||
queryTerm: queryTerm,
|
||||
order: order,
|
||||
orderDesc: orderDesc,
|
||||
periodStart: periodStart,
|
||||
periodEnd: periodEnd,
|
||||
showAdvancedFilters: showAdvancedFilters,
|
||||
),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(
|
||||
'${categoryTab.value}-${includeReplies.value}-${mediaOnly.value}-${queryTerm.value}-${order.value}-${orderDesc.value}-${periodStart.value}-${periodEnd.value}',
|
||||
),
|
||||
pubName: name,
|
||||
pinned: false,
|
||||
type: categoryTab.value == 1
|
||||
? 0
|
||||
: (categoryTab.value == 2 ? 1 : null),
|
||||
includeReplies: includeReplies.value,
|
||||
mediaOnly: mediaOnly.value,
|
||||
queryTerm: queryTerm.value,
|
||||
order: order.value,
|
||||
orderDesc: orderDesc.value,
|
||||
periodStart: periodStart.value,
|
||||
periodEnd: periodEnd.value,
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: Text(error.toString())),
|
||||
),
|
||||
loading: () => AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user