Skip to content

Commit

Permalink
feat: changed the interface of MarkDefDescriptor to become more flexi…
Browse files Browse the repository at this point in the history
…ble with generating InlineSpan instead of just a TextSpan. This allows greater decorations to be attached to an annotation.
  • Loading branch information
pavanpodila committed Apr 3, 2024
1 parent d669325 commit 2e9d455
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 37 deletions.
22 changes: 22 additions & 0 deletions packages/sanity/flutter_sanity_portable_text/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ void _registerCustomMark() {

return style;
},
spanBuilder: (context, markDef, text, style) {
return WidgetSpan(
child: Container(
color: Colors.black12,
padding: const EdgeInsets.only(right: 4),
child: GestureDetector(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.bolt,
color: (markDef as CustomMarkDef).color,
size: 20,
),
Text(text, style: style),
],
),
onTap: () => debugPrint('Tapped on $markDef'),
),
),
);
},
fromJson: (json) => CustomMarkDef(color: json['color'], key: json['key']),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ import '../flutter_sanity_portable_text.dart';

part 'markdef_descriptor.g.dart';

/// Builds a [GestureRecognizer] for a given [MarkDef].
typedef GestureRecognizerBuilder = GestureRecognizer Function(
BuildContext context,
MarkDef mark,
);

/// Builds a [TextStyle] for a given [MarkDef].
typedef MarkDefTextStyleBuilder = TextStyle Function(
BuildContext context,
Expand All @@ -24,6 +18,14 @@ typedef MarkDefFromJson = MarkDef Function(
Map<String, dynamic> json,
);

/// Builds an [InlineSpan] for a given [MarkDef].
typedef MarkDefSpanBuilder = InlineSpan Function(
BuildContext context,
MarkDef mark,
String text,
TextStyle style,
);

/// Describes a [MarkDef] and its associated builders.
final class MarkDefDescriptor {
/// The schema type of the mark.
Expand All @@ -40,13 +42,13 @@ final class MarkDefDescriptor {
final MarkDefTextStyleBuilder? styleBuilder;

/// Builds a [GestureRecognizer] for a given [MarkDef].
final GestureRecognizerBuilder? recognizerBuilder;
final MarkDefSpanBuilder? spanBuilder;

MarkDefDescriptor({
required this.schemaType,
required this.fromJson,
this.styleBuilder,
this.recognizerBuilder,
this.spanBuilder,
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

import '../flutter_sanity_portable_text.dart';
Expand All @@ -22,7 +21,7 @@ class PortableTextBlock extends StatelessWidget {
final config = PortableTextConfig.shared;

final spans = model.children
.map((final span) => _convertSpan(span, Theme.of(context), context))
.map((final span) => _buildInlineSpan(span, Theme.of(context), context))
.toList(growable: false);

final content = Text.rich(
Expand All @@ -47,7 +46,7 @@ class PortableTextBlock extends StatelessWidget {
);
}

InlineSpan _convertSpan(
InlineSpan _buildInlineSpan(
final Span span,
final ThemeData theme,
final BuildContext context,
Expand Down Expand Up @@ -79,10 +78,9 @@ class PortableTextBlock extends StatelessWidget {
pendingMarkDefs.add(markDef);
}

GestureRecognizer? recognizer;
int totalRecognizers = 0;
String? errorText;

// Build the style across all marks
for (final markDef in pendingMarkDefs) {
final descriptor = config.markDefs[markDef.type];
if (descriptor == null) {
Expand All @@ -91,31 +89,49 @@ class PortableTextBlock extends StatelessWidget {
}

style = descriptor.styleBuilder?.call(context, markDef, style) ?? style;
}

// Now build the final InlineSpan, if any
InlineSpan? inlineSpan;
int totalSpans = 0;

recognizer = descriptor.recognizerBuilder?.call(context, markDef);
// Only go ahead if there are no errors from previous step
if (errorText == null) {
for (final markDef in pendingMarkDefs) {
final descriptor = config.markDefs[markDef.type];
inlineSpan =
descriptor?.spanBuilder?.call(context, markDef, span.text, style);

if (recognizer != null) {
totalRecognizers++;
if (inlineSpan == null) {
continue;
}

if (totalRecognizers > 1) {
errorText =
'There can only be one recognizer for a set of MarkDefs. We found more than one.';
totalSpans++;
if (totalSpans > 1) {
final markDefChain = pendingMarkDefs.map((e) => e.type).join(' -> ');
errorText = '''
We currently support a single custom markDef generating the InlineSpan.
We found $totalSpans. The chain of markDefs was: $markDefChain.
Suggestion: Try to refactor your custom markDef chain to only generate a single InlineSpan.
You can rely on TextStyles instead for custom styling.''';
break;
}
}
}

return errorText != null
? WidgetSpan(
child: ErrorView(
message: errorText,
asBlock: false,
),
)
: TextSpan(
text: span.text,
recognizer: recognizer,
style: style,
);
if (errorText != null) {
return WidgetSpan(
child: GestureDetector(
child: ErrorView(
message: errorText,
asBlock: false,
),
),
);
}

return inlineSpan ?? TextSpan(text: span.text, style: style);
}

InlineSpan _bulletMark(final BuildContext context) {
Expand Down
34 changes: 28 additions & 6 deletions packages/system/vyuh_feature_system/lib/feature.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.dart';
import 'package:go_router/go_router.dart';
Expand Down Expand Up @@ -114,11 +113,34 @@ final feature = FeatureDescriptor(
decoration: TextDecoration.underline,
);
},
recognizerBuilder: (context, def) {
return TapGestureRecognizer()
..onTap = () {
(def as InvokeActionMarkDef).action.execute(context);
};
spanBuilder: (context, def, text, style) {
return WidgetSpan(
child: GestureDetector(
onTap: () {
(def as InvokeActionMarkDef).action.execute(context);
},
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.horizontal,
alignment: WrapAlignment.start,
children: [
Text(
text,
style: style,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
),
Icon(
Icons.arrow_forward,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
],
),
),
);
},
)
],
Expand Down

0 comments on commit 2e9d455

Please sign in to comment.