@@ -4,6 +4,9 @@ import 'package:flutter/foundation.dart';
44import 'package:flutter/gestures.dart' ;
55import 'package:flutter/material.dart' ;
66
7+ import '../generated/l10n/zulip_localizations.dart' ;
8+ import 'icons.dart' ;
9+ import 'store.dart' ;
710import 'theme.dart' ;
811
912/// An app-wide [Typography] for Zulip, customized from the Material default.
@@ -533,3 +536,139 @@ class _TextWithLinkState extends State<TextWithLink> {
533536 span);
534537 }
535538}
539+
540+ /// Data to size and position a square icon in a span of text.
541+ class InlineIconGeometryData {
542+ /// What size the icon should be,
543+ /// as a fraction of the surrounding text's font size.
544+ final double sizeFactor;
545+
546+ /// Where to assign the icon's baseline, as a fraction of the icon's size,
547+ /// when the span is rendered with [TextBaseline.alphabetic] .
548+ ///
549+ /// This is ignored when the span is rendered with [TextBaseline.ideographic] ;
550+ /// zero is used instead.
551+ final double alphabeticBaselineFactor;
552+
553+ /// How much horizontal padding should separate the icon from surrounding text,
554+ /// as a fraction of the icon's size.
555+ final double paddingFactor;
556+
557+ const InlineIconGeometryData ._({
558+ required this .sizeFactor,
559+ required this .alphabeticBaselineFactor,
560+ required this .paddingFactor,
561+ });
562+
563+ factory InlineIconGeometryData .forIcon (IconData icon) {
564+ final result = _inlineIconGeometries[icon];
565+ assert (result != null );
566+ return result ?? _defaultGeometry;
567+ }
568+
569+ // Values are ad hoc unless otherwise specified.
570+ static final Map <IconData , InlineIconGeometryData > _inlineIconGeometries = {
571+ ZulipIcons .globe: InlineIconGeometryData ._(
572+ sizeFactor: 0.8 ,
573+ alphabeticBaselineFactor: 1 / 8 ,
574+ paddingFactor: 1 / 4 ),
575+
576+ ZulipIcons .hash_sign: InlineIconGeometryData ._(
577+ sizeFactor: 0.8 ,
578+ alphabeticBaselineFactor: 1 / 16 ,
579+ paddingFactor: 1 / 4 ),
580+
581+ ZulipIcons .lock: InlineIconGeometryData ._(
582+ sizeFactor: 0.8 ,
583+ alphabeticBaselineFactor: 1 / 16 ,
584+ paddingFactor: 1 / 4 ),
585+ };
586+
587+ static final _defaultGeometry = InlineIconGeometryData ._(
588+ sizeFactor: 0.8 ,
589+ alphabeticBaselineFactor: 1 / 16 ,
590+ paddingFactor: 1 / 4 ,
591+ );
592+ }
593+
594+ /// An icon, sized and aligned for use in a span of text.
595+ WidgetSpan iconWidgetSpan ({
596+ required IconData icon,
597+ required double fontSize,
598+ required TextBaseline baselineType,
599+ required Color ? color,
600+ bool padBefore = false ,
601+ bool padAfter = false ,
602+ }) {
603+ final InlineIconGeometryData (
604+ : sizeFactor,
605+ : alphabeticBaselineFactor,
606+ : paddingFactor,
607+ ) = InlineIconGeometryData .forIcon (icon);
608+
609+ final size = sizeFactor * fontSize;
610+
611+ final effectiveBaselineOffset = switch (baselineType) {
612+ TextBaseline .alphabetic => alphabeticBaselineFactor * size,
613+ TextBaseline .ideographic => 0.0 ,
614+ };
615+
616+ Widget child = Icon (size: size, color: color, icon);
617+
618+ if (effectiveBaselineOffset != 0 ) {
619+ child = Transform .translate (
620+ offset: Offset (0 , effectiveBaselineOffset),
621+ child: child);
622+ }
623+
624+ if (padBefore || padAfter) {
625+ final padding = paddingFactor * size;
626+ child = Padding (
627+ padding: EdgeInsetsDirectional .only (
628+ start: padBefore ? padding : 0 ,
629+ end: padAfter ? padding : 0 ,
630+ ),
631+ child: child);
632+ }
633+
634+ return WidgetSpan (
635+ alignment: PlaceholderAlignment .baseline,
636+ baseline: baselineType,
637+ child: child);
638+ }
639+
640+ /// An [InlineSpan] with a channel privacy icon and channel name.
641+ ///
642+ /// Pass this to [Text.rich] , which can be styled arbitrarily.
643+ /// Pass the [fontSize] of surrounding text
644+ /// so that the icon is sized appropriately.
645+ InlineSpan channelTopicLabelSpan ({
646+ required BuildContext context,
647+ required int channelId,
648+ required double fontSize,
649+ required Color color,
650+ }) {
651+ final zulipLocalizations = ZulipLocalizations .of (context);
652+ final store = PerAccountStoreWidget .of (context);
653+ final channel = store.streams[channelId];
654+ final subscription = store.subscriptions[channelId];
655+ final swatch = colorSwatchFor (context, subscription);
656+ final channelIcon = channel != null ? iconDataForStream (channel) : null ;
657+ final baselineType = localizedTextBaseline (context);
658+
659+ return TextSpan (children: [
660+ if (channelIcon != null )
661+ iconWidgetSpan (
662+ icon: channelIcon,
663+ fontSize: fontSize,
664+ baselineType: baselineType,
665+ color: swatch.iconOnPlainBackground,
666+ padAfter: true ),
667+ if (channel != null )
668+ TextSpan (text: channel.name)
669+ else
670+ TextSpan (
671+ style: TextStyle (fontStyle: FontStyle .italic),
672+ text: zulipLocalizations.unknownChannelName),
673+ ]);
674+ }
0 commit comments