Sender Invitation
This commit is contained in:
36
frontend/pweb/lib/pages/invitations/widgets/list/body.dart
Normal file
36
frontend/pweb/lib/pages/invitations/widgets/list/body.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/invitation/invitation.dart';
|
||||
|
||||
import 'package:pweb/pages/invitations/widgets/card/card.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class InvitationListBody extends StatelessWidget {
|
||||
final List<Invitation> invitations;
|
||||
|
||||
const InvitationListBody({super.key, required this.invitations});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (invitations.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(loc.invitationListEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: invitations.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
itemBuilder: (_, index) => InvitationsCard(invitation: invitations[index]),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
frontend/pweb/lib/pages/invitations/widgets/list/list.dart
Normal file
16
frontend/pweb/lib/pages/invitations/widgets/list/list.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/invitations/widgets/list/view.dart';
|
||||
|
||||
|
||||
class InvitationsList extends StatefulWidget {
|
||||
const InvitationsList({super.key});
|
||||
|
||||
@override
|
||||
State<InvitationsList> createState() => _InvitationsListState();
|
||||
}
|
||||
|
||||
class _InvitationsListState extends State<InvitationsList> {
|
||||
@override
|
||||
Widget build(BuildContext context) => const InvitationListView();
|
||||
}
|
||||
111
frontend/pweb/lib/pages/invitations/widgets/list/view.dart
Normal file
111
frontend/pweb/lib/pages/invitations/widgets/list/view.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/invitation/invitation.dart';
|
||||
import 'package:pshared/provider/invitations.dart';
|
||||
|
||||
import 'package:pweb/models/invitation_filter.dart';
|
||||
import 'package:pweb/pages/invitations/widgets/filter/invitation_filter.dart';
|
||||
import 'package:pweb/pages/invitations/widgets/filter/chips.dart';
|
||||
import 'package:pweb/pages/invitations/widgets/list/body.dart';
|
||||
import 'package:pweb/pages/invitations/widgets/search_field.dart';
|
||||
import 'package:pweb/widgets/error/snackbar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class InvitationListView extends StatefulWidget {
|
||||
const InvitationListView({super.key});
|
||||
|
||||
@override
|
||||
State<InvitationListView> createState() => _InvitationListViewState();
|
||||
}
|
||||
|
||||
class _InvitationListViewState extends State<InvitationListView> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
InvitationFilter _filter = InvitationFilter.all;
|
||||
String _query = '';
|
||||
Object? _lastError;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setQuery(String query) {
|
||||
setState(() => _query = query.trim().toLowerCase());
|
||||
}
|
||||
|
||||
void _setFilter(InvitationFilter filter) {
|
||||
setState(() => _filter = filter);
|
||||
}
|
||||
|
||||
void _notifyError(BuildContext context, Object error, AppLocalizations loc) {
|
||||
if (identical(error, _lastError)) {
|
||||
return;
|
||||
}
|
||||
_lastError = error;
|
||||
postNotifyUserOfErrorX(
|
||||
context: context,
|
||||
errorSituation: loc.errorLoadingInvitations,
|
||||
exception: error,
|
||||
);
|
||||
}
|
||||
|
||||
List<Invitation> _filteredInvitations(List<Invitation> invitations) {
|
||||
final showArchived = _filter == InvitationFilter.archived;
|
||||
Iterable<Invitation> filtered = invitations
|
||||
.where((inv) => showArchived ? inv.isArchived : !inv.isArchived)
|
||||
.where((inv) => invitationFilterMatches(_filter, inv));
|
||||
|
||||
if (_query.isNotEmpty) {
|
||||
filtered = filtered.where((inv) {
|
||||
return inv.inviteeDisplayName.toLowerCase().contains(_query)
|
||||
|| inv.content.email.toLowerCase().contains(_query);
|
||||
});
|
||||
}
|
||||
|
||||
final sorted = filtered.toList()
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
return sorted;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<InvitationsProvider>();
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
_notifyError(context, provider.error!, loc);
|
||||
} else {
|
||||
_lastError = null;
|
||||
}
|
||||
|
||||
final invitations = _filteredInvitations(provider.invitations);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InvitationSearchField(
|
||||
controller: _searchController,
|
||||
hintText: loc.invitationSearchHint,
|
||||
onChanged: _setQuery,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InvitationFilterChips(
|
||||
selectedFilter: _filter,
|
||||
onSelected: _setFilter,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
InvitationListBody(invitations: invitations),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user