You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
7.1 KiB
Dart
211 lines
7.1 KiB
Dart
import 'dart:developer';
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:fluffychat/config/app_config.dart';
|
|
import 'package:fluffychat/pangea/utils/any_state_holder.dart';
|
|
import 'package:fluffychat/pangea/widgets/common_widgets/overlay_container.dart';
|
|
import '../../config/themes.dart';
|
|
import '../../widgets/matrix.dart';
|
|
import 'error_handler.dart';
|
|
|
|
class OverlayUtil {
|
|
static showPositionedCard({
|
|
required BuildContext context,
|
|
required Widget cardToShow,
|
|
required Size cardSize,
|
|
required String transformTargetId,
|
|
backDropToDismiss = true,
|
|
Color? borderColor,
|
|
}) {
|
|
try {
|
|
MatrixState.pAnyState.closeOverlay();
|
|
|
|
final LayerLinkAndKey layerLinkAndKey =
|
|
MatrixState.pAnyState.layerLinkAndKey(transformTargetId);
|
|
|
|
final Offset cardOffset = _calculateCardOffset(
|
|
cardSize: cardSize,
|
|
transformTargetKey: layerLinkAndKey.key,
|
|
);
|
|
|
|
MatrixState.pAnyState.overlay = OverlayEntry(
|
|
builder: (context) => Stack(
|
|
children: [
|
|
if (backDropToDismiss) const TransparentBackdrop(),
|
|
Positioned(
|
|
width: cardSize.width,
|
|
height: cardSize.height,
|
|
child: CompositedTransformFollower(
|
|
link: layerLinkAndKey.link,
|
|
showWhenUnlinked: false,
|
|
offset: cardOffset,
|
|
child: Material(
|
|
borderOnForeground: false,
|
|
color: Colors.transparent,
|
|
clipBehavior: Clip.antiAlias,
|
|
child: OverlayContainer(
|
|
cardToShow: cardToShow,
|
|
borderColor: borderColor,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
Overlay.of(layerLinkAndKey.key.currentContext!)
|
|
.insert(MatrixState.pAnyState.overlay!);
|
|
} catch (err, stack) {
|
|
debugger(when: kDebugMode);
|
|
ErrorHandler.logError(e: err, s: stack);
|
|
}
|
|
}
|
|
|
|
/// calculates the card offset relative to the target
|
|
/// identified by [transformTargetKey]
|
|
static Offset _calculateCardOffset({
|
|
required Size cardSize,
|
|
required LabeledGlobalKey transformTargetKey,
|
|
final double minPadding = 10.0,
|
|
}) {
|
|
// debugger(when: kDebugMode);
|
|
//Note: assumes overlay in chatview
|
|
final OverlayConstraints constraints =
|
|
ChatViewConstraints(transformTargetKey.currentContext!);
|
|
|
|
final RenderObject? targetRenderBox =
|
|
transformTargetKey.currentContext!.findRenderObject();
|
|
if (targetRenderBox == null) return Offset.zero;
|
|
final Offset transformTargetOffset =
|
|
(targetRenderBox as RenderBox).localToGlobal(Offset.zero);
|
|
final Size transformTargetSize = targetRenderBox.size;
|
|
|
|
// ideally horizontally centered on target
|
|
double dx = transformTargetSize.width / 2 - cardSize.width / 2;
|
|
// make sure it's not off the left edge of the screen
|
|
// if transformTargetOffset.dx + dc < constraints.x0 + minPadding
|
|
|
|
if (transformTargetOffset.dx + dx < minPadding + constraints.x0) {
|
|
debugPrint("setting dx");
|
|
dx = minPadding + constraints.x0 - transformTargetOffset.dx;
|
|
}
|
|
// make sure it's not off the right edge of the screen
|
|
if (transformTargetOffset.dx + dx + cardSize.width + minPadding >
|
|
constraints.x1) {
|
|
dx = constraints.x1 -
|
|
transformTargetOffset.dx -
|
|
cardSize.width -
|
|
minPadding;
|
|
}
|
|
|
|
// if there's more room above target,
|
|
// put the card there
|
|
// else,
|
|
// put it below
|
|
// debugPrint(
|
|
// "transformTargetOffset.dx ${transformTargetOffset.dx} transformTargetOffset.dy ${transformTargetOffset.dy}");
|
|
// debugPrint(
|
|
// "transformTargetSize.width ${transformTargetSize.width} transformTargetSize.height ${transformTargetSize.height}");
|
|
double dy = transformTargetOffset.dy >
|
|
constraints.y1 -
|
|
transformTargetOffset.dy -
|
|
transformTargetSize.height
|
|
? -cardSize.height - minPadding
|
|
: transformTargetSize.height + minPadding;
|
|
// make sure it's not off the top edge of the screen
|
|
if (dy < minPadding + constraints.y0 - transformTargetOffset.dy) {
|
|
dy = minPadding + constraints.y0 - transformTargetOffset.dy;
|
|
}
|
|
// make sure it's not off the bottom edge of the screen
|
|
if (transformTargetOffset.dy + dy + cardSize.height + minPadding >
|
|
constraints.y1) {
|
|
dy = constraints.y1 -
|
|
transformTargetOffset.dy -
|
|
cardSize.height -
|
|
minPadding;
|
|
}
|
|
// debugPrint("dx $dx dy $dy");
|
|
|
|
return Offset(dx, dy);
|
|
}
|
|
}
|
|
|
|
class TransparentBackdrop extends StatelessWidget {
|
|
const TransparentBackdrop({
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
borderOnForeground: false,
|
|
color: Colors.transparent,
|
|
clipBehavior: Clip.antiAlias,
|
|
child: InkWell(
|
|
hoverColor: Colors.transparent,
|
|
splashColor: Colors.transparent,
|
|
focusColor: Colors.transparent,
|
|
highlightColor: Colors.transparent,
|
|
onTap: () {
|
|
MatrixState.pAnyState.closeOverlay();
|
|
},
|
|
child: Container(
|
|
height: double.infinity,
|
|
width: double.infinity,
|
|
color: Colors.transparent,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// global coordinates that the overlay should stay inside
|
|
abstract class OverlayConstraints {
|
|
late double x0;
|
|
late double y0;
|
|
late double x1;
|
|
late double y1;
|
|
}
|
|
|
|
class ChatViewConstraints implements OverlayConstraints {
|
|
@override
|
|
late double x0;
|
|
@override
|
|
late double y0;
|
|
@override
|
|
late double x1;
|
|
@override
|
|
late double y1;
|
|
|
|
ChatViewConstraints(BuildContext context) {
|
|
final MediaQueryData mediaQueryData =
|
|
MediaQuery.of(Scaffold.of(context).context);
|
|
final bool isColumnMode = FluffyThemes.isColumnMode(context);
|
|
|
|
x0 = isColumnMode
|
|
? AppConfig.columnWidth + 70.0
|
|
: max(mediaQueryData.viewPadding.left, mediaQueryData.viewInsets.left);
|
|
y0 = max(mediaQueryData.viewPadding.top, mediaQueryData.viewInsets.top);
|
|
x1 = mediaQueryData.size.width -
|
|
max(mediaQueryData.viewPadding.right, mediaQueryData.viewInsets.right);
|
|
y1 = mediaQueryData.size.height -
|
|
max(
|
|
mediaQueryData.viewPadding.bottom,
|
|
mediaQueryData.viewInsets.bottom,
|
|
);
|
|
|
|
// https://medium.com/flutter-community/a-flutter-guide-to-visual-overlap-padding-viewpadding-and-viewinsets-a63e214be6e8
|
|
// debugPrint(
|
|
// "viewInsets ${mediaQueryData.viewInsets.left} ${mediaQueryData.viewInsets.top} ${mediaQueryData.viewInsets.right} ${mediaQueryData.viewInsets.bottom}");
|
|
// debugPrint(
|
|
// "padding ${mediaQueryData.padding.left} ${mediaQueryData.padding.top} ${mediaQueryData.padding.right} ${mediaQueryData.padding.bottom}");
|
|
// debugPrint(
|
|
// "viewPadding ${mediaQueryData.viewPadding.left} ${mediaQueryData.viewPadding.top} ${mediaQueryData.viewPadding.right} ${mediaQueryData.viewPadding.bottom}");
|
|
// debugPrint("chatViewConstraints x0: $x0 y0: $y0 x1: $x1 y1: $y1");
|
|
}
|
|
}
|