From 81f65750b7db28c49c0706833f191663510f4389 Mon Sep 17 00:00:00 2001 From: lars Date: Fri, 10 Oct 2025 02:01:43 +0200 Subject: [PATCH] added functionality to show own votes in polls --- .../talk/getPoll/getPollState.dart | 22 +++++++ .../talk/getPoll/getPollStateResponse.dart | 50 ++++++++++++++++ .../talk/getPoll/getPollStateResponse.g.dart | 60 +++++++++++++++++++ .../pages/talk/components/chatBubble.dart | 40 +++++++++++++ .../pages/talk/components/chatMessage.dart | 8 +++ .../talk/components/pollOptionsList.dart | 47 +++++++++++++++ 6 files changed, 227 insertions(+) create mode 100644 lib/api/marianumcloud/talk/getPoll/getPollState.dart create mode 100644 lib/api/marianumcloud/talk/getPoll/getPollStateResponse.dart create mode 100644 lib/api/marianumcloud/talk/getPoll/getPollStateResponse.g.dart create mode 100644 lib/view/pages/talk/components/pollOptionsList.dart diff --git a/lib/api/marianumcloud/talk/getPoll/getPollState.dart b/lib/api/marianumcloud/talk/getPoll/getPollState.dart new file mode 100644 index 0000000..e7950c8 --- /dev/null +++ b/lib/api/marianumcloud/talk/getPoll/getPollState.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:http/http.dart' as http; + +import '../talkApi.dart'; +import 'getPollStateResponse.dart'; + +class GetPollState extends TalkApi { + String token; + int pollId; + GetPollState({required this.token, required this.pollId}) : super('v1/poll/$token/$pollId', null); + + @override + GetPollStateResponse assemble(String raw) { + log(raw); + return GetPollStateResponse.fromJson(jsonDecode(raw)['ocs']); + } + + @override + Future request(Uri uri, Object? body, Map? headers) => http.get(uri, headers: headers); +} diff --git a/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.dart b/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.dart new file mode 100644 index 0000000..6678803 --- /dev/null +++ b/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.dart @@ -0,0 +1,50 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../../../apiResponse.dart'; + +part 'getPollStateResponse.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetPollStateResponse extends ApiResponse { + GetPollStateResponseObject data; + + GetPollStateResponse(this.data); + + factory GetPollStateResponse.fromJson(Map json) => _$GetPollStateResponseFromJson(json); + Map toJson() => _$GetPollStateResponseToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetPollStateResponseObject { + int id; + String question; + List options; + dynamic votes; + String actorType; + String actorId; + String actorDisplayName; + int status; + int resultMode; + int maxVotes; + List votedSelf; + int? numVoters; + List? details; + + GetPollStateResponseObject( + this.id, + this.question, + this.options, + this.votes, + this.actorType, + this.actorId, + this.actorDisplayName, + this.status, + this.resultMode, + this.maxVotes, + this.votedSelf, + this.numVoters, + this.details); + + factory GetPollStateResponseObject.fromJson(Map json) => _$GetPollStateResponseObjectFromJson(json); + Map toJson() => _$GetPollStateResponseObjectToJson(this); +} diff --git a/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.g.dart b/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.g.dart new file mode 100644 index 0000000..f54d062 --- /dev/null +++ b/lib/api/marianumcloud/talk/getPoll/getPollStateResponse.g.dart @@ -0,0 +1,60 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getPollStateResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetPollStateResponse _$GetPollStateResponseFromJson( + Map json) => + GetPollStateResponse( + GetPollStateResponseObject.fromJson(json['data'] as Map), + )..headers = (json['headers'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ); + +Map _$GetPollStateResponseToJson( + GetPollStateResponse instance) => + { + if (instance.headers case final value?) 'headers': value, + 'data': instance.data.toJson(), + }; + +GetPollStateResponseObject _$GetPollStateResponseObjectFromJson( + Map json) => + GetPollStateResponseObject( + (json['id'] as num).toInt(), + json['question'] as String, + (json['options'] as List).map((e) => e as String).toList(), + json['votes'], + json['actorType'] as String, + json['actorId'] as String, + json['actorDisplayName'] as String, + (json['status'] as num).toInt(), + (json['resultMode'] as num).toInt(), + (json['maxVotes'] as num).toInt(), + (json['votedSelf'] as List) + .map((e) => (e as num).toInt()) + .toList(), + (json['numVoters'] as num?)?.toInt(), + (json['details'] as List?)?.map((e) => e as String).toList(), + ); + +Map _$GetPollStateResponseObjectToJson( + GetPollStateResponseObject instance) => + { + 'id': instance.id, + 'question': instance.question, + 'options': instance.options, + 'votes': instance.votes, + 'actorType': instance.actorType, + 'actorId': instance.actorId, + 'actorDisplayName': instance.actorDisplayName, + 'status': instance.status, + 'resultMode': instance.resultMode, + 'maxVotes': instance.maxVotes, + 'votedSelf': instance.votedSelf, + 'numVoters': instance.numVoters, + 'details': instance.details, + }; diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index fdf5239..9d1dcf6 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:bubble/bubble.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis; import 'package:flowder/flowder.dart'; @@ -6,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:jiffy/jiffy.dart'; import 'package:open_filex/open_filex.dart'; +import '../../../../api/marianumcloud/talk/getPoll/getPollState.dart'; import '../../../../extensions/text.dart'; import 'package:provider/provider.dart'; @@ -18,11 +21,13 @@ import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart' import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart'; import '../../../../model/chatList/chatProps.dart'; import '../../../../widget/debug/debugTile.dart'; +import '../../../../widget/loadingSpinner.dart'; import '../../files/fileElement.dart'; import 'answerReference.dart'; import 'chatBubbleStyles.dart'; import 'chatMessage.dart'; import '../messageReactions.dart'; +import 'pollOptionsList.dart'; class ChatBubble extends StatefulWidget { final BuildContext context; @@ -297,6 +302,41 @@ class _ChatBubbleState extends State with SingleTickerProviderStateM onLongPress: showOptionsDialog, onDoubleTap: showOptionsDialog, onTap: () { + if(message.originalData?['object']?.type == RichObjectStringObjectType.talkPoll) { + log(message.originalData!['object']!.id); + var pollState = GetPollState(token: widget.bubbleData.token, pollId: int.parse(message.originalData!['object']!.id)).run(); + List? ownVotes; + showDialog(context: context, builder: (context) => AlertDialog( + title: Text(message.originalData!['object']!.name, textScaler: TextScaler.linear(0.9)), + content: FutureBuilder( + future: pollState, + builder: (context, snapshot) { + if(snapshot.connectionState == ConnectionState.waiting) return const LoadingSpinner(); + + var pollData = snapshot.data!.data; + ownVotes = pollData.votedSelf; + return PollOptionsList( + pollData: pollData, + callback: (votes) => ownVotes = votes + ); + } + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + if(ownVotes == null) return; + }, + child: const Text('Stimme abgeben') + ), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Abbrechen') + ), + ], + )); + } + if(message.file == null) return; if(downloadProgress > 0) { diff --git a/lib/view/pages/talk/components/chatMessage.dart b/lib/view/pages/talk/components/chatMessage.dart index 90abb14..d969039 100644 --- a/lib/view/pages/talk/components/chatMessage.dart +++ b/lib/view/pages/talk/components/chatMessage.dart @@ -32,6 +32,14 @@ class ChatMessage { onOpen: onOpen, ); + if(originalData?['object']?.type == RichObjectStringObjectType.talkPoll) { + return ListTile( + leading: const Icon(Icons.poll_outlined), + title: Text(originalData!['object']!.name), + contentPadding: const EdgeInsets.only(left: 10), + ); + } + if(file == null) return contentWidget; return Padding( diff --git a/lib/view/pages/talk/components/pollOptionsList.dart b/lib/view/pages/talk/components/pollOptionsList.dart new file mode 100644 index 0000000..78175f5 --- /dev/null +++ b/lib/view/pages/talk/components/pollOptionsList.dart @@ -0,0 +1,47 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../../api/marianumcloud/talk/getPoll/getPollStateResponse.dart'; + +class PollOptionsList extends StatefulWidget { + final GetPollStateResponseObject pollData; + final Function(List) callback; + const PollOptionsList({super.key, required this.pollData, required this.callback}); + + @override + State createState() => _PollOptionsListState(); +} + +class _PollOptionsListState extends State { + late List ownVotes; + + @override + void initState() { + super.initState(); + ownVotes = widget.pollData.votedSelf; + } + + @override + Widget build(BuildContext context) => ListView( + shrinkWrap: true, + children: [ + ...widget.pollData.options.map( + (option) => CheckboxListTile( + value: ownVotes.contains(widget.pollData.options.indexOf(option)), + title: Text(option), + onChanged: (value) { + var optionId = widget.pollData.options.indexOf(option); + setState(() { + if(ownVotes.contains(optionId)) { + ownVotes.remove(optionId); + } else { + ownVotes.add(optionId); + } + }); + widget.callback(ownVotes); + } + ) + ) + ], + ); +}