diff --git a/assets/translations/en.json b/assets/translations/en.json index ed22a53..e36b491 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -42,6 +42,9 @@ "please_enter_the_posting_key": "Please enter the posting key", "username": "username", "posting_key": "posting key", + "poll_vote": "Vote", + "poll_voted": "voted", + "poll_revote": "Revote", "no_threads_found": "No threads found", "age_limit": "\u2022 Only accounts older than {} days allowed", "interpretation_token": "\u2022 User HP based vote interpretation", diff --git a/lib/core/locales/locale_text.dart b/lib/core/locales/locale_text.dart index bb703ca..b622fcf 100644 --- a/lib/core/locales/locale_text.dart +++ b/lib/core/locales/locale_text.dart @@ -1,21 +1,27 @@ import 'package:easy_localization/easy_localization.dart'; class LocaleText { - static String isAddedToYourBookmarks(String content) =>"is_added_to_your_bookmarks".tr(args: [content]); - static String isRemovedToYourBookmarks(String content) => "is_removed_from_your_bookmarks".tr(args: [content]); - static String successfullLoginMessage(String content) => "successfull_login_message".tr(args: [content]); + static String isAddedToYourBookmarks(String content) => + "is_added_to_your_bookmarks".tr(args: [content]); + static String isRemovedToYourBookmarks(String content) => + "is_removed_from_your_bookmarks".tr(args: [content]); + static String successfullLoginMessage(String content) => + "successfull_login_message".tr(args: [content]); static String get notLoggedIn => "not_logged_in".tr(); static String get pleaseLoginFirst => "please_login_first".tr(); static String get cancel => "cancel".tr(); - static const String login = "login"; + static const String login = "login"; static String get bookmarks => "bookmarks".tr(); static String get darkMode => "dark_mode".tr(); static String get lightMode => "light_mode".tr(); static String get logOut => "log_out".tr(); static String get scanTapQRCode => "scan_tap_qr_code".tr(); - static String get authorizeThisRequestWithKeyChainForHiveApp => "authorize_this_request_with_keychain_for_hive_app".tr(); - static String get timeoutTimerForHiveAuthQr => "timeout_timer_for_hiveAuth_qr".tr(); - static String get sorryWeAreUnableToReachOurServer => "sorry_we_are_unable_to_reach_our_server".tr(); + static String get authorizeThisRequestWithKeyChainForHiveApp => + "authorize_this_request_with_keychain_for_hive_app".tr(); + static String get timeoutTimerForHiveAuthQr => + "timeout_timer_for_hiveAuth_qr".tr(); + static String get sorryWeAreUnableToReachOurServer => + "sorry_we_are_unable_to_reach_our_server".tr(); static String get tryAgain => "try_again".tr(); static String get replyCannotBeEmpty => "reply_cannot_be_empty".tr(); static String get addAComment => "add_a_comment".tr(); @@ -31,12 +37,14 @@ class LocaleText { static String get emHiveAuthTokenMessage => "em_hive_auth_token_message".tr(); static String get emAuthNackMessage => "em_auth_nack_message".tr(); static String get emPostingLoginMessage => "em_posting_login_message".tr(); - static String get emCommentDeclineMessage => "em_comment_decline_message".tr(); + static String get emCommentDeclineMessage => + "em_comment_decline_message".tr(); static String get emTimeOutMessage => "em_time_out_message".tr(); static String get emVoteFailureMessage => "em_vote_failure_message".tr(); static String get smHiveAuthLoginMessage => "sm_hive_auth_login_message".tr(); static String get smPostingLoginMessage => "sm_posting_login_message".tr(); - static String get smCommentPublishMessage => "sm_comment_publish_message".tr(); + static String get smCommentPublishMessage => + "sm_comment_publish_message".tr(); static String get smVoteSuccessMessage => "sm_vote_success_message".tr(); static String get somethingWentWrong => "something_went_wrong".tr(); static const String loginWithPostingKey = "login_with_posting_key"; @@ -47,7 +55,11 @@ class LocaleText { static const String noThreadsFound = "no_threads_found"; //polls realted texts - static String ageLimit (int days) => "age_limit".tr(args: [days.toString()]); + static String ageLimit(int days) => "age_limit".tr(args: [days.toString()]); static String get interpretationToken => "interpretation_token".tr(); - static String maxChoices (int choices) => "max_choices".tr(args: [choices.toString()]); + static String maxChoices(int choices) => + "max_choices".tr(args: [choices.toString()]); + static String get pollVote => "poll_vote".tr(); + static String get pollVoted => "poll_voted".tr(); + static String get pollRevote => "poll_revote".tr(); } diff --git a/lib/core/providers/global_providers.dart b/lib/core/providers/global_providers.dart index d6ad530..5c357af 100644 --- a/lib/core/providers/global_providers.dart +++ b/lib/core/providers/global_providers.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:waves/core/utilities/theme/theme_mode.dart'; import 'package:waves/features/threads/presentation/thread_feed/controller/poll_controller.dart'; import 'package:waves/features/threads/presentation/thread_feed/controller/thread_feed_controller.dart'; +import 'package:waves/features/user/presentation/user_profile/controller/user_profile_controller.dart'; import 'package:waves/features/user/view/user_controller.dart'; class GlobalProviders { @@ -14,6 +15,17 @@ class GlobalProviders { lazy: false, create: (context) => UserController(), ), + ChangeNotifierProxyProvider( + create: (context) => UserProfileController(accountName: null), + update: (context, userController, previousProfileController) { + if (previousProfileController == null) { + return UserProfileController(accountName: userController.userName); + } else { + previousProfileController.updateAccountName(userController.userName); + return previousProfileController; + } + } + ), ChangeNotifierProxyProvider( create: (context) => ThreadFeedController(observer: null), update: (context, userController, previousThreadFeedController) { diff --git a/lib/core/services/poll_service/poll_model.dart b/lib/core/services/poll_service/poll_model.dart index fba34c7..4d7ba1d 100644 --- a/lib/core/services/poll_service/poll_model.dart +++ b/lib/core/services/poll_service/poll_model.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:waves/core/utilities/enum.dart'; +import 'package:waves/core/locales/locale_text.dart'; class PollModel { final String author; @@ -10,7 +10,7 @@ class PollModel { final String parentAuthor; final double protocolVersion; final String question; - final String preferredInterpretation; + final PollPreferredInterpretation? preferredInterpretation; final String? token; final DateTime endTime; final String status; @@ -55,7 +55,8 @@ class PollModel { parentAuthor: json['parent_author'], protocolVersion: json['protocol_version'].toDouble(), question: json['question'], - preferredInterpretation: json['preferred_interpretation'], + preferredInterpretation: PollPreferredInterpretation.fromString( + json['preferred_interpretation']), token: json['token'], endTime: DateTime.parse(json['end_time']), status: json['status'], @@ -97,10 +98,13 @@ class PollModel { } double get totalInterpretedVotes { + return pollChoices.fold(0.0, (prev, entry) { + num val = preferredInterpretation == PollPreferredInterpretation.tokens + ? entry.votes?.hiveHp ?? 0 + : entry.votes?.totalVotes ?? 0; - //TODO: return value based on selected interpretation; - return pollChoices.fold(0, (val, entry) => val + (entry.votes?.totalVotes ?? 0)); - + return prev + val; + }); } List userVotedIds(String? username) { @@ -113,7 +117,6 @@ class PollModel { } void injectPollVoteCache(String username, List selection) { - double userHp = 1; //TOOD: use real user hp //extract previously votes choices and new choices @@ -150,35 +153,35 @@ class PollModel { pollChoices = [ ...notTouchedChoices, ...removedChoices.map((pc) => pc.copyWith( - votes: Votes( - totalVotes: (pc.votes?.totalVotes ?? 0) - 1, - hiveHp: (pc.votes?.hiveHp ?? 0) - userHp, - hiveProxiedHp: 0, - hiveHpInclProxied: (pc.votes?.hiveHpInclProxied ?? 0) - userHp, - splSpsp: 0, - ), - )), + votes: Votes( + totalVotes: (pc.votes?.totalVotes ?? 0) - 1, + hiveHp: (pc.votes?.hiveHp ?? 0) - userHp, + hiveProxiedHp: 0, + hiveHpInclProxied: (pc.votes?.hiveHpInclProxied ?? 0) - userHp, + splSpsp: 0, + ), + )), ...newChoices.map((pc) => pc.copyWith( - votes: Votes( - totalVotes: (pc.votes?.totalVotes ?? 0) + 1, - hiveHp: (pc.votes?.hiveHp ?? 0) + userHp, - hiveProxiedHp: 0, - hiveHpInclProxied: (pc.votes?.hiveHpInclProxied ?? 0) + userHp, - splSpsp: 0, - ), - )), + votes: Votes( + totalVotes: (pc.votes?.totalVotes ?? 0) + 1, + hiveHp: (pc.votes?.hiveHp ?? 0) + userHp, + hiveProxiedHp: 0, + hiveHpInclProxied: (pc.votes?.hiveHpInclProxied ?? 0) + userHp, + splSpsp: 0, + ), + )), ]..sort((a, b) => a.choiceNum.compareTo(b.choiceNum)); //update poll voters with updated selection pollVoters = [ ...otherVoters, PollVoter( - name: username, - choices: selection, - hiveHp: userHp, - splSpsp: 0, - hiveProxiedHp: 0, - hiveHpInclProxied: userHp) + name: username, + choices: selection, + hiveHp: userHp, + splSpsp: 0, + hiveProxiedHp: 0, + hiveHpInclProxied: userHp) ]; } } @@ -224,13 +227,11 @@ class PollChoice { }; } - - PollChoice copyWith({Votes? votes}) { + PollChoice copyWith({Votes? votes}) { return PollChoice( - choiceNum: choiceNum, - choiceText: choiceText, - votes: votes ?? this.votes - ); + choiceNum: choiceNum, + choiceText: choiceText, + votes: votes ?? this.votes); } } @@ -273,16 +274,25 @@ class Votes { }; } - Votes copyWith({int? totalVotes, double? hiveHp, double? hiveHpInclProxied}) { + Votes copyWith({int? totalVotes, double? hiveHp, double? hiveHpInclProxied}) { return Votes( - totalVotes: totalVotes ?? this.totalVotes, - hiveHp: hiveHp ?? this.hiveHp, - hiveProxiedHp: hiveProxiedHp, - hiveHpInclProxied: hiveHpInclProxied ?? this.hiveHpInclProxied, - splSpsp: splSpsp - ); - - + totalVotes: totalVotes ?? this.totalVotes, + hiveHp: hiveHp ?? this.hiveHp, + hiveProxiedHp: hiveProxiedHp, + hiveHpInclProxied: hiveHpInclProxied ?? this.hiveHpInclProxied, + splSpsp: splSpsp); + } + + num getInterprettedVotes(PollPreferredInterpretation? interpretation) { + return interpretation == PollPreferredInterpretation.tokens + ? hiveHp + : totalVotes; + } + + String getInterprettedSymbol(PollPreferredInterpretation? interpretation) { + return interpretation == PollPreferredInterpretation.tokens + ? "HP" + : LocaleText.pollVoted; } } @@ -369,3 +379,42 @@ class PollStats { }; } } + +enum PollPreferredInterpretation + implements Comparable { + tokens(value: 'tokens'), + numberOfVotes(value: 'number_of_votes'); + + const PollPreferredInterpretation({ + required this.value, + }); + + final String value; + + // Lookup map to find enum from string + static final Map _valueMap = { + 'tokens': PollPreferredInterpretation.tokens, + 'number_of_votes': PollPreferredInterpretation.numberOfVotes, + }; + + // Convert string to enum + static PollPreferredInterpretation? fromString(String? value) { + if (value == null) { + return null; + } + + final result = _valueMap[value]; + if (result == null) { + throw ArgumentError('Unknown value: $value'); + } + return result; + } + + // Convert enum to string + String toShortString() { + return value; + } + + @override + int compareTo(PollPreferredInterpretation other) => 0; +} diff --git a/lib/core/utilities/theme/theme_mode.dart b/lib/core/utilities/theme/theme_mode.dart index 9963b7f..b7e21b3 100644 --- a/lib/core/utilities/theme/theme_mode.dart +++ b/lib/core/utilities/theme/theme_mode.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; class ThemeController extends ChangeNotifier { - - final Color _primaryThemeColor = Colors.blue; + final Color _primaryThemeColor = const Color(0xFF357CE5); final Color _primaryColor = Colors.black; final Color _primaryColorTwo = const Color.fromARGB(255, 8, 8, 8); final Color _secondaryColor = Colors.white; @@ -178,4 +177,18 @@ class ThemeController extends ChangeNotifier { ), ); } + + get pollThemeData => isLightTheme() + ? getLightTheme().copyWith( + colorScheme: const ColorScheme.light( + brightness: Brightness.light, + primary: Color(0xFF90B5EB), + secondary: Color(0xFFC0C5C7), + surface: Color(0xFFF6F6F6))) + : getDarkTheme().copyWith( + colorScheme: const ColorScheme.dark( + brightness: Brightness.light, + primary: Color(0xff254C87), + secondary: Color(0xff526D91), + surface: Color(0xff2e3d51))); } diff --git a/lib/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart b/lib/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart index 9f15c56..f51b8d7 100644 --- a/lib/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart +++ b/lib/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart @@ -1,3 +1,4 @@ +import 'package:waves/core/services/poll_service/poll_model.dart'; import 'package:waves/core/utilities/save_convert.dart'; import 'package:waves/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data_video.dart'; @@ -15,45 +16,7 @@ enum ContentType implements Comparable { int compareTo(ContentType other) => 0; } -enum PollPreferredInterpretation - implements Comparable { - tokens(value: 'tokens'), - numberOfVotes(value: 'number_of_votes'); - const PollPreferredInterpretation({ - required this.value, - }); - - final String value; - - // Lookup map to find enum from string - static final Map _valueMap = { - 'tokens': PollPreferredInterpretation.tokens, - 'number_of_votes': PollPreferredInterpretation.numberOfVotes, - }; - - // Convert string to enum - static PollPreferredInterpretation? fromString(String? value) { - - if(value == null){ - return null; - } - - final result = _valueMap[value]; - if (result == null) { - throw ArgumentError('Unknown value: $value'); - } - return result; - } - - // Convert enum to string - String toShortString() { - return value; - } - - @override - int compareTo(PollPreferredInterpretation other) => 0; -} class PollFilters { final int accountAge; diff --git a/lib/features/threads/presentation/comments/comment_detail/view/comment_detail_view.dart b/lib/features/threads/presentation/comments/comment_detail/view/comment_detail_view.dart index 754e469..399d520 100644 --- a/lib/features/threads/presentation/comments/comment_detail/view/comment_detail_view.dart +++ b/lib/features/threads/presentation/comments/comment_detail/view/comment_detail_view.dart @@ -13,10 +13,12 @@ import 'package:waves/core/routes/routes.dart'; import 'package:waves/core/utilities/constants/ui_constants.dart'; import 'package:waves/core/utilities/enum.dart'; import 'package:waves/features/threads/models/thread_feeds/thread_feed_model.dart'; +import 'package:waves/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart'; import 'package:waves/features/threads/presentation/comments/comment_detail/controller/comment_detail_controller.dart'; import 'package:waves/features/threads/presentation/comments/comment_detail/widgets/tag_scroll.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/interaction_tile.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/markdown/thread_markdown.dart'; +import 'package:waves/features/threads/presentation/thread_feed/widgets/post_poll/post_poll.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/thread_feed_divider.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/thread_tile.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/thread_user_info_tile.dart'; @@ -119,6 +121,7 @@ class CommentDetailView extends StatelessWidget { ThreadUserInfoTile(item: item), const Gap(15), ThreadMarkDown(item: item), + item.jsonMetadata?.contentType == ContentType.poll ? PostPoll(item: item) : Container() , if (item.jsonMetadata?.tags != null && item.jsonMetadata!.tags!.isNotEmpty) Padding( diff --git a/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_choices.dart b/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_choices.dart index 581a9ed..df9d4ed 100644 --- a/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_choices.dart +++ b/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_choices.dart @@ -2,7 +2,6 @@ library flutter_polls; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; // FlutterPolls widget. @@ -33,14 +32,10 @@ class PollChoices extends HookWidget { this.pollOptionsHeight = 36, this.pollOptionsWidth, this.pollOptionsBorderRadius, - this.pollOptionsFillColor, this.pollOptionsSplashColor = Colors.grey, this.pollOptionsBorder, this.votedPollOptionsBorder, this.votedPollOptionsRadius, - this.votedBackgroundColor = const Color(0xffEEF0EB), - this.votedProgressColor = const Color(0xff84D2F6), - this.leadingVotedProgessColor = const Color(0xff0496FF), this.voteInProgressColor = const Color(0xffEEF0EB), this.votedCheckmark, this.votedPercentageTextStyle, @@ -75,10 +70,11 @@ class PollChoices extends HookWidget { /// If the user has not voted, this callback is called. /// If the callback returns true, the tapped [PollOption] is considered as voted. /// Else Nothing happens, - final Future Function(PollOption pollOption, int newTotalVotes)? onVoted; + final Future Function(PollOption pollOption, int newTotalVotes)? + onVoted; final Function(int choiceId, bool status) onSelection; - final Map selectedIds; + final List selectedIds; /// The title of the poll. Can be any widget with a bounded size. final Widget pollTitle; @@ -177,11 +173,6 @@ class PollChoices extends HookWidget { /// If null, the border is not drawn. final BoxBorder? votedPollOptionsBorder; - /// Color of a [PollOption]. - /// The color is the same for all options. - /// Defaults to [Colors.blue]. - final Color? pollOptionsFillColor; - /// Splashes a [PollOption] when the user taps it. /// Defaults to [Colors.grey]. final Color? pollOptionsSplashColor; @@ -190,18 +181,6 @@ class PollChoices extends HookWidget { /// Defaults to Radius.circular(8). final Radius? votedPollOptionsRadius; - /// Color of the background of a [PollOption] when the user has voted. - /// Defaults to [const Color(0xffEEF0EB)]. - final Color? votedBackgroundColor; - - /// Color of the progress bar of a [PollOption] when the user has voted. - /// Defaults to [const Color(0xff84D2F6)]. - final Color? votedProgressColor; - - /// Color of the leading progress bar of a [PollOption] when the user has voted. - /// Defaults to [const Color(0xff0496FF)]. - final Color? leadingVotedProgessColor; - /// Color of the background of a [PollOption] when the user clicks to vote and its still in progress. /// Defaults to [const Color(0xffEEF0EB)]. final Color? voteInProgressColor; @@ -226,31 +205,17 @@ class PollChoices extends HookWidget { @override Widget build(BuildContext context) { + ThemeData theme = Theme.of(context); + //Color of the leading progress bar of a [PollOption] when the user has voted. + Color leadingVotedProgessColor = theme.colorScheme.primary; + // Color of the background of a [PollOption] when the user has voted. + Color votedBackgroundColor = theme.colorScheme.surface; - // final votedOption = useState(hasVoted == false - // ? null - // : pollOptions - // .where( - // (pollOption) => userVotedOptionIds?.contains(pollOption.id) ?? false, - // ) - // .toList() - // .first); - - // final totalVotes = useState(pollOptions.fold( - // 0, - // (acc, option) => acc + option.votes, - // )); - - // useEffect(() { - // totalVotes.value = pollOptions.fold( - // 0, - // (acc, option) => acc + option.votes, - // ); - // return; - // }, [pollOptions]); - - // totalVotes.value = totalVotes.value; + /// The color is the same for all options. + Color pollOptionsFillColor = theme.colorScheme.surface; + // Color of the progress bar of a [PollOption] when the user has voted. + Color votedProgressColor = theme.colorScheme.secondary; return Column( key: ValueKey(pollId), @@ -295,26 +260,36 @@ class PollChoices extends HookWidget { animationDuration: votedAnimationDuration, backgroundColor: votedBackgroundColor, progressColor: - (userVotedOptionIds?.contains(pollOption.id) ?? false) + (userVotedOptionIds?.contains(pollOption.id) ?? + false) ? leadingVotedProgessColor : votedProgressColor, center: Container( width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 14, + padding: const EdgeInsets.only( + right: 12, ), child: Row( children: [ - Checkbox.adaptive( + Checkbox( value: (userVotedOptionIds?.contains(pollOption.id) ?? false), - onChanged: null), + onChanged: null, + fillColor: + WidgetStateProperty.resolveWith< + Color>((Set states) { + if (states.contains(WidgetState.selected)) { + return theme.primaryColor; + } + return Colors.transparent; + }),), pollOption.title, const SizedBox(width: 10), const Spacer(), Text( - pollOption.votes.toString(), - style: votedPercentageTextStyle, - ), + '${pollOption.votes.toString()} ${pollOption.votesPostfix ?? ''}', + style: Theme.of(context) + .textTheme + .bodySmall), ], ), ), @@ -322,7 +297,6 @@ class PollChoices extends HookWidget { ) : Container( key: UniqueKey(), - margin: EdgeInsets.only( bottom: heightBetweenOptions ?? 8, ), @@ -331,13 +305,9 @@ class PollChoices extends HookWidget { // Disables clicking while loading if (_isloading) return; - bool preVal = selectedIds[pollOption.id] ?? false; - - onSelection( - pollOption.id, - !preVal - ); + bool preVal = selectedIds.contains(pollOption.id); + onSelection(pollOption.id, !preVal); }, splashColor: pollOptionsSplashColor, borderRadius: pollOptionsBorderRadius ?? @@ -352,7 +322,9 @@ class PollChoices extends HookWidget { color: pollOptionsFillColor, border: pollOptionsBorder ?? Border.all( - color: (selectedIds[pollOption.id] ?? false) ? Colors.blue : pollOptionsFillColor!, + color: selectedIds.contains(pollOption.id) + ? theme.primaryColor + : pollOptionsFillColor!, width: 2, ), borderRadius: pollOptionsBorderRadius ?? @@ -363,7 +335,9 @@ class PollChoices extends HookWidget { child: Row( children: [ const Checkbox.adaptive( - value: false, onChanged: null), + value: false, + onChanged: null, + ), pollOption.title ], ), @@ -385,9 +359,11 @@ class PollOption { required this.id, required this.title, required this.votes, + this.votesPostfix, }); final int id; final Widget title; - int votes; + num votes; + String? votesPostfix; } diff --git a/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_header.dart b/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_header.dart index b91aa30..d88895a 100644 --- a/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_header.dart +++ b/lib/features/threads/presentation/thread_feed/widgets/post_poll/poll_header.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:timeago/timeago.dart' as timeago; import 'package:waves/core/locales/locale_text.dart'; +import 'package:waves/core/services/poll_service/poll_model.dart'; import 'package:waves/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart'; class PollHeader extends StatelessWidget { diff --git a/lib/features/threads/presentation/thread_feed/widgets/post_poll/post_poll.dart b/lib/features/threads/presentation/thread_feed/widgets/post_poll/post_poll.dart index e744531..db26896 100644 --- a/lib/features/threads/presentation/thread_feed/widgets/post_poll/post_poll.dart +++ b/lib/features/threads/presentation/thread_feed/widgets/post_poll/post_poll.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:provider/provider.dart'; +import 'package:waves/core/locales/locale_text.dart'; import 'package:waves/core/services/poll_service/poll_model.dart'; -import 'package:waves/features/auth/presentation/controller/auth_controller.dart'; +import 'package:waves/core/utilities/theme/theme_mode.dart'; import 'package:waves/features/threads/models/thread_feeds/thread_feed_model.dart'; import 'package:waves/features/threads/models/thread_feeds/thread_json_meta_data/thread_json_meta_data.dart'; import 'package:waves/features/threads/presentation/thread_feed/controller/poll_controller.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/post_poll/poll_choices.dart'; import 'package:waves/features/threads/presentation/thread_feed/widgets/post_poll/poll_header.dart'; +import 'package:waves/features/user/presentation/user_profile/controller/user_profile_controller.dart'; import 'package:waves/features/user/view/user_controller.dart'; class PostPoll extends StatefulWidget { @@ -20,13 +21,12 @@ class PostPoll extends StatefulWidget { } class _PostPollState extends State { - Map selection = {}; + List selection = []; bool enableRevote = false; bool isVoting = false; @override void initState() { - // TODO: implement initState super.initState(); if (widget.item.jsonMetadata!.contentType == ContentType.poll) { @@ -41,22 +41,28 @@ class _PostPollState extends State { String author = widget.item.author; String permlink = widget.item.permlink; + ThemeController themeController = context.watch(); String? username = context.select( (userController) => userController.userName); PollModel? poll = context.select( (pollController) => pollController.getPollData(author, permlink)); + //evaluate voting eligibitliy based on account age limit and end time; + int accountAgeDays = context.select( + (userProfileController) => userProfileController.accountAgeDays); + int minAgeDays = meta?.filters?.accountAge ?? 0; bool hasEnded = poll?.endTime.isBefore(DateTime.now()) ?? false; + bool votingProhibited = hasEnded || minAgeDays >= accountAgeDays; + //check if user already voted List userVotedIds = poll?.userVotedIds(username) ?? []; bool hasVoted = userVotedIds.isNotEmpty; - bool voteEnabled = poll != null && - !hasEnded && - (!hasVoted || enableRevote) && - selection.entries - .fold(false, (prevVal, entry) => entry.value || prevVal); + //setting for enabling disabling vote button + bool voteEnabled = + poll != null && (!hasVoted || enableRevote) && selection.isNotEmpty; + //prepare for vote action boadcast onCastVote() async { PollController pollController = context.read(); @@ -65,26 +71,40 @@ class _PostPollState extends State { isVoting = true; }); - List selectedIds = selection.entries - .where((entry) => entry.value) - .map((entry) => entry.key) - .toList(); - await pollController.castVote( - context, poll!.author, poll.permlink, selectedIds); + context, poll!.author, poll.permlink, selection); setState(() { enableRevote = false; isVoting = false; - selection = {}; + selection = []; }); } } - onSelection(int id, bool value) { - setState(() { - selection = {...selection, id: value}; - }); + //change selection is user interact with choices + onSelection(int choiceNum, bool value) { + int? maxSelectable = meta?.maxChoicesVoted ?? 1; + + if (maxSelectable > 1) { + // handle multiple choice selection + bool maxSelected = selection.length >= maxSelectable; + + int index = selection.indexOf(choiceNum); + if (index >= 0) { + selection.removeAt(index); + } else if (!maxSelected) { + selection.add(choiceNum); + } + setState(() { + selection = List.from(selection); + }); + } else { + // if only one choice allowed, overwrite selection + setState(() { + selection = [choiceNum]; + }); + } } onRevote() { @@ -104,8 +124,16 @@ class _PostPollState extends State { return choices .map((e) => PollOption( id: e.choiceNum, - title: Text(e.choiceText, maxLines: 2), - votes: e.votes?.totalVotes ?? 0)) + title: Text( + e.choiceText, + maxLines: 2, + style: Theme.of(context).textTheme.bodySmall, + ), + votes: + e.votes?.getInterprettedVotes(meta.preferredInterpretation) ?? + 0, + votesPostfix: + e.votes?.getInterprettedSymbol(meta.preferredInterpretation))) .toList(); } @@ -113,27 +141,25 @@ class _PostPollState extends State { margin: const EdgeInsets.only(top: 12), child: Column( children: [ - PollChoices( - pollId: widget.item.permlink, - onSelection: (id, status) => onSelection(id, status), - pollTitle: PollHeader( - meta: meta, - ), - pollOptions: pollOptions(), - selectedIds: selection, - pollEnded: hasEnded, - hasVoted: !enableRevote && hasVoted, - heightBetweenOptions: 16, - pollOptionsHeight: 40, - userVotedOptionIds: userVotedIds, - totalVotes: poll?.totalInterpretedVotes ?? 0, - votedBackgroundColor: const Color(0xff2e3d51), - pollOptionsFillColor: const Color(0xff2e3d51), - leadingVotedProgessColor: const Color(0xff357ce6), - votedProgressColor: const Color(0xff526d91), - votedCheckmark: - const Icon(Icons.check, color: Colors.white, size: 24), - ), + Theme( + data: themeController.pollThemeData, + child: PollChoices( + pollId: widget.item.permlink, + onSelection: (id, status) => onSelection(id, status), + pollTitle: PollHeader( + meta: meta, + ), + pollOptions: pollOptions(), + selectedIds: selection, + pollEnded: votingProhibited, + hasVoted: !enableRevote && hasVoted, + heightBetweenOptions: 16, + pollOptionsHeight: 32, + userVotedOptionIds: userVotedIds, + totalVotes: poll?.totalInterpretedVotes ?? 0, + votedCheckmark: + const Icon(Icons.check, color: Colors.white, size: 24), + )), Align( alignment: Alignment.centerLeft, child: Row( @@ -142,27 +168,28 @@ class _PostPollState extends State { if (hasVoted && !enableRevote && (meta.voteChange ?? false)) TextButton( onPressed: () => onRevote(), - child: const Text("Revote"), + child: Text(LocaleText.pollRevote), + ), + if (!votingProhibited) + ElevatedButton.icon( + onPressed: voteEnabled ? () => onCastVote() : null, + icon: isVoting + ? Container( + width: 24.0, + height: 24.0, + padding: const EdgeInsets.all(4.0), + child: const CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation(Colors.white), + strokeWidth: 2, + ), + ) + : const Icon(Icons.bar_chart, color: Colors.white), + label: Text(LocaleText.pollVote, style: const TextStyle(color: Colors.white)), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 32)), ), - ElevatedButton.icon( - onPressed: voteEnabled ? () => onCastVote() : null, - icon: isVoting - ? Container( - width: 24.0, - height: 24.0, - padding: const EdgeInsets.all(4.0), - child: const CircularProgressIndicator( - valueColor: - AlwaysStoppedAnimation(Colors.white), - strokeWidth: 2, - ), - ) - : const Icon(Icons.bar_chart), - label: const Text("Vote"), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(horizontal: 32)), - ), ], ), ) diff --git a/lib/features/user/presentation/user_profile/controller/user_profile_controller.dart b/lib/features/user/presentation/user_profile/controller/user_profile_controller.dart index 4127f7e..8f7e0b8 100644 --- a/lib/features/user/presentation/user_profile/controller/user_profile_controller.dart +++ b/lib/features/user/presentation/user_profile/controller/user_profile_controller.dart @@ -8,7 +8,7 @@ import 'package:waves/features/user/repository/user_repository.dart'; class UserProfileController extends ChangeNotifier { final UserRepository _userRepository = getIt(); - final String accountName; + String? accountName; UserModel? data; ViewState viewState = ViewState.loading; @@ -17,8 +17,13 @@ class UserProfileController extends ChangeNotifier { } void _init() async { + + if(accountName == null){ + return; + } + ActionSingleDataResponse response = - await _userRepository.getAccountInfo(accountName); + await _userRepository.getAccountInfo(accountName!); if (response.isSuccess && response.data != null) { viewState = ViewState.data; data = response.data; @@ -29,8 +34,13 @@ class UserProfileController extends ChangeNotifier { } Future getFollowCount() async { + + if(accountName == null){ + return FollowCountModel(followerCount: 0, followingCount: 0); + } + ActionSingleDataResponse response = - await _userRepository.getFollowCount(accountName); + await _userRepository.getFollowCount(accountName!); if (response.isSuccess) { return response.data!; } else { @@ -38,6 +48,20 @@ class UserProfileController extends ChangeNotifier { } } + int get accountAgeDays { + if(data != null){ + Duration diff = data!.created.difference(DateTime.now()); + return diff.inDays.abs(); + } + + return 0; + } + + void updateAccountName(String? username) { + accountName = username; + refresh(); + } + void refresh() { viewState = ViewState.loading; notifyListeners(); diff --git a/lib/main.dart b/lib/main.dart index 30a3bef..cf4f270 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,6 @@ import 'package:waves/core/providers/global_providers.dart'; import 'package:waves/core/routes/app_router.dart'; import 'package:waves/core/services/user_local_service.dart'; import 'package:waves/core/utilities/theme/theme_mode.dart'; -import 'package:firebase_analytics/firebase_analytics.dart'; import 'core/dependency_injection/dependency_injection.dart' as get_it; void main() async { diff --git a/pubspec.yaml b/pubspec.yaml index 034c7ed..7e5550d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+20 +version: 1.0.0+21 environment: sdk: '>=3.3.2 <4.0.0'