diff --git a/backend/ForumBackend.qml b/backend/ForumBackend.qml index 54cc132..e3b2958 100644 --- a/backend/ForumBackend.qml +++ b/backend/ForumBackend.qml @@ -150,10 +150,10 @@ Object { loginFinished = false //do not set loggedIn to false => ability to change login data - if (configModel.get(0).support_md5) { + if (configModel.supportMd5) { console.log("md5") password = Md5Utils.md5(password) - } else if (configModel.get(0).support_sha1) { //Untested yet + } else if (configModel.supportSHA1) { //Untested yet console.log("sha1") password = Sha1Utils.sha1(password) } else { diff --git a/backend/ForumConfigModel.qml b/backend/ForumConfigModel.qml index 3b7cf5a..0ac1301 100644 --- a/backend/ForumConfigModel.qml +++ b/backend/ForumConfigModel.qml @@ -38,18 +38,28 @@ XmlListModel { query: "/methodResponse/params/param/value/struct" property bool hasLoaded: false + + property string version property bool isVBulletin: false + property bool supportMd5: false + property bool supportSHA1: false + property bool subscribeForum: true - XmlRole { name: "support_md5"; query: "member[name='support_md5']/value/string()" } - XmlRole { name: "support_sha1"; query: "member[name='support_sha1']/value/string()" } + XmlRole { name: "support_md5"; query: "member[name='support_md5']/value/number()" } + XmlRole { name: "support_sha1"; query: "member[name='support_sha1']/value/number()" } XmlRole { name: "version"; query: "member[name='version']/value/string()" } + XmlRole { name: "subscribe_forum"; query: "member[name='subscribe_forum']/value/number()" } onStatusChanged: { if (status === XmlListModel.Ready) { var element = get(0) - if (element.version.trim().indexOf("vb") === 0) { - isVBulletin = true - } + + version = element.version.trim() + isVBulletin = version.indexOf("vb") === 0 + supportMd5 = (typeof(element.support_md5) === "number") ? element.support_md5 : false + supportSHA1 = (typeof(element.support_sha1) === "number") ? element.support_sha1 : false + subscribeForum = (typeof(element.subscribe_forum) === "number") ? element.subscribe_forum : true + console.log("version: " + element.version.trim()) console.log("configModel has loaded") diff --git a/ui/components/Notification.qml b/ui/components/Notification.qml index 711a8f1..f8a7144 100644 --- a/ui/components/Notification.qml +++ b/ui/components/Notification.qml @@ -37,14 +37,14 @@ Rectangle { function show(text) { queue.push(text) - if (!showing) { + if (!showing && !timer.running) { //!timer.running for the time between two notifications (when timer.interval === 800) update() } } function update() { notification.text = "" - notification.text = queue.pop() + notification.text = queue.shift() notification.showing = true } diff --git a/ui/viewing/SubForumList.qml b/ui/viewing/SubForumList.qml index 5d93fdd..a45cec7 100644 --- a/ui/viewing/SubForumList.qml +++ b/ui/viewing/SubForumList.qml @@ -37,13 +37,10 @@ ListView { id: forumsList property alias current_forum: categoryModel.parentForumId - property int current_topic: -1 - property int selected_forum: -1 - property string selected_title: "" - property bool canPost: false - property bool hasTopics: false property string mode: "" property bool moreLoading: false + property bool hasTopics: false + property bool canPost: false property bool viewSubscriptions: false @@ -56,19 +53,16 @@ ListView { delegate: SubForumListItem { text: StringUtils.base64_decode(model.name) subText: StringUtils.base64_decode(model.description) - replies: model.topic ? (parseInt(model.posts) + 1) : 0 //+1 to include OP + replies: model.topic ? (model.posts + 1) : 0 //+1 to include OP author: model.topic ? StringUtils.base64_decode(model.author) : "" - has_new: model.has_new === '1' ? true : false + has_new: model.has_new progression: true onTriggered: { - selected_title = text if (model.topic) { - current_topic = -1 - current_topic = model.id + forumsPage.pushThreadPage(model.id, text) } else { - selected_forum = -1 - selected_forum = model.id + forumsPage.pushSubForumPage(model.id, text, model.can_subscribe, model.is_subscribed) } } @@ -161,10 +155,12 @@ ListView { property bool viewSubscriptions: forumsList.viewSubscriptions query: viewSubscriptions ? "/methodResponse/params/param/value/struct/member[name='forums']/value/array/data/value/struct" : "/methodResponse/params/param/value/array/data/value/struct" - XmlRole { name: "id"; query: "member[name='forum_id']/value/string()" } + XmlRole { name: "id"; query: "member[name='forum_id']/value/number()" } XmlRole { name: "name"; query: "member[name='forum_name']/value/base64/string()" } XmlRole { name: "description"; query: "member[name='description']/value/base64/string()" } XmlRole { name: "logo"; query: "member[name='logo_url']/value/string()" } + XmlRole { name: "can_subscribe"; query: "member[name='can_subscribe']/value/number()" } + XmlRole { name: "is_subscribed"; query: "member[name='is_subscribed']/value/number()" } property bool checkingForChildren: false @@ -178,7 +174,7 @@ ListView { if (!checkingForChildren) { console.debug("categoryModel has: " + count + " items"); - if (count !== 1 || parentForumId !== parseInt(get(0).id)) { + if (count !== 1 || parentForumId !== get(0).id) { insertResults() } else { //Header with a child attribute if (!topicModel.hasLoadedCompletely) { @@ -218,7 +214,7 @@ ListView { var element = get(i) //We need to declare even unused properties here //Needed when there are both topics and categories in a subforum - var pushObject = {"topic": false, "id": element.id.trim(), "name": element.name.trim(), "description": viewSubscriptions ? "" : element.description.trim(), "logo": element.logo.trim(), "author": "", "posts": "-1", "has_new": "0"} + var pushObject = {"topic": false, "id": element.id, "name": element.name.trim(), "description": viewSubscriptions ? "" : element.description.trim(), "logo": element.logo.trim(), "author": "", "posts": -1, "has_new": 0, "can_subscribe": viewSubscriptions ? 1 : element.can_subscribe, "is_subscribed": viewSubscriptions ? 1 : element.is_subscribed} if (!isForumOverview) { forumListModel.insert(i, pushObject) } else { @@ -356,12 +352,12 @@ ListView { property bool viewSubscriptions: forumsList.viewSubscriptions query: "/methodResponse/params/param/value/struct/member/value/array/data/value/struct" - XmlRole { name: "id"; query: "member[name='topic_id']/value/string()" } + XmlRole { name: "id"; query: "member[name='topic_id']/value/number()" } XmlRole { name: "title"; query: "member[name='topic_title']/value/base64/string()" } // XmlRole { name: "description"; query: "member[name='short_content']/value/base64/string()" } XmlRole { name: "author"; query: "member[name='topic_author_name']/value/base64/string()" } - XmlRole { name: "posts"; query: "member[name='reply_number']/value/int/string()" } - XmlRole { name: "has_new"; query: "member[name='new_post']/value/boolean/string()" } + XmlRole { name: "posts"; query: "member[name='reply_number']/value/int/number()" } + XmlRole { name: "has_new"; query: "member[name='new_post']/value/boolean/number()" } onStatusChanged: { if (status === 1) { @@ -369,7 +365,7 @@ ListView { hasTopics = true //no else needed (and it may interfere with moreLoading) //TODO: Check if if is needed or if it won't be added twice even without the if - if (count === 1 && forumListModel.count > 0 && get(0).id.trim() === forumListModel.get(forumListModel.count - 1).id && forumListModel.get(forumListModel.count - 1).topic === true) { + if (count === 1 && forumListModel.count > 0 && get(0).id === forumListModel.get(forumListModel.count - 1).id && forumListModel.get(forumListModel.count - 1).topic === true) { //Do not add the element as it is a duplicate of the last one which was added //Happens if a forum contains n * backend.topicsLoadCount topics (with n = 2, 3, 4, ...) and loadMore() is called (sadly, that's how the API handles the request) @@ -385,7 +381,7 @@ ListView { var element = get(i); //We need to declare even unused properties here //Needed when there are both topics and categories in a subforum - forumListModel.append({"topic": true, "id": element.id.trim(), "name": element.title.trim(), "description": "" /*element.description.trim()*/, "logo": "", "author": element.author.trim(), "posts": element.posts.trim(), "has_new": element.has_new.trim()}); + forumListModel.append({"topic": true, "id": element.id, "name": element.title.trim(), "description": "", "logo": "", "author": element.author.trim(), "posts": element.posts, "has_new": element.has_new, "can_subscribe": 1, "is_subscribed": 0}); } } } diff --git a/ui/viewing/SubForumPage.qml b/ui/viewing/SubForumPage.qml index 3501146..c636c98 100644 --- a/ui/viewing/SubForumPage.qml +++ b/ui/viewing/SubForumPage.qml @@ -28,7 +28,8 @@ import QtQuick 2.2 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 1.0 -import '../components' +import "../components" +import "../../backend" PageWithBottomEdge { id: forumsPage @@ -41,11 +42,17 @@ PageWithBottomEdge { property alias current_forum: forumsList.current_forum property bool isForumOverview: current_forum === 0 - property alias selectedTitle: forumsList.selected_title + property int selectedId: -1 + property string selectedTitle: "" + property bool selectedCanSubscribe: false + property bool selectedIsSubscribed: false property alias loadingSpinnerRunning: loadingSpinner.running property bool showSections: false + property bool isSubscribed: false + property bool canSubscribe: false + bottomEdgeTitle: i18n.tr("Subscriptions") bottomEdgeEnabled: !disableBottomEdge && current_forum >= 0 && backend.currentSession.loggedIn bottomEdgePageSource: (!disableBottomEdge && current_forum >= 0) ? Qt.resolvedUrl("SubForumPage.qml") : "" @@ -117,10 +124,47 @@ PageWithBottomEdge { } } + Action { + id: subscribeAction + text: isSubscribed ? i18n.tr("Unsubscribe") : i18n.tr("Subscribe") + iconName: isSubscribed ? "starred" : "non-starred" + visible: backend.currentSession.loggedIn && !viewSubscriptions && backend.currentSession.configModel.subscribeForum && canSubscribe + + onTriggered: subscriptionChange() + + function subscriptionChange() { + if (isSubscribed) { + subscribeRequest.query = 'unsubscribe_forum' + current_forum + '' + } else { + subscribeRequest.query = 'subscribe_forum' + current_forum + '' + } + + if (subscribeRequest.start()) { + isSubscribed = !isSubscribed //If the api request fails, it will be changed back later + subscribeRequest.notificationQueue.push(isSubscribed ? i18n.tr("Subscribed to this subforum") : i18n.tr("Unsubscribed from this subforum")) + } + } + } + + ApiRequest { + id: subscribeRequest + checkSuccess: true + allowMultipleRequests: true + property var notificationQueue: [] //Needed when subscribeRequest.queryQueue.length > 1 + + onQuerySuccessResult: { + if (success) { + notification.show(notificationQueue.shift()) + } else { + isSubscribed = !isSubscribed + notificationQueue.shift() + } + } + } + function onNewTopicCreated(subject, topicId) { selectedTitle = subject - forumsList.current_topic = -1 - forumsList.current_topic = topicId //Show topic + pushThreadPage(topicId) //Show thread forumsList.reload() } @@ -128,6 +172,7 @@ PageWithBottomEdge { readonly property var headerActions: [ reloadAction, newTopicAction, + subscribeAction, loginAction ] @@ -177,43 +222,54 @@ PageWithBottomEdge { mode: (forumsPage.head.sections.selectedIndex === 1) ? "TOP" : ((forumsPage.head.sections.selectedIndex === 2) ? "ANN" : "") - onSelected_forumChanged: { - if (selected_forum > 0) { - component = Qt.createComponent("SubForumPage.qml"); - - if (component.status === Component.Ready) { - finishSubForumPageCreation(); - } else { - component.statusChanged.connect(finishSubForumPageCreation); - } - } - } + } - function finishSubForumPageCreation() { - var page = component.createObject(mainView, {"title": selectedTitle, "current_forum": selected_forum, "loadingSpinnerRunning": true, "disableBottomEdge": disableBottomEdge}) - pageStack.push(page) + function pushSubForumPage(forumId, title, canSubscribe, isSubscribed) { + selectedId = forumId + selectedTitle = title + selectedCanSubscribe = (typeof(canSubscribe) === "bool" || typeof(canSubscribe) === "number") ? canSubscribe : true + selectedIsSubscribed = (typeof(isSubscribed) === "bool" || typeof(isSubscribed) === "number") ? isSubscribed : false + component = Qt.createComponent("SubForumPage.qml") + + if (component.status === Component.Ready) { + finishSubForumPageCreation() + } else { + component.statusChanged.connect(finishSubForumPageCreation) } + } - onCurrent_topicChanged: { - if (current_topic > 0) { - component = Qt.createComponent("ThreadPage.qml") - - if (component.status === Component.Ready) { - finishThreadPageCreation(); - } else { - component.statusChanged.connect(finishThreadPageCreation); + function finishSubForumPageCreation() { + var page = component.createObject(mainView, {"title": selectedTitle, "current_forum": selectedId, "loadingSpinnerRunning": true, "disableBottomEdge": disableBottomEdge, "canSubscribe": selectedCanSubscribe, "isSubscribed": selectedIsSubscribed}) + page.onIsSubscribedChanged.connect(function() { //Change is_subscribed attribute when the subscription state is changed + for (var i = 0; i < forumsList.model.count; i++) { + if (forumsList.model.get(i).id === selectedId) { + forumsList.model.setProperty(i, "is_subscribed", page.isSubscribed ? 1 : 0) //is_subscribed requires a number + break } } - } + }) + pageStack.push(page) + } - function finishThreadPageCreation() { - var vBulletinAnnouncement = backend.currentSession.configModel.isVBulletin && forumsList.mode === "ANN" - var page = component.createObject(mainView, {"title": selectedTitle, "loadingSpinnerRunning": true, "forum_id": current_forum, "vBulletinAnnouncement": vBulletinAnnouncement}) - page.current_topic = current_topic //Need to set vBulletinAnnouncement before current_topic!!! Therefore, this is executed after the creation of the Page. - pageStack.push(page) + function pushThreadPage(topicId, title) { + selectedId = topicId + selectedTitle = title + component = Qt.createComponent("ThreadPage.qml") + + if (component.status === Component.Ready) { + finishThreadPageCreation() + } else { + component.statusChanged.connect(finishThreadPageCreation) } } + function finishThreadPageCreation() { + var vBulletinAnnouncement = backend.currentSession.configModel.isVBulletin && forumsList.mode === "ANN" + var page = component.createObject(mainView, {"title": selectedTitle, "loadingSpinnerRunning": true, "forum_id": current_forum, "vBulletinAnnouncement": vBulletinAnnouncement}) + page.current_topic = selectedId //Need to set vBulletinAnnouncement before current_topic!!! Therefore, this is executed after the creation of the Page. + pageStack.push(page) + } + Label { id: emptyView text: viewSubscriptions ? i18n.tr("You are not subscribed to any topics or forums") : ((forumsList.mode === "") ? i18n.tr("No topics available here") : ((forumsList.mode === "TOP") ? i18n.tr("No stickies available here") : i18n.tr("No announcements available here"))) diff --git a/ui/viewing/ThreadList.qml b/ui/viewing/ThreadList.qml index a9445d5..fa958ae 100644 --- a/ui/viewing/ThreadList.qml +++ b/ui/viewing/ThreadList.qml @@ -41,6 +41,8 @@ ListView { property int totalPostCount: -1 property bool canReply: false property bool isClosed: false + property bool canSubscribe: false + property bool isSubscribed: false property bool vBulletinAnnouncement: false @@ -132,6 +134,32 @@ ListView { isClosed = isClosedSubstring.trim() === "1" } + + //Check if can subscribe + + var canSubscribeStringPosition = xml.indexOf("can_subscribe"); + if (canSubscribeStringPosition < 0) { + canSubscribe = true + } else { + var openBoolTagPosition = xml.indexOf("", canSubscribeStringPosition); + var closeBoolTagPosition = xml.indexOf("", openBoolTagPosition); + var canSubscribeSubstring = xml.substring(openBoolTagPosition + 9, closeBoolTagPosition); //equals + "".length + + canSubscribe = canSubscribeSubstring.trim() === "1" + } + + //Check if is subscribed + + var isSubscribedStringPosition = xml.indexOf("is_subscribed"); + if (isSubscribedStringPosition < 0) { + isSubscribed = false + } else { + var openBoolTagPosition = xml.indexOf("", isSubscribedStringPosition); + var closeBoolTagPosition = xml.indexOf("", openBoolTagPosition); + var isSubscribedSubstring = xml.substring(openBoolTagPosition + 9, closeBoolTagPosition); //equals + "".length + + isSubscribed = isSubscribedSubstring.trim() === "1" + } } } diff --git a/ui/viewing/ThreadPage.qml b/ui/viewing/ThreadPage.qml index ebbc143..8d0dce2 100644 --- a/ui/viewing/ThreadPage.qml +++ b/ui/viewing/ThreadPage.qml @@ -30,6 +30,7 @@ import Ubuntu.Components 1.1 import Ubuntu.Components.ListItems 1.0 import Ubuntu.Components.Popups 1.0 import "../components" +import "../../backend" PageWithBottomEdge { id: threadPage @@ -75,6 +76,14 @@ PageWithBottomEdge { } head.actions: [ + Action { + id: reloadAction + text: i18n.tr("Reload") + iconName: "reload" + onTriggered: { + threadList.reload() + } + }, Action { id: loginAction text: i18n.tr("Login") @@ -84,6 +93,27 @@ PageWithBottomEdge { pageStack.push(loginPage) } }, + Action { + id: subscribeAction + text: threadList.isSubscribed ? i18n.tr("Unsubscribe") : i18n.tr("Subscribe") + iconName: threadList.isSubscribed ? "starred" : "non-starred" + visible: backend.currentSession.loggedIn && threadList.canSubscribe + + onTriggered: subscriptionChange() + + function subscriptionChange() { + if (threadList.isSubscribed) { + subscribeRequest.query = 'unsubscribe_topic' + current_topic + '' + } else { + subscribeRequest.query = 'subscribe_topic' + current_topic + '' + } + + if (subscribeRequest.start()) { + threadList.isSubscribed = !threadList.isSubscribed //If the api request fails, it will be changed back later + subscribeRequest.notificationQueue.push(threadList.isSubscribed ? i18n.tr("Subscribed to this topic") : i18n.tr("Unsubscribed from this topic")) + } + } + }, Action { id: gotoAction text: i18n.tr("Go To Page") @@ -95,17 +125,25 @@ PageWithBottomEdge { popup.itemSelector.selectedIndex = selected // popup.itemSelector.positionViewAtIndex(selected, ListView.Center) //TODO: Add to UI Toolkit? } - }, - Action { - id: reloadAction - text: i18n.tr("Reload") - iconName: "reload" - onTriggered: { - threadList.reload() - } } ] + ApiRequest { + id: subscribeRequest + checkSuccess: true + allowMultipleRequests: true + property var notificationQueue: [] //Needed when subscribeRequest.queryQueue.length > 1 + + onQuerySuccessResult: { + if (success) { + notification.show(notificationQueue.shift()) + } else { + threadList.isSubscribed = !threadList.isSubscribed + notificationQueue.shift() + } + } + } + head.contents: Label { width: parent.width anchors.verticalCenter: parent.verticalCenter