Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ class PageStyleCover {

bool get isPresets => isPureColor || isGradient || isBuiltInImage;
bool get isPhoto => isCustomImage || isLocalImage;
bool get isAlignEnable => isPhoto || isUnsplashImage || isBuiltInImage;

bool get isNone => type == PageStyleCoverImageType.none;
bool get isPureColor => type == PageStyleCoverImageType.pureColor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/desktop_cover_align.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
Expand All @@ -24,14 +25,18 @@ class DesktopCover extends StatefulWidget {
required this.node,
required this.coverType,
this.coverDetails,
this.enableAlign = false,
this.onAlignControllerCreated,
});

final ViewPB view;
final Node node;
final EditorState editorState;
final CoverType coverType;
final String? coverDetails;

final bool enableAlign;
final Function(DesktopCoverAlignController? alignController)?
onAlignControllerCreated;
@override
State<DesktopCover> createState() => _DesktopCoverState();
}
Expand All @@ -42,6 +47,27 @@ class _DesktopCoverState extends State<DesktopCover> {
);
String? get coverDetails =>
widget.node.attributes[DocumentHeaderBlockKeys.coverDetails];
String? get coverAlign =>
widget.node.attributes[DocumentHeaderBlockKeys.coverOffset];

late final DesktopCoverAlignController coverAlignController;

@override
void initState() {
super.initState();
coverAlignController = DesktopCoverAlignController(coverAlign);
if (widget.onAlignControllerCreated != null) {
widget.onAlignControllerCreated!(coverAlignController);
}
}

@override
void didUpdateWidget(covariant DesktopCover oldWidget) {
if (widget.coverDetails != oldWidget.coverDetails) {
coverAlignController.reset();
Comment on lines +66 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): reset() on coverAlignController may not restore previous alignment.

Currently, reset() always sets alignment to center, which may not match the new coverDetails. Please update the controller to initialize alignment based on the new coverDetails.

}
super.didUpdateWidget(oldWidget);
}

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -74,6 +100,13 @@ class _DesktopCoverState extends State<DesktopCover> {
child: FlowyNetworkImage(
url: cover.value,
userProfilePB: userProfilePB,
imageBuilder: (context, provider) {
return DesktopCoverAlign(
controller: coverAlignController,
imageProvider: provider,
alignEnable: widget.enableAlign,
);
},
),
);
}
Expand All @@ -82,9 +115,12 @@ class _DesktopCoverState extends State<DesktopCover> {
return SizedBox(
height: height,
width: double.infinity,
child: Image.asset(
PageStyleCoverImageType.builtInImagePath(cover.value),
fit: BoxFit.cover,
child: DesktopCoverAlign(
controller: coverAlignController,
imageProvider: AssetImage(
PageStyleCoverImageType.builtInImagePath(cover.value),
),
alignEnable: widget.enableAlign,
),
);
}
Expand Down Expand Up @@ -115,9 +151,12 @@ class _DesktopCoverState extends State<DesktopCover> {
return SizedBox(
height: height,
width: double.infinity,
child: Image.file(
File(cover.value),
fit: BoxFit.cover,
child: DesktopCoverAlign(
controller: coverAlignController,
imageProvider: FileImage(
File(cover.value),
),
alignEnable: widget.enableAlign,
),
);
}
Expand All @@ -134,6 +173,7 @@ class _DesktopCoverState extends State<DesktopCover> {
if (detail == null) {
return const SizedBox.shrink();
}

switch (widget.coverType) {
case CoverType.file:
if (isURL(detail)) {
Expand All @@ -144,20 +184,33 @@ class _DesktopCoverState extends State<DesktopCover> {
userProfilePB: userProfilePB,
errorWidgetBuilder: (context, url, error) =>
const SizedBox.shrink(),
imageBuilder: (context, provider) {
return DesktopCoverAlign(
controller: coverAlignController,
imageProvider: provider,
alignEnable: widget.enableAlign,
);
},
);
}
final imageFile = File(detail);
if (!imageFile.existsSync()) {
return const SizedBox.shrink();
}
return Image.file(
imageFile,
fit: BoxFit.cover,
final provider = FileImage(imageFile);
return DesktopCoverAlign(
controller: coverAlignController,
imageProvider: provider,
alignEnable: widget.enableAlign,
);

case CoverType.asset:
return Image.asset(
PageStyleCoverImageType.builtInImagePath(detail),
fit: BoxFit.cover,
final provider =
AssetImage(PageStyleCoverImageType.builtInImagePath(detail));
return DesktopCoverAlign(
controller: coverAlignController,
imageProvider: provider,
alignEnable: widget.enableAlign,
);
case CoverType.color:
final color = widget.coverDetails?.tryToColor() ?? Colors.white;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import 'package:flutter/material.dart';

class DesktopCoverAlignController extends ChangeNotifier {
DesktopCoverAlignController(String? offset) {
double x = 0;
double y = 0;
if (offset != null) {
final splits = offset.split(',');

try {
x = double.parse(splits.first);
} catch (e) {
x = 0;
}
try {
y = double.parse(splits.last);
} catch (e) {
y = 0;
}
}

_initialAlignment = Alignment(x, y);
_adjustedAlign = _initialAlignment;
}

late final Alignment _initialAlignment;

late Alignment _adjustedAlign;

Alignment get alignment => _adjustedAlign;

void reset() {
_adjustedAlign = Alignment.center;
Comment on lines +32 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): reset() always sets alignment to center, not to initial value.

reset() should set _adjustedAlign to _initialAlignment to properly restore the original alignment.

notifyListeners();
}

void cancel() {
_adjustedAlign = _initialAlignment;
notifyListeners();
}

void changeAlign(double x, double y) {
_adjustedAlign = Alignment(x, y);
Comment on lines +42 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): changeAlign does not notify listeners after updating alignment.

Please add notifyListeners() at the end of changeAlign to ensure UI updates when alignment changes.

}

bool get isModified => _adjustedAlign != _initialAlignment;

String getAlignAttribute() {
return "${_adjustedAlign.x.toStringAsFixed(1)},${_adjustedAlign.y.toStringAsFixed(1)}";
}
}

class DesktopCoverAlign extends StatefulWidget {
const DesktopCoverAlign({
super.key,
required this.controller,
required this.imageProvider,
this.fit = BoxFit.cover,
this.alignEnable = false,
});
final DesktopCoverAlignController controller;
final ImageProvider imageProvider;
final BoxFit fit;
final bool alignEnable;

@override
State<DesktopCoverAlign> createState() => _DesktopCoverAlignState();
}

class _DesktopCoverAlignState extends State<DesktopCoverAlign> {
ImageStreamListener? _imageStreamListener;
ImageStream? _imageStream;
Size? _imageSize;

Size? _frameSize;

double x = 0;
double y = 0;
late final DesktopCoverAlignController controller;

@override
void initState() {
super.initState();
controller = widget.controller;
final alignment = controller.alignment;
x = alignment.x;
y = alignment.y;
controller.addListener(updateAlign);
}

@override
void dispose() {
controller.removeListener(updateAlign);
super.dispose();

_stopImageStream();
}

@override
void didChangeDependencies() {
_resolveImage();
super.didChangeDependencies();
}

@override
void didUpdateWidget(DesktopCoverAlign oldWidget) {
if (widget.imageProvider != oldWidget.imageProvider) {
controller.reset();
_resolveImage();
}
super.didUpdateWidget(oldWidget);
}

void updateAlign() {
setState(() {
x = controller.alignment.x;
y = controller.alignment.y;
});
}

void _resolveImage() {
final ImageStream newStream = widget.imageProvider.resolve(
const ImageConfiguration(),
);
_updateSourceStream(newStream);
}

ImageStreamListener _getOrCreateListener() {
void handleImageFrame(ImageInfo info, bool synchronousCall) {
void setupCB() {
_imageSize = Size(
info.image.width.toDouble(),
info.image.height.toDouble(),
);
}

synchronousCall ? setupCB() : setState(setupCB);
}

_imageStreamListener = ImageStreamListener(
handleImageFrame,
);

return _imageStreamListener!;
}

void _updateSourceStream(ImageStream newStream) {
if (_imageStream?.key == newStream.key) {
return;
}
if (_imageStreamListener != null) {
_imageStream?.removeListener(_imageStreamListener!);
}
_imageStream = newStream;
_imageStream!.addListener(_getOrCreateListener());
}

void _stopImageStream() {
if (_imageStreamListener != null) {
_imageStream?.removeListener(_imageStreamListener!);
}
}

void _changeAlignOffset(Offset offset) {
setState(() {
if (_imageSize == null || _frameSize == null) return;

final imageRatio = _imageSize!.aspectRatio;
final frameRatio = _frameSize!.aspectRatio;
final isVertical = imageRatio < frameRatio;

final imageFrameHeight =
_frameSize!.width / _imageSize!.width * _imageSize!.height;
Comment on lines +172 to +173
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential division by zero in imageFrameHeight and imageFrameWidth calculations.

Add checks to ensure _imageSize!.width and _imageSize!.height are not zero before performing these calculations.

final imageFrameWidth =
_frameSize!.height / _imageSize!.height * _imageSize!.width;
final exceedWidth = imageFrameWidth - _frameSize!.width;
final exceedHeight = imageFrameHeight - _frameSize!.height;

if (isVertical) {
final targetY = y + offset.dy / exceedHeight * 2;
if (targetY >= -1 && targetY <= 1) {
y = targetY;
}
} else {
final targetX = x + offset.dx / exceedWidth * 2;
if (targetX >= -1 && targetX <= 1) {
x = targetX;
}
}
Comment on lines +179 to +189
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): No clamping for x/y values outside [-1, 1] range.

Clamping targetX and targetY to [-1, 1] would ensure the UI remains responsive even with large or fast drags.

Suggested change
if (isVertical) {
final targetY = y + offset.dy / exceedHeight * 2;
if (targetY >= -1 && targetY <= 1) {
y = targetY;
}
} else {
final targetX = x + offset.dx / exceedWidth * 2;
if (targetX >= -1 && targetX <= 1) {
x = targetX;
}
}
if (isVertical) {
final targetY = y + offset.dy / exceedHeight * 2;
y = targetY.clamp(-1.0, 1.0);
} else {
final targetX = x + offset.dx / exceedWidth * 2;
x = targetX.clamp(-1.0, 1.0);
}

widget.controller.changeAlign(x, y);
setState(() {});
});
}

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
_frameSize =
Size(constraints.biggest.width, constraints.biggest.height);
_imageSize ??= _frameSize;

Widget child = Image(
image: widget.imageProvider,
width: _frameSize!.width,
height: _frameSize!.height,
fit: widget.fit,
alignment: Alignment(-x, -y),
);
if (widget.alignEnable && _imageSize != null) {
child = GestureDetector(
onHorizontalDragUpdate: (details) {
final delta = details.delta;
_changeAlignOffset(delta);
},
onVerticalDragUpdate: (details) {
final delta = details.delta;
_changeAlignOffset(delta);
},
child: child,
);
}
return child;
},
);
}
}
Loading
Loading