Compare commits

...

65 commits

Author SHA1 Message Date
Morgan McMillian 31424205e9 prep for release of 0.9.5 2020-02-16 07:33:22 -08:00
Morgan McMillian 8c1d527068 fix html parsing of user profile description, issue #56 2020-02-05 22:37:32 -08:00
Morgan McMillian bd441eac0e Refresh post when repost (or remove repost) is performed, issue #57 2020-02-05 22:01:49 -08:00
Morgan McMillian 14e536947b Scope & rewrite in links to what is actually contained in the href 2020-02-05 22:00:52 -08:00
Morgan McMillian c1bbb23daf rewrite & as & so that links will render properly, issue #54 2020-02-02 07:26:42 -08:00
Morgan McMillian c322df38c0 prep for 0.9.4 release 2020-02-01 06:54:46 -08:00
Morgan McMillian 5b0a94eca4 fixed post rendering to properly display surrogate pair characters 2020-02-01 06:53:58 -08:00
Morgan McMillian e883b8dcba update longpost url, issue #53 2020-01-28 23:14:34 -08:00
Morgan McMillian 8896b2c076 prepping for release 2019-10-13 19:08:59 -07:00
Morgan McMillian dd1a6f1672 well that was silly of me 2019-10-13 17:29:13 -07:00
Morgan McMillian 3e9499670d fix reply post counting and enable send action when there is something to post 2019-10-13 17:23:47 -07:00
Morgan McMillian 82d5a1bc0d update about page and version bump 2019-10-13 16:35:09 -07:00
Morgan McMillian fddd407504 handle international utf8 text when copying to the clipboard resolves issue #25 2019-10-13 16:32:36 -07:00
Morgan McMillian c024f45348 do not count markdown links against total post text length.
resolves issue #52
2019-10-13 14:39:42 -07:00
Morgan McMillian ecfc81e79c update link in changelog 2019-08-10 07:25:32 -07:00
Morgan McMillian ae350e9bdd removed old incomplete changelog file 2019-08-10 07:20:14 -07:00
Morgan McMillian 9e87eafdd7 new changelog and version bump 2019-08-10 07:19:31 -07:00
Morgan McMillian 8d0a707765 filter out the current username from all reply types issue #51 2019-08-03 17:33:50 -07:00
Morgan McMillian 3b2b578533 Update call to only retrieve the raw type that's needed. Issue #50 2019-08-03 16:53:22 -07:00
Morgan McMillian aba4a2bdea Address redirect problem with new login issue #49 2019-08-03 16:39:00 -07:00
Morgan McMillian 35289499a8 probably not needed for version control 2019-08-03 16:37:55 -07:00
Morgan McMillian ebe17fadd7 includepath added by ide 2019-08-03 16:36:08 -07:00
Morgan McMillian 8461564c0c Convert normal post to long post when length exceeds maximum to resolve #39 2018-04-14 16:34:45 -07:00
Morgan McMillian 0dc89f87fb add settings toggle for hiding long posts in the timeline 2018-04-14 13:19:51 -07:00
Morgan McMillian f12a602e4d add long post data to postitem in stream and thread views. 2018-04-14 13:01:58 -07:00
Morgan McMillian 97ce9579ec pull out a single photo url and thumbnail if it exists 2018-04-08 16:14:31 -07:00
Morgan McMillian 6473657bf8 clear the timeline on logout should resolve #44 2018-04-07 18:49:38 -07:00
Morgan McMillian f8fee791bc replace reposted items with the original post object and reference who did the repost resolves #40 2018-04-07 18:31:32 -07:00
Morgan McMillian bdc5e9ebf5 re-enable profile via tap on username or avatar resolves #22 2018-02-27 22:19:32 -08:00
Morgan McMillian 8ea6fcaf09 fetch updated post after bookmark resolves #24 2018-02-27 21:14:40 -08:00
Morgan McMillian e386981bec Switch to new parser for user profile text fixes #43 2018-02-27 19:37:01 -08:00
Morgan McMillian 91ab0585f6 Fix links that have already been parsed and oh deal with those brackets #38 2018-02-25 18:57:49 -08:00
Morgan McMillian 57f6d128c5 parse text around links because blackberry #38 2018-02-25 18:23:22 -08:00
Morgan McMillian 17590d3c5a Use thumbnail url if present when rendering the post item fixes #32 2018-02-25 07:26:10 -08:00
Morgan McMillian ec769a3771 version bump 2018-01-15 09:34:11 -08:00
Morgan McMillian 3a2dee0979 replace html parsing with text rendering and link parsing function
issue #37
2018-01-15 08:23:46 -08:00
Morgan McMillian fde3a7cdec Split the avatar images into a seperate cache 2017-11-18 16:58:57 -08:00
Morgan McMillian f298fe7f61 version bump to 0.9.0 2017-11-17 17:10:48 -08:00
Morgan McMillian c00e68164c set minimum avatar size resolves #33 2017-11-17 14:46:07 -08:00
Morgan McMillian a44de91b3a attempt to add raw paramater on bookmark and repost calls for issue #24 2017-11-17 14:40:12 -08:00
Morgan McMillian cc9520525e sync translation file 2017-11-17 14:16:59 -08:00
Morgan McMillian 75e98014f4 increased default cache size 2017-11-17 14:14:24 -08:00
Morgan McMillian dd0190ad8b adjusted up image button size 2017-11-17 14:10:41 -08:00
Morgan McMillian 12aed636ea Test scaling of the image button for giggles 2017-11-17 13:49:32 -08:00
Morgan McMillian f1ce4d39db add new icon 2017-11-17 13:12:11 -08:00
Morgan McMillian ce0a6d8459 Added toggle to hide avatar images 2017-11-17 09:46:10 -08:00
Morgan McMillian d5c4d01412 Bump initial count, resolves #19 2017-11-16 22:31:42 -08:00
Morgan McMillian 9e18b77294 Added missing active tab settings
Resolves #26
2017-11-16 21:34:55 -08:00
Morgan McMillian 49d58341c3 prevent network requests when images are being hidden 2017-11-16 17:03:29 -08:00
Morgan McMillian cf0919d06a Add setting to hide photos in the timeline 2017-11-16 14:55:45 -08:00
Morgan McMillian b5bfd3e09e Replace WebImageView with NetImageManager and NetImageTracker classes
Resolves #31 and should take care of #30 #28 #23 #12
2017-11-16 13:28:35 -08:00
Morgan McMillian 58d094a6d5 sync translation file 2017-11-16 13:18:24 -08:00
Morgan McMillian 1307bb2f2a Adapt useritem to use NetImageManager 2017-11-16 13:11:26 -08:00
Morgan McMillian b7dc358c33 Adapt profile page to use NetImageManager 2017-11-16 13:06:59 -08:00
Morgan McMillian 6f41f3b948 Open image in preview card 2017-11-16 12:35:30 -08:00
Morgan McMillian 3ec3521b38 fix images on thread page 2017-11-16 11:57:07 -08:00
Morgan McMillian baf15b623a fix scroll state handler 2017-11-16 11:23:54 -08:00
Morgan McMillian 18f100b55e Try loading photos using the NetImageTracker 2017-11-16 11:02:41 -08:00
Morgan McMillian 0ed88761f9 Switch avatar loading to NetImageTracker 2017-11-16 08:42:30 -08:00
Morgan McMillian 8378e71435 Include previous added image classes into build 2017-11-16 08:37:55 -08:00
Morgan McMillian f954e15fb6 Add NetImageManager and NetImageTracker classes from BlackBerry 2017-11-16 08:37:13 -08:00
Morgan McMillian e525492bc6 Added mime type to the showImage method. 2017-11-12 09:26:43 -08:00
Morgan McMillian fdb0b22ee4 added show image method to invoke the picture preview card, may not be yet functional 2017-11-12 07:41:09 -08:00
Morgan McMillian 282f3e1b0b replace full image url with thumbnail 2017-11-05 09:50:22 -08:00
Morgan McMillian d2fd41d81e Fix simulator build 2017-11-05 06:46:43 -08:00
30 changed files with 1408 additions and 620 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ translations/*.qm
.settings/
src/globals.h
buildnum
assets/.assets.index

128
CHANGELOG.md Normal file
View file

@ -0,0 +1,128 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [0.9.5] - 2020-02-16
### Fixed
- Refresh post following a repost (issue #57)
- Fix HTML parsing on user descriptions (issue #56)
- Fix parsing of links containing & (issue #54)
## [0.9.4] - 2020-02-01
### Fixed
- Updated longpost url (issue #53)
- Rendering posts with links containing surrogate pair characters
## [0.9.3] - 2019-10-13
### Fixed
- Don't count markdown links against total post text count (issue #52)
- Handle utf8 text copying to the clipboard (issue #25)
## [0.9.2] - 2019-08-10
### Fixed
- Authentication error with new install on 10.3.3 devices (issue #49)
- Own username populating as mention in replies (issue #51)
### Changed
- Optimized calls for raw to only posts (issue #50)
- New changelog format (issue #41)
## [0.9.1] - 2018-01-15
### Fixed
- replace html parsing with text rendering and link parsing function (issue #37)
- split the avatar images into a seperate cache
## [0.9.0] - 2017-11-18
### Added
- settings to prevent image loading
### Changed
- replaced webimageview class with netimagemanager class
- open images in preview card
### Fixed
- set minimum avatar size (issue #33)
- add raw paramater on bookmark and repost calls (issue #24)
- increase initial count (issue #19)
- added missing active tab settings (issue #26)
- image loading on thread view
- simulator builds
## [0.8.0] - 2017-02-18
### Added
- invoke hooks for sharing posts (issue #15)
- added copy text to clipboard (issue #7)
- render oembed photos (issue #12)
### Fixed
- post rendering problems (issue #18)
## [0.7.0] - 2017-02-07
### Fixed
- handle bookmark status update (issue #10)
- tweak navigation focus policy for BlackBerry Classic (issue #9)
- markdown link rendering (issue #4)
- oauth work around for 10.3.3 security update (issue #11)
- fixed project setup removing hard coded system paths
## [0.6.0] - 2016-11-13
### Added
- Added posts, following, and followers view to profile page
- Added support for changing visual theme (dark mode)
### Fixed
- Fixed reply-all from thread view page
## [0.5.0] - 2016-11-05
### Added
- about page
- setting for CC on reply all
- bookmark tab
- delete post
- keyboard shortcuts
## [0.4.0] - 2016-10-22
### Added
- mentions tab
- link to new root CA on login page
- user interactions (follow, block, mute)
### Changed
- app cover reflects unread for active tab
### Fixed
- profile display on small screens
## [0.3.0] - 2016-10-01
### Added
- pull to load new posts
- add/remove bookmarks
- repost and quote posts
### Fixed
- login and logout
## [0.2.0] - 2016-09-24
### Added
- thread view
- post status icons
## [0.1.0] - 2016-09-22
### Added
- Initial release
[0.9.5]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/0.9.5
[0.9.4]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/0.9.4
[0.9.3]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/0.9.3
[0.9.2]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/0.9.2
[0.9.1]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_9_1
[0.9.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_9_0
[0.8.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_8_0
[0.7.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_7_0
[0.6.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_6_0
[0.5.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_5_0
[0.4.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_4_0
[0.3.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_3_0
[0.2.0]: https://gitlab.dreamfall.space/thrrgilag/goober-bb10/-/tags/Goober_0_2_0

View file

@ -1,4 +0,0 @@
* Sat Nov 12 2016 Morgan McMillian <gilag@monkeystew.com> 0.6.0
- Fixed reply-all from thread view page
- Added posts, following, and followers view to profile page
- Added support for changing visual theme (dark mode)

View file

@ -5,3 +5,11 @@ LIBS += -lbbdata -lbbsystem -lbb
QT += network
include(config.pri)
simulator {
INCLUDEPATH += $$quote($$BASEDIR/../bb-cascades-oauth/oauth)
DEPENDPATH += $$quote($$BASEDIR/../bb-cascades-oauth/oauth)
LIBS += -lkqoauth
LIBS += $$quote(-L$$BASEDIR/../bb-cascades-oauth/oauth/o-g)
PRE_TARGETDEPS += $$quote($$BASEDIR/../bb-cascades-oauth/oauth/o-g)
}

View file

@ -1,50 +1,51 @@
1
48
AppCover.qml
LoginSheet.qml
main.qml
49
moment.js
NewPostSheet.qml
RefreshItem.qml
SettingsPage.qml
ThreadPage.qml
PostItem.qml
WebViewSheet.qml
UserItem.qml
UserPage.qml
icons/at.png
icons/bell-slash.png
icons/bell.png
icons/bookmark.png
icons/messages.png
icons/comments.png
icons/default_cover.png
icons/globe.png
icons/home.png
icons/ic_add.png
icons/ic_add_bookmarks.png
icons/ic_cancel.png
icons/ic_compose.png
icons/ic_contact.png
icons/ic_del_bookmarks.png
icons/ic_edit.png
icons/ic_favorite.png
icons/bell.png
icons/minus-circle.png
icons/ic_info.png
icons/ic_reload.png
icons/laughing_man.png
icons/default_cover.png
icons/star.png
icons/ic_reply.png
icons/ic_reply_all.png
icons/ic_to_bottom.png
icons/ic_to_top.png
icons/laughing_man.png
icons/messages.png
icons/minus-circle.png
icons/quote-left.png
icons/refresh.png
icons/retweet.png
icons/sign-out.png
icons/ic_doctype_picture.png
icons/bookmark.png
icons/star-o.png
icons/star.png
icons/ic_cancel.png
icons/at.png
icons/ic_reload.png
icons/home.png
icons/ic_copy.png
icons/retweet.png
icons/bell-slash.png
icons/ic_favorite.png
icons/ic_contact.png
icons/refresh.png
icons/quote-left.png
icons/globe.png
icons/ic_del_bookmarks.png
icons/ic_add_bookmarks.png
icons/ic_to_top.png
icons/ic_edit.png
icons/ic_compose.png
icons/ic_to_bottom.png
StreamTab.qml
WebViewSheet.qml
PostItem.qml
ProfilePage.qml
ThreadPage.qml
parser.js
AboutPage.qml
LoginSheet.qml
AppCover.qml
SettingsPage.qml
main.qml
NewPostSheet.qml
UserPage.qml

View file

@ -37,7 +37,7 @@ Page {
horizontalAlignment: HorizontalAlignment.Center
topPadding: ui.sdu(2)
Label {
text: "Copyright &copy; 2016-2017 Morgan McMillian"
text: "Copyright &copy; 2016-2020 Morgan McMillian"
textStyle.fontSize: FontSize.XSmall
textFormat: TextFormat.Html
}
@ -46,7 +46,7 @@ Page {
horizontalAlignment: HorizontalAlignment.Center
topPadding: ui.sdu(3)
Label {
text: "<a href=\"https://monkeystew.org\">https://monkeystew.org</a>"
text: "<a href=\"https://thrrgilag.net\">https://thrrgilag.net</a>"
textFormat: TextFormat.Html
}
}

View file

@ -25,8 +25,10 @@ Sheet {
property alias input: postText
property alias text: postText.text
property int count: 256
property string counttext
signal sendPost(string text)
signal postChanging(string text)
Page {
titleBar: TitleBar {
@ -56,20 +58,19 @@ Sheet {
id: postText
preferredHeight: 350.0
onTextChanging: {
postChanging(text)
if (postText.text.length > 0) {
sendAction.enabled = true;
sendAction.enabled = true
} else {
sendAction.enabled = false;
sendAction.enabled = false
}
count = 256 - postText.text.length
if (count < 0) sendAction.enabled = false;
}
}
}
Container {
horizontalAlignment: HorizontalAlignment.Right
Label {
text: count.toString()
text: counttext
}
}
}

View file

@ -18,7 +18,7 @@
*/
import bb.cascades 1.4
import org.labsquare 1.0
import com.netimage 1.0
import "moment.js" as Moment
import "parser.js" as Parser
@ -36,24 +36,36 @@ Container {
opacity: (ListItemData.is_deleted) ? 0.5 : 1.0
visible: (ListItemData.is_deleted) ? false : true
Container {
Container { // post header
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
Container {
layout: StackLayout {
orientation: LayoutOrientation.LeftToRight
}
Container {
Container { // user avatar
background: (theme === VisualStyle.Bright) ? Color.create("#e9e9e9") : Color.create("#282828")
WebImageView {
ImageButton {
id: avatar
url: ListItemData.user.content.avatar_image.link
minWidth: ui.du(12)
minHeight: ui.du(12)
maxWidth: ui.du(12)
maxHeight: ui.du(12)
//imageSource: "asset:///icons/laughing_man.png"
defaultImage: tracker.image
onClicked: {
postitem.ListItem.view.viewProfile(ListItemData.user)
}
attachedObjects: [
NetImageTracker {
id: tracker
source: (postitem.ListItem.view.hideAvatar()) ? "" : ListItemData.user.content.avatar_image.link
manager: postitem.ListItem.view.userImageManager
}
]
}
}
Container {
Container { // user name
leftMargin: ui.sdu(3.0)
Container {
Label {
@ -65,19 +77,19 @@ Container {
Container {
Label {
id: username
text: ListItemData.user.username
// text: "<a href=\"#profile\">" + ListItemData.user.username + "</a>"
// activeTextHandler: ActiveTextHandler {
// onTriggered: {
// postitem.ListItem.view.viewProfile(ListItemData.user)
// }
// }
// textFormat: TextFormat.Html
// text: ListItemData.user.username
text: "<a href=\"#profile\">" + ListItemData.user.username + "</a>"
activeTextHandler: ActiveTextHandler {
onTriggered: {
postitem.ListItem.view.viewProfile(ListItemData.user)
}
}
textFormat: TextFormat.Html
}
}
}
}
Container {
Container { // post counts
horizontalAlignment: HorizontalAlignment.Right
Container {
layout: StackLayout {
@ -133,35 +145,107 @@ Container {
}
}
}
Container {
Container { // post text
topMargin: ui.sdu(2.0)
bottomMargin: ui.sdu(2.0)
Label {
text: Parser.parsePostData(ListItemData.content.html)
text: Parser.fixPostHtml(ListItemData.content.html)
multiline: true
textFormat: TextFormat.Html
navigation.focusPolicy: NavigationFocusPolicy.NotFocusable
}
}
Container {
Container { // long post body
topMargin: ui.sdu(2.0)
bottomMargin: ui.sdu(2.0)
visible: {
if (postitem.ListItem.view.hideLongPosts()) {
return false
} else {
if (typeof ListItemData.longpost_body !== "undefined") {
return true
}
}
return false
}
Divider {
}
Label {
text: (typeof ListItemData.longpost_body !== "undefined") ? ListItemData.longpost_body : ""
multiline: true
textFormat: TextFormat.Html
navigation.focusPolicy: NavigationFocusPolicy.NotFocusable
}
}
Container { // post embeds
// TODO: add tab to open full image in a page
horizontalAlignment: HorizontalAlignment.Center
bottomMargin: ui.sdu(2.0)
WebImageView {
url: {
var oembed = ""
ImageButton {
id: pimage
maxWidth: ui.du(20)
maxHeight: ui.du(20)
visible: {
var isphoto = false
if (typeof ListItemData.raw !== "undefined") {
ListItemData.raw.forEach(function (item) {
if (item["type"] == "io.pnut.core.oembed") {
oembed = item["value"]["url"]
if (item["value"]["type"] == "photo") {
isphoto = true
}
}
})
}
return oembed
return isphoto
}
scalingMethod: ScalingMethod.AspectFit
defaultImage: (postitem.ListItem.view.hidePhoto()) ? "" : thumbtracker.image
onClicked: {
postitem.ListItem.view.showImage(ptracker.imageSource)
}
attachedObjects: [
NetImageTracker {
id: ptracker
manager: postitem.ListItem.view.listImageManager
source: {
var photo_url = ""
if (typeof ListItemData.raw !== "undefined" && !postitem.ListItem.view.hidePhoto()) {
ListItemData.raw.forEach(function (item) {
if (item["type"] == "io.pnut.core.oembed") {
if (item["value"]["type"] == "photo") {
photo_url = item["value"]["url"]
}
}
})
}
return photo_url
}
},
NetImageTracker {
id: thumbtracker
manager: postitem.ListItem.view.listImageManager
source: {
var photo_url = ""
if (typeof ListItemData.raw !== "undefined" && !postitem.ListItem.view.hidePhoto()) {
ListItemData.raw.forEach(function (item) {
if (item["type"] == "io.pnut.core.oembed") {
if (item["value"]["type"] == "photo") {
if (item["value"]["thumbnail_url"]) {
photo_url = item["value"]["thumbnail_url"]
} else {
photo_url = item["value"]["url"]
}
// photo_url = item["value"]["url"]
}
}
})
}
return photo_url
}
}
]
}
}
Container {
Container { // post footer
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
Container {
@ -174,8 +258,15 @@ Container {
Container {
horizontalAlignment: HorizontalAlignment.Right
Label { // "Broadsword Hack"
text: ListItemData.source.name
text: {
if (ListItemData.reposted_by) {
return "reposted by @" + ListItemData.reposted_by
} else {
return ListItemData.source.name
}
}
textStyle.fontSize: FontSize.XSmall
textStyle.fontStyle: FontStyle.Italic
}
}
}
@ -188,7 +279,7 @@ Container {
ActionItem {
title: qsTr("Reply")
onTriggered: {
replySheet.text = "@" + ListItemData.user.username + " ";
replySheet.text = atMention(ListItemData.user.username);
replySheet.open();
replySheet.input.requestFocus();
}
@ -197,7 +288,7 @@ Container {
ActionItem {
title: qsTr("Reply All")
onTriggered: {
replySheet.text = "@" + ListItemData.user.username + " " + parseMentions(ListItemData.content.entities.mentions);
replySheet.text = atMention(ListItemData.user.username) + parseMentions(ListItemData.content.entities.mentions);
console.log(JSON.stringify(ListItemData.content.entities.mentions))
replySheet.open();
replySheet.input.requestFocus();
@ -286,15 +377,21 @@ Container {
onSendPost: {
postitem.ListItem.view.sendReply(text, ListItemData.id);
}
onPostChanging: {
replySheet.count = 256 - postitem.ListItem.view.postLength(text)
if (replySheet.count < 0) {
replySheet.counttext = "longpost"
} else {
replySheet.counttext = replySheet.count.toString()
}
}
}
]
function parseMentions(mentions) {
var mtext = ""
for(var i = 0; i < mentions.length; i++) {
var mu = mentions[i].text
if (mu !== postitem.ListItem.view.getUserName()) {
mtext += "@" + mu + " "
}
mtext += atMention(mu)
}
if (mtext.length > 0) {
if (postitem.ListItem.view.ccOnReply()) {
@ -306,4 +403,11 @@ Container {
return ""
}
}
function atMention(username) {
var atname = ""
if (username !== postitem.ListItem.view.getUserName()) {
atname = "@" + username + " "
}
return atname
}
}

View file

@ -18,11 +18,12 @@
*/
import bb.cascades 1.4
import org.labsquare 1.0
import com.netimage 1.0
import "parser.js" as Parser
Page {
property variant imgManager: imgManager
property variant user
property variant theme: Application.themeSupport.theme.colorTheme.style
property string lorem: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non enim tellus. Donec vestibulum enim urna, eget faucibus diam commodo a. Donec eget hendrerit metus. Pellentesque vehicula nisi nec vehicula ullamcorper. Aliquam a elit eget mi fringilla porta fermentum eget eros. Phasellus vestibulum nulla sed elit congue adipiscing. Cras imperdiet urna ac ipsum volutpat lobortis. Maecenas vehicula tortor at viverra convallis. Curabitur nibh massa, tristique id felis ut, venenatis faucibus dui. Donec fringilla, mi nec tincidunt dignissim, neque nunc semper mi, quis rutrum diam turpis sit amet erat. Cras a sodales nisi. Nunc sit amet diam sed lectus molestie cursus convallis et erat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis vitae varius leo. Mauris eu leo a nunc bibendum rutrum euismod et ipsum. "
@ -45,13 +46,21 @@ Page {
Container {
Container {
opacity: 0.6
WebImageView {
ImageView {
id: cover
preferredWidth: 1440
maxHeight: ui.du(24)
scalingMethod: ScalingMethod.AspectFill
//imageSource: "asset:///icons/default_cover.png"
url: user.content.cover_image.link
//url: user.content.cover_image.link
image: cimgtracker.image
attachedObjects: [
NetImageTracker {
id: cimgtracker
manager: imgManager
source: user.content.cover_image.link
}
]
}
}
}
@ -64,11 +73,19 @@ Page {
verticalAlignment: VerticalAlignment.Center
Container {
background: (theme === VisualStyle.Bright) ? Color.create("#e9e9e9") : Color.create("#282828")
WebImageView {
ImageView {
//imageSource: "asset:///icons/laughing_man.png"
url: user.content.avatar_image.link
//url: user.content.avatar_image.link
maxHeight: ui.du(14)
maxWidth: ui.du(14)
image: aimgtracker.image
attachedObjects: [
NetImageTracker {
id: aimgtracker
manager: imgManager
source: user.content.avatar_image.link
}
]
}
}
Container {
@ -110,7 +127,7 @@ Page {
preferredWidth: ui.du(45)
Label {
//text: lorem
text: Parser.parsePostData(user.content.html)
text: Parser.parsePostText(user.content)
multiline: true
textFormat: TextFormat.Html
}
@ -278,6 +295,11 @@ Page {
ComponentDefinition {
id: userListPage
source: "UserPage.qml"
},
NetImageManager {
id: imgManager
cacheId: "feedImageManager"
cacheSize: 250
}
]
}

View file

@ -34,7 +34,7 @@ Page {
Container {
topPadding: ui.sdu(2)
}
Container {
Container { // Unified home
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
@ -71,7 +71,7 @@ Page {
}
}
Divider {}
Container {
Container { // CC setting
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
@ -110,7 +110,124 @@ Page {
}
}
Divider {}
Container {
Container { // Hide photos setting
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
Container {
verticalAlignment: VerticalAlignment.Center
leftPadding: ui.sdu(3)
Label {
text: qsTr("Hide photos")
textStyle.fontSize: FontSize.Medium
textStyle.fontWeight: FontWeight.Bold
}
Label {
text: qsTr("Hide embeded photos in timeline")
textStyle.fontSize: FontSize.Small
multiline: true
}
}
Container {
id: imgtoggle
rightPadding: ui.sdu(3)
horizontalAlignment: HorizontalAlignment.Right
verticalAlignment: VerticalAlignment.Center
ToggleButton {
checked: _app.setting("hidephoto")
onCheckedChanged: {
if (checked) {
_app.setSetting("hidephoto", true)
reload()
} else {
_app.setSetting("hidephoto", false)
reload()
}
}
accessibility.name: "inlineimages"
}
}
}
Divider {}
Container { // Hide avatar setting
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
Container {
verticalAlignment: VerticalAlignment.Center
leftPadding: ui.sdu(3)
Label {
text: qsTr("Hide avatars")
textStyle.fontSize: FontSize.Medium
textStyle.fontWeight: FontWeight.Bold
}
Label {
text: qsTr("Hide avatar images in timeline")
textStyle.fontSize: FontSize.Small
multiline: true
}
}
Container {
id: avatartoggle
rightPadding: ui.sdu(3)
horizontalAlignment: HorizontalAlignment.Right
verticalAlignment: VerticalAlignment.Center
ToggleButton {
checked: _app.setting("hideavatar")
onCheckedChanged: {
if (checked) {
_app.setSetting("hideavatar", true)
reload()
} else {
_app.setSetting("hideavatar", false)
reload()
}
}
accessibility.name: "avatars"
}
}
}
Divider {}
Container { // Hide longposts setting
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
Container {
verticalAlignment: VerticalAlignment.Center
leftPadding: ui.sdu(3)
Label {
text: qsTr("Hide long posts")
textStyle.fontSize: FontSize.Medium
textStyle.fontWeight: FontWeight.Bold
}
Label {
text: qsTr("Hide long posts in timeline")
textStyle.fontSize: FontSize.Small
multiline: true
}
}
Container {
id: longposttoggle
rightPadding: ui.sdu(3)
horizontalAlignment: HorizontalAlignment.Right
verticalAlignment: VerticalAlignment.Center
ToggleButton {
checked: _app.setting("hidelongp")
onCheckedChanged: {
if (checked) {
_app.setSetting("hidelongp", true)
reload()
} else {
_app.setSetting("hidelongp", false)
reload()
}
}
accessibility.name: "longposts"
}
}
}
Divider {}
Container { // Theme Setting
leftPadding: ui.sdu(3)
rightPadding: ui.sdu(3)
DropDown {

View file

@ -20,6 +20,7 @@
import bb.cascades 1.4
import com.monkeystew.pnut 1.0
import com.monkeystew.qtimer 1.0
import com.netimage 1.0
NavigationPane {
id: nav
@ -42,6 +43,9 @@ NavigationPane {
Container {
ListView {
id: streamView
property variant listImageManager: feedImageManager
property variant userImageManager: userImageManager
property bool fetching: false
dataModel: ArrayDataModel {
id: postModel
}
@ -86,7 +90,7 @@ NavigationPane {
nav.push(page);
}
function sendReply(text, pid) {
pnut.sendReply(text, pid);
pnut.sendPost(text, pid);
}
function getUserName() {
return _app.setting("username");
@ -109,6 +113,9 @@ NavigationPane {
function getPost(pid) {
pnut.getPost(pid)
}
function postLength(text) {
return pnut.postLength(text)
}
function ccOnReply() {
if (_app.setting("cc") === "true") {
return true
@ -116,14 +123,40 @@ NavigationPane {
return false
}
}
function hidePhoto() {
if (_app.setting("hidephoto") === "true") {
return true
} else {
return false
}
}
function hideAvatar() {
if (_app.setting("hideavatar") === "true") {
return true
} else {
return false
}
}
function hideLongPosts() {
if (_app.setting("hidelongp") === "true") {
return true
} else {
return false
}
}
function copyText(text) {
_app.copyText(text)
}
function showImage(filename) {
_app.showImage(filename)
}
attachedObjects: [
ListScrollStateHandler {
onAtEndChanged: {
streamView.fetching = false
if (atEnd) {
if (!at_end && pnut.beforeId != 0) {
if (!streamView.fetching && pnut.beforeId != 0) {
streamView.fetching = true
pnut.getStream(endpoint, Pnut.STREAM_OLDER);
}
}
@ -134,6 +167,15 @@ NavigationPane {
nav.parent.unreadContentCount = unread
}
}
},
NetImageManager {
id: feedImageManager
cacheId: "feedImageManager"
cacheSize: 250
},
NetImageManager {
id: userImageManager
cacheId: "userImageManager"
}
]
}
@ -212,7 +254,15 @@ NavigationPane {
id: newPostSheet
onSendPost: {
console.log("send: " + text)
pnut.sendPost(text)
pnut.sendPost(text, 0)
}
onPostChanging: {
newPostSheet.count = 256 - pnut.postLength(text)
if (newPostSheet.count < 0) {
newPostSheet.counttext = "longpost"
} else {
newPostSheet.counttext = newPostSheet.count.toString()
}
}
},
WebViewSheet {
@ -356,6 +406,7 @@ NavigationPane {
pnut.authorize()
}
function logout() {
postModel.clear()
pnut.logout()
}
function reload() {

View file

@ -18,6 +18,7 @@
*/
import bb.cascades 1.4
import com.netimage 1.0
Page {
@ -31,6 +32,8 @@ Page {
Container {
ListView {
id: threadView
property variant listImageManager: feedImageManager
property variant userImageManager: userImageManager
dataModel: ArrayDataModel {
id: threadModel
}
@ -41,7 +44,7 @@ Page {
}
]
function sendReply(text, pid) {
pnut.sendReply(text, pid);
pnut.sendPost(text, pid);
}
function getUserName() {
return _app.setting("username")
@ -64,6 +67,41 @@ Page {
return false
}
}
function hidePhoto() {
if (_app.setting("hidephoto") === "true") {
return true
} else {
return false
}
}
function hideAvatar() {
if (_app.setting("hideavatar") === "true") {
return true
} else {
return false
}
}
function hideLongPosts() {
if (_app.setting("hidelongp") === "true") {
return true
} else {
return false
}
}
function showImage(filename) {
_app.showImage(filename)
}
attachedObjects: [
NetImageManager {
id: feedImageManager
cacheId: "feedImageManager"
cacheSize: 250
},
NetImageManager {
id: userImageManager
cacheId: "userImageManager"
}
]
}
}

View file

@ -1,5 +1,6 @@
import bb.cascades 1.4
import org.labsquare 1.0
import com.netimage 1.0
import "parser.js" as Parser
Container {
id: useritem
@ -20,12 +21,25 @@ Container {
Container {
minWidth: ui.du(12)
background: (theme === VisualStyle.Bright) ? Color.create("#e9e9e9") : Color.create("#282828")
WebImageView {
ImageView {
id: avatar
url: ListItemData.content.avatar_image.link
//url: ListItemData.content.avatar_image.link
maxWidth: ui.du(12)
maxHeight: ui.du(12)
//imageSource: "asset:///icons/laughing_man.png"
image: aimgtracker.image
attachedObjects: [
NetImageTracker {
id: aimgtracker
manager: imgManager
source: ListItemData.content.avatar_image.link
},
NetImageManager {
id: imgManager
cacheId: "feedImageManager"
cacheSize: 250
}
]
}
}
Container {
@ -41,7 +55,7 @@ Container {
Container {
Label {
id: description
text: ListItemData.content.html
text: Parser.fixPostHtml(ListItemData.content.html)
multiline: true
textFormat: TextFormat.Html
}

View file

@ -3,6 +3,7 @@ import bb.cascades 1.4
Sheet {
peekEnabled: false
property alias url: webview.url
property bool authredir: true
Page {
ScrollView {
scrollViewProperties.pinchToZoomEnabled: true
@ -12,12 +13,15 @@ Sheet {
settings.zoomToFitEnabled: true
onUrlChanged: {
//console.log("::" + url)
// console.log("::" + url)
// Workaround for 10.3.3
if (url.toString().match(/http\:\/\/.*#access_token\=/i)) {
//console.log("Matched url: " + url.toString());
webview.stop();
webview.url = url.toString();
if (authredir === true) {
if (url.toString().match(/http\:\/\/.*#access_token\=/i)) {
// console.log("Matched url: " + url.toString());
webview.stop();
webview.url = url.toString();
authredir = false;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -58,6 +58,12 @@ TabbedPane {
case "HomeTab":
return homeStream
break;
case "MentionTab":
return mentionStream
break;
case "BookmarksTab":
return bookmarksStream
break;
default:
return globalStream
}

View file

@ -17,348 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function parsePostData(t) {
t = t.replace(/&#033;/g,"\u0021");
t = t.replace(/&#034;/g,"\u0022");
t = t.replace(/&#035;/g,"\u0023");
t = t.replace(/&#036;/g,"\u0024");
t = t.replace(/&#037;/g,"\u0025");
t = t.replace(/&#038;/g,"\u0026");
t = t.replace(/&#039;/g,"\u0027");
t = t.replace(/&#040;/g,"\u0028");
t = t.replace(/&#041;/g,"\u0029");
t = t.replace(/&#042;/g,"\u002A");
t = t.replace(/&#043;/g,"\u002B");
t = t.replace(/&#044;/g,"\u002C");
t = t.replace(/&#045;/g,"\u002D");
t = t.replace(/&#046;/g,"\u002E");
t = t.replace(/&#047;/g,"\u002F");
t = t.replace(/&#048;/g,"\u0030");
t = t.replace(/&#049;/g,"\u0031");
t = t.replace(/&#050;/g,"\u0032");
t = t.replace(/&#051;/g,"\u0033");
t = t.replace(/&#052;/g,"\u0034");
t = t.replace(/&#053;/g,"\u0035");
t = t.replace(/&#054;/g,"\u0036");
t = t.replace(/&#055;/g,"\u0037");
t = t.replace(/&#056;/g,"\u0038");
t = t.replace(/&#057;/g,"\u0039");
t = t.replace(/&#058;/g,"\u003A");
t = t.replace(/&#059;/g,"\u003B");
t = t.replace(/&#060;/g,"\u003C");
t = t.replace(/&#061;/g,"\u003D");
t = t.replace(/&#062;/g,"\u003E");
t = t.replace(/&#063;/g,"\u003F");
t = t.replace(/&#064;/g,"\u0040");
t = t.replace(/&#065;/g,"\u0041");
t = t.replace(/&#066;/g,"\u0042");
t = t.replace(/&#067;/g,"\u0043");
t = t.replace(/&#068;/g,"\u0044");
t = t.replace(/&#069;/g,"\u0045");
t = t.replace(/&#070;/g,"\u0046");
t = t.replace(/&#071;/g,"\u0047");
t = t.replace(/&#072;/g,"\u0048");
t = t.replace(/&#073;/g,"\u0049");
t = t.replace(/&#074;/g,"\u004A");
t = t.replace(/&#075;/g,"\u004B");
t = t.replace(/&#076;/g,"\u004C");
t = t.replace(/&#077;/g,"\u004D");
t = t.replace(/&#078;/g,"\u004E");
t = t.replace(/&#079;/g,"\u004F");
t = t.replace(/&#080;/g,"\u0050");
t = t.replace(/&#081;/g,"\u0051");
t = t.replace(/&#082;/g,"\u0052");
t = t.replace(/&#083;/g,"\u0053");
t = t.replace(/&#084;/g,"\u0054");
t = t.replace(/&#085;/g,"\u0055");
t = t.replace(/&#086;/g,"\u0056");
t = t.replace(/&#087;/g,"\u0057");
t = t.replace(/&#088;/g,"\u0058");
t = t.replace(/&#089;/g,"\u0059");
t = t.replace(/&#090;/g,"\u005A");
t = t.replace(/&#091;/g,"\u005B");
t = t.replace(/&#092;/g,"\u005C");
t = t.replace(/&#093;/g,"\u005D");
t = t.replace(/&#094;/g,"\u005E");
t = t.replace(/&#095;/g,"\u005F");
t = t.replace(/&#096;/g,"\u0060");
t = t.replace(/&#097;/g,"\u0061");
t = t.replace(/&#098;/g,"\u0062");
t = t.replace(/&#099;/g,"\u0063");
t = t.replace(/&#100;/g,"\u0064");
t = t.replace(/&#101;/g,"\u0065");
t = t.replace(/&#102;/g,"\u0066");
t = t.replace(/&#103;/g,"\u0067");
t = t.replace(/&#104;/g,"\u0068");
t = t.replace(/&#105;/g,"\u0069");
t = t.replace(/&#106;/g,"\u006A");
t = t.replace(/&#107;/g,"\u006B");
t = t.replace(/&#108;/g,"\u006C");
t = t.replace(/&#109;/g,"\u006D");
t = t.replace(/&#110;/g,"\u006E");
t = t.replace(/&#111;/g,"\u006F");
t = t.replace(/&#112;/g,"\u0070");
t = t.replace(/&#113;/g,"\u0071");
t = t.replace(/&#114;/g,"\u0072");
t = t.replace(/&#115;/g,"\u0073");
t = t.replace(/&#116;/g,"\u0074");
t = t.replace(/&#117;/g,"\u0075");
t = t.replace(/&#118;/g,"\u0076");
t = t.replace(/&#119;/g,"\u0077");
t = t.replace(/&#120;/g,"\u0078");
t = t.replace(/&#121;/g,"\u0079");
t = t.replace(/&#122;/g,"\u007A");
t = t.replace(/&#123;/g,"\u007B");
t = t.replace(/&#124;/g,"\u007C");
t = t.replace(/&#125;/g,"\u007D");
t = t.replace(/&#126;/g,"\u007E");
t = t.replace(/&#127;/g,"\u007F");
t = t.replace(/&#128;/g,"\u0080");
t = t.replace(/&#130;/g,"\u0082");
t = t.replace(/&#131;/g,"\u0083");
t = t.replace(/&#132;/g,"\u0084");
t = t.replace(/&#133;/g,"\u0085");
t = t.replace(/&#134;/g,"\u0086");
t = t.replace(/&#135;/g,"\u0087");
t = t.replace(/&#136;/g,"\u0088");
t = t.replace(/&#137;/g,"\u0089");
t = t.replace(/&#138;/g,"\u008A");
t = t.replace(/&#139;/g,"\u008B");
t = t.replace(/&#140;/g,"\u008C");
t = t.replace(/&#141;/g,"\u008D");
t = t.replace(/&#142;/g,"\u008E");
t = t.replace(/&#145;/g,"\u0091");
t = t.replace(/&#146;/g,"\u0092");
t = t.replace(/&#147;/g,"\u0093");
t = t.replace(/&#148;/g,"\u0094");
t = t.replace(/&#149;/g,"\u0095");
t = t.replace(/&#150;/g,"\u0096");
t = t.replace(/&#151;/g,"\u0097");
t = t.replace(/&#152;/g,"\u0098");
t = t.replace(/&#153;/g,"\u0099");
t = t.replace(/&#154;/g,"\u009A");
t = t.replace(/&#155;/g,"\u009B");
t = t.replace(/&#156;/g,"\u009C");
t = t.replace(/&#158;/g,"\u009E");
t = t.replace(/&#159;/g,"\u009F");
t = t.replace(/&#161;/g,"\u00A1");
t = t.replace(/&#162;/g,"\u00A2");
t = t.replace(/&#163;/g,"\u00A3");
t = t.replace(/&#164;/g,"\u00A4");
t = t.replace(/&#165;/g,"\u00A5");
t = t.replace(/&#166;/g,"\u00A6");
t = t.replace(/&#167;/g,"\u00A7");
t = t.replace(/&#168;/g,"\u00A8");
t = t.replace(/&#169;/g,"\u00A9");
t = t.replace(/&#170;/g,"\u00AA");
t = t.replace(/&#171;/g,"\u00AB");
t = t.replace(/&#172;/g,"\u00AC");
t = t.replace(/&#174;/g,"\u00AE");
t = t.replace(/&#175;/g,"\u00AF");
t = t.replace(/&#176;/g,"\u00B0");
t = t.replace(/&#177;/g,"\u00B1");
t = t.replace(/&#178;/g,"\u00B2");
t = t.replace(/&#179;/g,"\u00B3");
t = t.replace(/&#180;/g,"\u00B4");
t = t.replace(/&#181;/g,"\u00B5");
t = t.replace(/&#182;/g,"\u00B6");
t = t.replace(/&#183;/g,"\u00B7");
t = t.replace(/&#184;/g,"\u00B8");
t = t.replace(/&#185;/g,"\u00B9");
t = t.replace(/&#186;/g,"\u00BA");
t = t.replace(/&#187;/g,"\u00BB");
t = t.replace(/&#188;/g,"\u00BC");
t = t.replace(/&#189;/g,"\u00BD");
t = t.replace(/&#190;/g,"\u00BE");
t = t.replace(/&#191;/g,"\u00BF");
t = t.replace(/&#192;/g,"\u00C0");
t = t.replace(/&#193;/g,"\u00C1");
t = t.replace(/&#194;/g,"\u00C2");
t = t.replace(/&#195;/g,"\u00C3");
t = t.replace(/&#196;/g,"\u00C4");
t = t.replace(/&#197;/g,"\u00C5");
t = t.replace(/&#198;/g,"\u00C6");
t = t.replace(/&#199;/g,"\u00C7");
t = t.replace(/&#200;/g,"\u00C8");
t = t.replace(/&#201;/g,"\u00C9");
t = t.replace(/&#202;/g,"\u00CA");
t = t.replace(/&#203;/g,"\u00CB");
t = t.replace(/&#204;/g,"\u00CC");
t = t.replace(/&#205;/g,"\u00CD");
t = t.replace(/&#206;/g,"\u00CE");
t = t.replace(/&#207;/g,"\u00CF");
t = t.replace(/&#208;/g,"\u00D0");
t = t.replace(/&#209;/g,"\u00D1");
t = t.replace(/&#210;/g,"\u00D2");
t = t.replace(/&#211;/g,"\u00D3");
t = t.replace(/&#212;/g,"\u00D4");
t = t.replace(/&#213;/g,"\u00D5");
t = t.replace(/&#214;/g,"\u00D6");
t = t.replace(/&#215;/g,"\u00D7");
t = t.replace(/&#216;/g,"\u00D8");
t = t.replace(/&#217;/g,"\u00D9");
t = t.replace(/&#218;/g,"\u00DA");
t = t.replace(/&#219;/g,"\u00DB");
t = t.replace(/&#220;/g,"\u00DC");
t = t.replace(/&#221;/g,"\u00DD");
t = t.replace(/&#222;/g,"\u00DE");
t = t.replace(/&#223;/g,"\u00DF");
t = t.replace(/&#224;/g,"\u00E0");
t = t.replace(/&#225;/g,"\u00E1");
t = t.replace(/&#226;/g,"\u00E2");
t = t.replace(/&#227;/g,"\u00E3");
t = t.replace(/&#228;/g,"\u00E4");
t = t.replace(/&#229;/g,"\u00E5");
t = t.replace(/&#230;/g,"\u00E6");
t = t.replace(/&#231;/g,"\u00E7");
t = t.replace(/&#232;/g,"\u00E8");
t = t.replace(/&#233;/g,"\u00E9");
t = t.replace(/&#234;/g,"\u00EA");
t = t.replace(/&#235;/g,"\u00EB");
t = t.replace(/&#236;/g,"\u00EC");
t = t.replace(/&#237;/g,"\u00ED");
t = t.replace(/&#238;/g,"\u00EE");
t = t.replace(/&#239;/g,"\u00EF");
t = t.replace(/&#240;/g,"\u00F0");
t = t.replace(/&#241;/g,"\u00F1");
t = t.replace(/&#242;/g,"\u00F2");
t = t.replace(/&#243;/g,"\u00F3");
t = t.replace(/&#244;/g,"\u00F4");
t = t.replace(/&#245;/g,"\u00F5");
t = t.replace(/&#246;/g,"\u00F6");
t = t.replace(/&#247;/g,"\u00F7");
t = t.replace(/&#248;/g,"\u00F8");
t = t.replace(/&#249;/g,"\u00F9");
t = t.replace(/&#250;/g,"\u00FA");
t = t.replace(/&#251;/g,"\u00FB");
t = t.replace(/&#252;/g,"\u00FC");
t = t.replace(/&#253;/g,"\u00FD");
t = t.replace(/&#254;/g,"\u00FE");
t = t.replace(/&#255;/g,"\u00FF");
t = t.replace(/&quot;/g,"\u0022");
t = t.replace(/&lt;3/g, "\u2764");
t = t.replace(/&lt;/g,"\u02C2");
t = t.replace(/&gt;/g,"\u02C3");
t = t.replace(/&euro;/g,"\u0080");
t = t.replace(/&sbquo;/g,"\u0082");
t = t.replace(/&fnof;/g,"\u0083");
t = t.replace(/&bdquo;/g,"\u0084");
t = t.replace(/&hellip;/g,"\u0085");
t = t.replace(/&dagger;/g,"\u0086");
t = t.replace(/&Dagger;/g,"\u0087");
t = t.replace(/&circ;/g,"\u0088");
t = t.replace(/&permil;/g,"\u0089");
t = t.replace(/&Scaron;/g,"\u008A");
t = t.replace(/&lsaquo;/g,"\u008B");
t = t.replace(/&OElig;/g,"\u008C");
t = t.replace(/&lsquo;/g,"\u0091");
t = t.replace(/&rsquo;/g,"\u0092");
t = t.replace(/&ldquo;/g,"\u0093");
t = t.replace(/&rdquo;/g,"\u0094");
t = t.replace(/&bull;/g,"\u0095");
t = t.replace(/&ndash;/g,"\u0096");
t = t.replace(/&mdash;/g,"\u0097");
t = t.replace(/&tilde;/g,"\u0098");
t = t.replace(/&trade;/g,"\u0099");
t = t.replace(/&scaron;/g,"\u009A");
t = t.replace(/&rsaquo;/g,"\u009B");
t = t.replace(/&oelig;/g,"\u009C");
t = t.replace(/&yuml;/g,"\u009F");
t = t.replace(/&iexcl;/g,"\u00A1");
t = t.replace(/&cent;/g,"\u00A2");
t = t.replace(/&pound;/g,"\u00A3");
t = t.replace(/&curren;/g,"\u00A4");
t = t.replace(/&yen;/g,"\u00A5");
t = t.replace(/&brvbar;/g,"\u00A6");
t = t.replace(/&sect;/g,"\u00A7");
t = t.replace(/&uml;/g,"\u00A8");
t = t.replace(/&copy;/g,"\u00A9");
t = t.replace(/&ordf;/g,"\u00AA");
t = t.replace(/&laquo;/g,"\u00AB");
t = t.replace(/&not;/g,"\u00AC");
t = t.replace(/&reg;/g,"\u00AE");
t = t.replace(/&macr;/g,"\u00AF");
t = t.replace(/&deg;/g,"\u00B0");
t = t.replace(/&plusmn;/g,"\u00B1");
t = t.replace(/&sup2;/g,"\u00B2");
t = t.replace(/&sup3;/g,"\u00B3");
t = t.replace(/&acute;/g,"\u00B4");
t = t.replace(/&micro;/g,"\u00B5");
t = t.replace(/&para;/g,"\u00B6");
t = t.replace(/&middot;/g,"\u00B7");
t = t.replace(/&cedil;/g,"\u00B8");
t = t.replace(/&sup1;/g,"\u00B9");
t = t.replace(/&ordm;/g,"\u00BA");
t = t.replace(/&raquo;/g,"\u00BB");
t = t.replace(/&frac14;/g,"\u00BC");
t = t.replace(/&frac12;/g,"\u00BD");
t = t.replace(/&frac34;/g,"\u00BE");
t = t.replace(/&iquest;/g,"\u00BF");
t = t.replace(/&Agrave;/g,"\u00C0");
t = t.replace(/&Aacute;/g,"\u00C1");
t = t.replace(/&Acirc;/g,"\u00C2");
t = t.replace(/&Atilde;/g,"\u00C3");
t = t.replace(/&Auml;/g,"\u00C4");
t = t.replace(/&Aring;/g,"\u00C5");
t = t.replace(/&AElig;/g,"\u00C6");
t = t.replace(/&Ccedil;/g,"\u00C7");
t = t.replace(/&Egrave;/g,"\u00C8");
t = t.replace(/&Eacute;/g,"\u00C9");
t = t.replace(/&Ecirc;/g,"\u00CA");
t = t.replace(/&Euml;/g,"\u00CB");
t = t.replace(/&Igrave;/g,"\u00CC");
t = t.replace(/&Iacute;/g,"\u00CD");
t = t.replace(/&Icirc;/g,"\u00CE");
t = t.replace(/&Iuml;/g,"\u00CF");
t = t.replace(/&ETH;/g,"\u00D0");
t = t.replace(/&Ntilde;/g,"\u00D1");
t = t.replace(/&Ograve;/g,"\u00D2");
t = t.replace(/&Oacute;/g,"\u00D3");
t = t.replace(/&Ocirc;/g,"\u00D4");
t = t.replace(/&Otilde;/g,"\u00D5");
t = t.replace(/&Ouml;/g,"\u00D6");
t = t.replace(/&times;/g,"\u00D7");
t = t.replace(/&Oslash;/g,"\u00D8");
t = t.replace(/&Ugrave;/g,"\u00D9");
t = t.replace(/&Uacute;/g,"\u00DA");
t = t.replace(/&Ucirc;/g,"\u00DB");
t = t.replace(/&Uuml;/g,"\u00DC");
t = t.replace(/&Yacute;/g,"\u00DD");
t = t.replace(/&THORN;/g,"\u00DE");
t = t.replace(/&szlig;/g,"\u00DF");
t = t.replace(/&agrave;/g,"\u00E0");
t = t.replace(/&aacute;/g,"\u00E1");
t = t.replace(/&acirc;/g,"\u00E2");
t = t.replace(/&atilde;/g,"\u00E3");
t = t.replace(/&auml;/g,"\u00E4");
t = t.replace(/&aring;/g,"\u00E5");
t = t.replace(/&aelig;/g,"\u00E6");
t = t.replace(/&ccedil;/g,"\u00E7");
t = t.replace(/&egrave;/g,"\u00E8");
t = t.replace(/&eacute;/g,"\u00E9");
t = t.replace(/&ecirc;/g,"\u00EA");
t = t.replace(/&euml;/g,"\u00EB");
t = t.replace(/&igrave;/g,"\u00EC");
t = t.replace(/&iacute;/g,"\u00ED");
t = t.replace(/&icirc;/g,"\u00EE");
t = t.replace(/&iuml;/g,"\u00EF");
t = t.replace(/&eth;/g,"\u00F0");
t = t.replace(/&ntilde;/g,"\u00F1");
t = t.replace(/&ograve;/g,"\u00F2");
t = t.replace(/&oacute;/g,"\u00F3");
t = t.replace(/&ocirc;/g,"\u00F4");
t = t.replace(/&otilde;/g,"\u00F5");
t = t.replace(/&ouml;/g,"\u00F6");
t = t.replace(/&divide;/g,"\u00F7");
t = t.replace(/&oslash;/g,"\u00F8");
t = t.replace(/&ugrave;/g,"\u00F9");
t = t.replace(/&uacute;/g,"\u00FA");
t = t.replace(/&ucirc;/g,"\u00FB");
t = t.replace(/&uuml;/g,"\u00FC");
t = t.replace(/&yacute;/g,"\u00FD");
t = t.replace(/&thorn;/g,"\u00FE");
t = t.replace(/&yuml;/g,"\u00FF");
t = t.replace(/<br>/g,"<br/>");
t = t.replace(/(?!&amp;)&/g, "&amp;");
return t;
};
function fixPostHtml(data) {
data = data.replace(/<\\?\/?span[^>]*>/g, "");
data = data.replace(/<br>/g, "<br/>");
var hrefreg = /<a\shref=\\?"([^>]*)\\?">/;
var hrefm = hrefreg.exec(data);
if (hrefm != null) {
var href = hrefm[1].replace(/&/g, "&amp;");
data = data.replace(hrefreg, "<a href=\"" + href + "\">");
}
return data;
}

View file

@ -46,6 +46,7 @@
<configuration name="Simulator-Debug">
<platformArchitecture>x86</platformArchitecture>
<asset path="x86/o-g/Goober" entry="true" type="Qnx/Elf">Goober</asset>
<asset path="../bb-cascades-oauth/oauth/o-g/libkqoauth.so" type="Qnx/Elf">lib/libkqoauth.so.1</asset>
</configuration>
<!-- The name that is displayed in the BlackBerry 10 application installer.
@ -55,7 +56,7 @@
<!-- A string value of the format <0-999>.<0-999>.<0-999> that represents application version which can be used to check for application upgrade.
Values can also be 1-part or 2-part. It is not necessary to have a 3-part value.
An updated version of application must have a versionNumber value higher than the previous version. Required. -->
<versionNumber>0.8.0</versionNumber>
<versionNumber>0.9.5</versionNumber>
<!-- Fourth digit segment of the package version. First three segments are taken from the
<versionNumber> element. Must be an integer from 0 to 2^16-1 -->

View file

@ -94,6 +94,7 @@ config_pri_assets {
$$quote($$BASEDIR/assets/icons/ic_contact.png) \
$$quote($$BASEDIR/assets/icons/ic_copy.png) \
$$quote($$BASEDIR/assets/icons/ic_del_bookmarks.png) \
$$quote($$BASEDIR/assets/icons/ic_doctype_picture.png) \
$$quote($$BASEDIR/assets/icons/ic_edit.png) \
$$quote($$BASEDIR/assets/icons/ic_favorite.png) \
$$quote($$BASEDIR/assets/icons/ic_info.png) \
@ -121,18 +122,22 @@ config_pri_source_group1 {
$$quote($$BASEDIR/src/ActiveFrameQML.cpp) \
$$quote($$BASEDIR/src/Pnut.cpp) \
$$quote($$BASEDIR/src/Pnut_test.cpp) \
$$quote($$BASEDIR/src/WebImageView.cpp) \
$$quote($$BASEDIR/src/applicationui.cpp) \
$$quote($$BASEDIR/src/main.cpp)
$$quote($$BASEDIR/src/main.cpp) \
$$quote($$BASEDIR/src/netimagemanager.cpp) \
$$quote($$BASEDIR/src/netimagetracker.cpp)
HEADERS += \
$$quote($$BASEDIR/src/ActiveFrameQML.h) \
$$quote($$BASEDIR/src/Pnut.h) \
$$quote($$BASEDIR/src/WebImageView.h) \
$$quote($$BASEDIR/src/applicationui.hpp) \
$$quote($$BASEDIR/src/globals.h)
$$quote($$BASEDIR/src/globals.h) \
$$quote($$BASEDIR/src/netimagemanager.h) \
$$quote($$BASEDIR/src/netimagetracker.h)
}
INCLUDEPATH += $$quote($$BASEDIR/src)
CONFIG += precompile_header
PRECOMPILED_HEADER = $$quote($$BASEDIR/precompiled.h)
@ -144,6 +149,9 @@ lupdate_inclusion {
$$quote($$BASEDIR/../src/*.cc) \
$$quote($$BASEDIR/../src/*.cpp) \
$$quote($$BASEDIR/../src/*.cxx) \
$$quote($$BASEDIR/..//*.qml) \
$$quote($$BASEDIR/..//*.js) \
$$quote($$BASEDIR/..//*.qs) \
$$quote($$BASEDIR/../assets/*.qml) \
$$quote($$BASEDIR/../assets/*.js) \
$$quote($$BASEDIR/../assets/*.qs) \

View file

@ -20,6 +20,8 @@
#include "Pnut.h"
#include "globals.h"
#include <QDebug>
#include <QDateTime>
#include <QRegExp>
#include <bb/data/JsonDataAccess>
const QString Pnut::PNUT_API_ROOT = QString("https://api.pnut.io/v0");
@ -214,7 +216,8 @@ void Pnut::onAuthorizedRequestReady(QByteArray data, int id)
endpoint == "/users/me/bookmarks")
{
Pnut::RequestType rtype = rtype_map[id];
emit streamReceived(variant.toMap()["data"].toList(), rtype);
QVariantList streamlist = variant.toMap()["data"].toList();
emit streamReceived(parseStream(streamlist), rtype);
req_map.remove(id);
rtype_map.remove(id);
}
@ -225,7 +228,8 @@ void Pnut::onAuthorizedRequestReady(QByteArray data, int id)
}
else if (endpoint == ":thread")
{
emit threadReceived(variant.toMap()["data"].toList());
QVariantList streamlist = variant.toMap()["data"].toList();
emit threadReceived(parseStream(streamlist));
req_map.remove(id);
}
else if (endpoint == ":userstream")
@ -299,7 +303,13 @@ void Pnut::onAuthorizedRequestReady(QByteArray data, int id)
{
qDebug() << "Bookmark successful!";
req_map.remove(id);
emit postReceived(variant.toMap()["data"].toMap());
getPost(variant.toMap()["data"].toMap()["id"].toString());
}
else if (endpoint.startsWith(":repost"))
{
qDebug() << "Repost successful!";
req_map.remove(id);
getPost(variant.toMap()["data"].toMap()["id"].toString());
}
else
{
@ -315,6 +325,12 @@ void Pnut::onAuthorizedRequestReady(QByteArray data, int id)
qDebug() << "Create post success!";
req_map.remove(id);
}
else if (endpoint.startsWith(":repost"))
{
qDebug() << "Repost successful!";
req_map.remove(id);
getPost(variant.toMap()["data"].toMap()["id"].toString());
}
else
{
qDebug() << "Good but not sure what";
@ -335,7 +351,7 @@ void Pnut::getStream(QString endpoint, Pnut::RequestType rtype)
QUrl url(PNUT_API_ROOT + endpoint);
KQOAuthParameters parameters;
parameters.insert("count", "50");
parameters.insert("include_raw", "1");
parameters.insert("include_post_raw", "1");
switch (rtype)
{
case Pnut::STREAM_NEWER:
@ -350,11 +366,35 @@ void Pnut::getStream(QString endpoint, Pnut::RequestType rtype)
getRequest(url, parameters, req_id);
}
void Pnut::sendPost(QString text)
void Pnut::sendPost(QString text, int pid=0)
{
QByteArray post;
QVariantMap map;
QVariantList raw;
if (postLength(text) > 256)
{
QVariantMap longpost;
QVariantMap rawobj;
longpost["title"] = "";
longpost["body"] = text;
longpost["tstamp"] = QDateTime::currentMSecsSinceEpoch();
rawobj["type"] = "nl.chimpnut.blog.post";
rawobj["value"] = longpost;
raw.append(rawobj);
text.truncate(100);
text = text + "... - https://longpo.st/p/{object_id} - #longpost";
}
map["text"] = text;
if (pid > 0)
{
map["reply_to"] = pid;
}
if (raw.length() > 0)
{
map["raw"] = raw;
}
QVariant data(map);
bb::data::JsonDataAccess jda;
jda.saveToBuffer(data, &post);
@ -401,7 +441,7 @@ void Pnut::getThread(QString pid)
{
QUrl url(PNUT_API_ROOT + "/posts/" + pid + "/thread");
KQOAuthParameters parameters;
parameters.insert("include_raw", "1");
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":thread";
getRequest(url, parameters, req_id);
}
@ -410,6 +450,7 @@ void Pnut::setBookmark(QString pid)
{
QUrl url(PNUT_API_ROOT + "/posts/" + pid + "/bookmark");
KQOAuthParameters parameters;
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":bookmark";
putRequest(url, parameters, req_id);
}
@ -418,6 +459,7 @@ void Pnut::deleteBookmark(QString pid)
{
QUrl url(PNUT_API_ROOT + "/posts/" + pid + "/bookmark");
KQOAuthParameters parameters;
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":bookmark";
deleteRequest(url, parameters, req_id);
}
@ -425,6 +467,7 @@ void Pnut::deleteBookmark(QString pid)
void Pnut::repost(QString pid) {
QUrl url(PNUT_API_ROOT + "/posts/" + pid + "/repost");
KQOAuthParameters parameters;
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":repost";
putRequest(url, parameters, req_id);
}
@ -433,6 +476,7 @@ void Pnut::deleteRepost(QString pid)
{
QUrl url(PNUT_API_ROOT + "/posts/" + pid + "/repost");
KQOAuthParameters parameters;
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":repost";
deleteRequest(url, parameters, req_id);
}
@ -505,6 +549,7 @@ void Pnut::getPost(QString pid)
{
QUrl url(PNUT_API_ROOT + "/posts/" + pid);
KQOAuthParameters parameters;
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":getpost:" + pid;
getRequest(url, parameters, req_id);
}
@ -513,6 +558,8 @@ void Pnut::getUserStream(QString uid)
{
QUrl url(PNUT_API_ROOT + "/users/" + uid + "/posts");
KQOAuthParameters parameters;
parameters.insert("count", "150");
parameters.insert("include_post_raw", "1");
req_map[++req_id] = ":userstream";
getRequest(url, parameters, req_id);
}
@ -533,9 +580,55 @@ void Pnut::getFollowing(QString uid)
getRequest(url, parameters, req_id);
}
int Pnut::postLength(QString text)
{
QRegExp mdlink("\\[([^\\]]+)\\][^\\)]+\\)");
text.replace(mdlink, "\\1");
return text.length();
}
void Pnut::onOpenBrowser(QUrl url)
{
qDebug() << "_onOpenBrowser_";
qDebug() << url;
emit openBrowser(url);
}
QVariantList Pnut::parseStream(QVariantList stream)
{
QVariantList pstream;
foreach (QVariant vitem, stream) {
QVariantMap item = vitem.toMap();
if (item.find("repost_of") != item.end())
{
QString username = item["user"].toMap()["username"].toString();
item = item["repost_of"].toMap();
item.insert("reposted_by", username);
}
if (item.find("raw") != item.end())
{
QVariantList rawlist = item["raw"].toList();
foreach (QVariant ritem, rawlist) {
QVariantMap raw = ritem.toMap();
if (raw["type"] == "io.pnut.core.oembed")
{
QVariantMap value = raw["value"].toMap();
if (value["type"] == "photo")
{
item.insert("photo_url", value["url"].toString());
if (value.find("thumbnail_url") != value.end())
{
item.insert("thumbnail_url", value["thumbnail_url"].toString());
}
}
}
if (raw["type"] == "nl.chimpnut.blog.post") {
QVariantMap value = raw["value"].toMap();
item.insert("longpost_body", value["body"]);
}
}
}
pstream.append(item);
}
return pstream;
}

View file

@ -67,7 +67,7 @@ public:
Q_INVOKABLE void authorize();
Q_INVOKABLE void getStream(QString endpoint, Pnut::RequestType rtype);
Q_INVOKABLE void sendPost(QString text);
Q_INVOKABLE void sendPost(QString text, int pid);
Q_INVOKABLE void sendReply(QString text, int pid);
Q_INVOKABLE void getUser(QString uid);
Q_INVOKABLE void followUser(QString uid);
@ -88,6 +88,7 @@ public:
Q_INVOKABLE void getUserStream(QString uid);
Q_INVOKABLE void getFollowers(QString uid);
Q_INVOKABLE void getFollowing(QString uid);
Q_INVOKABLE int postLength(QString text);
public slots:
void onRequestReady(QByteArray data);
@ -131,6 +132,7 @@ private:
void postRequest(QUrl url, KQOAuthParameters parameters, int id, QByteArray data);
void putRequest(QUrl url, KQOAuthParameters parameters, int id);
void deleteRequest(QUrl url, KQOAuthParameters parameters, int id);
QVariantList parseStream(QVariantList stream);
};
#endif /* PNUT_H_ */

View file

@ -1,71 +0,0 @@
#include "WebImageView.h"
#include <bb/cascades/Image>
QNetworkAccessManager * WebImageView::mNetManager = new QNetworkAccessManager;
QNetworkDiskCache * WebImageView::mNetworkDiskCache = new QNetworkDiskCache();
WebImageView::WebImageView() {
mNetworkDiskCache->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation));
mNetManager->setCache(mNetworkDiskCache);
}
const QUrl& WebImageView::url() const {
return mUrl;
}
void WebImageView::setUrl(const QUrl& url) {
mUrl = url;
mLoading = 0;
resetImage();
QNetworkRequest request;
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
request.setUrl(url);
QNetworkReply * reply = mNetManager->get(QNetworkRequest(request));
connect(reply,SIGNAL(finished()), this, SLOT(imageLoaded()));
connect(reply,SIGNAL(downloadProgress ( qint64 , qint64 )), this, SLOT(dowloadProgressed(qint64,qint64)));
emit urlChanged();
}
double WebImageView::loading() const {
return mLoading;
}
void WebImageView::imageLoaded() {
QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
QVariant possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
mRedirectUrl = this->redirectUrl(possibleRedirectUrl.toUrl(),mRedirectUrl);
if (!mRedirectUrl.isEmpty()) {
this->setUrl(mRedirectUrl);
} else {
setImage(Image(reply->readAll()));
}
reply->deleteLater();
}
void WebImageView::dowloadProgressed(qint64 bytes,qint64 total) {
mLoading = double(bytes)/double(total);
emit loadingChanged();
}
QUrl WebImageView::redirectUrl(const QUrl& possibleRedirectUrl, const QUrl& oldRedirectUrl) const {
QUrl redirectUrl;
if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl) {
redirectUrl = possibleRedirectUrl;
}
return redirectUrl;
}

View file

@ -1,41 +0,0 @@
#ifndef WEBIMAGEVIEW_H_
#define WEBIMAGEVIEW_H_
#include <bb/cascades/ImageView>
#include <QUrl>
using namespace bb::cascades;
class QNetworkAccessManager;
class WebImageView: public bb::cascades::ImageView {
Q_OBJECT
Q_PROPERTY (QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY (float loading READ loading NOTIFY loadingChanged)
public:
WebImageView();
const QUrl& url() const;
double loading() const;
public Q_SLOTS:
void setUrl(const QUrl& url);
private Q_SLOTS:
void imageLoaded();
void dowloadProgressed(qint64,qint64);
signals:
void urlChanged();
void loadingChanged();
private:
static QNetworkAccessManager * mNetManager;
static QNetworkDiskCache * mNetworkDiskCache;
QUrl mUrl;
QUrl mRedirectUrl;
float mLoading;
QUrl redirectUrl(const QUrl& possibleRedirectUrl, const QUrl& oldRedirectUrl) const;
};
#endif /* WEBIMAGEVIEW_H_ */

View file

@ -16,8 +16,9 @@
#include "applicationui.hpp"
#include "Pnut.h"
#include "WebImageView.h"
#include "ActiveFrameQML.h"
#include "netimagemanager.h"
#include "netimagetracker.h"
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
@ -25,6 +26,9 @@
#include <bb/cascades/LocaleHandler>
#include <bb/ApplicationInfo>
#include <bb/system/Clipboard>
#include <bb/system/InvokeRequest>
#include <bb/system/InvokeManager>
#include <bb/system/InvokeTargetReply>
using namespace bb::cascades;
@ -52,7 +56,8 @@ ApplicationUI::ApplicationUI() :
qmlRegisterType<Pnut>("com.monkeystew.pnut", 1, 0, "Pnut");
qmlRegisterType<QTimer> ("com.monkeystew.qtimer", 1, 0, "QTimer");
qmlRegisterType<WebImageView>("org.labsquare", 1, 0, "WebImageView");
qmlRegisterType<NetImageTracker>("com.netimage", 1, 0, "NetImageTracker");
qmlRegisterType<NetImageManager>("com.netimage", 1, 0, "NetImageManager");
m_appSettings = new QSettings("Morgan McMillian", "Goober");
@ -101,11 +106,13 @@ QString ApplicationUI::appversion()
return appinfo.version();
}
void ApplicationUI::copyText(QByteArray text)
void ApplicationUI::copyText(QString text)
{
bb::system::Clipboard clipboard;
clipboard.clear();
clipboard.insert("text/plain", text);
QByteArray textstr;
textstr.append(text.toUtf8());
clipboard.insert("text/plain", textstr);
}
void ApplicationUI::onInvoke(const bb::system::InvokeRequest& request)
@ -115,3 +122,23 @@ void ApplicationUI::onInvoke(const bb::system::InvokeRequest& request)
emit createPost(request.data());
}
}
void ApplicationUI::showImage(const QString& filename)
{
qDebug() << "showImage called!";
bb::system::InvokeManager manager;
bb::system::InvokeRequest request;
request.setUri(QUrl::fromLocalFile(filename));
request.setTarget("sys.pictures.card.previewer");
request.setAction("bb.action.VIEW");
bb::system::InvokeTargetReply *targetReply = manager.invoke(request);
manager.setParent(this);
if (targetReply == NULL) {
qDebug() << "InvokeTargetReply is NULL: targetReply = " << targetReply;
} else {
targetReply->setParent(this);
}
qDebug() << "targetReply = " << targetReply;
}

View file

@ -47,7 +47,8 @@ public:
Q_INVOKABLE QVariant setting(const QString &key);
Q_INVOKABLE void setSetting(const QString &key, const QString &value);
Q_INVOKABLE QString appversion();
Q_INVOKABLE void copyText(QByteArray text);
Q_INVOKABLE void copyText(QString text);
Q_INVOKABLE void showImage(const QString &filename);
Q_SIGNALS:
void createPost(QByteArray text);

199
src/netimagemanager.cpp Normal file
View file

@ -0,0 +1,199 @@
/* Copyright (c) 2012 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "netimagemanager.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QDir>
using namespace bb::cascades;
const char* const NetImageManager::mDefaultId = "netimagemanager";
NetImageManager::NetImageManager(QObject *parent) :
QObject(parent) {
mCacheId = mDefaultId;
mCacheSize = 125;
QString diskPath = QDir::homePath() + "/" + mCacheId;
if (!QDir(diskPath).exists()) {
QDir().mkdir(diskPath);
}
// Connect to the sslErrors signal to the onSslErrors() function. This will help us see what errors
// we get when connecting to the address given by mWeatherAdress.
connect(&mAccessManager,
SIGNAL(sslErrors ( QNetworkReply * , const QList<QSslError> & )),
this,
SLOT(onSslErrors ( QNetworkReply * , const QList<QSslError> & )));
// Connect to the reply finished signal to httpFinsihed() Slot function.
connect(&mAccessManager, SIGNAL(finished(QNetworkReply *)), this,
SLOT(httpFinished(QNetworkReply *)));
}
NetImageManager::~NetImageManager() {
}
void NetImageManager::lookUpImage(const QString imageName) {
QUrl url = QUrl(imageName);
// Check if image is stored on disc
// The qHash is a bucket type hash so the doubling is to remove possible collisions.
QString diskPath = QDir::homePath() + "/" + mCacheId + "/"
+ QString::number(qHash(url.host())) + "_"
+ QString::number(qHash(url.path())) + ".JPG";
QFile imageFile(diskPath);
// If the file exists, send a signal the image is ready
if (imageFile.exists()) {
emit imageReady(diskPath, url.toString());
} else {
// otherwise let's download the file, but first we show a loading image
emit imageReady(imageName, "loading");
QNetworkRequest request(url);
if (mQueue.isEmpty()) {
mAccessManager.get(request);
}
mQueue.append(request);
}
}
void NetImageManager::setCacheId(QString cacheId) {
if (mCacheId != cacheId) {
mCacheId = cacheId;
QString diskPath = QDir::homePath() + "/" + mCacheId;
if (!QDir(diskPath).exists()) {
QDir().mkdir(diskPath);
}
emit cacheIdChanged(mCacheId);
}
houseKeep();
}
QString NetImageManager::cacheId() {
return mCacheId;
}
void NetImageManager::setCacheSize(int cacheSize) {
if (mCacheSize != cacheSize) {
mCacheSize = cacheSize;
emit cacheSizeChanged(mCacheSize);
}
houseKeep();
}
int NetImageManager::cacheSize() {
return mCacheSize;
}
void NetImageManager::houseKeep() {
QString diskPath = QDir::homePath() + "/" + mCacheId + "/";
QDir directory(diskPath);
if (directory.count() > (uint) mCacheSize) {
//Find the oldest file and delete it.
QFileInfoList list = directory.entryInfoList(QDir::Files, QDir::Time);
QFile::remove(list.at(list.size() - 1).absoluteFilePath());
//maybe there are more then the permitted amount of files here, let's call housekeeping again
houseKeep();
}
}
void NetImageManager::httpFinished(QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError) {
QImage qImage;
qImage.loadFromData(reply->readAll());
if (qImage.isNull()) {
return;
}
// When the download is finished we make a hash-tag for the image out of it's url so we can find it again,
// then we save it as a .JPG. The qHash is a bucket type hash so the doubling is to remove possible collisions.
QString diskPath = QDir::homePath() + "/" + mCacheId + "/"
+ QString::number(qHash(reply->url().host())) + "_"
+ QString::number(qHash(reply->url().path())) + ".JPG";
if (qImage.save(diskPath)) {
// houseKeep() is called to see that we don't save more then we are allowed in the cache
houseKeep();
emit imageReady(diskPath, reply->url().toString());
}
//we remove the first item in the download queue
if (!mQueue.isEmpty()) {
mQueue.removeFirst();
if (!mQueue.isEmpty()) {
QNetworkRequest request = mQueue.first();
mAccessManager.get(request);
}
}
} else {
//Handle error
qDebug() << "Could Not access image" << reply->url().toString();
}
reply->deleteLater();
}
void NetImageManager::onDialogFinished(bb::system::SystemUiResult::Type type)
{
exit(0);
}
void NetImageManager::onSslErrors(QNetworkReply * reply,
const QList<QSslError> & errors) {
foreach (QSslError e, errors)
qDebug() << "SSL error: " << e;
SystemDialog *dialog = new SystemDialog("OK");
dialog->setTitle(tr("SSL errors received"));
dialog->setBody(tr("We have received information about a security breach in the protocol. Press \"OK\" to terminate the application"));
// Connect your functions to handle the predefined signals for the buttons.
// The slot will check the SystemUiResult to see which button was clicked.
bool success = connect(dialog,
SIGNAL(finished(bb::system::SystemUiResult::Type)),
this,
SLOT(onDialogFinished(bb::system::SystemUiResult::Type)));
if (success) {
// Signal was successfully connected.
// Now show the dialog box in your UI
dialog->show();
} else {
// Failed to connect to signal.
dialog->deleteLater();
}
}

146
src/netimagemanager.h Normal file
View file

@ -0,0 +1,146 @@
/* Copyright (c) 2012 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _NETIMAGECACHE_H_
#define _NETIMAGECACHE_H_
#include <bb/cascades/Image>
#include <bb/system/SystemDialog>
#include <QtNetwork/QNetworkRequest>
#include <QObject>
#include <QtGui/QImage>
using namespace bb::cascades;
using namespace bb::system;
/**
* NetImageManager is a cache service for our Internet downloaded images.
* You can set the size of the cache and an id for the cache.
* If you want to reuse the cache between different pages it's possible.
*/
class NetImageManager: public QObject
{
Q_OBJECT
/**
* This property sets the name of the image cache, if none is set we will use it to the
* default "netimagemanager".
*/
Q_PROPERTY(QString cacheId READ cacheId WRITE setCacheId NOTIFY cacheIdChanged)
/**
* Sets the size of the cache in number of files in the directory, if not set, it defaults
* to 125 files.
*/
Q_PROPERTY(int cacheSize READ cacheSize WRITE setCacheSize NOTIFY cacheSizeChanged)
public:
/**
* This is our constructor which initializes the member variables.
* @param parent The parent QObject, if not specified, 0 is used.
*/
NetImageManager(QObject *parent = 0);
~NetImageManager();
/**
* This function sets the cacheId property.
*
* @param cacheId The cacheId used for storing downloaded in a folder.
*/
void setCacheId(QString cacheId);
/**
* This function return the chacheId, that is the location of the folder where images
* are stored for the cache object.
*
* @return The cacheId
*/
QString cacheId();
/**
* This function sets the cacheSize property.
*
* @param cacheId The cacheSize used for cleaning the cache folder.
*/
void setCacheSize(int cacheSize);
/**
* This function return the chacheSize.
*
* @return The cacheId
*/
int cacheSize();
/**
* Check if the image exists in cache
*
* @return the full path to the image if it exists otherwise 0
*/
QString getNetImage(QString imageName);
void lookUpImage(const QString imageName);
/**
* Check if the cache is full and if so deletes the oldest
*/
void houseKeep();
public slots:
/**
* Slot called for by the dialog that you get with SSL-errors
*/
void onDialogFinished(bb::system::SystemUiResult::Type type);
signals:
/**
* This signal is emitted when a new cacheId has been set
*/
void cacheIdChanged(QString cacheId);
void cacheSizeChanged(int cacheSize);
void imageReady(const QString filePath, const QString imageName);
private slots:
/**
* This Slot function is called when the network request is complete.
*/
void httpFinished( QNetworkReply * reply );
/**
* This Slot function is connected to the mAccessManager sslErrors signal. This function
* allows us to see what errors we get when connecting to the address given by mWeatherAdress.
*
* @param reply The network reply
* @param errors SSL Error List
*/
void onSslErrors(QNetworkReply * reply, const QList<QSslError> & errors);
private:
// Property variables
QString mCacheId;
int mCacheSize;
// String constant for the default id of the image cache
static const char* const mDefaultId;
// The network parameters; used for accessing a file from the Internet
QNetworkAccessManager mAccessManager;
QList<QNetworkRequest> mQueue;
};
#endif // _NETIMAGECACHE_H_

108
src/netimagetracker.cpp Normal file
View file

@ -0,0 +1,108 @@
/* Copyright (c) 2012 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "netimagetracker.h"
#include <bb/cascades/ImageTracker>
using namespace bb;
using namespace bb::cascades;
NetImageTracker::NetImageTracker(QObject *parent) :
ImageTracker(parent), mManager(0)
{
mIsCreated = false;
connect(this, SIGNAL(creationCompleted()), this, SLOT(onCreationCompleted()));
}
void NetImageTracker::onCreationCompleted()
{
mIsCreated = true;
if (!mSource.isEmpty() && mManager) {
// Once creation of the tracker has completed, the lookup of the image is ready to
// be executed. If the tracker is used within a list item this lookup is not needed
// since the update of list item data will enforce a refresh of the imageSource that
// will perform the lookup. But for the tracker to work in ImageViews that are not
// part of a list item we need this here.
mManager->lookUpImage(mSource);
}
}
void NetImageTracker::onImageReady(const QString filePath, const QString imageName)
{
// The NetImageManager will emit a signal to all NetImageTrackers, make sure the
// image that is ready belongs to this tracker.
if (imageName.compare(mSource) == 0) {
if (imageName.compare("loading") == 0) {
// If we don't have an image to display, let's display a loading image
QUrl url = QUrl("asset:///icons/ic_doctype_picture.png");
setImageSource(url);
} else {
// Set the path to the image that is now downloaded and cached in the data folder on the device.
QUrl url = QUrl(filePath);
setImageSource(url);
}
}
}
void NetImageTracker::setSource(const QString source)
{
if (!source.isEmpty() && mSource.compare(source) != 0) {
mSource = source;
if (mManager) {
// If a manger has been set make a request to look up the image. Otherwise
// the request is delayed to onCreationCompleted or at the next time a call
// to set the source is made.
mManager->lookUpImage(mSource);
} else {
qWarning()
<< "This NetImageTracker does not have any NetImageManager, set up one as an attached object and add it to the property.";
}
emit sourceChanged(mSource);
}
}
QString NetImageTracker::source()
{
return mSource;
}
void NetImageTracker::setManager(NetImageManager *manager)
{
if (mManager != manager) {
// Change the manager that is used for the tracker.
if (mManager) {
disconnect(mManager, SIGNAL(imageReady(const QString , const QString )), this,
SLOT(onImageReady( const QString , const QString )));
delete (mManager);
}
mManager = manager;
emit managerChanged(mManager);
connect(mManager, SIGNAL(imageReady(const QString , const QString )), this,
SLOT(onImageReady( const QString , const QString )));
}
}
NetImageManager *NetImageTracker::manager()
{
return mManager;
}

115
src/netimagetracker.h Normal file
View file

@ -0,0 +1,115 @@
/* Copyright (c) 2012 Research In Motion Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _NETIMAGETRACKER_H_
#define _NETIMAGETRACKER_H_
#include "netimagemanager.h"
#include <bb/cascades/ImageTracker>
using namespace bb::cascades;
/**
* The NetImageTracker is used so that Cascades can be informed when an image is downloaded
* via a NetImageManager.
*/
class NetImageTracker: public bb::cascades::ImageTracker
{
Q_OBJECT
/**
* The NetImageManager property points to a manager that is used for downloading and caching
* images.
*/
Q_PROPERTY(NetImageManager *manager READ manager WRITE setManager NOTIFY managerChanged)
/**
* Sets the NetImageTracker source, the remote networked url that points to the image.
* You need to set one as an attached object in your QML files.
*/
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
public:
/**
* Constructor; Sets up the net image view
* @param parent The parent Container.
*/
NetImageTracker(QObject *parent = 0);
public slots:
/**
* Setter function for setting the trackers net image manager
*
* @param manager in manager that we're going to use.
*/
void setManager(NetImageManager *manager);
/**
* Getter for Trackers Manager object
* @return
*/
NetImageManager *manager();
/**
* Setter for the source object.
*
* @param source in string with path to the source.
*/
void setSource(const QString source);
/**
* Getter for source
* @return a string of the source
*/
QString source();
signals:
/**
* Signal that emits when the source have changed, wont happen automatically
*
* @param source the new source
*/
void sourceChanged(QString source);
/**
* signal that is emitted if the manager is changed, will not happen automatically.
*
* @param imageCache the new NetImageManager we want to have .
*/
void managerChanged(NetImageManager *imageCache);
private slots:
/**
* Emitted when we are done with the setup of this class
*/
void onCreationCompleted();
/**
* Emitted when we have a image that is ready for consumption
*
* @param filePath the path to the image that we can do what we want with
* @param imageName the actual name of the file, useful!
*/
void onImageReady(const QString filePath, const QString imageName);
private:
QString mSource;
NetImageManager *mManager;
bool mIsCreated;
};
#endif // ifndef _NETIMAGETRACKER_H_

View file

@ -9,20 +9,33 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NetImageManager</name>
<message>
<location filename="../src/netimagemanager.cpp" line="177"/>
<source>SSL errors received</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/netimagemanager.cpp" line="178"/>
<source>We have received information about a security breach in the protocol. Press &quot;OK&quot; to terminate the application</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NewPostSheet</name>
<message>
<location filename="../assets/NewPostSheet.qml" line="35"/>
<location filename="../assets/NewPostSheet.qml" line="37"/>
<source>Send</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/NewPostSheet.qml" line="44"/>
<location filename="../assets/NewPostSheet.qml" line="46"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/NewPostSheet.qml" line="50"/>
<location filename="../assets/NewPostSheet.qml" line="52"/>
<source>New Post</source>
<translation type="unfinished"></translation>
</message>
@ -30,52 +43,52 @@
<context>
<name>PostItem</name>
<message>
<location filename="../assets/PostItem.qml" line="189"/>
<location filename="../assets/PostItem.qml" line="280"/>
<source>Reply</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="198"/>
<location filename="../assets/PostItem.qml" line="289"/>
<source>Reply All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="208"/>
<source>Add Bookmark</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="208"/>
<location filename="../assets/PostItem.qml" line="299"/>
<source>Delete Bookmark</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="219"/>
<source>Repost</source>
<location filename="../assets/PostItem.qml" line="299"/>
<source>Add Bookmark</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="219"/>
<location filename="../assets/PostItem.qml" line="310"/>
<source>Delete Repost</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="230"/>
<location filename="../assets/PostItem.qml" line="310"/>
<source>Repost</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="321"/>
<source>Quote</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="239"/>
<location filename="../assets/PostItem.qml" line="330"/>
<source>View Profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="246"/>
<location filename="../assets/PostItem.qml" line="337"/>
<source>Copy text</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/PostItem.qml" line="276"/>
<location filename="../assets/PostItem.qml" line="367"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
@ -83,55 +96,55 @@
<context>
<name>ProfilePage</name>
<message>
<location filename="../assets/ProfilePage.qml" line="128"/>
<location filename="../assets/ProfilePage.qml" line="201"/>
<location filename="../assets/ProfilePage.qml" line="145"/>
<location filename="../assets/ProfilePage.qml" line="218"/>
<source>Following</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="141"/>
<location filename="../assets/ProfilePage.qml" line="216"/>
<location filename="../assets/ProfilePage.qml" line="158"/>
<location filename="../assets/ProfilePage.qml" line="233"/>
<source>Followers</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="154"/>
<location filename="../assets/ProfilePage.qml" line="188"/>
<location filename="../assets/ProfilePage.qml" line="171"/>
<location filename="../assets/ProfilePage.qml" line="205"/>
<source>Posts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="167"/>
<location filename="../assets/ProfilePage.qml" line="184"/>
<source>Bookmarks</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="233"/>
<location filename="../assets/ProfilePage.qml" line="250"/>
<source>Unfollow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="233"/>
<location filename="../assets/ProfilePage.qml" line="250"/>
<source>Follow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="246"/>
<location filename="../assets/ProfilePage.qml" line="263"/>
<source>Unmute</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="246"/>
<location filename="../assets/ProfilePage.qml" line="263"/>
<source>Mute</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="259"/>
<location filename="../assets/ProfilePage.qml" line="276"/>
<source>Unblock</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/ProfilePage.qml" line="259"/>
<location filename="../assets/ProfilePage.qml" line="276"/>
<source>Block</source>
<translation type="unfinished"></translation>
</message>
@ -172,17 +185,47 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="117"/>
<location filename="../assets/SettingsPage.qml" line="121"/>
<source>Hide photos</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="126"/>
<source>Hide embeded photos in timeline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="160"/>
<source>Hide avatars</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="165"/>
<source>Hide avatar images in timeline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="199"/>
<source>Hide long posts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="204"/>
<source>Hide long posts in timeline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="234"/>
<source>Theme</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="119"/>
<location filename="../assets/SettingsPage.qml" line="236"/>
<source>Bright</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/SettingsPage.qml" line="124"/>
<location filename="../assets/SettingsPage.qml" line="241"/>
<source>Dark</source>
<translation type="unfinished"></translation>
</message>
@ -190,22 +233,22 @@
<context>
<name>StreamTab</name>
<message>
<location filename="../assets/StreamTab.qml" line="143"/>
<location filename="../assets/StreamTab.qml" line="185"/>
<source>New Post</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/StreamTab.qml" line="157"/>
<location filename="../assets/StreamTab.qml" line="199"/>
<source>To Top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/StreamTab.qml" line="185"/>
<location filename="../assets/StreamTab.qml" line="227"/>
<source>To Bottom</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/StreamTab.qml" line="172"/>
<location filename="../assets/StreamTab.qml" line="214"/>
<source>Reload</source>
<translation type="unfinished"></translation>
</message>
@ -213,12 +256,12 @@
<context>
<name>UserItem</name>
<message>
<location filename="../assets/UserItem.qml" line="54"/>
<location filename="../assets/UserItem.qml" line="68"/>
<source>Unfollow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/UserItem.qml" line="54"/>
<location filename="../assets/UserItem.qml" line="68"/>
<source>Follow</source>
<translation type="unfinished"></translation>
</message>
@ -236,26 +279,26 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/main.qml" line="68"/>
<location filename="../assets/main.qml" line="72"/>
<location filename="../assets/main.qml" line="74"/>
<location filename="../assets/main.qml" line="78"/>
<source>Home</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/main.qml" line="90"/>
<location filename="../assets/main.qml" line="94"/>
<location filename="../assets/main.qml" line="96"/>
<location filename="../assets/main.qml" line="100"/>
<source>Mentions</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/main.qml" line="112"/>
<location filename="../assets/main.qml" line="116"/>
<location filename="../assets/main.qml" line="118"/>
<location filename="../assets/main.qml" line="122"/>
<source>Bookmarks</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../assets/main.qml" line="134"/>
<location filename="../assets/main.qml" line="138"/>
<location filename="../assets/main.qml" line="140"/>
<location filename="../assets/main.qml" line="144"/>
<source>Global</source>
<translation type="unfinished"></translation>
</message>