Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions assets/images/ic_keyboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/images/ic_message.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/images/ic_navigation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions assets/images/ic_reading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions core/lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export 'presentation/views/button/multi_click_widget.dart';
export 'presentation/views/semantics/checkbox_semantics.dart';
export 'presentation/views/semantics/text_field_semantics.dart';
export 'presentation/views/semantics/icon_semantics.dart';
export 'presentation/views/shortcut/key_shortcut.dart';

// Resources
export 'presentation/resources/assets_paths.dart';
Expand Down
1 change: 1 addition & 0 deletions core/lib/presentation/extensions/color_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ extension AppColor on Color {
static const iconFolder = Color(0xFF297EF2);
static const folderDivider = Color(0xFFE4E8EC);
static const gray424244 = Color(0xFF424244);
static const gray200 = Color(0xFFCCCCCC);
static const lightGrayF4F4F4 = Color(0xFFF4F4F4);
static const gray959DAD = Color(0xFF959DAD);
static const redFF3347 = Color(0xFFFF3347);
Expand Down
4 changes: 4 additions & 0 deletions core/lib/presentation/resources/image_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ class ImagePaths {
String get icPremium => _getImagePath('ic_premium.svg');
String get icCloud => _getImagePath('ic_cloud.svg');
String get icNoRules => _getImagePath('ic_no_rules.svg');
String get icKeyboard => _getImagePath('ic_keyboard.svg');
String get icMessage => _getImagePath('ic_message.svg');
String get icNavigation => _getImagePath('ic_navigation.svg');
String get icReading => _getImagePath('ic_reading.svg');

String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:math' as math;

import 'package:core/presentation/constants/constants_ui.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/html/html_interaction.dart';
import 'package:core/utils/html/html_template.dart';
Expand All @@ -13,6 +14,7 @@ import 'package:universal_html/html.dart' as html;

typedef OnClickHyperLinkAction = Function(Uri?);
typedef OnMailtoClicked = void Function(Uri? uri);
typedef OnIFrameKeyboardShortcutAction = void Function(KeyShortcut keyShortcut);

class HtmlContentViewerOnWeb extends StatefulWidget {

Expand All @@ -28,6 +30,8 @@ class HtmlContentViewerOnWeb extends StatefulWidget {

final OnClickHyperLinkAction? onClickHyperLinkAction;

final OnIFrameKeyboardShortcutAction? onIFrameKeyboardShortcutAction;

// if widthContent is bigger than width of htmlContent, set this to true let widget able to resize to width of htmlContent
final bool allowResizeToDocumentSize;

Expand Down Expand Up @@ -63,6 +67,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
this.htmlContentMinWidth = ConstantsUI.htmlContentMinWidth,
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
this.viewMaxHeight,
this.onIFrameKeyboardShortcutAction,
}) : super(key: key);

@override
Expand All @@ -86,7 +91,6 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
bool _iframeLoaded = false;
static const String iframeOnLoadMessage = 'iframeHasBeenLoaded';
static const String onClickHyperLinkName = 'onClickHyperLink';
static const String onScrollChangedEvent = 'onScrollChanged';

@override
void initState() {
Expand All @@ -109,6 +113,9 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
if (_isScrollChangedEventTriggered(type)) {
_handleIframeOnScrollChangedListener(data, widget.scrollController!);
return;
} else if (_isIframeKeyboardEventTriggered(type)) {
_handleOnIFrameKeyboardEvent(data);
return;
}

if (data['message'] == iframeOnLoadMessage) {
Expand All @@ -126,29 +133,33 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
_handleHyperLinkEvent(data['url']);
}
} catch (e) {
logError('_HtmlContentViewerOnWebState::_handleMessageEvent:Exception = $e');
logError('$runtimeType::_handleMessageEvent:Exception = $e');
}
}

bool _isScrollChangedEventTriggered(String? type) {
return widget.scrollController != null &&
widget.scrollController?.hasClients == true &&
type?.contains('toDart: $onScrollChangedEvent') == true;
type?.contains('toDart: iframeScrolling') == true;
}

void _handleIframeOnScrollChangedListener(
dynamic data,
ScrollController controller,
) {
final deltaY = data['deltaY'] ?? 0.0;
final newOffset = controller.offset + deltaY;
log('_HtmlContentViewerOnWebState::_handleIframeOnScrollChangedListener:deltaY = $deltaY | newOffset = $newOffset');
if (newOffset < controller.position.minScrollExtent) {
controller.jumpTo(controller.position.minScrollExtent);
} else if (newOffset > controller.position.maxScrollExtent) {
controller.jumpTo(controller.position.maxScrollExtent);
} else {
controller.jumpTo(newOffset);
try {
final deltaY = data['deltaY'] ?? 0.0;
final newOffset = controller.offset + deltaY;
log('$runtimeType::_handleIframeOnScrollChangedListener:deltaY = $deltaY | newOffset = $newOffset');
if (newOffset < controller.position.minScrollExtent) {
controller.jumpTo(controller.position.minScrollExtent);
} else if (newOffset > controller.position.maxScrollExtent) {
controller.jumpTo(controller.position.maxScrollExtent);
} else {
controller.jumpTo(newOffset);
}
} catch (e) {
logError('$runtimeType::_handleIframeOnScrollChangedListener:Exception = $e');
}
}

Expand Down Expand Up @@ -209,10 +220,29 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
}
}

bool _isIframeKeyboardEventTriggered(String? type) {
return widget.onIFrameKeyboardShortcutAction != null &&
type?.contains('toDart: iframeKeydown') == true;
}

void _handleOnIFrameKeyboardEvent(dynamic data) {
try {
final shortcut = KeyShortcut(
key: data['key'] as String,
code: data['code'] as String,
shift: data['shift'] == true,
);
log('$runtimeType::_handleOnIFrameKeyboardEvent:📥 Shortcut pressed: $shortcut');
widget.onIFrameKeyboardShortcutAction?.call(shortcut);
} catch (e) {
logError('$runtimeType::_handleOnIFrameKeyboardEvent: Exception = $e');
}
}

@override
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
super.didUpdateWidget(oldWidget);
log('_HtmlContentViewerOnWebState::didUpdateWidget():Old-Direction: ${oldWidget.direction} | Current-Direction: ${widget.direction}');
log('$runtimeType::didUpdateWidget():Old-Direction: ${oldWidget.direction} | Current-Direction: ${widget.direction}');
if (widget.contentHtml != oldWidget.contentHtml ||
widget.direction != oldWidget.direction) {
_setUpWeb();
Expand Down Expand Up @@ -240,6 +270,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
window.addEventListener('load', handleOnLoad);
window.addEventListener('pagehide', (event) => {
window.parent.removeEventListener('message', handleMessage, false);
window.removeEventListener('load', handleOnLoad);
});

function handleMessage(e) {
Expand Down Expand Up @@ -319,17 +350,6 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>

${!widget.autoAdjustHeight ? 'resizeObserver.observe(document.body);' : ''}
}

${widget.scrollController != null ? '''
window.addEventListener('wheel', function (event) {
const deltaY = event.deltaY;
window.parent.postMessage(JSON.stringify({
"view": "$_createdViewId",
"type": "toDart: $onScrollChangedEvent",
"deltaY": deltaY
}), "*");
});
''' : ''}
</script>
''';

Expand All @@ -349,6 +369,10 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
HtmlInteraction.scriptsHandleLazyLoadingBackgroundImage,
HtmlInteraction.generateNormalizeImageScript(widget.widthContent),
if (widget.enableQuoteToggle) HtmlUtils.quoteToggleScript,
if (widget.scrollController != null)
HtmlInteraction.scriptHandleIframeScrollingListener(_createdViewId),
if (widget.onIFrameKeyboardShortcutAction != null)
HtmlInteraction.scriptHandleIframeKeyboardListener(_createdViewId),
].join();

final htmlTemplate = HtmlUtils.generateHtmlDocument(
Expand Down
21 changes: 21 additions & 0 deletions core/lib/presentation/views/shortcut/key_shortcut.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';

class KeyShortcut with EquatableMixin{
final String key;
final String code;
final bool shift;

KeyShortcut({
required this.key,
required this.code,
this.shift = false,
});

bool matches(String expectedKey, {bool shift = false}) {
return key.toLowerCase() == expectedKey.toLowerCase() &&
this.shift == shift;
}

@override
List<Object?> get props => [key, code, shift];
}
52 changes: 49 additions & 3 deletions core/lib/utils/html/html_interaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class HtmlInteraction {
''';

static const String scriptsHandleContentSizeChanged = '''
<script>
<script type="text/javascript">
const bodyResizeObserver = new ResizeObserver(entries => {
window.flutter_inappwebview.callHandler('$contentSizeChangedEventJSChannelName', '');
})
Expand All @@ -70,7 +70,7 @@ class HtmlInteraction {
''';

static const String scriptsHandleLazyLoadingBackgroundImage = '''
<script>
<script type="text/javascript">
const lazyImages = document.querySelectorAll('[lazy]');
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
Expand Down Expand Up @@ -286,7 +286,13 @@ class HtmlInteraction {
}, {
passive: false,
});
window.addEventListener('keydown', function(e) {
window.addEventListener('keydown', disableZoomControl);

window.addEventListener('pagehide', (event) => {
window.removeEventListener('keydown', disableZoomControl);
});

function disableZoomControl(event) {
if (event.metaKey || event.ctrlKey) {
switch (event.key) {
case '=':
Expand All @@ -295,7 +301,47 @@ class HtmlInteraction {
break;
}
}
}
</script>
''';

static String scriptHandleIframeKeyboardListener(String viewId) => '''
<script type="text/javascript">
window.addEventListener('keydown', handleIframeKeydown);

window.addEventListener('pagehide', (event) => {
window.removeEventListener('keydown', handleIframeKeydown);
});

function handleIframeKeydown(event) {
const payload = {
view: '$viewId',
type: 'toDart: iframeKeydown',
key: event.key,
code: event.code,
shift: event.shiftKey
};
window.parent.postMessage(JSON.stringify(payload), "*");
}
</script>
''';

static String scriptHandleIframeScrollingListener(String viewId) => '''
<script type="text/javascript">
window.addEventListener('wheel', handleIframeScrolling);

window.addEventListener('pagehide', (event) => {
window.removeEventListener('wheel', handleIframeScrolling);
});

function handleIframeScrolling(event) {
const payload = {
view: '$viewId',
type: 'toDart: iframeScrolling',
deltaY: event.deltaY,
};
window.parent.postMessage(JSON.stringify(payload), "*");
}
</script>
''';
}
13 changes: 13 additions & 0 deletions lib/features/base/extensions/logical_key_set_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:flutter/services.dart';

extension LogicalKeySetHelper on Set<LogicalKeyboardKey> {
bool isKey(LogicalKeyboardKey key) => contains(key);

bool isOnly(LogicalKeyboardKey key) => length == 1 && contains(key);

bool isShift() =>
contains(LogicalKeyboardKey.shiftLeft) ||
contains(LogicalKeyboardKey.shiftRight);

bool isShiftPlus(LogicalKeyboardKey key) => isShift() && contains(key);
}
Loading
Loading