From ced286186094a041c158bfc6376ae29814070052 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 19 Nov 2020 10:54:41 +0900 Subject: [PATCH] info: improved layout --- lib/widgets/fullscreen/info/common.dart | 84 ++++++++++++++++++------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/lib/widgets/fullscreen/info/common.dart b/lib/widgets/fullscreen/info/common.dart index a91078d9d..5df0ffc7c 100644 --- a/lib/widgets/fullscreen/info/common.dart +++ b/lib/widgets/fullscreen/info/common.dart @@ -1,6 +1,9 @@ +import 'dart:math'; + import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; class SectionRow extends StatelessWidget { final IconData icon; @@ -54,36 +57,71 @@ class _InfoRowGroupState extends State { int get maxValueLength => widget.maxValueLength; + static const keyValuePadding = 16; + static final baseStyle = TextStyle(fontFamily: 'Concourse'); + static final keyStyle = baseStyle.copyWith(color: Colors.white70, height: 1.7); + @override Widget build(BuildContext context) { if (keyValues.isEmpty) return SizedBox.shrink(); + + // compute the size of keys and space in order to align values + final textScaleFactor = MediaQuery.textScaleFactorOf(context); + final keySizes = Map.fromEntries(keyValues.keys.map((key) => MapEntry(key, _getSpanWidth(TextSpan(text: '$key', style: keyStyle), textScaleFactor)))); + final baseSpaceWidth = _getSpanWidth(TextSpan(text: '\u200A' * 100, style: baseStyle), textScaleFactor); + final lastKey = keyValues.keys.last; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText.rich( - TextSpan( - children: keyValues.entries.expand( - (kv) { - final key = kv.key; - var value = kv.value; - final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key); - if (showPreviewOnly) { - value = '${value.substring(0, maxValueLength)}…'; - } - return [ - TextSpan(text: '$key ', style: TextStyle(color: Colors.white70, height: 1.7)), - TextSpan(text: '$value${key == lastKey ? '' : '\n'}', recognizer: showPreviewOnly ? _buildTapRecognizer(key) : null), - ]; - }, - ).toList(), - ), - style: TextStyle(fontFamily: 'Concourse'), - ), - ], + return LayoutBuilder( + builder: (context, constraints) { + // find longest key below threshold + final maxBaseValueX = constraints.maxWidth / 3; + final baseValueX = keySizes.values.where((size) => size < maxBaseValueX).fold(0.0, max); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText.rich( + TextSpan( + children: keyValues.entries.expand( + (kv) { + final key = kv.key; + var value = kv.value; + // long values are clipped, and made expandable by tapping them + final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key); + if (showPreviewOnly) { + value = '${value.substring(0, maxValueLength)}…'; + } + + // as of Flutter v1.22.4, `SelectableText` cannot contain `WidgetSpan` + // so we add padding using multiple hair spaces instead + final thisSpaceSize = max(0.0, (baseValueX - keySizes[key])) + keyValuePadding; + final spaceCount = (100 * thisSpaceSize / baseSpaceWidth).round(); + + return [ + TextSpan(text: '$key', style: keyStyle), + TextSpan(text: '\u200A' * spaceCount), + TextSpan(text: '$value${key == lastKey ? '' : '\n'}', recognizer: showPreviewOnly ? _buildTapRecognizer(key) : null), + ]; + }, + ).toList(), + ), + style: baseStyle, + ), + ], + ); + }, ); } + double _getSpanWidth(TextSpan span, double textScaleFactor) { + final para = RenderParagraph( + span, + textDirection: TextDirection.ltr, + textScaleFactor: textScaleFactor, + )..layout(BoxConstraints(), parentUsesSize: true); + return para.getMaxIntrinsicWidth(double.infinity); + } + GestureRecognizer _buildTapRecognizer(String key) { return TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key)); }