Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ class ProposalBriefCard extends StatefulWidget {
State<ProposalBriefCard> createState() => _ProposalBriefCardState();
}

class _Author extends StatelessWidget {
final String? author;
class _AuthorAndCollaborators extends StatelessWidget {
final CatalystId? author;
final List<CatalystId>? collaborators;

const _Author({
required this.author,
const _AuthorAndCollaborators({
this.author,
this.collaborators,
});

@override
Expand All @@ -48,13 +50,16 @@ class _Author extends StatelessWidget {
children: [
ProfileAvatar(
size: 32,
username: author,
username: author?.username,
),
UsernameText(
key: const Key('Author'),
author,
style: context.textTheme.titleSmall?.copyWith(
color: context.colors.textOnPrimaryLevel1,
Flexible(
child: AccountsText(
ids: [?author, ...?collaborators],
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.titleSmall?.copyWith(
color: context.colors.textOnPrimaryLevel1,
),
),
),
],
Expand Down Expand Up @@ -222,7 +227,10 @@ class _ProposalBriefCardState extends State<ProposalBriefCard> {
),
const SizedBox(height: 4),
_Title(text: proposal.title),
_Author(author: proposal.author),
_AuthorAndCollaborators(
author: proposal.author,
collaborators: proposal.acceptedCollaboratorsIds,
),
_FundsAndDuration(
funds: proposal.formattedFunds,
duration: proposal.duration,
Expand Down
114 changes: 114 additions & 0 deletions catalyst_voices/apps/voices/lib/widgets/user/accounts_text.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
import 'package:catalyst_voices/widgets/common/affix_decorator.dart';
import 'package:catalyst_voices/widgets/user/catalyst_id_text.dart';
import 'package:catalyst_voices/widgets/user/username_text.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:flutter/material.dart';

class AccountsText extends StatelessWidget {
final List<CatalystId> ids;
final TextStyle? style;
final int? maxLines;
final TextOverflow? overflow;

const AccountsText({
super.key,
required this.ids,
this.style,
this.maxLines,
this.overflow,
});

@override
Widget build(BuildContext context) {
final firstId = ids.firstOrNull;
final firstUsername = firstId?.username;
final isAnonymous = firstId != null && (firstUsername == null || firstUsername.isBlank);

final effectiveFirstUsername = isAnonymous ? context.l10n.anonymousUsername : firstUsername;

return Text.rich(
TextSpan(
text: effectiveFirstUsername,
style: TextStyle(fontStyle: isAnonymous ? FontStyle.italic : FontStyle.normal),
children: [
if (ids.length > 1) ...[
const TextSpan(text: ' & '),
WidgetSpan(
child: Tooltip(
richMessage: WidgetSpan(child: _OtherTooltipOverlay(ids.sublist(1))),
padding: EdgeInsets.zero,
decoration: const BoxDecoration(),
constraints: const BoxConstraints(maxWidth: 268),
enableTapToDismiss: false,
child: Text(
context.l10n.others(ids.length - 1),
style: (style ?? const TextStyle()).copyWith(
color: Theme.of(context).linksPrimary,
),
),
),
),
],
],
),
style: style,
maxLines: maxLines,
overflow: overflow,
);
}
}

class _OtherTooltipOverlay extends StatelessWidget {
final List<CatalystId> ids;

const _OtherTooltipOverlay(this.ids);

@override
Widget build(BuildContext context) {
return Material(
color: context.colors.elevationsOnSurfaceNeutralLv1White,
elevation: 4,
borderRadius: BorderRadius.circular(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: ids.map(_OtherTooltipOverlayTile.new).toList(),
),
);
}
}

class _OtherTooltipOverlayTile extends StatelessWidget {
final CatalystId id;

const _OtherTooltipOverlayTile(this.id);

@override
Widget build(BuildContext context) {
final idStyle = context.textTheme.labelSmall;
final usernameStyle = (context.textTheme.bodyLarge ?? const TextStyle()).copyWith(
color: context.colors.textOnPrimaryLevel0,
);

return Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
).add(const EdgeInsets.only(right: 8)),
child: AffixDecorator(
suffix: CatalystIdText(
id,
isCompact: true,
showCopy: false,
copyEnabled: false,
tooltipEnabled: false,
style: idStyle,
),
child: UsernameText(id.username, style: usernameStyle, maxLines: 1),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class CatalystIdText extends StatefulWidget {
final bool showLabel;
final bool showUsername;
final bool includeUsername;
final bool copyEnabled;
final bool tooltipEnabled;
final TextStyle? style;
final TextStyle? labelStyle;
final double labelGap;
Expand All @@ -32,6 +34,8 @@ class CatalystIdText extends StatefulWidget {
this.showLabel = false,
this.showUsername = false,
this.includeUsername = true,
this.copyEnabled = true,
this.tooltipEnabled = true,
this.style,
this.labelStyle,
this.labelGap = 6,
Expand All @@ -58,8 +62,8 @@ class _CatalystIdTextState extends State<CatalystIdText> {
child: Offstage(
offstage: _effectiveData.isEmpty,
child: _TapDetector(
onTap: _copyDataToClipboard,
onHoverExit: _handleHoverExit,
onTap: widget.copyEnabled ? _copyDataToClipboard : null,
onHoverExit: widget.copyEnabled ? _handleHoverExit : null,
child: TooltipVisibility(
visible: _tooltipVisible,
child: Row(
Expand All @@ -72,7 +76,7 @@ class _CatalystIdTextState extends State<CatalystIdText> {
constraints: const BoxConstraints(),
child: _Chip(
_effectiveData,
onTap: _copyDataToClipboard,
onTap: widget.copyEnabled ? _copyDataToClipboard : null,
style: widget.style,
backgroundColor: widget.backgroundColor,
),
Expand All @@ -96,7 +100,9 @@ class _CatalystIdTextState extends State<CatalystIdText> {
void didUpdateWidget(CatalystIdText oldWidget) {
super.didUpdateWidget(oldWidget);

if (widget.data != oldWidget.data || widget.isCompact != oldWidget.isCompact) {
if (widget.data != oldWidget.data ||
widget.isCompact != oldWidget.isCompact ||
widget.tooltipEnabled != oldWidget.tooltipEnabled) {
_fullDataAsString = _buildFullData();
_effectiveData = _buildTextData();
_tooltipVisible = _isTooltipVisible();
Expand Down Expand Up @@ -165,7 +171,11 @@ class _CatalystIdTextState extends State<CatalystIdText> {
}

bool _isTooltipVisible() {
return widget.isCompact && _effectiveData.length < _fullDataAsString.length;
if (!widget.tooltipEnabled || !widget.isCompact) {
return false;
}

return _effectiveData.length < _fullDataAsString.length;
}

void _removeHighlight() {
Expand All @@ -187,7 +197,7 @@ class _CatalystIdTextState extends State<CatalystIdText> {

class _Chip extends StatelessWidget {
final String data;
final VoidCallback onTap;
final VoidCallback? onTap;
final Color? backgroundColor;
final TextStyle? style;

Expand Down Expand Up @@ -276,8 +286,8 @@ class _LabelText extends StatelessWidget {
}

class _TapDetector extends StatelessWidget {
final VoidCallback onTap;
final VoidCallback onHoverExit;
final VoidCallback? onTap;
final VoidCallback? onHoverExit;
final Widget child;

const _TapDetector({
Expand All @@ -292,7 +302,7 @@ class _TapDetector extends StatelessWidget {
onTap: onTap,
// there is a gap between text and copy
behavior: HitTestBehavior.translucent,
mouseRegionOnExit: (event) => onHoverExit(),
mouseRegionOnExit: onHoverExit != null ? (event) => onHoverExit!.call() : null,
child: child,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ class UsernameText extends StatelessWidget {

@override
Widget build(BuildContext context) {
final isAnonymous = data == null || (data?.isBlank ?? false);
final username = data;
final isAnonymous = username == null || username.isBlank;
final effectiveUsername = isAnonymous ? context.l10n.anonymousUsername : username;

final effectiveStyle = (style ?? const TextStyle()).copyWith(
fontStyle: isAnonymous ? FontStyle.italic : FontStyle.normal,
);

return DefaultTextStyle.merge(
return Text(
effectiveUsername,
style: effectiveStyle,
maxLines: maxLines,
overflow: overflow,
child: Text(isAnonymous ? context.l10n.anonymousUsername : data!),
);
}
}
1 change: 1 addition & 0 deletions catalyst_voices/apps/voices/lib/widgets/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export 'toggles/voices_radio.dart';
export 'toggles/voices_switch.dart';
export 'tooltips/voices_plain_tooltip.dart';
export 'tooltips/voices_rich_tooltip.dart';
export 'user/accounts_text.dart';
export 'user/catalyst_id_text.dart';
export 'user/profile_avatar.dart';
export 'user/profile_container.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,15 @@
"@orderProposalsUpdateDateDesc": {
"description": "Order proposals by update date option"
},
"others": "{count} {count, plural, =1{Other} other{Others}}",
"@others": {
"description": "In context of proposal author and collaborators. Eg. Author & 5 Others",
"placeholders": {
"count": {
"type": "int"
}
}
},
"paginationProposalsCounter": "{from}-{to} of {max} {max, plural, =0{proposals} =1{proposal} other{proposals}}",
"@paginationProposalsCounter": {
"description": "Below pagination list, next to page switch controls",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export 'proposal/proposal_or_document.dart';
export 'proposal/proposal_version.dart';
export 'proposal/proposal_votes.dart';
export 'proposal/proposal_with_context.dart';
export 'proposal/proposals_collaboration_status.dart';
export 'proposals/proposals_filters_v2.dart';
export 'proposals/proposals_order.dart';
export 'proposals/proposals_total_ask_filters.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:equatable/equatable.dart';

final class ProposalBriefData extends Equatable {
final DocumentRef id;
final String authorName;
final CatalystId? author;
final String title;
final String description;
final String categoryName;
Expand All @@ -18,7 +18,7 @@ final class ProposalBriefData extends Equatable {

const ProposalBriefData({
required this.id,
required this.authorName,
required this.author,
required this.title,
required this.description,
required this.categoryName,
Expand All @@ -35,7 +35,7 @@ final class ProposalBriefData extends Equatable {
@override
List<Object?> get props => [
id,
authorName,
author,
title,
description,
categoryName,
Expand Down
Loading