mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
mod: reply pic showing
This commit is contained in:
61
lib/common/widgets/imageview.dart
Normal file
61
lib/common/widgets/imageview.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:PiliPalaX/common/widgets/nine_grid_view.dart';
|
||||||
|
import 'package:PiliPalaX/pages/preview/view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Widget image(
|
||||||
|
double maxWidth,
|
||||||
|
List<dynamic> picArr,
|
||||||
|
) {
|
||||||
|
double imageWidth = (maxWidth - 2 * 5) / 3;
|
||||||
|
double imageHeight = imageWidth;
|
||||||
|
if (picArr.length == 1) {
|
||||||
|
dynamic width = picArr[0]['img_width'];
|
||||||
|
dynamic height = picArr[0]['img_height'];
|
||||||
|
double ratioWH = width / height;
|
||||||
|
double ratioHW = height / width;
|
||||||
|
double maxRatio = 22 / 9;
|
||||||
|
imageWidth = ratioWH > 1.5
|
||||||
|
? maxWidth
|
||||||
|
: (ratioWH >= 1 || (height > width && ratioHW < 1.5))
|
||||||
|
? 2 * imageWidth
|
||||||
|
: imageWidth;
|
||||||
|
imageHeight = imageWidth * min(ratioHW, maxRatio);
|
||||||
|
}
|
||||||
|
return NineGridView(
|
||||||
|
type: NineGridType.weiBo,
|
||||||
|
margin: const EdgeInsets.only(top: 6),
|
||||||
|
bigImageWidth: imageWidth,
|
||||||
|
bigImageHeight: imageHeight,
|
||||||
|
space: 5,
|
||||||
|
height: picArr.length == 1 ? imageHeight : null,
|
||||||
|
width: picArr.length == 1 ? imageWidth : maxWidth,
|
||||||
|
itemCount: picArr.length,
|
||||||
|
itemBuilder: (context, index) => GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(
|
||||||
|
initialPage: index,
|
||||||
|
imgList: picArr.map((item) => item['img_src'] as String).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: picArr[index]['img_src'],
|
||||||
|
width: imageWidth,
|
||||||
|
height: imageWidth,
|
||||||
|
origAspectRatio:
|
||||||
|
picArr[index]['img_width'] / picArr[index]['img_height'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
560
lib/common/widgets/nine_grid_view.dart
Normal file
560
lib/common/widgets/nine_grid_view.dart
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: Sky24n
|
||||||
|
* @GitHub: https://github.com/Sky24n
|
||||||
|
* @Description: NineGridView.
|
||||||
|
* @Date: 2020/06/16
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// NineGridView Type.
|
||||||
|
enum NineGridType {
|
||||||
|
/// normal NineGridView.
|
||||||
|
normal,
|
||||||
|
|
||||||
|
/// like WeChat NineGridView.
|
||||||
|
weChat,
|
||||||
|
|
||||||
|
/// like WeiBo International NineGridView.
|
||||||
|
weiBo,
|
||||||
|
|
||||||
|
/// like WeChat group.
|
||||||
|
weChatGp,
|
||||||
|
|
||||||
|
/// like DingTalk group.
|
||||||
|
dingTalkGp,
|
||||||
|
|
||||||
|
/// like QQ group.
|
||||||
|
qqGp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// big images size cache map.
|
||||||
|
Map<String, Rect> ngvBigImageSizeMap = HashMap();
|
||||||
|
|
||||||
|
/// NineGridView.
|
||||||
|
/// like WeChat, WeiBo International, WeChat group, DingTalk group, QQ group.
|
||||||
|
///
|
||||||
|
/// Another [NineGridView](https://github.com/flutterchina/flukit) in [flukit](https://github.com/flutterchina/flukit) UI Kit,using GridView implementation。
|
||||||
|
class NineGridView extends StatefulWidget {
|
||||||
|
/// create NineGridView.
|
||||||
|
/// If you want to show a single big picture.
|
||||||
|
/// It is recommended to use a medium-quality picture, because the original picture is too large and takes time to load.
|
||||||
|
/// 单张大图建议使用中等质量图片,因为原图太大加载耗时。
|
||||||
|
/// you need input (bigImageWidth + bigImageHeight) or (bigImage + bigImageUrl).
|
||||||
|
const NineGridView({
|
||||||
|
super.key,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.space = 3,
|
||||||
|
this.arcAngle = 0,
|
||||||
|
this.initIndex = 1,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
this.margin = EdgeInsets.zero,
|
||||||
|
this.alignment,
|
||||||
|
this.color,
|
||||||
|
this.decoration,
|
||||||
|
this.type = NineGridType.weChat,
|
||||||
|
required this.itemCount,
|
||||||
|
required this.itemBuilder,
|
||||||
|
this.bigImageWidth,
|
||||||
|
this.bigImageHeight,
|
||||||
|
this.bigImage,
|
||||||
|
this.bigImageUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// View width.
|
||||||
|
final double? width;
|
||||||
|
|
||||||
|
/// View height.
|
||||||
|
final double? height;
|
||||||
|
|
||||||
|
/// The number of logical pixels between each child.
|
||||||
|
final double space;
|
||||||
|
|
||||||
|
/// QQ group arc angle (0 ~ 180).
|
||||||
|
final double arcAngle;
|
||||||
|
|
||||||
|
/// QQ group init index (0 or 1). def 1.
|
||||||
|
final int initIndex;
|
||||||
|
|
||||||
|
/// View padding.
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
/// View margin.
|
||||||
|
final EdgeInsets margin;
|
||||||
|
|
||||||
|
/// Align the [child] within the container.
|
||||||
|
final AlignmentGeometry? alignment;
|
||||||
|
|
||||||
|
/// The color to paint behind the [child].
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
/// The decoration to paint behind the [child].
|
||||||
|
final Decoration? decoration;
|
||||||
|
|
||||||
|
/// NineGridView type.
|
||||||
|
final NineGridType type;
|
||||||
|
|
||||||
|
/// The total number of children this delegate can provide.
|
||||||
|
final int itemCount;
|
||||||
|
|
||||||
|
/// Called to build children for the view.
|
||||||
|
final IndexedWidgetBuilder itemBuilder;
|
||||||
|
|
||||||
|
/// Single big picture width.
|
||||||
|
final double? bigImageWidth;
|
||||||
|
|
||||||
|
/// Single big picture height.
|
||||||
|
final double? bigImageHeight;
|
||||||
|
|
||||||
|
/// It is recommended to use a medium-quality picture, because the original picture is too large and takes time to load.
|
||||||
|
/// 单张大图建议使用中等质量图片,因为原图太大加载耗时。
|
||||||
|
/// Single big picture Image.
|
||||||
|
final Image? bigImage;
|
||||||
|
|
||||||
|
/// Single big picture url.
|
||||||
|
final String? bigImageUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return _NineGridViewState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _NineGridViewState.
|
||||||
|
class _NineGridViewState extends State<NineGridView> {
|
||||||
|
/// init view size.
|
||||||
|
Rect _initSize(BuildContext context) {
|
||||||
|
int itemCount = math.min(9, widget.itemCount);
|
||||||
|
EdgeInsets padding = widget.padding;
|
||||||
|
if (itemCount == 0) {
|
||||||
|
return Rect.fromLTRB(0, 0, padding.horizontal, padding.vertical);
|
||||||
|
}
|
||||||
|
double width = widget.width ??
|
||||||
|
(MediaQuery.of(context).size.width - widget.margin.horizontal);
|
||||||
|
width = width - padding.horizontal;
|
||||||
|
double space = widget.space;
|
||||||
|
double itemW;
|
||||||
|
if (widget.type == NineGridType.weiBo &&
|
||||||
|
(itemCount == 1 || itemCount == 2 || itemCount == 4)) {
|
||||||
|
itemW = (width - space) / 2;
|
||||||
|
} else {
|
||||||
|
itemW = (width - space * 2) / 3;
|
||||||
|
}
|
||||||
|
bool fourGrid = (itemCount == 4 && widget.type != NineGridType.normal);
|
||||||
|
int column = fourGrid ? 2 : math.min(3, itemCount);
|
||||||
|
int row = fourGrid ? 2 : (itemCount / 3).ceil();
|
||||||
|
double realWidth =
|
||||||
|
itemW * column + space * (column - 1) + padding.horizontal;
|
||||||
|
double realHeight = itemW * row + space * (row - 1) + padding.vertical;
|
||||||
|
return Rect.fromLTRB(itemW, 0, realWidth, realHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build nine grid view.
|
||||||
|
Widget _buildChild(BuildContext context, double itemW) {
|
||||||
|
int itemCount = math.min(9, widget.itemCount);
|
||||||
|
double space = widget.space;
|
||||||
|
int column = (itemCount == 4 && widget.type != NineGridType.normal) ? 2 : 3;
|
||||||
|
List<Widget> list = [];
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
list.add(Positioned(
|
||||||
|
top: (space + itemW) * (i ~/ column),
|
||||||
|
left: (space + itemW) * (i % column),
|
||||||
|
child: SizedBox(
|
||||||
|
width: itemW,
|
||||||
|
height: itemW,
|
||||||
|
child: widget.itemBuilder(context, i),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: list,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build one child.
|
||||||
|
Widget? _buildOneChild(BuildContext context) {
|
||||||
|
double? bigImgWidth = widget.bigImageWidth?.toDouble();
|
||||||
|
double? bigImgHeight = widget.bigImageHeight?.toDouble();
|
||||||
|
if (!_isZero(bigImgWidth) && !_isZero(bigImgHeight)) {
|
||||||
|
return _getOneChild(context, bigImgWidth!, bigImgHeight!);
|
||||||
|
} else if (widget.bigImage != null) {
|
||||||
|
String bigImageUrl = widget.bigImageUrl!;
|
||||||
|
Rect? bigImgRect = ngvBigImageSizeMap[bigImageUrl];
|
||||||
|
bigImgWidth = bigImgRect?.width;
|
||||||
|
bigImgHeight = bigImgRect?.height;
|
||||||
|
if (!_isZero(bigImgWidth) && !_isZero(bigImgHeight)) {
|
||||||
|
return _getOneChild(context, bigImgWidth!, bigImgHeight!);
|
||||||
|
} else {
|
||||||
|
_ImageUtil().getImageSize(widget.bigImage)?.then((rect) {
|
||||||
|
ngvBigImageSizeMap[bigImageUrl] = rect;
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
}).catchError((e) {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get one child.
|
||||||
|
Widget _getOneChild(BuildContext context, double width, double height) {
|
||||||
|
Rect rect = _getBigImgSize(width, height);
|
||||||
|
return SizedBox(
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
child: widget.itemBuilder(context, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build weChat group.
|
||||||
|
Widget _buildWeChatGroup(BuildContext context) {
|
||||||
|
int itemCount = math.min(9, widget.itemCount);
|
||||||
|
double width = widget.width! - widget.padding.horizontal;
|
||||||
|
double space = widget.space;
|
||||||
|
double itemW;
|
||||||
|
|
||||||
|
int column = itemCount < 5 ? 2 : 3;
|
||||||
|
int row = 0;
|
||||||
|
if (itemCount == 1) {
|
||||||
|
row = 1;
|
||||||
|
itemW = width;
|
||||||
|
} else if (itemCount < 5) {
|
||||||
|
row = itemCount == 2 ? 1 : 2;
|
||||||
|
itemW = (width - space) / 2;
|
||||||
|
} else if (itemCount < 7) {
|
||||||
|
row = 2;
|
||||||
|
itemW = (width - space * 2) / 3;
|
||||||
|
} else {
|
||||||
|
row = 3;
|
||||||
|
itemW = (width - space * 2) / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int first = itemCount % column;
|
||||||
|
List<Widget> list = [];
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
double left;
|
||||||
|
if (first > 0 && i < first) {
|
||||||
|
left = (width - itemW * first - space * (first - 1)) / 2 +
|
||||||
|
(itemW + space) * i;
|
||||||
|
} else {
|
||||||
|
left = (space + itemW) * ((i - first) % column);
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemIndex = (first > 0 && i < first)
|
||||||
|
? 0
|
||||||
|
: (first > 0 ? (i + column - first) : i) ~/ column;
|
||||||
|
|
||||||
|
double top = (width - itemW * row - space * (row - 1)) / 2 +
|
||||||
|
(space + itemW) * itemIndex;
|
||||||
|
|
||||||
|
list.add(Positioned(
|
||||||
|
top: top,
|
||||||
|
left: left,
|
||||||
|
child: SizedBox(
|
||||||
|
width: itemW,
|
||||||
|
height: itemW,
|
||||||
|
child: widget.itemBuilder(context, i),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: list,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build dingTalk group.
|
||||||
|
Widget _buildDingTalkGroup(BuildContext context) {
|
||||||
|
double width = widget.width! - widget.padding.horizontal;
|
||||||
|
int itemCount = math.min(4, widget.itemCount);
|
||||||
|
double itemW = (width - widget.space) / 2;
|
||||||
|
List<Widget> children = [];
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
children.add(Positioned(
|
||||||
|
top: (widget.space + itemW) * (i ~/ 2),
|
||||||
|
left: (widget.space + itemW) *
|
||||||
|
(((itemCount == 3 && i == 2) ? i + 1 : i) % 2),
|
||||||
|
child: SizedBox(
|
||||||
|
width: itemCount == 1 ? width : itemW,
|
||||||
|
height:
|
||||||
|
(itemCount == 1 || itemCount == 2 || (itemCount == 3 && i == 0))
|
||||||
|
? width
|
||||||
|
: itemW,
|
||||||
|
child: widget.itemBuilder(context, i),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
return ClipOval(
|
||||||
|
child: Stack(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build QQ group.
|
||||||
|
Widget _buildQQGroup(BuildContext context) {
|
||||||
|
double width = widget.width! - widget.padding.horizontal;
|
||||||
|
int itemCount = math.min(5, widget.itemCount);
|
||||||
|
if (itemCount == 1) {
|
||||||
|
return ClipOval(
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: width,
|
||||||
|
child: widget.itemBuilder(context, 0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> children = [];
|
||||||
|
double startDegree = 0;
|
||||||
|
double r = 0;
|
||||||
|
double r1 = 0;
|
||||||
|
double centerX = width / 2;
|
||||||
|
double centerY = width / 2;
|
||||||
|
switch (itemCount) {
|
||||||
|
case 2:
|
||||||
|
startDegree = 135;
|
||||||
|
r = width / (2 + 2 * math.sin(math.pi / 4));
|
||||||
|
r1 = r;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
startDegree = 210;
|
||||||
|
r = width / (2 + 4 * math.sin(math.pi * (3 - 2) / (2 * 3)));
|
||||||
|
r1 = r / math.cos(math.pi * (3 - 2) / (2 * 3));
|
||||||
|
double R = r *
|
||||||
|
(1 + math.sin(math.pi / itemCount)) /
|
||||||
|
math.sin(math.pi / itemCount);
|
||||||
|
double dy = 0.5 * (width - R - r * (1 + 1 / math.tan(math.pi / 3)));
|
||||||
|
centerY = dy + r + r1;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
startDegree = 180;
|
||||||
|
r = width / 4;
|
||||||
|
r1 = r / math.cos(math.pi / 4);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
startDegree = 126;
|
||||||
|
r = width / (2 + 4 * math.sin(math.pi * (5 - 2) / (2 * 5)));
|
||||||
|
r1 = r / math.cos(math.pi * (5 - 2) / (2 * 5));
|
||||||
|
double R = r *
|
||||||
|
(1 + math.sin(math.pi / itemCount)) /
|
||||||
|
math.sin(math.pi / itemCount);
|
||||||
|
double dy = 0.5 * (width - R - r * (1 + 1 / math.tan(math.pi / 5)));
|
||||||
|
centerY = dy + r + r1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
double degree1 = (itemCount == 2 || itemCount == 4) ? (-math.pi / 4) : 0;
|
||||||
|
double x = centerX + r1 * math.sin(degree1 + i * 2 * math.pi / itemCount);
|
||||||
|
double y = centerY - r1 * math.cos(degree1 + i * 2 * math.pi / itemCount);
|
||||||
|
|
||||||
|
double degree = startDegree + i * 2 * 180 / itemCount;
|
||||||
|
if (degree >= 360) degree = degree % 360;
|
||||||
|
double previousX = r + 2 * r * math.sin(degree / 180 * math.pi);
|
||||||
|
double previousY = r - 2 * r * math.cos(degree / 180 * math.pi);
|
||||||
|
|
||||||
|
Widget child = Positioned.fromRect(
|
||||||
|
rect: Rect.fromCircle(center: Offset(x, y), radius: r),
|
||||||
|
child: ClipPath(
|
||||||
|
clipper: QQClipper(
|
||||||
|
total: itemCount,
|
||||||
|
index: i,
|
||||||
|
initIndex: widget.initIndex,
|
||||||
|
previousX: previousX,
|
||||||
|
previousY: previousY,
|
||||||
|
degree: degree,
|
||||||
|
arcAngle: widget.arcAngle,
|
||||||
|
space: widget.space,
|
||||||
|
),
|
||||||
|
child: widget.itemBuilder(context, i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
children.add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stack(children: children);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// double is zero.
|
||||||
|
bool _isZero(double? value) {
|
||||||
|
return value == null || value == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get big image size.
|
||||||
|
Rect _getBigImgSize(double originalWidth, double originalHeight) {
|
||||||
|
double width = widget.width ??
|
||||||
|
(MediaQuery.of(context).size.width - widget.margin.horizontal);
|
||||||
|
width = width - widget.padding.horizontal;
|
||||||
|
double itemW = (width - widget.space * 2) / 3;
|
||||||
|
|
||||||
|
//double devicePixelRatio = MediaQuery.of(context)?.devicePixelRatio ?? 3;
|
||||||
|
double devicePixelRatio = 1.0;
|
||||||
|
double tempWidth = originalWidth / devicePixelRatio;
|
||||||
|
double tempHeight = originalHeight / devicePixelRatio;
|
||||||
|
double maxW = itemW * 2 + widget.space;
|
||||||
|
double minW = width / 2;
|
||||||
|
|
||||||
|
double relWidth = tempWidth >= maxW ? maxW : math.max(minW, tempWidth);
|
||||||
|
|
||||||
|
double relHeight;
|
||||||
|
double ratio = tempWidth / tempHeight;
|
||||||
|
if (tempWidth == tempHeight) {
|
||||||
|
relHeight = relWidth;
|
||||||
|
} else if (tempWidth > tempHeight) {
|
||||||
|
relHeight = relWidth / (math.min(ratio, 4 / 3));
|
||||||
|
} else {
|
||||||
|
relHeight = relWidth / (math.max(ratio, 3 / 4));
|
||||||
|
}
|
||||||
|
return Rect.fromLTRB(0, 0, relWidth, relHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget? child = Container();
|
||||||
|
double? realWidth = widget.width;
|
||||||
|
double? realHeight = widget.height;
|
||||||
|
switch (widget.type) {
|
||||||
|
case NineGridType.normal:
|
||||||
|
case NineGridType.weiBo:
|
||||||
|
case NineGridType.weChat:
|
||||||
|
Rect size = _initSize(context);
|
||||||
|
if (widget.itemCount == 1) {
|
||||||
|
child = _buildOneChild(context);
|
||||||
|
if (child == null) {
|
||||||
|
realWidth = size.right;
|
||||||
|
realHeight = size.bottom;
|
||||||
|
child = _buildChild(context, size.left);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
realWidth = size.right;
|
||||||
|
realHeight = size.bottom;
|
||||||
|
child = _buildChild(context, size.left);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NineGridType.weChatGp:
|
||||||
|
child = _buildWeChatGroup(context);
|
||||||
|
break;
|
||||||
|
case NineGridType.dingTalkGp:
|
||||||
|
child = _buildDingTalkGroup(context);
|
||||||
|
break;
|
||||||
|
case NineGridType.qqGp:
|
||||||
|
child = _buildQQGroup(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
alignment: widget.alignment,
|
||||||
|
color: widget.color,
|
||||||
|
decoration: widget.decoration,
|
||||||
|
margin: widget.margin,
|
||||||
|
padding: widget.padding,
|
||||||
|
width: realWidth,
|
||||||
|
height: realHeight,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// image util.
|
||||||
|
class _ImageUtil {
|
||||||
|
late ImageStreamListener listener;
|
||||||
|
late ImageStream imageStream;
|
||||||
|
|
||||||
|
/// get image size.
|
||||||
|
Future<Rect>? getImageSize(Image? image) {
|
||||||
|
if (image == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Completer<Rect> completer = Completer<Rect>();
|
||||||
|
listener = ImageStreamListener(
|
||||||
|
(ImageInfo info, bool synchronousCall) {
|
||||||
|
imageStream.removeListener(listener);
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(Rect.fromLTWH(
|
||||||
|
0, 0, info.image.width.toDouble(), info.image.height.toDouble()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (dynamic exception, StackTrace? stackTrace) {
|
||||||
|
imageStream.removeListener(listener);
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(exception, stackTrace);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
imageStream = image.image.resolve(const ImageConfiguration());
|
||||||
|
imageStream.addListener(listener);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// QQ Clipper.
|
||||||
|
class QQClipper extends CustomClipper<Path> {
|
||||||
|
QQClipper({
|
||||||
|
this.total = 0,
|
||||||
|
this.index = 0,
|
||||||
|
this.initIndex = 1,
|
||||||
|
this.previousX = 0,
|
||||||
|
this.previousY = 0,
|
||||||
|
this.degree = 0,
|
||||||
|
this.arcAngle = 0,
|
||||||
|
this.space = 0,
|
||||||
|
}) : assert(arcAngle >= 0 && arcAngle <= 180);
|
||||||
|
|
||||||
|
final int total;
|
||||||
|
final int index;
|
||||||
|
final int initIndex;
|
||||||
|
final double previousX;
|
||||||
|
final double previousY;
|
||||||
|
final double degree;
|
||||||
|
final double arcAngle;
|
||||||
|
final double space;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Path getClip(Size size) {
|
||||||
|
double r = size.width / 2;
|
||||||
|
Path path = Path();
|
||||||
|
List<Offset> points = [];
|
||||||
|
|
||||||
|
if (total == 2 && index == initIndex) {
|
||||||
|
path.addOval(Rect.fromLTRB(0, 0, size.width, size.height));
|
||||||
|
} else {
|
||||||
|
/// arcAngle and space, prefer to use arcAngle.
|
||||||
|
double spaceA = arcAngle > 0
|
||||||
|
? (arcAngle / 2)
|
||||||
|
: (math.acos((r - math.min(r, space)) / r) / math.pi * 180);
|
||||||
|
double startA = degree + spaceA;
|
||||||
|
double endA = degree - spaceA;
|
||||||
|
for (double i = startA; i <= 360 + endA; i = i + 1) {
|
||||||
|
double x1 = r + r * math.sin(d2r(i));
|
||||||
|
double y1 = r - r * math.cos(d2r(i));
|
||||||
|
points.add(Offset(x1, y1));
|
||||||
|
}
|
||||||
|
|
||||||
|
double spaceB = math.atan(
|
||||||
|
r * math.sin(d2r(spaceA)) / (2 * r - r * math.cos(d2r(spaceA)))) /
|
||||||
|
math.pi *
|
||||||
|
180;
|
||||||
|
double r1 = (2 * r - r * math.cos(d2r(spaceA))) / math.cos(d2r(spaceB));
|
||||||
|
double startB = degree - 180 - spaceB;
|
||||||
|
double endB = degree - 180 + spaceB;
|
||||||
|
List<Offset> pointsB = [];
|
||||||
|
for (double i = startB; i < endB; i = i + 1) {
|
||||||
|
double x1 = previousX + r1 * math.sin(d2r(i));
|
||||||
|
double y1 = previousY - r1 * math.cos(d2r(i));
|
||||||
|
pointsB.add(Offset(x1, y1));
|
||||||
|
}
|
||||||
|
points.addAll(pointsB.reversed);
|
||||||
|
path.addPolygon(points, true);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// degree to radian.
|
||||||
|
double d2r(double degree) {
|
||||||
|
return degree / 180 * math.pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReclip(CustomClipper<Path> oldClipper) {
|
||||||
|
return this != oldClipper;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:PiliPalaX/common/widgets/imageview.dart';
|
||||||
import 'package:PiliPalaX/http/video.dart';
|
import 'package:PiliPalaX/http/video.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -938,130 +939,142 @@ InlineSpan buildContent(
|
|||||||
}
|
}
|
||||||
// 图片渲染
|
// 图片渲染
|
||||||
if (content.pictures.isNotEmpty) {
|
if (content.pictures.isNotEmpty) {
|
||||||
final List<String> picList = <String>[];
|
// final List<String> picList = <String>[];
|
||||||
final int len = content.pictures.length;
|
// final int len = content.pictures.length;
|
||||||
spanChildren.add(const TextSpan(text: '\n'));
|
spanChildren.add(const TextSpan(text: '\n'));
|
||||||
if (len == 1) {
|
spanChildren.add(
|
||||||
Map pictureItem = content.pictures.first;
|
WidgetSpan(
|
||||||
picList.add(pictureItem['img_src']);
|
child: LayoutBuilder(
|
||||||
spanChildren.add(
|
builder: (_, constraints) => image(
|
||||||
WidgetSpan(
|
constraints.maxWidth,
|
||||||
child: LayoutBuilder(
|
content.pictures,
|
||||||
builder: (BuildContext context, BoxConstraints box) {
|
),
|
||||||
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
|
),
|
||||||
// double width = (box.maxWidth / 2).truncateToDouble();
|
),
|
||||||
double height = 100;
|
);
|
||||||
try {
|
// if (len == 1) {
|
||||||
height = ((box.maxWidth /
|
// Map pictureItem = content.pictures.first;
|
||||||
2 *
|
// picList.add(pictureItem['img_src']);
|
||||||
pictureItem['img_height'] /
|
// spanChildren.add(
|
||||||
pictureItem['img_width']))
|
// WidgetSpan(
|
||||||
.truncateToDouble();
|
// child: LayoutBuilder(
|
||||||
} catch (_) {}
|
// builder: (BuildContext context, BoxConstraints box) {
|
||||||
|
// double maxHeight = box.maxWidth * 0.6; // 设置最大高度
|
||||||
|
// // double width = (box.maxWidth / 2).truncateToDouble();
|
||||||
|
// double height = 100;
|
||||||
|
// try {
|
||||||
|
// height = ((box.maxWidth /
|
||||||
|
// 2 *
|
||||||
|
// pictureItem['img_height'] /
|
||||||
|
// pictureItem['img_width']))
|
||||||
|
// .truncateToDouble();
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
showDialog(
|
// showDialog(
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (BuildContext context) {
|
// builder: (BuildContext context) {
|
||||||
return ImagePreview(initialPage: 0, imgList: picList);
|
// return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
child: Container(
|
// child: Container(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
// padding: const EdgeInsets.only(top: 4),
|
||||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
// constraints: BoxConstraints(maxHeight: maxHeight),
|
||||||
width: box.maxWidth / 2,
|
// width: box.maxWidth / 2,
|
||||||
height: height,
|
// height: height,
|
||||||
child: Stack(
|
// child: Stack(
|
||||||
children: [
|
// children: [
|
||||||
Positioned.fill(
|
// Positioned.fill(
|
||||||
child: NetworkImgLayer(
|
// child: NetworkImgLayer(
|
||||||
src: pictureItem['img_src'],
|
// src: pictureItem['img_src'],
|
||||||
width: box.maxWidth / 2,
|
// width: box.maxWidth / 2,
|
||||||
height: height,
|
// height: height,
|
||||||
semanticsLabel: '图片1,共1张',
|
// semanticsLabel: '图片1,共1张',
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
height > Get.size.height * 0.9
|
// height > Get.size.height * 0.9
|
||||||
? const PBadge(
|
// ? const PBadge(
|
||||||
text: '长图',
|
// text: '长图',
|
||||||
right: 8,
|
// right: 8,
|
||||||
bottom: 8,
|
// bottom: 8,
|
||||||
)
|
// )
|
||||||
: const SizedBox(),
|
// : const SizedBox(),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
} else if (len > 1) {
|
// } else if (len > 1) {
|
||||||
List<Widget> list = [];
|
// List<Widget> list = [];
|
||||||
for (var i = 0; i < len; i++) {
|
// for (var i = 0; i < len; i++) {
|
||||||
picList.add(content.pictures[i]['img_src']);
|
// picList.add(content.pictures[i]['img_src']);
|
||||||
list.add(
|
// list.add(
|
||||||
LayoutBuilder(
|
// LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
// builder: (context, BoxConstraints box) {
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
showDialog(
|
// showDialog(
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
return ImagePreview(initialPage: i, imgList: picList);
|
// return ImagePreview(initialPage: i, imgList: picList);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
child: NetworkImgLayer(
|
// child: NetworkImgLayer(
|
||||||
src: content.pictures[i]['img_src'],
|
// src: content.pictures[i]['img_src'],
|
||||||
width: box.maxWidth,
|
// width: box.maxWidth,
|
||||||
height: box.maxWidth,
|
// height: box.maxWidth,
|
||||||
origAspectRatio: content.pictures[i]['img_width'] /
|
// origAspectRatio: content.pictures[i]['img_width'] /
|
||||||
content.pictures[i]['img_height'],
|
// content.pictures[i]['img_height'],
|
||||||
semanticsLabel: '图片${i + 1},共$len张',
|
// semanticsLabel: '图片${i + 1},共$len张',
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
spanChildren.add(
|
// spanChildren.add(
|
||||||
WidgetSpan(
|
// WidgetSpan(
|
||||||
child: Semantics(
|
// // child: NineGridView(),
|
||||||
explicitChildNodes: true,
|
// child: Semantics(
|
||||||
container: true,
|
// explicitChildNodes: true,
|
||||||
child: LayoutBuilder(
|
// container: true,
|
||||||
builder: (context, BoxConstraints box) {
|
// child: LayoutBuilder(
|
||||||
double maxWidth = box.maxWidth;
|
// builder: (context, BoxConstraints box) {
|
||||||
double crossCount = len < 3 ? 2 : 3;
|
// double maxWidth = box.maxWidth;
|
||||||
double height = maxWidth /
|
// double crossCount = len < 3 ? 2 : 3;
|
||||||
crossCount *
|
// double height = maxWidth /
|
||||||
(len % crossCount == 0
|
// crossCount *
|
||||||
? len ~/ crossCount
|
// (len % crossCount == 0
|
||||||
: len ~/ crossCount + 1) +
|
// ? len ~/ crossCount
|
||||||
6;
|
// : len ~/ crossCount + 1) +
|
||||||
return Container(
|
// 6;
|
||||||
padding: const EdgeInsets.only(top: 6),
|
// return Container(
|
||||||
height: height,
|
// padding: const EdgeInsets.only(top: 6),
|
||||||
child: GridView.count(
|
// height: height,
|
||||||
padding: EdgeInsets.zero,
|
// child: GridView.count(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
// padding: EdgeInsets.zero,
|
||||||
crossAxisCount: crossCount.toInt(),
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
mainAxisSpacing: 4.0,
|
// crossAxisCount: crossCount.toInt(),
|
||||||
crossAxisSpacing: 4.0,
|
// mainAxisSpacing: 4.0,
|
||||||
childAspectRatio: 1,
|
// crossAxisSpacing: 4.0,
|
||||||
children: list,
|
// childAspectRatio: 1,
|
||||||
),
|
// children: list,
|
||||||
);
|
// ),
|
||||||
},
|
// );
|
||||||
)),
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// ),
|
||||||
}
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 笔记链接
|
// 笔记链接
|
||||||
|
|||||||
Reference in New Issue
Block a user