info: improved layout
This commit is contained in:
parent
ba031a0144
commit
ced2861860
1 changed files with 61 additions and 23 deletions
|
@ -1,6 +1,9 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
class SectionRow extends StatelessWidget {
|
class SectionRow extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
|
@ -54,36 +57,71 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
||||||
|
|
||||||
int get maxValueLength => widget.maxValueLength;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (keyValues.isEmpty) return SizedBox.shrink();
|
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;
|
final lastKey = keyValues.keys.last;
|
||||||
return Column(
|
return LayoutBuilder(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
builder: (context, constraints) {
|
||||||
children: [
|
// find longest key below threshold
|
||||||
SelectableText.rich(
|
final maxBaseValueX = constraints.maxWidth / 3;
|
||||||
TextSpan(
|
final baseValueX = keySizes.values.where((size) => size < maxBaseValueX).fold(0.0, max);
|
||||||
children: keyValues.entries.expand(
|
|
||||||
(kv) {
|
return Column(
|
||||||
final key = kv.key;
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
var value = kv.value;
|
children: [
|
||||||
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
|
SelectableText.rich(
|
||||||
if (showPreviewOnly) {
|
TextSpan(
|
||||||
value = '${value.substring(0, maxValueLength)}…';
|
children: keyValues.entries.expand(
|
||||||
}
|
(kv) {
|
||||||
return [
|
final key = kv.key;
|
||||||
TextSpan(text: '$key ', style: TextStyle(color: Colors.white70, height: 1.7)),
|
var value = kv.value;
|
||||||
TextSpan(text: '$value${key == lastKey ? '' : '\n'}', recognizer: showPreviewOnly ? _buildTapRecognizer(key) : null),
|
// long values are clipped, and made expandable by tapping them
|
||||||
];
|
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
|
||||||
},
|
if (showPreviewOnly) {
|
||||||
).toList(),
|
value = '${value.substring(0, maxValueLength)}…';
|
||||||
),
|
}
|
||||||
style: TextStyle(fontFamily: 'Concourse'),
|
|
||||||
),
|
// 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) {
|
GestureRecognizer _buildTapRecognizer(String key) {
|
||||||
return TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
|
return TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue