added Feeds List view

This commit is contained in:
Hauke Schade 2012-11-20 01:21:26 +01:00
parent 8d32d2a33b
commit 42835577ca
2 changed files with 354 additions and 16 deletions

257
qml/ttrss/Feeds.qml Normal file
View file

@ -0,0 +1,257 @@
//Copyright Hauke Schade, 2012
//
//This file is part of TTRss.
//
//TTRss 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 2 of the License, or (at your option) any later version.
//TTRss 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 TTRss (on a Maemo/Meego system there is a copy
//in /usr/share/common-licenses. If not, see http://www.gnu.org/licenses/.
import QtQuick 1.1
import com.nokia.meego 1.0
Page {
id: feedsPage
tools: feedsTools
property int categoryId: 0
property int numStatusUpdates
property bool loading: false
property string pageTitle: ""
anchors.margins: rootWindow.pageMargin
ListModel {
id: feedsModel
}
Component {
id: listHeading
Rectangle {
width: parent.width
height: 60
radius: 10
color: "orange"
visible: pageTitle !== ""
Text {
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
text: pageTitle
font.weight: Font.Bold
font.pixelSize: 26
}
}
}
ListView {
id: listView
anchors.fill: parent
model: feedsModel
header: listHeading
delegate: Item {
id: listItem
height: 88
width: parent.width
BorderImage {
id: background
anchors.fill: parent
// Fill page borders
anchors.leftMargin: -feedsPage.anchors.leftMargin
anchors.rightMargin: -feedsPage.anchors.rightMargin
visible: mouseArea.pressed
source: "image://theme/meegotouch-list-background-pressed-center"
}
Row {
anchors.left: parent.left
anchors.right: drilldownarrow.left
clip: true
Column {
clip: true
Label {
id: mainText
text: model.title
font.weight: Font.Bold
font.pixelSize: 26
color: (model.unreadcount > 0) ? "#000033" : "#888888";
}
Label {
id: subText
text: model.subtitle
font.weight: Font.Light
font.pixelSize: 22
color: (model.unreadcount > 0) ? "#cc6633" : "#888888"
visible: text != ""
}
}
}
Image {
id: drilldownarrow
source: "image://theme/icon-m-common-drilldown-arrow" + (theme.inverted ? "-inverse" : "")
anchors.right: parent.right;
anchors.verticalCenter: parent.verticalCenter
visible: (model.feedId !== null)
}
MouseArea {
id: mouseArea
anchors.fill: background
onClicked: {
showFeed(model.feedId);
}
}
}
}
ScrollDecorator {
flickableItem: listView
}
function showFeeds() {
var ttrss = rootWindow.getTTRSS();
var feeds = ttrss.getFeeds(categoryId);
var showAll = ttrss.getShowAll();
feedsModel.clear();
if(feeds && categoryId) {
var emptyList = feeds.length;
var unreadcount;
console.log("showing feeds for category: "+categoryId+"\n");
//First add feed with unread items
for(var feed in feeds) {
unreadcount = feeds[feed].unread;
if( unreadcount && (unreadcount > 0)) {
emptyList = false;
feedsModel.append({
title: ttrss.html_entity_decode(feeds[feed].title, 'ENT_QUOTES'),
subtitle: "Unread: " + unreadcount,
unreadcount: unreadcount,
feedId: feeds[feed].id,
});
}
}
//If we're showing all feeds, add the ones with no unread items
if(showAll) {
for(var feed in feeds) {
unreadcount = feeds[feed].unread;
if(unreadcount === 0) {
feedsModel.append({
title: ttrss.html_entity_decode(feeds[feed].title,'ENT_QUOTES'),
subtitle: "Unread: " + unreadcount,
unreadcount: unreadcount,
feedId: feeds[feed].id,
});
}
}
}
if(emptyList) {
if(showAll ||(feeds.length === 0) ) {
feedsModel.append({
title: qsTr("No feeds in category"),
subtitle: "",
feedId: null,
unreadCount: 0,
});
} else {
feedsModel.append({
title: qsTr("Category has no unread items"),
subtitle: "",
feedId: null,
unreadCount: 0,
});
}
}
}
}
onCategoryIdChanged: {
var ttrss = rootWindow.getTTRSS();
numStatusUpdates = ttrss.getNumStatusUpdates();
ttrss.updateFeeds(categoryId, showFeeds);
}
Component.onCompleted: {
var ttrss = rootWindow.getTTRSS();
numStatusUpdates = ttrss.getNumStatusUpdates();
ttrss.updateFeeds(categoryId, showFeeds);
}
onStatusChanged: {
var ttrss = rootWindow.getTTRSS();
if(status === PageStatus.Deactivating)
numStatusUpdates = ttrss.getNumStatusUpdates();
else if (status === PageStatus.Activating) {
if(ttrss.getNumStatusUpdates() > numStatusUpdates) {
numStatusUpdates = ttrss.getNumStatusUpdates();
ttrss.updateFeeds(categoryId, showFeeds);
}
}
}
function showFeed(feedId) {
if(feedId !== null) {
console.log("Loading items for "+feedId+"\n");
var component = Qt.createComponent("ItemList.qml");
if (component.status === Component.Ready)
pageStack.push(component, { feedId: feedId });
else
console.log("Error loading component:", component.errorString());
}
}
ToolBarLayout {
id: feedsTools
ToolIcon { iconId: "toolbar-back"; onClicked: { feedsMenu.close(); pageStack.pop(); } }
ToolIcon {
iconId: "toolbar-refresh";
visible: !loading;
onClicked: { rootWindow.getTTRSS().updateFeeds(categoryId, showFeeds); }
}
BusyIndicator {
visible: loading
running: loading
platformStyle: BusyIndicatorStyle { size: 'medium' }
}
ToolIcon { iconId: "toolbar-view-menu" ; onClicked: (feedsMenu.status === DialogStatus.Closed) ? feedsMenu.open() : feedsMenu.close() }
}
Menu {
id: feedsMenu
visualParent: pageStack
MenuLayout {
MenuItem {
id: toggleUnread
text: qsTr("Toggle Unread Only")
onClicked: {
var ttrss = rootWindow.getTTRSS();
var oldval = ttrss.getShowAll();
var newval = !oldval;
ttrss.setShowAll(newval);
//console.log("Updating categories with showAll: "+newval+"\n");
ttrss.updateFeeds(categoryId, showFeeds);
}
}
MenuItem {
text: qsTr("About")
onClicked: {
rootWindow.openFile("About.qml");
}
}
}
}
}

View file

@ -20,24 +20,26 @@ var state={
'password': null,
'token': null,
'apilevel': 0,
// 'categoryunread': null, //category unread counts (created by updateUnread() )
// 'feedtree': null, //feeds arranged by category including unread count (created by makeFeedTree() )
// 'feedlist': null, //feeds arranged in an associative array (key = id)
'numStatusUpdates': 0, //each time the state updates such that the app might want to redisplay we update this (get via getNumStatusUpdates)
'showall': false, //boolean should all items be shown (or only those with unread stuff?)
'closeIfEmpty': false, //Should pages close if they have no content to display
// 'feedcache': {}, //as feed items are retrieved they are stored here for re-use
'tracelevel': 4, //1 = errors, 2 = key info, 3 = network traffic, 4 info, 5 high detail
'categories': {},
'feeds': {},
'lastcategoryid': null,
};
var requestsPending={
'token' : false,
'categories' : false,
'feeds' : false,
};
var responsesPending={
'token' : false,
'categories' : false,
'feeds' : false,
};
var constants={
@ -157,7 +159,7 @@ function updateCategories(callback) {
var params = {
'op': 'getCategories',
'sid': state['token'],
'unread_only': false
'unread_only': state['showAll']
}
var http = new XMLHttpRequest();
@ -184,16 +186,8 @@ function process_updateCategories(callback, httpreq) {
state['categories'] = {};
for(var i = 0; i < responseObject.content.length; i++) {
if (responseObject.content[i].order_id) {
var feedid = responseObject.content[i].order_id;
trace(4, "Setting feedlist key:"+feedid);
state['categories'][feedid] = responseObject.content[i];
}
else {
// special categories
var feedid = responseObject.content[i].id;
state['categories'][feedid] = responseObject.content[i];
}
var feedid = responseObject.content[i].id;
state['categories'][feedid] = responseObject.content[i];
}
// TODO sort
}
@ -219,6 +213,78 @@ function process_updateCategories(callback, httpreq) {
callback(0);
}
function updateFeeds(catId, callback) {
if(responsesPending['feeds'])
return;
// needs to be logged in
if(!state['token']) {
requestsPending['feeds'] = true;
state['lastcategoryid'] = catId;
processPendingRequests(callback);
return;
}
responsesPending['feeds'] = true;
var params = {
'op': 'getFeeds',
'sid': state['token'],
'cat_id': catId,
'unread_only': state['showAll']
}
var http = new XMLHttpRequest();
http.open("POST", state['url'], true);
http.setRequestHeader('Content-type','application/json; charset=utf-8');
http.onreadystatechange = function() {
if (http.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
trace(3, "Response Headers -->");
trace(3, http.getAllResponseHeaders());
}
else if (http.readyState === XMLHttpRequest.DONE)
process_updateFeeds(catId, callback, http);
}
http.send(JSON.stringify(params));
}
function process_updateFeeds(catId, callback, httpreq) {
trace(3, "readystate: "+httpreq.readyState+" status: "+httpreq.status);
trace(3, "response: "+httpreq.responseText);
if(httpreq.status === 200) {
var responseObject=JSON.parse(httpreq.responseText);
trace(1,dump(responseObject))
if (responseObject.status === 0) {
state['feeds'][catId] = [];
for(var i = 0; i < responseObject.content.length; i++) {
var feedid = responseObject.content[i].id;
state['feeds'][catId][feedid] = responseObject.content[i];
}
}
else {
if(responseObject.content.error)
errorText = "Update Feeds failed: "+responseObject.content.error;
else
errorText = "Update Feeds failed (received http code: "+http.status+")";
}
}
else {
trace(1, "Update Feeds Error: received http code: "+httpreq.status+" full text: "+httpreq.responseText);
if(callback)
callback(40, "Update Feeds Error: received http code: "+httpreq.status+" full text: "+httpreq.responseText);
}
responsesPending['feeds'] = false;
if(state['feeds'][catId])
if(!processPendingRequests(callback))
//This action is complete (as there's no other requests to do, fire callback saying all ok
if(callback)
callback(0);
}
function processPendingRequests(callback) {
trace(4, 'In pPR');
var foundWork = false;
@ -242,6 +308,17 @@ function processPendingRequests(callback) {
else
updateCategories(callback);
}
else if (requestsPending['feeds']) {
trace(4, 'feeds request pending');
foundWork = true;
if(responsesPending['feeds'])
return foundWork;
if(!state['token'])
//Get the auth token
login(callback);
else
updateFeeds(state['lastcategoryid'], callback);
}
return foundWork;
}
@ -253,7 +330,7 @@ function getShowAll() {
//Sets whether only unread items should be shown
function setShowAll(showAll) {
state['showall'] = showAll;
state['showall'] = !!showAll;
state['numStatusUpdates']++;
}
@ -272,3 +349,7 @@ function getNumStatusUpdates() {
function getCategories() {
return state['categories'];
}
function getFeeds(catId) {
return state['feeds'][catId];
}