diff --git a/resources/icons/Icon_Donate.svg b/resources/icons/Icon_Donate.svg
new file mode 100644
index 00000000..913daefd
--- /dev/null
+++ b/resources/icons/Icon_Donate.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index 8b29766e..b28deddc 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -62,8 +62,8 @@ extern const QString defaultDownloadPath;
X(FlipSelf, true) \
X(ShowMardownOption, 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.
* Note: this is using a non-constructable class instead of a
diff --git a/src/app/constant/JamiQmlUtils.qml b/src/app/constant/JamiQmlUtils.qml
index 4eaa02f8..4b5aa62b 100644
--- a/src/app/constant/JamiQmlUtils.qml
+++ b/src/app/constant/JamiQmlUtils.qml
@@ -20,6 +20,7 @@
pragma Singleton
import QtQuick
import net.jami.Adapters 1.1
+import net.jami.Enums 1.1
Item {
property string qmlFilePrefix: "file:/"
@@ -69,4 +70,9 @@ Item {
function clamp(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)));
+ }
}
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 98f339d5..817ce642 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -835,4 +835,9 @@ Item {
// Appearence
property string theme: qsTr("Theme")
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")
}
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 7a42dd6f..b66d7345 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -657,6 +657,11 @@ Item {
property color darkThemeCheckedColor: "#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) {
darkTheme = dark;
}
diff --git a/src/app/mainview/components/DonationBanner.qml b/src/app/mainview/components/DonationBanner.qml
new file mode 100644
index 00000000..b7538878
--- /dev/null
+++ b/src/app/mainview/components/DonationBanner.qml
@@ -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 .
+ */
+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
+ }
+ }
+ }
+}
diff --git a/src/app/mainview/components/SidePanel.qml b/src/app/mainview/components/SidePanel.qml
index 170ade69..5ed1b592 100644
--- a/src/app/mainview/components/SidePanel.qml
+++ b/src/app/mainview/components/SidePanel.qml
@@ -16,21 +16,19 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
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"
SidePanelBase {
id: root
+
objectName: "SidePanel"
color: JamiTheme.backgroundColor
@@ -39,7 +37,7 @@ SidePanelBase {
target: LRCInstance
function onCurrentAccountIdChanged() {
- clearContactSearchBar()
+ clearContactSearchBar();
}
}
@@ -47,8 +45,8 @@ SidePanelBase {
target: ConversationsAdapter
function onConversationReady() {
- selectTab(SidePanelTabBar.Conversations)
- clearContactSearchBar()
+ selectTab(SidePanelTabBar.Conversations);
+ clearContactSearchBar();
}
}
@@ -56,46 +54,46 @@ SidePanelBase {
target: ConversationsAdapter
function onShowSearchStatus(status) {
- searchStatusText.text = status
+ searchStatusText.text = status;
}
function onTextFilterChanged(text) {
// In the swarm details, "Go to conversation" can
// change the search bar. Be sure to be synced
- contactSearchBar.textContent = text
+ contactSearchBar.textContent = text;
}
}
function toggleCreateSwarmView() {
if (!inNewSwarm) {
- viewCoordinator.present("NewSwarmPage")
- const newSwarmPage = viewCoordinator.getView("NewSwarmPage")
+ viewCoordinator.present("NewSwarmPage");
+ const newSwarmPage = viewCoordinator.getView("NewSwarmPage");
newSwarmPage.removeMember.connect((convId, member) => {
- removeMember(convId, member)
- })
+ removeMember(convId, member);
+ });
newSwarmPage.createSwarmClicked.connect((title, description, avatar) => {
- var uris = []
- for (var idx in newSwarmPage.members) {
- var uri = newSwarmPage.members[idx].uri
- if (uris.indexOf(uri) === -1) {
- uris.push(uri)
+ var uris = [];
+ for (var idx in newSwarmPage.members) {
+ var uri = newSwarmPage.members[idx].uri;
+ if (uris.indexOf(uri) === -1) {
+ uris.push(uri);
+ }
}
- }
- let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris)
- viewCoordinator.dismiss("NewSwarmPage")
- LRCInstance.selectConversation(convuid)
- })
+ let convuid = ConversationsAdapter.createSwarm(title, description, avatar, uris);
+ viewCoordinator.dismiss("NewSwarmPage");
+ LRCInstance.selectConversation(convuid);
+ });
} else {
- viewCoordinator.dismiss("NewSwarmPage")
+ viewCoordinator.dismiss("NewSwarmPage");
}
}
function clearContactSearchBar() {
- contactSearchBar.clearText()
+ contactSearchBar.clearText();
}
function selectTab(tabIndex) {
- sidePanelTabBar.selectTab(tabIndex)
+ sidePanelTabBar.selectTab(tabIndex);
}
property bool inNewSwarm: viewCoordinator.currentViewName === "NewSwarmPage"
@@ -104,65 +102,64 @@ SidePanelBase {
property var highlightedMembers: []
onHighlightedMembersChanged: {
if (inNewSwarm) {
- const newSwarmPage = viewCoordinator.getView("NewSwarmPage")
- newSwarmPage.members = highlightedMembers
+ const newSwarmPage = viewCoordinator.getView("NewSwarmPage");
+ newSwarmPage.members = highlightedMembers;
}
}
function refreshHighlighted(convId, highlightedStatus) {
- var newH = root.highlighted
- var newHm = root.highlightedMembers
-
+ var newH = root.highlighted;
+ var newHm = root.highlightedMembers;
if (highlightedStatus) {
- var item = ConversationsAdapter.getConvInfoMap(convId)
- var added = false
+ var item = ConversationsAdapter.getConvInfoMap(convId);
+ var added = false;
for (var idx in item.uris) {
- var uri = item.uris[idx]
- if (!Array.from(newHm).find(r => r.uri === uri) &&
- uri !== CurrentAccount.uri) {
- newHm.push({"uri": uri, "convId": convId})
- added = true
+ var uri = item.uris[idx];
+ if (!Array.from(newHm).find(r => r.uri === uri) && uri !== CurrentAccount.uri) {
+ newHm.push({
+ "uri": uri,
+ "convId": convId
+ });
+ added = true;
}
}
if (!added)
- return false
+ return false;
} else {
- newH = Array.from(newH).filter(r => r !== convId)
- newHm = Array.from(newHm).filter(r => r.convId !== convId)
+ newH = Array.from(newH).filter(r => r !== convId);
+ newHm = Array.from(newHm).filter(r => r.convId !== convId);
}
-
- newH.push(convId)
- root.highlighted = newH
- root.highlightedMembers = newHm
- ConversationsAdapter.ignoreFiltering(root.highlighted)
- return true
+ newH.push(convId);
+ root.highlighted = newH;
+ root.highlightedMembers = newHm;
+ ConversationsAdapter.ignoreFiltering(root.highlighted);
+ return true;
}
function clearHighlighted() {
- root.highlighted = []
- root.highlightedMembers = []
+ root.highlighted = [];
+ root.highlightedMembers = [];
}
function removeMember(convId, member) {
- var refreshHighlighted = true
- var newHm = []
+ var refreshHighlighted = true;
+ var newHm = [];
for (var hm in root.highlightedMembers) {
- var m = root.highlightedMembers[hm]
+ var m = root.highlightedMembers[hm];
if (m.convId === convId && m.uri === member) {
continue;
} else if (m.convId === convId) {
- refreshHighlighted = false
+ refreshHighlighted = false;
}
- newHm.push(m)
+ newHm.push(m);
}
- root.highlightedMembers = newHm
-
+ root.highlightedMembers = newHm;
if (refreshHighlighted) {
// Remove highlighted status if necessary
for (var d in swarmCurrentConversationList.contentItem.children) {
- var delegate = swarmCurrentConversationList.contentItem.children[d]
+ var delegate = swarmCurrentConversationList.contentItem.children[d];
if (delegate.convId === convId)
- delegate.highlighted = false
+ delegate.highlighted = false;
}
}
}
@@ -176,11 +173,16 @@ SidePanelBase {
color: JamiTheme.backgroundColor
}
- header: AccountComboBox {}
+ header: AccountComboBox {
+ }
Item {
anchors.fill: parent
+ onVisibleChanged: {
+ donation.donationVisible = Qt.binding(() => JamiQmlUtils.isDonationBannerVisible());
+ }
+
RowLayout {
id: titleBar
@@ -240,7 +242,7 @@ SidePanelBase {
sequence: "Ctrl+F"
context: Qt.ApplicationShortcut
onActivated: {
- contactSearchBar.forceActiveFocus()
+ contactSearchBar.forceActiveFocus();
}
}
@@ -250,20 +252,18 @@ SidePanelBase {
Layout.fillHeight: true
Layout.fillWidth: true
- onSearchBarTextChanged: function(text){
+ onSearchBarTextChanged: function (text) {
// not calling positionViewAtBeginning will cause
// sort animation visual bugs
- conversationListView.positionViewAtBeginning()
- ConversationsAdapter.ignoreFiltering(root.highlighted)
- ConversationsAdapter.setFilter(text)
+ conversationListView.positionViewAtBeginning();
+ ConversationsAdapter.ignoreFiltering(root.highlighted);
+ ConversationsAdapter.setFilter(text);
}
onReturnPressedWhileSearching: {
- var listView = searchResultsListView.count ?
- searchResultsListView :
- conversationListView
+ var listView = searchResultsListView.count ? searchResultsListView : conversationListView;
if (listView.count)
- listView.model.select(0)
+ listView.model.select(0);
}
}
@@ -291,8 +291,7 @@ SidePanelBase {
SidePanelTabBar {
id: sidePanelTabBar
- visible: ConversationsAdapter.pendingRequestCount &&
- !contactSearchBar.textContent && smartListLayout.visible
+ visible: ConversationsAdapter.pendingRequestCount && !contactSearchBar.textContent && smartListLayout.visible
anchors.top: startBar.bottom
anchors.topMargin: visible ? 10 : 0
width: page.width
@@ -311,7 +310,6 @@ SidePanelBase {
height: visible ? 42 : 0
color: JamiTheme.backgroundColor
-
Text {
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 {
id: smartListLayout
width: parent.width
- anchors.top: searchStatusRect.bottom
- anchors.topMargin: (sidePanelTabBar.visible ||
- searchStatusRect.visible) ? 0 : 12
+ anchors.top: donation.donationVisible ? donation.bottom : sidePanelTabBar.bottom
+ anchors.topMargin: (sidePanelTabBar.visible || searchStatusRect.visible) ? 0 : 12
anchors.bottom: parent.bottom
spacing: 4
@@ -350,14 +357,14 @@ SidePanelBase {
Layout.fillWidth: true
Layout.preferredHeight: visible ? contentHeight : 0
Layout.maximumHeight: {
- var otherContentHeight = conversationListView.contentHeight + 16
+ var otherContentHeight = conversationListView.contentHeight + 16;
if (conversationListView.visible)
if (otherContentHeight < parent.height / 2)
- return parent.height - otherContentHeight
+ return parent.height - otherContentHeight;
else
- return parent.height / 2
+ return parent.height / 2;
else
- return parent.height
+ return parent.height;
}
model: SearchResultsListModel
@@ -385,9 +392,8 @@ SidePanelBase {
visible: inNewSwarm
width: parent.width
- anchors.top: searchStatusRect.bottom
- anchors.topMargin: (sidePanelTabBar.visible ||
- searchStatusRect.visible) ? 0 : 12
+ anchors.top: donation.donationVisible ? donation.bottom : sidePanelTabBar.bottom
+ anchors.topMargin: (sidePanelTabBar.visible || searchStatusRect.visible) ? 0 : 12
anchors.bottom: parent.bottom
spacing: 4
@@ -404,8 +410,8 @@ SidePanelBase {
onVisibleChanged: {
if (!swarmCurrentConversationList.visible) {
- highlighted = false
- root.clearHighlighted()
+ highlighted = false;
+ root.clearHighlighted();
}
}
@@ -414,26 +420,26 @@ SidePanelBase {
// destroyed from the memory. So, re-add the highlighted
// status if necessary
if (Array.from(root.highlighted).find(r => r === UID)) {
- highlighted = true
+ highlighted = true;
}
}
onHighlightedChanged: function onHighlightedChanged() {
if (highlighted && Array.from(root.highlighted).find(r => r === UID)) {
// Due to scrolling destruction/reconstruction
- return
+ return;
}
- var currentHighlighted = root.highlighted
+ var currentHighlighted = root.highlighted;
if (!root.refreshHighlighted(UID, highlighted)) {
- highlighted = false
- return
+ highlighted = false;
+ return;
}
if (highlighted) {
- root.highlighted.push(UID)
+ root.highlighted.push(UID);
} 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
@@ -448,7 +454,9 @@ SidePanelBase {
interval: 750
running: isSharingPosition || isReceivingPosition
repeat: true
- onTriggered: {showIconArrow = !showIconArrow}
+ onTriggered: {
+ showIconArrow = !showIconArrow;
+ }
}
}
}