1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-10 03:53:23 +02:00

Donation campaign: add banner in smartlist

GitLab: #1334
Change-Id: I53b23eabab47389b9bea50f54afac28d41741b0b
This commit is contained in:
lcoursodon 2023-09-27 11:26:18 -04:00 committed by Andreas Traczyk
parent 6b9ce14ca9
commit b9d24298f7
7 changed files with 282 additions and 93 deletions

View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="43" height="52.655" viewBox="0 0 43 52.655">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_268" data-name="Rectangle 268" width="38" height="24" transform="translate(-0.407 0.083)" fill="#fff" stroke="#707070" stroke-width="1"/>
</clipPath>
</defs>
<g id="Icon_Donate" transform="translate(-22 -189.345)">
<rect id="Rectangle_267" data-name="Rectangle 267" width="43" height="10" rx="5" transform="translate(22 232)" fill="#9eb3c3"/>
<path id="Path_459" data-name="Path 459" d="M9.674,17.083,8.562,16.07C4.609,12.486,2,10.122,2,7.221A4.18,4.18,0,0,1,6.221,3,4.6,4.6,0,0,1,9.674,4.6,4.6,4.6,0,0,1,13.128,3a4.18,4.18,0,0,1,4.221,4.221c0,2.9-2.609,5.265-6.562,8.856Z" transform="translate(22.407 199.828)" fill="#ff0045" opacity="0.3"/>
<path id="Path_460" data-name="Path 460" d="M6.953,12.088l-.718-.654C3.684,9.122,2,7.6,2,5.724A2.7,2.7,0,0,1,4.724,3,2.966,2.966,0,0,1,6.953,4.035,2.966,2.966,0,0,1,9.182,3a2.7,2.7,0,0,1,2.724,2.724c0,1.872-1.684,3.4-4.235,5.716Z" transform="translate(45.571 186.345)" fill="#ff0045" opacity="0.16"/>
<g id="Mask_Group_38" data-name="Mask Group 38" transform="translate(24.407 213.918)" clip-path="url(#clip-path)">
<path id="Path_270" data-name="Path 270" d="M12.649,22.542l-1.544-1.406C5.621,16.163,2,12.883,2,8.857A5.8,5.8,0,0,1,7.857,3a6.377,6.377,0,0,1,4.792,2.226A6.377,6.377,0,0,1,17.442,3,5.8,5.8,0,0,1,23.3,8.857c0,4.025-3.621,7.306-9.105,12.289Z" transform="translate(5.992 5.54)" fill="#ff0045"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -62,8 +62,8 @@ extern const QString defaultDownloadPath;
X(FlipSelf, true) \ X(FlipSelf, true) \
X(ShowMardownOption, false) \ X(ShowMardownOption, false) \
X(ChatViewEnterIsNewLine, false) \ X(ChatViewEnterIsNewLine, false) \
X(ShowSendOption, false) X(ShowSendOption, false) \
X(DonateVisibleDate, "2999-02-01 05:00")
/* /*
* A class to expose settings keys in both c++ and QML. * A class to expose settings keys in both c++ and QML.
* Note: this is using a non-constructable class instead of a * Note: this is using a non-constructable class instead of a

View file

@ -20,6 +20,7 @@
pragma Singleton pragma Singleton
import QtQuick import QtQuick
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Enums 1.1
Item { Item {
property string qmlFilePrefix: "file:/" property string qmlFilePrefix: "file:/"
@ -69,4 +70,9 @@ Item {
function clamp(val, min, max) { function clamp(val, min, max) {
return Math.min(Math.max(val, min), max); return Math.min(Math.max(val, min), max);
} }
function isDonationBannerVisible() {
// The banner is visible if the current date is after the date set in the settings
return new Date() > new Date(Date.parse(UtilsAdapter.getAppValue(Settings.Key.DonateVisibleDate)));
}
} }

View file

@ -835,4 +835,9 @@ Item {
// Appearence // Appearence
property string theme: qsTr("Theme") property string theme: qsTr("Theme")
property string zoomLevel: qsTr("Text zoom level") property string zoomLevel: qsTr("Text zoom level")
//Donation campaign
property string donation: qsTr("Donate")
property string donationText: qsTr("If you enjoy using Jami and believe in our mission, would you make a donation?")
property string notNow: qsTr("Not now")
} }

View file

@ -657,6 +657,11 @@ Item {
property color darkThemeCheckedColor: "#03B9E9" property color darkThemeCheckedColor: "#03B9E9"
property color darkThemeBorderColor: "#03B9E9" property color darkThemeBorderColor: "#03B9E9"
// Donation campaign
property color donationButtonTextColor: "#005699"
property color donationBackgroundColor: "#D5E4EF"
property string donationUrl: "https://jami.net/donate/"
function setTheme(dark) { function setTheme(dark) {
darkTheme = dark; darkTheme = dark;
} }

View file

@ -0,0 +1,150 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import net.jami.Enums 1.1
import net.jami.Models 1.1
import "../../commoncomponents"
import "../../settingsview/components"
Rectangle {
id: donation
property bool donationVisible: JamiQmlUtils.isDonationBannerVisible()
width: parent.width - 30
height: donationTextRect.height + 45 > donationIcon.height + 20 ? donationTextRect.height + 45 : donationIcon.height + 20
radius: 5
color: JamiTheme.donationBackgroundColor
GridLayout {
id: donationLayout
anchors.fill: parent
columns: 3
rows: 2
rowSpacing: 0
columnSpacing: 10
Rectangle {
id: donationIcon
Layout.row: 0
Layout.column: 0
Layout.rowSpan: 2
Layout.preferredHeight: 70
Layout.preferredWidth: 45
Layout.leftMargin: 10
Layout.topMargin: 10
Layout.bottomMargin: 15
color: JamiTheme.transparentColor
Image {
id: donationImage
height: parent.height
width: 50
anchors.centerIn: parent
source: JamiResources.icon_donate_svg
}
}
Rectangle {
id: donationTextRect
Layout.topMargin: 10
Layout.row: 0
Layout.column: 1
Layout.columnSpan: 2
Layout.preferredHeight: donationText.height
Layout.preferredWidth: parent.width - 74
Layout.bottomMargin: 5
color: JamiTheme.transparentColor
Text {
id: donationText
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: parent.width
height: contentHeight
text: JamiStrings.donationText
wrapMode: Text.WordWrap
font.pointSize: JamiTheme.textFontSize
}
}
Rectangle {
id: notNowRect
Layout.row: 1
Layout.column: 1
Layout.preferredHeight: 30
Layout.preferredWidth: (parent.width - 55) / 2
color: JamiTheme.transparentColor
Text {
id: notNowText
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
// When the user clicks on "Not now", we set the donation date to 7 days from now (1 for the test)
// TODO reset to 7 days
UtilsAdapter.setAppValue(Settings.Key.DonateVisibleDate, new Date(new Date().getTime() + 1 * 24 * 60 * 60 * 1000).toISOString().slice(0, 16).replace("T", " "));
donation.donationVisible = Qt.binding(() => JamiQmlUtils.isDonationBannerVisible());
}
}
text: JamiStrings.notNow
color: JamiTheme.donationButtonTextColor
anchors.top: parent.top
anchors.left: parent.left
font.pointSize: JamiTheme.textFontSize
}
}
Rectangle {
id: donateRect
Layout.row: 1
Layout.column: 2
Layout.preferredHeight: 30
Layout.preferredWidth: (parent.width - 50) / 2
color: JamiTheme.transparentColor
Text {
id: donateText
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
Qt.openUrlExternally(JamiTheme.donationUrl);
}
}
text: JamiStrings.donation
font.pointSize: JamiTheme.textFontSize
color: JamiTheme.donationButtonTextColor
anchors.top: parent.top
anchors.left: parent.left
}
}
}
}

View file

@ -16,21 +16,19 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import net.jami.Enums 1.1 import net.jami.Enums 1.1
import net.jami.Models 1.1 import net.jami.Models 1.1
import "../../commoncomponents" import "../../commoncomponents"
import "../../settingsview/components" import "../../settingsview/components"
SidePanelBase { SidePanelBase {
id: root id: root
objectName: "SidePanel" objectName: "SidePanel"
color: JamiTheme.backgroundColor color: JamiTheme.backgroundColor
@ -39,7 +37,7 @@ SidePanelBase {
target: LRCInstance target: LRCInstance
function onCurrentAccountIdChanged() { function onCurrentAccountIdChanged() {
clearContactSearchBar() clearContactSearchBar();
} }
} }
@ -47,8 +45,8 @@ SidePanelBase {
target: ConversationsAdapter target: ConversationsAdapter
function onConversationReady() { function onConversationReady() {
selectTab(SidePanelTabBar.Conversations) selectTab(SidePanelTabBar.Conversations);
clearContactSearchBar() clearContactSearchBar();
} }
} }
@ -56,46 +54,46 @@ SidePanelBase {
target: ConversationsAdapter target: ConversationsAdapter
function onShowSearchStatus(status) { function onShowSearchStatus(status) {
searchStatusText.text = status searchStatusText.text = status;
} }
function onTextFilterChanged(text) { function onTextFilterChanged(text) {
// In the swarm details, "Go to conversation" can // In the swarm details, "Go to conversation" can
// change the search bar. Be sure to be synced // change the search bar. Be sure to be synced
contactSearchBar.textContent = text contactSearchBar.textContent = text;
} }
} }
function toggleCreateSwarmView() { function toggleCreateSwarmView() {
if (!inNewSwarm) { if (!inNewSwarm) {
viewCoordinator.present("NewSwarmPage") viewCoordinator.present("NewSwarmPage");
const newSwarmPage = viewCoordinator.getView("NewSwarmPage") const newSwarmPage = viewCoordinator.getView("NewSwarmPage");
newSwarmPage.removeMember.connect((convId, member) => { newSwarmPage.removeMember.connect((convId, member) => {
removeMember(convId, member) removeMember(convId, member);
}) });
newSwarmPage.createSwarmClicked.connect((title, description, avatar) => { newSwarmPage.createSwarmClicked.connect((title, description, avatar) => {
var uris = [] var uris = [];
for (var idx in newSwarmPage.members) { for (var idx in newSwarmPage.members) {
var uri = newSwarmPage.members[idx].uri var uri = newSwarmPage.members[idx].uri;
if (uris.indexOf(uri) === -1) { if (uris.indexOf(uri) === -1) {
uris.push(uri) uris.push(uri);
} }
} }
let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris) let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris);
viewCoordinator.dismiss("NewSwarmPage") viewCoordinator.dismiss("NewSwarmPage");
LRCInstance.selectConversation(convuid) LRCInstance.selectConversation(convuid);
}) });
} else { } else {
viewCoordinator.dismiss("NewSwarmPage") viewCoordinator.dismiss("NewSwarmPage");
} }
} }
function clearContactSearchBar() { function clearContactSearchBar() {
contactSearchBar.clearText() contactSearchBar.clearText();
} }
function selectTab(tabIndex) { function selectTab(tabIndex) {
sidePanelTabBar.selectTab(tabIndex) sidePanelTabBar.selectTab(tabIndex);
} }
property bool inNewSwarm: viewCoordinator.currentViewName === "NewSwarmPage" property bool inNewSwarm: viewCoordinator.currentViewName === "NewSwarmPage"
@ -104,65 +102,64 @@ SidePanelBase {
property var highlightedMembers: [] property var highlightedMembers: []
onHighlightedMembersChanged: { onHighlightedMembersChanged: {
if (inNewSwarm) { if (inNewSwarm) {
const newSwarmPage = viewCoordinator.getView("NewSwarmPage") const newSwarmPage = viewCoordinator.getView("NewSwarmPage");
newSwarmPage.members = highlightedMembers newSwarmPage.members = highlightedMembers;
} }
} }
function refreshHighlighted(convId, highlightedStatus) { function refreshHighlighted(convId, highlightedStatus) {
var newH = root.highlighted var newH = root.highlighted;
var newHm = root.highlightedMembers var newHm = root.highlightedMembers;
if (highlightedStatus) { if (highlightedStatus) {
var item = ConversationsAdapter.getConvInfoMap(convId) var item = ConversationsAdapter.getConvInfoMap(convId);
var added = false var added = false;
for (var idx in item.uris) { for (var idx in item.uris) {
var uri = item.uris[idx] var uri = item.uris[idx];
if (!Array.from(newHm).find(r => r.uri === uri) && if (!Array.from(newHm).find(r => r.uri === uri) && uri !== CurrentAccount.uri) {
uri !== CurrentAccount.uri) { newHm.push({
newHm.push({"uri": uri, "convId": convId}) "uri": uri,
added = true "convId": convId
});
added = true;
} }
} }
if (!added) if (!added)
return false return false;
} else { } else {
newH = Array.from(newH).filter(r => r !== convId) newH = Array.from(newH).filter(r => r !== convId);
newHm = Array.from(newHm).filter(r => r.convId !== convId) newHm = Array.from(newHm).filter(r => r.convId !== convId);
} }
newH.push(convId);
newH.push(convId) root.highlighted = newH;
root.highlighted = newH root.highlightedMembers = newHm;
root.highlightedMembers = newHm ConversationsAdapter.ignoreFiltering(root.highlighted);
ConversationsAdapter.ignoreFiltering(root.highlighted) return true;
return true
} }
function clearHighlighted() { function clearHighlighted() {
root.highlighted = [] root.highlighted = [];
root.highlightedMembers = [] root.highlightedMembers = [];
} }
function removeMember(convId, member) { function removeMember(convId, member) {
var refreshHighlighted = true var refreshHighlighted = true;
var newHm = [] var newHm = [];
for (var hm in root.highlightedMembers) { for (var hm in root.highlightedMembers) {
var m = root.highlightedMembers[hm] var m = root.highlightedMembers[hm];
if (m.convId === convId && m.uri === member) { if (m.convId === convId && m.uri === member) {
continue; continue;
} else if (m.convId === convId) { } else if (m.convId === convId) {
refreshHighlighted = false refreshHighlighted = false;
} }
newHm.push(m) newHm.push(m);
} }
root.highlightedMembers = newHm root.highlightedMembers = newHm;
if (refreshHighlighted) { if (refreshHighlighted) {
// Remove highlighted status if necessary // Remove highlighted status if necessary
for (var d in swarmCurrentConversationList.contentItem.children) { for (var d in swarmCurrentConversationList.contentItem.children) {
var delegate = swarmCurrentConversationList.contentItem.children[d] var delegate = swarmCurrentConversationList.contentItem.children[d];
if (delegate.convId === convId) if (delegate.convId === convId)
delegate.highlighted = false delegate.highlighted = false;
} }
} }
} }
@ -176,11 +173,16 @@ SidePanelBase {
color: JamiTheme.backgroundColor color: JamiTheme.backgroundColor
} }
header: AccountComboBox {} header: AccountComboBox {
}
Item { Item {
anchors.fill: parent anchors.fill: parent
onVisibleChanged: {
donation.donationVisible = Qt.binding(() => JamiQmlUtils.isDonationBannerVisible());
}
RowLayout { RowLayout {
id: titleBar id: titleBar
@ -240,7 +242,7 @@ SidePanelBase {
sequence: "Ctrl+F" sequence: "Ctrl+F"
context: Qt.ApplicationShortcut context: Qt.ApplicationShortcut
onActivated: { onActivated: {
contactSearchBar.forceActiveFocus() contactSearchBar.forceActiveFocus();
} }
} }
@ -250,20 +252,18 @@ SidePanelBase {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
onSearchBarTextChanged: function(text){ onSearchBarTextChanged: function (text) {
// not calling positionViewAtBeginning will cause // not calling positionViewAtBeginning will cause
// sort animation visual bugs // sort animation visual bugs
conversationListView.positionViewAtBeginning() conversationListView.positionViewAtBeginning();
ConversationsAdapter.ignoreFiltering(root.highlighted) ConversationsAdapter.ignoreFiltering(root.highlighted);
ConversationsAdapter.setFilter(text) ConversationsAdapter.setFilter(text);
} }
onReturnPressedWhileSearching: { onReturnPressedWhileSearching: {
var listView = searchResultsListView.count ? var listView = searchResultsListView.count ? searchResultsListView : conversationListView;
searchResultsListView :
conversationListView
if (listView.count) if (listView.count)
listView.model.select(0) listView.model.select(0);
} }
} }
@ -291,8 +291,7 @@ SidePanelBase {
SidePanelTabBar { SidePanelTabBar {
id: sidePanelTabBar id: sidePanelTabBar
visible: ConversationsAdapter.pendingRequestCount && visible: ConversationsAdapter.pendingRequestCount && !contactSearchBar.textContent && smartListLayout.visible
!contactSearchBar.textContent && smartListLayout.visible
anchors.top: startBar.bottom anchors.top: startBar.bottom
anchors.topMargin: visible ? 10 : 0 anchors.topMargin: visible ? 10 : 0
width: page.width width: page.width
@ -311,7 +310,6 @@ SidePanelBase {
height: visible ? 42 : 0 height: visible ? 42 : 0
color: JamiTheme.backgroundColor color: JamiTheme.backgroundColor
Text { Text {
id: searchStatusText id: searchStatusText
@ -326,13 +324,22 @@ SidePanelBase {
} }
} }
DonationBanner {
id: donation
anchors.horizontalCenter: parent.horizontalCenter
anchors.leftMargin: 15
anchors.rightMargin: 15
anchors.top: sidePanelTabBar.bottom
anchors.topMargin: 10
visible: donation.donationVisible
}
ColumnLayout { ColumnLayout {
id: smartListLayout id: smartListLayout
width: parent.width width: parent.width
anchors.top: searchStatusRect.bottom anchors.top: donation.donationVisible ? donation.bottom : sidePanelTabBar.bottom
anchors.topMargin: (sidePanelTabBar.visible || anchors.topMargin: (sidePanelTabBar.visible || searchStatusRect.visible) ? 0 : 12
searchStatusRect.visible) ? 0 : 12
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
spacing: 4 spacing: 4
@ -350,14 +357,14 @@ SidePanelBase {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: visible ? contentHeight : 0 Layout.preferredHeight: visible ? contentHeight : 0
Layout.maximumHeight: { Layout.maximumHeight: {
var otherContentHeight = conversationListView.contentHeight + 16 var otherContentHeight = conversationListView.contentHeight + 16;
if (conversationListView.visible) if (conversationListView.visible)
if (otherContentHeight < parent.height / 2) if (otherContentHeight < parent.height / 2)
return parent.height - otherContentHeight return parent.height - otherContentHeight;
else else
return parent.height / 2 return parent.height / 2;
else else
return parent.height return parent.height;
} }
model: SearchResultsListModel model: SearchResultsListModel
@ -385,9 +392,8 @@ SidePanelBase {
visible: inNewSwarm visible: inNewSwarm
width: parent.width width: parent.width
anchors.top: searchStatusRect.bottom anchors.top: donation.donationVisible ? donation.bottom : sidePanelTabBar.bottom
anchors.topMargin: (sidePanelTabBar.visible || anchors.topMargin: (sidePanelTabBar.visible || searchStatusRect.visible) ? 0 : 12
searchStatusRect.visible) ? 0 : 12
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
spacing: 4 spacing: 4
@ -404,8 +410,8 @@ SidePanelBase {
onVisibleChanged: { onVisibleChanged: {
if (!swarmCurrentConversationList.visible) { if (!swarmCurrentConversationList.visible) {
highlighted = false highlighted = false;
root.clearHighlighted() root.clearHighlighted();
} }
} }
@ -414,26 +420,26 @@ SidePanelBase {
// destroyed from the memory. So, re-add the highlighted // destroyed from the memory. So, re-add the highlighted
// status if necessary // status if necessary
if (Array.from(root.highlighted).find(r => r === UID)) { if (Array.from(root.highlighted).find(r => r === UID)) {
highlighted = true highlighted = true;
} }
} }
onHighlightedChanged: function onHighlightedChanged() { onHighlightedChanged: function onHighlightedChanged() {
if (highlighted && Array.from(root.highlighted).find(r => r === UID)) { if (highlighted && Array.from(root.highlighted).find(r => r === UID)) {
// Due to scrolling destruction/reconstruction // Due to scrolling destruction/reconstruction
return return;
} }
var currentHighlighted = root.highlighted var currentHighlighted = root.highlighted;
if (!root.refreshHighlighted(UID, highlighted)) { if (!root.refreshHighlighted(UID, highlighted)) {
highlighted = false highlighted = false;
return return;
} }
if (highlighted) { if (highlighted) {
root.highlighted.push(UID) root.highlighted.push(UID);
} else { } else {
root.highlighted = Array.from(root.highlighted).filter(r => r !== UID) root.highlighted = Array.from(root.highlighted).filter(r => r !== UID);
} }
root.clearContactSearchBar() root.clearContactSearchBar();
} }
} }
currentIndex: model.currentFilteredRow currentIndex: model.currentFilteredRow
@ -448,7 +454,9 @@ SidePanelBase {
interval: 750 interval: 750
running: isSharingPosition || isReceivingPosition running: isSharingPosition || isReceivingPosition
repeat: true repeat: true
onTriggered: {showIconArrow = !showIconArrow} onTriggered: {
showIconArrow = !showIconArrow;
}
} }
} }
} }