diff --git a/src/app/modules/main/chat_section/chat_content/input_area/controller.nim b/src/app/modules/main/chat_section/chat_content/input_area/controller.nim index fd0864482c..1505bbb480 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/controller.nim @@ -10,6 +10,12 @@ import ../../../../../core/eventemitter import ../../../../../core/unique_event_emitter import ./link_preview_cache +type + LinkPreviewSetting* {.pure.} = enum + AlwaysAsk + Enabled + Disabled + type Controller* = ref object of RootObj delegate: io_interface.AccessInterface @@ -22,6 +28,8 @@ type gifService: gif_service.Service messageService: message_service.Service linkPreviewCache: LinkPreviewCache + linkPreviewPersistentSetting: LinkPreviewSetting + linkPreviewCurrentMessageSetting: LinkPreviewSetting proc newController*( delegate: io_interface.AccessInterface, @@ -45,6 +53,8 @@ proc newController*( result.gifService = gifService result.messageService = messageService result.linkPreviewCache = newLinkPreiewCache() + result.linkPreviewPersistentSetting = LinkPreviewSetting.AlwaysAsk + result.linkPreviewCurrentMessageSetting = LinkPreviewSetting.AlwaysAsk proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs) @@ -89,6 +99,16 @@ proc getChatId*(self: Controller): string = proc belongsToCommunity*(self: Controller): bool = return self.belongsToCommunity + +proc setLinkPreviewEnabledForThisMessage*(self: Controller, enabled: bool) = + self.linkPreviewCurrentMessageSetting = if enabled: LinkPreviewSetting.Enabled else: LinkPreviewSetting.Disabled + self.delegate.setAskToEnableLinkPreview(false) + +proc resetLinkPreviews(self: Controller) = + self.delegate.setUrls(@[]) + self.linkPreviewCache.clear() + self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.AlwaysAsk + self.delegate.setAskToEnableLinkPreview(false) proc sendImages*(self: Controller, imagePathsAndDataJson: string, @@ -96,6 +116,7 @@ proc sendImages*(self: Controller, replyTo: string, preferredUsername: string = "", linkPreviews: seq[LinkPreview]): string = + self.resetLinkPreviews() self.chatService.sendImages( self.chatId, imagePathsAndDataJson, @@ -111,8 +132,8 @@ proc sendChatMessage*(self: Controller, contentType: int, preferredUsername: string = "", linkPreviews: seq[LinkPreview]) = - self.chatService.sendChatMessage( - self.chatId, + self.resetLinkPreviews() + self.chatService.sendChatMessage(self.chatId, msg, replyTo, contentType, @@ -165,11 +186,25 @@ proc addToRecentsGif*(self: Controller, item: GifDto) = proc isFavorite*(self: Controller, item: GifDto): bool = return self.gifService.isFavorite(item) +proc getLinkPreviewEnabled*(self: Controller): bool = + return self.linkPreviewPersistentSetting == LinkPreviewSetting.Enabled or self.linkPreviewCurrentMessageSetting == LinkPreviewSetting.Enabled + +proc canAskToEnableLinkPreview(self: Controller): bool = + return self.linkPreviewPersistentSetting == LinkPreviewSetting.AlwaysAsk and self.linkPreviewCurrentMessageSetting == LinkPreviewSetting.AlwaysAsk + proc setText*(self: Controller, text: string) = + if(text == ""): + self.resetLinkPreviews() + return + let urls = self.messageService.getTextUrls(text) self.delegate.setUrls(urls) - if len(urls) > 0: - let newUrls = self.linkPreviewCache.unknownUrls(urls) + let newUrls = self.linkPreviewCache.unknownUrls(urls) + + let askToEnableLinkPreview = len(newUrls) > 0 and self.canAskToEnableLinkPreview() + self.delegate.setAskToEnableLinkPreview(askToEnableLinkPreview) + + if self.getLinkPreviewEnabled() and len(urls) > 0: self.messageService.asyncUnfurlUrls(newUrls) proc linkPreviewsFromCache*(self: Controller, urls: seq[string]): Table[string, LinkPreview] = @@ -179,8 +214,22 @@ proc clearLinkPreviewCache*(self: Controller) = self.linkPreviewCache.clear() proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs) = + if not self.getLinkPreviewEnabled(): + return + let urls = self.linkPreviewCache.add(args.linkPreviews) self.delegate.updateLinkPreviewsFromCache(urls) -proc reloadLinkPreview*(self: Controller, url: string) = - self.messageService.asyncUnfurlUrls(@[url]) +proc loadLinkPreviews*(self: Controller, urls: seq[string]) = + if self.getLinkPreviewEnabled(): + self.messageService.asyncUnfurlUrls(urls) + +proc setLinkPreviewEnabled*(self: Controller, enabled: bool) = + if(enabled): + self.linkPreviewPersistentSetting = LinkPreviewSetting.Enabled + self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.Enabled + else: + self.linkPreviewPersistentSetting = LinkPreviewSetting.Disabled + self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.Disabled + + self.delegate.setAskToEnableLinkPreview(false) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/input_area/io_interface.nim b/src/app/modules/main/chat_section/chat_content/input_area/io_interface.nim index 8cebf96b14..1408c9f2bf 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/io_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/io_interface.nim @@ -111,5 +111,17 @@ method clearLinkPreviewCache*(self: AccessInterface) {.base.} = method linkPreviewsFromCache*(self: AccessInterface, urls: seq[string]): Table[string, LinkPreview] {.base.} = raise newException(ValueError, "No implementation available") -method reloadLinkPreview*(self: AccessInterface, url: string) {.base.} = +method loadLinkPreviews*(self: AccessInterface, urls: seq[string]) {.base.} = + raise newException(ValueError, "No implementation available") + +method getLinkPreviewEnabled*(self: AccessInterface): bool = + raise newException(ValueError, "No implementation available") + +method setLinkPreviewEnabled*(self: AccessInterface, enabled: bool) = + raise newException(ValueError, "No implementation available") + +method setAskToEnableLinkPreview*(self: AccessInterface, value: bool) = + raise newException(ValueError, "No implementation available") + +method setLinkPreviewEnabledForThisMessage*(self: AccessInterface, enabled: bool) = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/chat_content/input_area/module.nim b/src/app/modules/main/chat_section/chat_content/input_area/module.nim index f9dddc44c7..6672af604f 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/module.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/module.nim @@ -167,5 +167,17 @@ method setUrls*(self: Module, urls: seq[string]) = method linkPreviewsFromCache*(self: Module, urls: seq[string]): Table[string, LinkPreview] = return self.controller.linkPreviewsFromCache(urls) -method reloadLinkPreview*(self: Module, url: string) = - self.controller.reloadLinkPreview(url) \ No newline at end of file +method loadLinkPreviews*(self: Module, urls: seq[string]) = + self.controller.loadLinkPreviews(urls) + +method getLinkPreviewEnabled*(self: Module): bool = + return self.controller.getLinkPreviewEnabled() + +method setLinkPreviewEnabled*(self: Module, enabled: bool) = + self.controller.setLinkPreviewEnabled(enabled) + +method setAskToEnableLinkPreview*(self: Module, value: bool) = + self.view.setAskToEnableLinkPreview(value) + +method setLinkPreviewEnabledForThisMessage*(self: Module, value: bool) = + self.controller.setLinkPreviewEnabledForThisMessage(value) diff --git a/src/app/modules/main/chat_section/chat_content/input_area/preserved_properties.nim b/src/app/modules/main/chat_section/chat_content/input_area/preserved_properties.nim index 4f8b5a2ffa..ebaf8b4d5d 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/preserved_properties.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/preserved_properties.nim @@ -56,3 +56,4 @@ QtObject: read = getFileUrlsAndSourcesJson write = setFileUrlsAndSourcesJson notify = fileUrlsAndSourcesJsonChanged + diff --git a/src/app/modules/main/chat_section/chat_content/input_area/view.nim b/src/app/modules/main/chat_section/chat_content/input_area/view.nim index 644cb893be..167efda3c0 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/view.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/view.nim @@ -17,6 +17,7 @@ QtObject: preservedPropertiesVariant: QVariant linkPreviewModel: link_preview_model.Model linkPreviewModelVariant: QVariant + askToEnableLinkPreview: bool proc delete*(self: View) = self.QObject.delete @@ -40,6 +41,7 @@ QtObject: result.preservedPropertiesVariant = newQVariant(result.preservedProperties) result.linkPreviewModel = newLinkPreviewModel() result.linkPreviewModelVariant = newQVariant(result.linkPreviewModel) + result.askToEnableLinkPreview = false proc load*(self: View) = self.delegate.viewDidLoad() @@ -197,6 +199,17 @@ QtObject: QtProperty[QVariant] linkPreviewModel: read = getLinkPreviewModel + proc askToEnableLinkPreviewChanged(self: View) {.signal.} + proc getAskToEnableLinkPreview(self: View): bool {.slot.} = + return self.askToEnableLinkPreview + proc setAskToEnableLinkPreview*(self: View, value: bool) {.slot.} = + self.askToEnableLinkPreview = value + self.askToEnableLinkPreviewChanged() + + QtProperty[bool] askToEnableLinkPreview: + read = getAskToEnableLinkPreview + notify = askToEnableLinkPreviewChanged + # Currently used to fetch link previews, but could be used elsewhere proc setText*(self: View, text: string) {.slot.} = self.delegate.setText(text) @@ -207,10 +220,36 @@ QtObject: proc setUrls*(self: View, urls: seq[string]) = self.linkPreviewModel.setUrls(urls) - self.updateLinkPreviewsFromCache(urls) + if(self.delegate.getLinkPreviewEnabled()): + self.updateLinkPreviewsFromCache(urls) + else: + self.linkPreviewModel.removeAllPreviewData() proc clearLinkPreviewCache*(self: View) {.slot.} = self.delegate.clearLinkPreviewCache() proc reloadLinkPreview(self: View, link: string) {.slot.} = - self.delegate.reloadLinkPreview(link) + self.delegate.loadLinkPreviews(@[link]) + + proc loadLinkPreviews(self: View, links: seq[string]) = + self.delegate.loadLinkPreviews(links) + + proc enableLinkPreview(self: View) {.slot.} = + self.delegate.setLinkPreviewEnabled(true) + let links = self.linkPreviewModel.getLinks() + self.linkPreviewModel.clearItems() + self.loadLinkPreviews(links) + + proc disableLinkPreview(self: View) {.slot.} = + self.delegate.setLinkPreviewEnabled(false) + self.linkPreviewModel.removeAllPreviewData() + + proc setLinkPreviewEnabledForCurrentMessage(self: View, enabled: bool) {.slot.} = + self.delegate.setLinkPreviewEnabledForThisMessage(enabled) + let links = self.linkPreviewModel.getLinks() + self.linkPreviewModel.clearItems() + self.setUrls(links) + self.loadLinkPreviews(links) + + proc removeLinkPreviewData*(self: View, index: int) {.slot.} = + self.linkPreviewModel.removePreviewData(index) \ No newline at end of file diff --git a/src/app/modules/shared_models/link_preview_model.nim b/src/app/modules/shared_models/link_preview_model.nim index bef27335fd..691b0ddf56 100644 --- a/src/app/modules/shared_models/link_preview_model.nim +++ b/src/app/modules/shared_models/link_preview_model.nim @@ -213,8 +213,17 @@ QtObject: defer: modelIndex.delete self.dataChanged(modelIndex, modelIndex) + proc removeAllPreviewData*(self: Model) {.slot.} = + for i in 0 ..< self.items.len: + self.removePreviewData(i) + proc getUnfuledLinkPreviews*(self: Model): seq[LinkPreview] = result = @[] for item in self.items: if item.unfurled and item.linkPreview.hostName != "": - result.add(item.linkPreview) \ No newline at end of file + result.add(item.linkPreview) + + proc getLinks*(self: Model): seq[string] = + result = @[] + for item in self.items: + result.add(item.linkPreview.url) \ No newline at end of file diff --git a/storybook/pages/ChatInputLinksPreviewAreaPage.qml b/storybook/pages/ChatInputLinksPreviewAreaPage.qml index 5a7d750d05..cb5074f812 100644 --- a/storybook/pages/ChatInputLinksPreviewAreaPage.qml +++ b/storybook/pages/ChatInputLinksPreviewAreaPage.qml @@ -1,24 +1,81 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import QtGraphicalEffects 1.15 +import Storybook 1.0 + import StatusQ.Core.Theme 0.1 import shared.controls.chat 1.0 -Page { - Rectangle { - id: wrapper - anchors.fill: parent - color: Theme.palette.statusChatInput.secondaryBackgroundColor +SplitView { - ChatInputLinksPreviewArea { - id: chatInputLinkPreviewsArea - anchors.centerIn: parent - width: parent.width - imagePreviewModel: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"] - linkPreviewModel: linkPreviewListModel - onLinkRemoved: linkPreviewListModel.remove(index) + Logs { id: logs } + orientation: Qt.Vertical + + SplitView { + + SplitView.fillWidth: true + SplitView.fillHeight: true + + Pane { + SplitView.fillWidth: true + Rectangle { + id: wrapper + anchors.fill: parent + color: Theme.palette.statusChatInput.secondaryBackgroundColor + + ChatInputLinksPreviewArea { + id: chatInputLinkPreviewsArea + anchors.centerIn: parent + width: parent.width + imagePreviewArray: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"] + linkPreviewModel: showLinkPreviewSettings ? emptyModel : linkPreviewListModel + showLinkPreviewSettings: !linkPreviewEnabledSwitch.checked + visible: hasContent + + onImageRemoved: (index) => logs.logEvent("ChatInputLinksPreviewArea::onImageRemoved: " + index) + onImageClicked: (chatImage) => logs.logEvent("ChatInputLinksPreviewArea::onImageClicked: " + chatImage) + onLinkReload: (link) => logs.logEvent("ChatInputLinksPreviewArea::onLinkReload: " + link) + onLinkClicked: (link) => logs.logEvent("ChatInputLinksPreviewArea::onLinkClicked: " + link) + + onEnableLinkPreview: () => { + linkPreviewEnabledSwitch.checked = true + logs.logEvent("ChatInputLinksPreviewArea::onEnableLinkPreview") + } + onEnableLinkPreviewForThisMessage: () => logs.logEvent("ChatInputLinksPreviewArea::onEnableLinkPreviewForThisMessage") + onDisableLinkPreview: () => logs.logEvent("ChatInputLinksPreviewArea::onDisableLinkPreview") + onDismissLinkPreviewSettings: () => logs.logEvent("ChatInputLinksPreviewArea::onDismissLinkPreviewSettings") + onDismissLinkPreview: (index) => logs.logEvent("ChatInputLinksPreviewArea::onDismissLinkPreview: " + index) + } + } } + + Pane { + SplitView.preferredWidth: 300 + SplitView.fillHeight: true + ColumnLayout { + Label { + text: "Links preview enabled" + } + Switch { + id: linkPreviewEnabledSwitch + } + } + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + } + + ListModel { + id: emptyModel } ListModel { @@ -143,4 +200,4 @@ Page { // category: Panels -// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0 \ No newline at end of file +// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0 diff --git a/storybook/pages/LinkPreviewSettingsCardPage.qml b/storybook/pages/LinkPreviewSettingsCardPage.qml new file mode 100644 index 0000000000..cca13274b3 --- /dev/null +++ b/storybook/pages/LinkPreviewSettingsCardPage.qml @@ -0,0 +1,30 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import StatusQ.Core.Theme 0.1 + +import shared.controls.chat 1.0 + +Pane { + id: root + + layer.enabled: true + layer.samples: 4 + background: Rectangle { + color: Theme.palette.statusChatInput.secondaryBackgroundColor + } + + LinkPreviewSettingsCard { + id: previewMiniCard + anchors.centerIn: parent + onDismiss: ToolTip.show(qsTr("Link previews disabled for this message"), 1000) + onEnableLinkPreviewForThisMessage: ToolTip.show(qsTr("Link previews enabled for this message"), 1000) + onEnableLinkPreview: ToolTip.show(qsTr("Link previews enabled"), 1000) + onDisableLinkPreview: ToolTip.show(qsTr("Link previews disabled"), 1000) + } +} + + +//category: Controls + +//https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=91pnQgUZAqFJLcqM-0 diff --git a/storybook/pages/StatusChatInputPage.qml b/storybook/pages/StatusChatInputPage.qml index 25aa99f692..891cf43def 100644 --- a/storybook/pages/StatusChatInputPage.qml +++ b/storybook/pages/StatusChatInputPage.qml @@ -94,31 +94,11 @@ SplitView { sourceComponent: StatusChatInput { id: chatInput property var globalUtils: globalUtilsMock.globalUtils - property string unformattedText: textInput.getText(0, textInput.length) + property string unformattedText: chatInput.textInput.getText(0, chatInput.textInput.length) onUnformattedTextChanged: { textEditConnection.enabled = false - - var words = unformattedText.split(" ") - - fakeLinksModel.clear() - words.forEach(function(word){ - if(Utils.isURL(word)) { - fakeLinksModel.append({ - url: encodeURI(word), - unfurled: Math.floor(Math.random() * 2), - immutable: false, - hostname: Math.floor(Math.random() * 2) ? "youtube.com" : "", - title: "PSY - GANGNAM STYLE(강남스타일) M/V", - description: "This is the description of the link", - linkType: Math.floor(Math.random() * 3), - thumbnailWidth: 480, - thumbnailHeight: 360, - thumbnailUrl: "https://picsum.photos/480/360?random=1", - thumbnailDataUri: "" - }) - } - }) + d.loadLinkPreviews(unformattedText) textEditConnection.enabled = true } @@ -133,6 +113,13 @@ SplitView { enabled: enabledCheckBox.checked linkPreviewModel: fakeLinksModel + askToEnableLinkPreview: askToEnableLinkPreviewSwitch.checked + onAskToEnableLinkPreviewChanged: { + if(askToEnableLinkPreview) { + fakeLinksModel.clear() + d.loadLinkPreviews(unformattedText) + } + } usersStore: QtObject { readonly property var usersModel: fakeUsersModel } @@ -141,6 +128,26 @@ SplitView { logs.logEvent("StatusChatInput::sendMessage", ["PlainText"], [globalUtilsMock.globalUtils.plainText(chatInput.getTextWithPublicKeys())]) logs.logEvent("StatusChatInput::sendMessage", ["RawText"], [chatInput.textInput.text]) } + onEnableLinkPreviewForThisMessage: { + linkPreviewSwitch.checked = true + askToEnableLinkPreviewSwitch.checked = false + } + onEnableLinkPreview: { + linkPreviewSwitch.checked = true + askToEnableLinkPreviewSwitch.checked = false + } + onDisableLinkPreview: { + linkPreviewSwitch.checked = false + askToEnableLinkPreviewSwitch.checked = false + } + onDismissLinkPreviewSettings: { + askToEnableLinkPreviewSwitch.checked = false + linkPreviewSwitch.checked = false + } + onDismissLinkPreview: (index) => { + fakeLinksModel.setProperty(index, "unfurled", false) + fakeLinksModel.setProperty(index, "immutable", true) + } } } @@ -152,6 +159,36 @@ SplitView { logsView.logText: logs.logText } + + QtObject { + id: d + property bool linkPreviewsEnabled: linkPreviewSwitch.checked && !askToEnableLinkPreviewSwitch.checked + onLinkPreviewsEnabledChanged: { + loadLinkPreviews(chatInputLoader.item ? chatInputLoader.item.unformattedText : "") + } + function loadLinkPreviews(text) { + var words = text.split(" ") + + fakeLinksModel.clear() + words.forEach(function(word){ + if(Utils.isURL(word)) { + fakeLinksModel.append({ + url: encodeURI(word), + unfurled: d.linkPreviewsEnabled, + immutable: !d.linkPreviewsEnabled, + hostname: Math.floor(Math.random() * 2) ? "youtube.com" : "", + title: "PSY - GANGNAM STYLE(강남스타일) M/V", + description: "This is the description of the link", + linkType: Math.floor(Math.random() * 3), + thumbnailWidth: 480, + thumbnailHeight: 360, + thumbnailUrl: "https://picsum.photos/480/360?random=1", + thumbnailDataUri: "" + }) + } + }) + } + } } Pane { @@ -207,6 +244,18 @@ SplitView { text: "Links" Layout.fillWidth: true } + + Switch { + id: linkPreviewSwitch + text: "Link Preview enabled" + } + + Switch { + id: askToEnableLinkPreviewSwitch + text: "Ask to enable Link Preview" + checked: true + } + ComboBox { id: linksNb editable: true @@ -245,4 +294,4 @@ SplitView { // category: Components -// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0 \ No newline at end of file +// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0 diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml index 1f29548f73..17441cd519 100644 --- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml @@ -130,17 +130,17 @@ Item { chatInput.validateImagesAndShowImageArea(filesList) } - function restoreInputState() { + function restoreInputState(textInput) { if (!d.activeChatContentModule) { - chatInput.setText("") + chatInput.clear() chatInput.resetReplyArea() chatInput.resetImageArea() return } // Restore message text - chatInput.setText(d.activeChatContentModule.inputAreaModule.preservedProperties.text) + chatInput.setText(textInput) d.restoreInputReply() d.restoreInputAttachments() @@ -154,9 +154,14 @@ Item { } onActiveChatContentModuleChanged: { + let preservedText = "" + if (d.activeChatContentModule) { + preservedText = d.activeChatContentModule.inputAreaModule.preservedProperties.text + } + d.activeChatContentModule.inputAreaModule.clearLinkPreviewCache() // Call later to make sure activeUsersStore and activeMessagesStore bindings are updated - Qt.callLater(d.restoreInputState) + Qt.callLater(d.restoreInputState, preservedText) } } @@ -243,7 +248,12 @@ Item { store: root.rootStore usersStore: d.activeUsersStore linkPreviewModel: d.activeChatContentModule.inputAreaModule.linkPreviewModel + askToEnableLinkPreview: { + if(!d.activeChatContentModule || !d.activeChatContentModule.inputAreaModule || !d.activeChatContentModule.inputAreaModule.preservedProperties) + return false + return d.activeChatContentModule.inputAreaModule.askToEnableLinkPreview + } textInput.placeholderText: { if (!channelPostRestrictions.visible) { if (d.activeChatContentModule.chatDetails.blocked) @@ -315,6 +325,11 @@ Item { } onLinkPreviewReloaded: (link) => d.activeChatContentModule.inputAreaModule.reloadLinkPreview(link) + onEnableLinkPreview: () => d.activeChatContentModule.inputAreaModule.enableLinkPreview() + onDisableLinkPreview: () => d.activeChatContentModule.inputAreaModule.disableLinkPreview() + onEnableLinkPreviewForThisMessage: () => d.activeChatContentModule.inputAreaModule.setLinkPreviewEnabledForCurrentMessage(true) + onDismissLinkPreviewSettings: () => d.activeChatContentModule.inputAreaModule.setLinkPreviewEnabledForCurrentMessage(false) + onDismissLinkPreview: (index) => d.activeChatContentModule.inputAreaModule.removeLinkPreviewData(index) } ChatPermissionQualificationPanel { diff --git a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml index cedb724386..2b1c0fcdd0 100644 --- a/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml +++ b/ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml @@ -8,22 +8,44 @@ import StatusQ.Core 0.1 import shared.status 1.0 import shared.controls.chat 1.0 +import utils 1.0 + import SortFilterProxyModel 0.2 Control { id: root - required property var imagePreviewModel + required property var imagePreviewArray + /* + Expected roles: + string title + string url + bool unfurled + bool immutable + string hostname + string description + int linkType + int thumbnailWidth + int thumbnailHeight + string thumbnailUrl + string thumbnailDataUri + */ required property var linkPreviewModel + required property bool showLinkPreviewSettings readonly property alias hoveredUrl: d.hoveredUrl - readonly property int contentItemsCount: imagePreviewModel.length + d.filteredModel.count + readonly property bool hasContent: imagePreviewArray.length > 0 || showLinkPreviewSettings || linkPreviewRepeater.count > 0 signal imageRemoved(int index) signal imageClicked(var chatImage) signal linkReload(string link) signal linkClicked(string link) - signal linkRemoved(string link) + + signal enableLinkPreview() + signal enableLinkPreviewForThisMessage() + signal disableLinkPreview() + signal dismissLinkPreviewSettings() + signal dismissLinkPreview(int index) horizontalPadding: 12 topPadding: 12 @@ -60,6 +82,8 @@ Control { contentWidth: layout.width contentHeight: layout.height + onFlickStarted: settingsContextMenu.close() + RowLayout { id: layout spacing: 8 @@ -67,12 +91,13 @@ Control { id: imageArea Layout.preferredHeight: 64 spacing: layout.spacing - imageSource: imagePreviewModel + imageSource: imagePreviewArray onImageClicked: root.imageClicked(chatImage) onImageRemoved: root.imageRemoved(index) - visible: !!imagePreviewModel && imagePreviewModel.length > 0 + visible: !!imagePreviewArray && imagePreviewArray.length > 0 } Repeater { + id: linkPreviewRepeater model: d.filteredModel delegate: LinkPreviewMiniCard { // Model properties @@ -104,9 +129,10 @@ Control { unfurled && hostname === "" ? LinkPreviewMiniCard.State.LoadingFailed : !unfurled ? LinkPreviewMiniCard.State.Loading : LinkPreviewMiniCard.State.Invalid - onClose: root.linkPreviewModel.removePreviewData(d.filteredModel.mapToSource(index)) + onClose: root.dismissLinkPreview(d.filteredModel.mapToSource(index)) onRetry: root.linkReload(url) onClicked: root.linkClicked(url) + onRightClicked: settingsContextMenu.popup() onContainsMouseChanged: { if (containsMouse) { d.hoveredUrl = url @@ -121,6 +147,14 @@ Control { } } } + LinkPreviewSettingsCard { + id: settingsCard + visible: root.showLinkPreviewSettings + onDismiss: root.dismissLinkPreviewSettings() + onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage() + onEnableLinkPreview: root.enableLinkPreview() + onDisableLinkPreview: root.disableLinkPreview() + } } } } @@ -157,4 +191,12 @@ Control { ] } } + + LinkPreviewSettingsCard.ContextMenu { + id: settingsContextMenu + + onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage() + onEnableLinkPreview: root.enableLinkPreview() + onDisableLinkPreview: root.disableLinkPreview() + } } diff --git a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml index 37d8329020..a956d467dc 100644 --- a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml +++ b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml @@ -42,6 +42,7 @@ CalloutCard { signal close() signal retry() signal clicked(var eventPoint) + signal rightClicked(var eventPoint) implicitWidth: 260 implicitHeight: 64 @@ -181,7 +182,6 @@ CalloutCard { icon: "tiny/chevron-right" color: Theme.palette.baseColor1 visible: secondTitleText.visible - } StatusBaseText { id: secondTitleText @@ -246,4 +246,8 @@ CalloutCard { target: background onTapped: root.clicked(eventPoint) } + TapHandler { + acceptedButtons: Qt.RightButton + onTapped: root.rightClicked(eventPoint) + } } diff --git a/ui/imports/shared/controls/chat/LinkPreviewSettingsCard.qml b/ui/imports/shared/controls/chat/LinkPreviewSettingsCard.qml new file mode 100644 index 0000000000..b6599d4e25 --- /dev/null +++ b/ui/imports/shared/controls/chat/LinkPreviewSettingsCard.qml @@ -0,0 +1,144 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups 0.1 + +import utils 1.0 + +CalloutCard { + id: root + + signal dismiss() + signal enableLinkPreviewForThisMessage() + signal enableLinkPreview() + signal disableLinkPreview() + + implicitHeight: 64 + borderWidth: 0 + topPadding: 13 + bottomPadding: 13 + horizontalPadding: Style.current.padding + + contentItem: RowLayout { + spacing: Style.current.halfPadding + ColumnLayout { + spacing: 0 + Layout.fillHeight: true + Layout.fillWidth: true + StatusBaseText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + Layout.fillHeight: true + font.pixelSize: Style.current.additionalTextSize + font.weight: Font.Medium + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 1 + text: qsTr("Show link previews?") + } + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + font.pixelSize: Style.current.additionalTextSize + color: Theme.palette.baseColor1 + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 1 + text: qsTr("A preview of your link will be shown here before you send it") + } + } + ComboBox { + id: optionsComboBox + Layout.leftMargin: 12 + Layout.preferredHeight: 38 + leftPadding: 12 + rightPadding: 12 + hoverEnabled: true + flat: true + contentItem: RowLayout { + spacing: Style.current.halfPadding + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + font.pixelSize: Style.current.additionalTextSize + elide: Text.ElideRight + text: qsTr("Options") + color: Theme.palette.baseColor1 + } + StatusIcon { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + icon: "chevron-down" + color: Theme.palette.baseColor1 + } + } + background: Rectangle { + border.width: 1 + border.color: Theme.palette.directColor7 + color: optionsComboBox.popup.visible ? Theme.palette.baseColor2 : "transparent" + radius: Style.current.radius + HoverHandler { + cursorShape: Qt.PointingHandCursor + enabled: optionsComboBox.enabled + } + } + popup: ContextMenu { + y: - (height + 4) + onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage() + onEnableLinkPreview: root.enableLinkPreview() + onDisableLinkPreview: root.disableLinkPreview() + } + indicator: null + } + + StatusFlatRoundButton { + id: closeButton + Layout.preferredHeight: 38 + Layout.preferredWidth: 38 + type: StatusFlatRoundButton.Type.Secondary + icon.name: "close" + icon.color: Theme.palette.directColor1 + onClicked: root.dismiss() + } + } + + component ContextMenu: StatusMenu { + id: contextMenu + + signal enableLinkPreviewForThisMessage() + signal enableLinkPreview() + signal disableLinkPreview() + + hideDisabledItems: false + StatusAction { + text: qsTr("Link previews") + enabled: false + } + + StatusAction { + text: qsTr("Show for this message") + icon.name: "show" + onTriggered: contextMenu.enableLinkPreviewForThisMessage() + } + + StatusAction { + text: qsTr("Always show previews") + icon.name: "show" + onTriggered: contextMenu.enableLinkPreview() + } + + StatusMenuSeparator { } + + StatusAction { + text: qsTr("Never show previews") + icon.name: "hide" + type: StatusAction.Type.Danger + onTriggered: contextMenu.disableLinkPreview() + } + } +} diff --git a/ui/imports/shared/controls/chat/qmldir b/ui/imports/shared/controls/chat/qmldir index 1440fa3e11..a65337c403 100644 --- a/ui/imports/shared/controls/chat/qmldir +++ b/ui/imports/shared/controls/chat/qmldir @@ -5,6 +5,7 @@ FetchMoreMessagesButton 1.0 FetchMoreMessagesButton.qml GapComponent 1.0 GapComponent.qml LinkPreviewCard 1.0 LinkPreviewCard.qml LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml +LinkPreviewSettingsCard 1.0 LinkPreviewSettingsCard.qml UsernameLabel 1.0 UsernameLabel.qml UserProfileCard 1.0 UserProfileCard.qml DateGroup 1.0 DateGroup.qml diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index 877b4a0a4f..7f13edb54f 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -28,9 +28,13 @@ Rectangle { signal stickerSelected(string hashId, string packId, string url) signal sendMessage(var event) signal keyUpPress() - signal linkPreviewRemoved(string link) signal linkPreviewReloaded(string link) - + signal enableLinkPreview() + signal enableLinkPreviewForThisMessage() + signal disableLinkPreview() + signal dismissLinkPreviewSettings() + signal dismissLinkPreview(int index) + property var usersStore property var store @@ -61,6 +65,8 @@ Rectangle { property var linkPreviewModel: null + property bool askToEnableLinkPreview: false + property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this property? property alias suggestions: suggestionsBox @@ -1179,11 +1185,12 @@ Rectangle { ChatInputLinksPreviewArea { id: linkPreviewArea Layout.fillWidth: true - visible: contentItemsCount > 0 + visible: hasContent horizontalPadding: 12 topPadding: 12 - imagePreviewModel: control.fileUrlsAndSources + imagePreviewArray: control.fileUrlsAndSources linkPreviewModel: control.linkPreviewModel + showLinkPreviewSettings: control.askToEnableLinkPreview onImageRemoved: (index) => { //Just do a copy and replace the whole thing because it's a plain JS array and thre's no signal when a single item is removed let urls = control.fileUrlsAndSources @@ -1194,8 +1201,12 @@ Rectangle { } onImageClicked: (chatImage) => Global.openImagePopup(chatImage) onLinkReload: (link) => control.linkPreviewReloaded(link) - onLinkRemoved: (link) => control.linkPreviewRemoved(link) onLinkClicked: (link) => Global.openLink(link) + onEnableLinkPreview: () => control.enableLinkPreview() + onEnableLinkPreviewForThisMessage: () => control.enableLinkPreviewForThisMessage() + onDisableLinkPreview: () => control.disableLinkPreview() + onDismissLinkPreviewSettings: () => control.dismissLinkPreviewSettings() + onDismissLinkPreview: (index) => control.dismissLinkPreview(index) } RowLayout { diff --git a/ui/imports/shared/status/StatusChatInputImageArea.qml b/ui/imports/shared/status/StatusChatInputImageArea.qml index 1b3f065ad1..8a9bb6f057 100644 --- a/ui/imports/shared/status/StatusChatInputImageArea.qml +++ b/ui/imports/shared/status/StatusChatInputImageArea.qml @@ -20,7 +20,7 @@ Row { Repeater { id: rptImages - + Item { height: chatImage.height width: chatImage.width diff --git a/ui/imports/utils/UndoStackManager.qml b/ui/imports/utils/UndoStackManager.qml index 68ccd7bbd1..fbe566f19d 100644 --- a/ui/imports/utils/UndoStackManager.qml +++ b/ui/imports/utils/UndoStackManager.qml @@ -117,9 +117,7 @@ Item { } const newStackSize = Math.ceil(root.maxStackSize / 2) - print("Reducing undo stack to " + newStackSize + " items") for(var i = 1; i <= newStackSize; i++) { - print("Removing " + Math.ceil(root.maxStackSize / newStackSize) + " items from index " + i) d.undoStack.splice(i, Math.ceil(root.maxStackSize / newStackSize)) } }