mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-17 15:46:14 +08:00
custom emoji tooltip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
378
lib/common/widgets/custom_tooltip.dart
Normal file
378
lib/common/widgets/custom_tooltip.dart
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui' show clampDouble;
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum TooltipType { top, right }
|
||||||
|
|
||||||
|
class CustomTooltip extends StatefulWidget {
|
||||||
|
const CustomTooltip({
|
||||||
|
super.key,
|
||||||
|
this.type = TooltipType.top,
|
||||||
|
required this.overlayWidget,
|
||||||
|
required this.child,
|
||||||
|
this.indicator,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TooltipType type;
|
||||||
|
final Widget child;
|
||||||
|
final Widget Function() overlayWidget;
|
||||||
|
final Widget Function()? indicator;
|
||||||
|
|
||||||
|
static final List<CustomTooltipState> _openedTooltips =
|
||||||
|
<CustomTooltipState>[];
|
||||||
|
|
||||||
|
static bool dismissAllToolTips() {
|
||||||
|
if (_openedTooltips.isNotEmpty) {
|
||||||
|
final List<CustomTooltipState> openedTooltips = _openedTooltips.toList();
|
||||||
|
for (final CustomTooltipState state in openedTooltips) {
|
||||||
|
assert(state.mounted);
|
||||||
|
state._scheduleDismissTooltip();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomTooltip> createState() => CustomTooltipState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomTooltipState extends State<CustomTooltip>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
static const Duration _fadeInDuration = Duration(milliseconds: 150);
|
||||||
|
static const Duration _fadeOutDuration = Duration(milliseconds: 75);
|
||||||
|
|
||||||
|
final OverlayPortalController _overlayController = OverlayPortalController();
|
||||||
|
|
||||||
|
AnimationController? _backingController;
|
||||||
|
AnimationController get _controller {
|
||||||
|
return _backingController ??= AnimationController(
|
||||||
|
duration: _fadeInDuration,
|
||||||
|
reverseDuration: _fadeOutDuration,
|
||||||
|
vsync: this,
|
||||||
|
)..addStatusListener(_handleStatusChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurvedAnimation? _backingOverlayAnimation;
|
||||||
|
CurvedAnimation get _overlayAnimation {
|
||||||
|
return _backingOverlayAnimation ??= CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LongPressGestureRecognizer? _longPressRecognizer;
|
||||||
|
|
||||||
|
AnimationStatus _animationStatus = AnimationStatus.dismissed;
|
||||||
|
void _handleStatusChanged(AnimationStatus status) {
|
||||||
|
assert(mounted);
|
||||||
|
switch ((_animationStatus.isDismissed, status.isDismissed)) {
|
||||||
|
case (false, true):
|
||||||
|
CustomTooltip._openedTooltips.remove(this);
|
||||||
|
_overlayController.hide();
|
||||||
|
case (true, false):
|
||||||
|
_overlayController.show();
|
||||||
|
CustomTooltip._openedTooltips.add(this);
|
||||||
|
case (true, true) || (false, false):
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_animationStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleShowTooltip() {
|
||||||
|
_controller.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleDismissTooltip() {
|
||||||
|
_controller.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePointerDown(PointerDownEvent event) {
|
||||||
|
assert(mounted);
|
||||||
|
const Set<PointerDeviceKind> triggerModeDeviceKinds = <PointerDeviceKind>{
|
||||||
|
PointerDeviceKind.invertedStylus,
|
||||||
|
PointerDeviceKind.stylus,
|
||||||
|
PointerDeviceKind.touch,
|
||||||
|
PointerDeviceKind.unknown,
|
||||||
|
PointerDeviceKind.trackpad,
|
||||||
|
};
|
||||||
|
_longPressRecognizer ??= LongPressGestureRecognizer(
|
||||||
|
debugOwner: this,
|
||||||
|
supportedDevices: triggerModeDeviceKinds,
|
||||||
|
);
|
||||||
|
_longPressRecognizer!
|
||||||
|
..onLongPress = _scheduleShowTooltip
|
||||||
|
..addPointer(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCustomTooltipOverlay(BuildContext context) {
|
||||||
|
final OverlayState overlayState =
|
||||||
|
Overlay.of(context, debugRequiredFor: widget);
|
||||||
|
final RenderBox box = this.context.findRenderObject()! as RenderBox;
|
||||||
|
final Offset target = box.localToGlobal(
|
||||||
|
box.size.center(Offset.zero),
|
||||||
|
ancestor: overlayState.context.findRenderObject(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final _CustomTooltipOverlay overlayChild = _CustomTooltipOverlay(
|
||||||
|
verticalOffset: box.size.height / 2,
|
||||||
|
horizontslOffset: box.size.width / 2,
|
||||||
|
type: widget.type,
|
||||||
|
animation: _overlayAnimation,
|
||||||
|
target: target,
|
||||||
|
onDismiss: _scheduleDismissTooltip,
|
||||||
|
overlayWidget: widget.overlayWidget,
|
||||||
|
indicator: widget.indicator,
|
||||||
|
);
|
||||||
|
|
||||||
|
return SelectionContainer.maybeOf(context) == null
|
||||||
|
? overlayChild
|
||||||
|
: SelectionContainer.disabled(child: overlayChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
CustomTooltip._openedTooltips.remove(this);
|
||||||
|
_longPressRecognizer?.onLongPressCancel = null;
|
||||||
|
_longPressRecognizer?.dispose();
|
||||||
|
_backingController?.dispose();
|
||||||
|
_backingOverlayAnimation?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget result = Listener(
|
||||||
|
onPointerDown: _handlePointerDown,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
return OverlayPortal(
|
||||||
|
controller: _overlayController,
|
||||||
|
overlayChildBuilder: _buildCustomTooltipOverlay,
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomTooltipOverlay extends StatelessWidget {
|
||||||
|
const _CustomTooltipOverlay({
|
||||||
|
required this.verticalOffset,
|
||||||
|
required this.horizontslOffset,
|
||||||
|
required this.type,
|
||||||
|
required this.animation,
|
||||||
|
required this.target,
|
||||||
|
required this.onDismiss,
|
||||||
|
required this.overlayWidget,
|
||||||
|
this.indicator,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double verticalOffset;
|
||||||
|
final double horizontslOffset;
|
||||||
|
final TooltipType type;
|
||||||
|
final Animation<double> animation;
|
||||||
|
final Offset target;
|
||||||
|
final VoidCallback onDismiss;
|
||||||
|
final Widget Function() overlayWidget;
|
||||||
|
final Widget Function()? indicator;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: onDismiss,
|
||||||
|
child: CustomMultiChildLayout(
|
||||||
|
delegate: _CustomMultiTooltipPositionDelegate(
|
||||||
|
type: type,
|
||||||
|
target: target,
|
||||||
|
verticalOffset: verticalOffset,
|
||||||
|
horizontslOffset: horizontslOffset,
|
||||||
|
preferBelow: false,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
LayoutId(
|
||||||
|
id: 'overlay',
|
||||||
|
child: overlayWidget(),
|
||||||
|
),
|
||||||
|
if (indicator != null)
|
||||||
|
LayoutId(
|
||||||
|
id: 'indicator',
|
||||||
|
child: indicator!(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
|
||||||
|
_CustomMultiTooltipPositionDelegate({
|
||||||
|
required this.type,
|
||||||
|
required this.target,
|
||||||
|
required this.verticalOffset,
|
||||||
|
required this.horizontslOffset,
|
||||||
|
required this.preferBelow,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TooltipType type;
|
||||||
|
|
||||||
|
final Offset target;
|
||||||
|
|
||||||
|
final double verticalOffset;
|
||||||
|
|
||||||
|
final double horizontslOffset;
|
||||||
|
|
||||||
|
final bool preferBelow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout(Size size) {
|
||||||
|
switch (type) {
|
||||||
|
case TooltipType.top:
|
||||||
|
Size? indicatorSize;
|
||||||
|
if (hasChild('indicator')) {
|
||||||
|
indicatorSize = layoutChild('indicator', BoxConstraints.loose(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChild('overlay')) {
|
||||||
|
final overlaySize =
|
||||||
|
layoutChild('overlay', BoxConstraints.loose(size));
|
||||||
|
Offset offset = positionDependentBox(
|
||||||
|
type: type,
|
||||||
|
size: size,
|
||||||
|
childSize: overlaySize,
|
||||||
|
target: target,
|
||||||
|
verticalOffset: verticalOffset,
|
||||||
|
horizontslOffset: horizontslOffset,
|
||||||
|
preferBelow: preferBelow,
|
||||||
|
);
|
||||||
|
if (indicatorSize != null) {
|
||||||
|
offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1);
|
||||||
|
positionChild(
|
||||||
|
'indicator',
|
||||||
|
Offset(
|
||||||
|
target.dx - indicatorSize.width / 2,
|
||||||
|
offset.dy + overlaySize.height - 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
positionChild('overlay', offset);
|
||||||
|
}
|
||||||
|
case TooltipType.right:
|
||||||
|
Size? indicatorSize;
|
||||||
|
if (hasChild('indicator')) {
|
||||||
|
indicatorSize = layoutChild('indicator', BoxConstraints.loose(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChild('overlay')) {
|
||||||
|
final overlaySize =
|
||||||
|
layoutChild('overlay', BoxConstraints.loose(size));
|
||||||
|
Offset offset = positionDependentBox(
|
||||||
|
type: type,
|
||||||
|
size: size,
|
||||||
|
childSize: overlaySize,
|
||||||
|
target: target,
|
||||||
|
verticalOffset: verticalOffset,
|
||||||
|
horizontslOffset: horizontslOffset,
|
||||||
|
preferBelow: preferBelow,
|
||||||
|
);
|
||||||
|
if (indicatorSize != null) {
|
||||||
|
offset = Offset(offset.dx + indicatorSize.height - 1, offset.dy);
|
||||||
|
positionChild(
|
||||||
|
'indicator',
|
||||||
|
Offset(
|
||||||
|
offset.dx - indicatorSize.width + 1,
|
||||||
|
target.dy - indicatorSize.height / 2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
positionChild('overlay', offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRelayout(_CustomMultiTooltipPositionDelegate oldDelegate) {
|
||||||
|
return target != oldDelegate.target ||
|
||||||
|
verticalOffset != oldDelegate.verticalOffset ||
|
||||||
|
preferBelow != oldDelegate.preferBelow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrianglePainter extends CustomPainter {
|
||||||
|
TrianglePainter(this.color, {this.type = TooltipType.top});
|
||||||
|
final TooltipType type;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
Path path;
|
||||||
|
switch (type) {
|
||||||
|
case TooltipType.top:
|
||||||
|
path = Path()
|
||||||
|
..moveTo(0, 0)
|
||||||
|
..lineTo(size.width, 0)
|
||||||
|
..lineTo(size.width / 2, size.height)
|
||||||
|
..close();
|
||||||
|
case TooltipType.right:
|
||||||
|
path = Path()
|
||||||
|
..moveTo(0, size.height / 2)
|
||||||
|
..lineTo(size.width, 0)
|
||||||
|
..lineTo(size.width, size.height)
|
||||||
|
..close();
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(TrianglePainter oldDelegate) => color != oldDelegate.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset positionDependentBox({
|
||||||
|
required TooltipType type,
|
||||||
|
required Size size,
|
||||||
|
required Size childSize,
|
||||||
|
required Offset target,
|
||||||
|
required bool preferBelow,
|
||||||
|
double verticalOffset = 0.0,
|
||||||
|
double horizontslOffset = 0.0,
|
||||||
|
double margin = 10.0,
|
||||||
|
}) {
|
||||||
|
switch (type) {
|
||||||
|
case TooltipType.top:
|
||||||
|
// VERTICAL DIRECTION
|
||||||
|
final bool fitsBelow =
|
||||||
|
target.dy + verticalOffset + childSize.height <= size.height - margin;
|
||||||
|
final bool fitsAbove =
|
||||||
|
target.dy - verticalOffset - childSize.height >= margin;
|
||||||
|
final bool tooltipBelow =
|
||||||
|
fitsAbove == fitsBelow ? preferBelow : fitsBelow;
|
||||||
|
final double y;
|
||||||
|
if (tooltipBelow) {
|
||||||
|
y = math.min(target.dy + verticalOffset, size.height - margin);
|
||||||
|
} else {
|
||||||
|
y = math.max(target.dy - verticalOffset - childSize.height, margin);
|
||||||
|
} // HORIZONTAL DIRECTION
|
||||||
|
final double flexibleSpace = size.width - childSize.width;
|
||||||
|
final double x = flexibleSpace <= 2 * margin
|
||||||
|
// If there's not enough horizontal space for margin + child, center the
|
||||||
|
// child.
|
||||||
|
? flexibleSpace / 2.0
|
||||||
|
: clampDouble(
|
||||||
|
target.dx - childSize.width / 2, margin, flexibleSpace - margin);
|
||||||
|
return Offset(x, y);
|
||||||
|
case TooltipType.right:
|
||||||
|
final double dy = math.max(margin, target.dy - childSize.height / 2);
|
||||||
|
final double dx = math.min(
|
||||||
|
target.dx + horizontslOffset, size.width - childSize.width - margin);
|
||||||
|
return Offset(dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
class Meta {
|
class Meta {
|
||||||
int? size;
|
int? size;
|
||||||
|
String? alias;
|
||||||
|
|
||||||
Meta({this.size});
|
Meta({
|
||||||
|
this.size,
|
||||||
|
this.alias,
|
||||||
|
});
|
||||||
|
|
||||||
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
|
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
|
||||||
size: json['size'] as int?,
|
size: json['size'] as int?,
|
||||||
|
alias: json['alias'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/custom_tooltip.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
@@ -37,6 +38,9 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
|
|
||||||
Widget _buildBody(
|
Widget _buildBody(
|
||||||
ThemeData theme, LoadingState<List<Package>?> loadingState) {
|
ThemeData theme, LoadingState<List<Package>?> loadingState) {
|
||||||
|
late final color = Get.currentRoute.startsWith('/whisperDetail')
|
||||||
|
? theme.colorScheme.surface
|
||||||
|
: theme.colorScheme.onInverseSurface;
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => loadingWidget,
|
Loading() => loadingWidget,
|
||||||
Success(:var response) => response?.isNotEmpty == true
|
Success(:var response) => response?.isNotEmpty == true
|
||||||
@@ -62,20 +66,7 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
itemCount: e.emote!.length,
|
itemCount: e.emote!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = e.emote![index];
|
final item = e.emote![index];
|
||||||
return Material(
|
Widget child = Padding(
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius:
|
|
||||||
const BorderRadius.all(Radius.circular(8)),
|
|
||||||
onTap: () => widget.onChoose(
|
|
||||||
item,
|
|
||||||
isTextEmote
|
|
||||||
? null
|
|
||||||
: e.emote!.first.meta!.size == 1
|
|
||||||
? 24
|
|
||||||
: 42,
|
|
||||||
null),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
child: isTextEmote
|
child: isTextEmote
|
||||||
? Center(
|
? Center(
|
||||||
@@ -89,11 +80,60 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
src: item.url!,
|
src: item.url!,
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
semanticsLabel: item.text!,
|
|
||||||
type: ImageType.emote,
|
type: ImageType.emote,
|
||||||
boxFit: BoxFit.contain,
|
boxFit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
if (!isTextEmote) {
|
||||||
|
child = CustomTooltip(
|
||||||
|
indicator: () => CustomPaint(
|
||||||
|
size: const Size(14, 8),
|
||||||
|
painter: TrianglePainter(color),
|
||||||
),
|
),
|
||||||
|
overlayWidget: () => Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 4,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
src: item.url!,
|
||||||
|
width: 65,
|
||||||
|
height: 65,
|
||||||
|
type: ImageType.emote,
|
||||||
|
boxFit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.meta?.alias ??
|
||||||
|
item.text!.substring(
|
||||||
|
1, item.text!.length - 1),
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(8)),
|
||||||
|
onTap: () => widget.onChoose(
|
||||||
|
item,
|
||||||
|
isTextEmote
|
||||||
|
? null
|
||||||
|
: e.emote!.first.meta!.size == 1
|
||||||
|
? 24
|
||||||
|
: 42,
|
||||||
|
null),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/custom_tooltip.dart';
|
||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
@@ -44,6 +45,7 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(LoadingState<List<LiveEmoteDatum>?> loadingState) {
|
Widget _buildBody(LoadingState<List<LiveEmoteDatum>?> loadingState) {
|
||||||
|
late final color = Theme.of(context).colorScheme.onInverseSurface;
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => loadingWidget,
|
Loading() => loadingWidget,
|
||||||
Success(:var response) => response?.isNotEmpty == true
|
Success(:var response) => response?.isNotEmpty == true
|
||||||
@@ -88,6 +90,39 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
|||||||
widget.onSendEmoticonUnique(e);
|
widget.onSendEmoticonUnique(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: CustomTooltip(
|
||||||
|
indicator: () => CustomPaint(
|
||||||
|
size: const Size(14, 8),
|
||||||
|
painter: TrianglePainter(color),
|
||||||
|
),
|
||||||
|
overlayWidget: () => Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
spacing: 4,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
src: e.url!,
|
||||||
|
width: 65,
|
||||||
|
height: 65,
|
||||||
|
type: ImageType.emote,
|
||||||
|
boxFit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
e.emoji!.startsWith('[')
|
||||||
|
? e.emoji!.substring(
|
||||||
|
1, e.emoji!.length - 1)
|
||||||
|
: e.emoji!,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
@@ -100,6 +135,7 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user