202 lines
8.4 KiB
Dart
202 lines
8.4 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:gap/gap.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:island/models/poll.dart';
|
|
import 'package:island/models/publisher.dart';
|
|
import 'package:island/screens/creators/poll/poll_list.dart';
|
|
import 'package:island/widgets/content/sheet.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
import 'package:island/widgets/post/publishers_modal.dart';
|
|
|
|
/// Bottom sheet for selecting or creating a poll. Returns SnPoll via Navigator.pop.
|
|
class ComposePollSheet extends HookConsumerWidget {
|
|
/// Optional publisher name to filter polls and prefill creation.
|
|
final String? pubName;
|
|
|
|
const ComposePollSheet({super.key, this.pubName});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final selectedPublisher = useState<String?>(pubName);
|
|
final isPushing = useState(false);
|
|
final errorText = useState<String?>(null);
|
|
|
|
return SheetScaffold(
|
|
heightFactor: 0.6,
|
|
titleText: 'poll'.tr(),
|
|
child: DefaultTabController(
|
|
length: 2,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
TabBar(
|
|
tabs: [
|
|
Tab(text: 'pollsRecent'.tr()),
|
|
Tab(text: 'pollCreateNew'.tr()),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: TabBarView(
|
|
children: [
|
|
// Link/Select existing poll list
|
|
PagingHelperView(
|
|
provider: pollListNotifierProvider(pubName),
|
|
futureRefreshable: pollListNotifierProvider(pubName).future,
|
|
notifierRefreshable:
|
|
pollListNotifierProvider(pubName).notifier,
|
|
contentBuilder:
|
|
(data, widgetCount, endItemView) => ListView.builder(
|
|
padding: EdgeInsets.zero,
|
|
itemCount: widgetCount,
|
|
itemBuilder: (context, index) {
|
|
if (index == widgetCount - 1) {
|
|
return endItemView;
|
|
}
|
|
|
|
final poll = data.items[index];
|
|
|
|
return ListTile(
|
|
leading: const Icon(Symbols.how_to_vote, fill: 1),
|
|
title: Text(poll.title ?? 'untitled'.tr()),
|
|
subtitle: _buildPollSubtitle(poll),
|
|
onTap: () {
|
|
Navigator.of(context).pop(poll);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Create new poll and return it
|
|
SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'pollCreateNewHint',
|
|
).tr().fontSize(13).opacity(0.85).padding(bottom: 8),
|
|
ListTile(
|
|
title: Text(
|
|
selectedPublisher.value == null
|
|
? 'publisher'.tr()
|
|
: '@${selectedPublisher.value}',
|
|
),
|
|
subtitle: Text(
|
|
selectedPublisher.value == null
|
|
? 'publisherHint'.tr()
|
|
: 'selected'.tr(),
|
|
),
|
|
leading: const Icon(Symbols.account_circle),
|
|
trailing: const Icon(Symbols.chevron_right),
|
|
onTap: () async {
|
|
final picked =
|
|
await showModalBottomSheet<SnPublisher>(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
builder: (context) => const PublisherModal(),
|
|
);
|
|
if (picked != null) {
|
|
try {
|
|
final name = picked.name;
|
|
if (name.isNotEmpty) {
|
|
selectedPublisher.value = name;
|
|
errorText.value = null;
|
|
}
|
|
} catch (_) {
|
|
// ignore
|
|
}
|
|
}
|
|
},
|
|
),
|
|
if (errorText.value != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
top: 4,
|
|
),
|
|
child: Text(
|
|
errorText.value!,
|
|
style: TextStyle(color: Colors.red[700]),
|
|
),
|
|
),
|
|
const Gap(16),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: FilledButton.icon(
|
|
icon:
|
|
isPushing.value
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: const Icon(Symbols.add_circle),
|
|
label: Text('create'.tr()),
|
|
onPressed:
|
|
isPushing.value
|
|
? null
|
|
: () async {
|
|
final pub = selectedPublisher.value ?? '';
|
|
if (pub.isEmpty) {
|
|
errorText.value =
|
|
'publisherCannotBeEmpty'.tr();
|
|
return;
|
|
}
|
|
errorText.value = null;
|
|
|
|
isPushing.value = true;
|
|
// Push to creatorPollNew route and await result
|
|
final result = await GoRouter.of(
|
|
context,
|
|
).push<SnPoll>(
|
|
'/creators/$pub/polls/new',
|
|
);
|
|
|
|
if (result == null) {
|
|
isPushing.value = false;
|
|
return;
|
|
}
|
|
|
|
if (!context.mounted) return;
|
|
|
|
// Return created poll to caller of this bottom sheet
|
|
Navigator.of(context).pop(result);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
).padding(horizontal: 24, vertical: 24),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget? _buildPollSubtitle(SnPoll poll) {
|
|
try {
|
|
final SnPoll dyn = poll;
|
|
final List<SnPollQuestion> options = dyn.questions;
|
|
if (options.isEmpty) return null;
|
|
final preview = options.take(3).map((e) => e.title).join(' · ');
|
|
if (preview.trim().isEmpty) return null;
|
|
return Text(preview);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|