.github
android
api
assets
debian
drift_schemas
ios
lib
controllers
database
providers
screens
types
widgets
account
attachment
chat
navigation
post
post_comment_list.dart
post_item.dart
post_media_pending_list.dart
post_meta_editor.dart
post_mini_editor.dart
post_poll.dart
post_poll_editor.dart
post_reaction.dart
post_tags_field.dart
publisher_popover.dart
realm
about.dart
app_bar_leading.dart
connection_indicator.dart
context_menu.dart
dialog.dart
link_preview.dart
loading_indicator.dart
markdown_content.dart
notify_indicator.dart
unauthorized_hint.dart
universal_image.dart
updater.dart
version_label.dart
firebase_options.dart
logger.dart
main.dart
router.dart
theme.dart
linux
macos
snap
test
web
windows
.gitignore
.metadata
.roadsignrc
README.md
analysis_options.yaml
build.yaml
devtools_options.yaml
firebase.json
pubspec.lock
pubspec.yaml
roadsign.toml
139 lines
4.4 KiB
Dart
139 lines
4.4 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
import 'package:surface/providers/sn_network.dart';
|
|
import 'package:surface/providers/userinfo.dart';
|
|
import 'package:surface/types/poll.dart';
|
|
import 'package:surface/widgets/dialog.dart';
|
|
|
|
class PostPoll extends StatefulWidget {
|
|
final SnPoll poll;
|
|
|
|
const PostPoll({super.key, required this.poll});
|
|
|
|
@override
|
|
State<PostPoll> createState() => _PostPollState();
|
|
}
|
|
|
|
class _PostPollState extends State<PostPoll> {
|
|
bool _isBusy = false;
|
|
late SnPoll _poll;
|
|
|
|
@override
|
|
void initState() {
|
|
_poll = widget.poll;
|
|
_fetchAnswer();
|
|
super.initState();
|
|
}
|
|
|
|
String? _answeredChoice;
|
|
|
|
Future<void> _refreshPoll() async {
|
|
final sn = context.read<SnNetworkProvider>();
|
|
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}');
|
|
if (!mounted) return;
|
|
setState(() => _poll = SnPoll.fromJson(resp.data!));
|
|
}
|
|
|
|
Future<void> _fetchAnswer() async {
|
|
final ua = context.read<UserProvider>();
|
|
if (!ua.isAuthorized) return;
|
|
try {
|
|
setState(() => _isBusy = true);
|
|
final sn = context.read<SnNetworkProvider>();
|
|
final resp =
|
|
await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
|
_answeredChoice = resp.data?['answer'];
|
|
if (!mounted) return;
|
|
setState(() {});
|
|
} catch (err) {
|
|
if (!mounted) return;
|
|
// ignore because it may not found
|
|
} finally {
|
|
setState(() => _isBusy = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _voteForOption(SnPollOption option) async {
|
|
final ua = context.read<UserProvider>();
|
|
if (!ua.isAuthorized) return;
|
|
try {
|
|
setState(() => _isBusy = true);
|
|
final sn = context.read<SnNetworkProvider>();
|
|
await sn.client.post('/cgi/co/polls/${widget.poll.id}/answer', data: {
|
|
'answer': option.id,
|
|
});
|
|
if (!mounted) return;
|
|
HapticFeedback.heavyImpact();
|
|
_answeredChoice = option.id;
|
|
_refreshPoll();
|
|
} catch (err) {
|
|
if (!mounted) return;
|
|
context.showErrorDialog(err);
|
|
} finally {
|
|
setState(() => _isBusy = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
margin: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
for (final option in _poll.options)
|
|
Stack(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
child: Container(
|
|
height: 60,
|
|
width: MediaQuery.of(context).size.width *
|
|
(_poll.metric.byOptionsPercentage[option.id] ?? 0)
|
|
.toDouble(),
|
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
),
|
|
),
|
|
ListTile(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
minTileHeight: 60,
|
|
leading: _answeredChoice == option.id
|
|
? const Icon(Symbols.circle, fill: 1)
|
|
: const Icon(Symbols.circle),
|
|
title: Text(option.name),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'pollVotes'
|
|
.plural(_poll.metric.byOptions[option.id] ?? 0),
|
|
),
|
|
Text(' · ').padding(horizontal: 4),
|
|
Text(
|
|
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
|
),
|
|
],
|
|
),
|
|
if (option.description.isNotEmpty)
|
|
Text(option.description),
|
|
],
|
|
),
|
|
onTap: _isBusy ? null : () => _voteForOption(option),
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|