← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~qqworini/ubuntu-rssreader-app/reboot-add-opml-support into lp:ubuntu-rssreader-app

 

Joey Chan has proposed merging lp:~qqworini/ubuntu-rssreader-app/reboot-add-opml-support into lp:ubuntu-rssreader-app.

Commit message:
Add opml support.

User now can import their own opml file into Shorts. No export function yet.

Requested reviews:
  Ubuntu Shorts Developers (ubuntu-shorts-dev)
Related bugs:
  Bug #1248737 in Ubuntu Shorts App: "Allow to add feeds from the local file system"
  https://bugs.launchpad.net/ubuntu-rssreader-app/+bug/1248737
  Bug #1449424 in Ubuntu Shorts App: "Allow for importing feeds with *.opml files"
  https://bugs.launchpad.net/ubuntu-rssreader-app/+bug/1449424

For more details, see:
https://code.launchpad.net/~qqworini/ubuntu-rssreader-app/reboot-add-opml-support/+merge/282608

Add opml support.

User now can import their own opml file into Shorts. No export function yet.
-- 
Your team Ubuntu Shorts Developers is requested to review the proposed merge of lp:~qqworini/ubuntu-rssreader-app/reboot-add-opml-support into lp:ubuntu-rssreader-app.
=== modified file 'shorts/po/com.ubuntu.shorts.pot'
--- shorts/po/com.ubuntu.shorts.pot	2015-12-03 16:01:09 +0000
+++ shorts/po/com.ubuntu.shorts.pot	2016-01-14 15:39:13 +0000
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-12-03 17:59+0800\n"
+"POT-Creation-Date: 2016-01-14 22:46+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -42,37 +42,59 @@
 msgid "Large"
 msgstr ""
 
+#: ../qml/content/ImportFeeds.qml:30
+msgid "Import Feeds"
+msgstr ""
+
+#: ../qml/content/ImportFeeds.qml:42 ../qml/pages/TopicManagement.qml:258
+msgid "Confirm"
+msgstr ""
+
+#: ../qml/content/ImportFeeds.qml:305
+msgid "Please select an opml file"
+msgstr ""
+
+#: ../qml/content/ImportFeeds.qml:310
+msgid "Open"
+msgstr ""
+
+#: ../qml/content/ImportFeeds.qml:293
+msgid ""
+"Attention please, before importing opml file, Shorts only support one opml "
+"structure. <br><br>"
+msgstr ""
+
 #: ../qml/nongoogle/AppendNGFeedPage.qml:30 ../qml/pages/AppendFeedPage.qml:31
 #: ../qml/shorts-app.qml:255 ../qml/shorts-app.qml:390
 #: ../qml/shorts-app.qml:398
 msgid "Add feeds"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:105
+#: ../qml/nongoogle/AppendNGFeedPage.qml:94
 #: ../qml/pages/AppendFeedPage.qml:137
 msgid "Type a keyword or URL"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:162
+#: ../qml/nongoogle/AppendNGFeedPage.qml:149
 msgid "Feed Title:"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:170
-#: ../qml/nongoogle/AppendNGFeedPage.qml:185
+#: ../qml/nongoogle/AppendNGFeedPage.qml:157
+#: ../qml/nongoogle/AppendNGFeedPage.qml:172
 msgid "No data"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:177
+#: ../qml/nongoogle/AppendNGFeedPage.qml:164
 msgid "Feed Description:"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:210
+#: ../qml/nongoogle/AppendNGFeedPage.qml:197
 #: ../qml/pages/AppendFeedPage.qml:243 ../qml/pages/CreateTopicPage.qml:38
 #: ../qml/pages/TopicManagement.qml:239 ../qml/shorts-app.qml:481
 msgid "Cancel"
 msgstr ""
 
-#: ../qml/nongoogle/AppendNGFeedPage.qml:243
+#: ../qml/nongoogle/AppendNGFeedPage.qml:230
 #: ../qml/pages/AppendFeedPage.qml:276
 msgid "Next"
 msgstr ""
@@ -163,19 +185,19 @@
 msgid "Edit Feed"
 msgstr ""
 
-#: ../qml/pages/EditFeedPage.qml:20
+#: ../qml/pages/EditFeedPage.qml:19
 msgid "Done"
 msgstr ""
 
-#: ../qml/pages/EditFeedPage.qml:93
+#: ../qml/pages/EditFeedPage.qml:82
 msgid "Title: "
 msgstr ""
 
-#: ../qml/pages/EditFeedPage.qml:116
+#: ../qml/pages/EditFeedPage.qml:103
 msgid "URL: "
 msgstr ""
 
-#: ../qml/pages/EditFeedPage.qml:134
+#: ../qml/pages/EditFeedPage.qml:120
 msgid "Topic: "
 msgstr ""
 
@@ -183,18 +205,31 @@
 msgid "Settings"
 msgstr ""
 
-#: ../qml/pages/PageSettings.qml:21
+#: ../qml/pages/PageSettings.qml:38
+msgid ""
+"For those who living in some special regions cannot access Google, the "
+"switch below can disable Google RSS engine, Shorts will directly gets data "
+"from RSS sources."
+msgstr ""
+
+#: ../qml/pages/PageSettings.qml:46
 msgid "Use Google Search: "
 msgstr ""
 
+#: ../qml/pages/PageSettings.qml:71
+msgid ""
+"For those users, who want to import their RSS feeds from other sources, "
+"please press the button below."
+msgstr ""
+
+#: ../qml/pages/PageSettings.qml:81
+msgid "Import OMPL"
+msgstr ""
+
 #: ../qml/pages/TopicManagement.qml:13 ../qml/shorts-app.qml:219
 msgid "Edit topics"
 msgstr ""
 
-#: ../qml/pages/TopicManagement.qml:258
-msgid "Confirm"
-msgstr ""
-
 #: ../qml/pages/TopicManagement.qml:274
 msgid "Add Feed"
 msgstr ""

=== added file 'shorts/qml/content/ContentPickerDialog.qml'
--- shorts/qml/content/ContentPickerDialog.qml	1970-01-01 00:00:00 +0000
+++ shorts/qml/content/ContentPickerDialog.qml	2016-01-14 15:39:13 +0000
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright 2016 Canonical Ltd.
+ *
+ * This file is part of shorts-app.
+ *
+ * shorts-app 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; version 3.
+ *
+ * shorts-app is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.ListItems 1.3 as ListItem
+import Ubuntu.Components.Popups 1.3
+
+import Ubuntu.Content 1.1
+
+Page {
+    id: picker
+    visible: false
+
+    property var pickerParent
+
+    ContentPeerPicker {
+        visible: parent.visible
+
+        // Type of handler: Source, Destination, or Share
+        handler: ContentHandler.Source
+        // well know content type
+        contentType: ContentType.All
+
+        onPeerSelected: {
+            peer.selectionType = ContentTransfer.Single;
+            pickerParent.activeTransfer = peer.request();
+            pageStack.pop();
+        }
+
+        onCancelPressed: {
+            pageStack.pop();
+        }
+    }
+}

=== added file 'shorts/qml/content/ImportFeeds.qml'
--- shorts/qml/content/ImportFeeds.qml	1970-01-01 00:00:00 +0000
+++ shorts/qml/content/ImportFeeds.qml	2016-01-14 15:39:13 +0000
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2013, 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.ListItems 1.3 as ListItem
+import Ubuntu.Components.Popups 1.3
+
+import QtQuick.XmlListModel 2.0
+import Ubuntu.Content 1.1
+
+import "../utils/databasemodule_v2.js" as DB
+//import "../."
+
+Page {
+    id: importFeeds
+    title: i18n.tr("Import Feeds")
+    flickable: null
+
+    property list<ContentItem> importItems
+    property var activeTransfer
+    property list<ContentPeer> peers
+
+
+    property var headActions: [acConfirmbutton]
+    head.actions: repeaterFeedList.opmlList == undefined ? null : headActions
+    Action {
+        id: acConfirmbutton
+        text:  i18n.tr("Confirm")
+        iconName: "tick"
+        onTriggered: {
+            DB.importOPMLobject(repeaterFeedList.opmlList)
+            mainView.reloadViews()
+            pageStack.pop()
+            mainView.refresh()
+        }
+    }
+
+    Component.onCompleted: {
+//        timerPush.start()
+    }
+
+    /////////////////////////////////////////////////////////////////////   content hub    start here
+    ContentTransferHint {
+        anchors.fill: importFeeds
+        activeTransfer: importFeeds.activeTransfer
+    }
+
+    Connections {
+        target: importFeeds.activeTransfer
+        onStateChanged: {
+            console.log("StateChanged: " + importFeeds.activeTransfer.state);
+            if (importFeeds.activeTransfer.state === ContentTransfer.Charged) {
+                importFeeds.importItems = importFeeds.activeTransfer.items;
+                console.log("importItems[0].url: " + importFeeds.importItems[0].url);
+                opmlParser.opmlPath = importFeeds.importItems[0].url
+            }
+        }
+    }
+
+    Connections {
+        target: ContentHub
+        onImportRequested: {
+            console.log ("Import requested: " + transfer.state);
+//            titleItem.text = "Imported items";
+            importFeeds.activeTransfer = transfer;
+            if (importFeeds.activeTransfer.state === ContentTransfer.Charged) {
+                importFeeds.importItems = importFeeds.activeTransfer.items;
+//                opmlParser.opmlPath = importFeeds.importItems[0].url
+            }
+        }
+    }
+
+    Timer {
+        id: timerPush
+        interval: 100; running: false; repeat: false; triggeredOnStart: false
+        onTriggered: {
+//            pageStack.pop()
+//            pageStack.push(Qt.resolvedUrl("./ContentPickerDialog.qml"))
+            pageStack.push(contentPickerDialog)
+        }
+    }
+
+    //////////////////////////////////////        content hub
+    ContentPickerDialog{
+        id: contentPickerDialog
+        pickerParent: importFeeds
+    }
+    /////////////////////////////////////////////////////////////////////   content hub    end here
+
+    /////////////////////////////////////////////////////////////////////   opml content    start here
+    Flickable {
+        id: scrollArea
+        objectName: "repeaterFeedList_flickable"
+
+        clip: true
+
+        anchors {
+            fill: parent
+            bottomMargin: units.gu(1); topMargin: units.gu(1)
+        }
+
+        contentWidth: width
+        contentHeight: contentItem.childrenRect.height
+
+        Column {
+            anchors {
+                left: parent.left; right: parent.right
+            }
+            height: childrenRect.height
+            spacing: units.gu(1)
+
+            Repeater {
+                id: repeaterFeedList
+
+                model: 0
+                property var opmlList: undefined
+
+                function addOpmlObjs (opmlObjs) {
+                    console.log("opmlObjs.length: ", opmlObjs.length)
+                    repeaterFeedList.model = opmlObjs.length
+                    opmlList = opmlObjs
+                }
+
+                Column {
+                    id: columnContent
+                    anchors {
+                        left: parent.left; right: parent.right
+                    }
+                    height: liStandardRoot.isHidden ? units.gu(6.5) : childrenRect.height
+                    clip:true
+
+                    Behavior on height { UbuntuNumberAnimation{} }
+
+                    ListItem.Empty {
+                        id: liStandardRoot
+
+                        property var opmlRoot: repeaterFeedList.opmlList == undefined ?
+                            undefined : repeaterFeedList.opmlList[index]
+                        property bool isHidden: true
+                        property alias isChecked: checkboxRoot.checked
+
+                        onIsCheckedChanged: {
+                            if (liStandardRoot.isChecked) {
+                                liStandardRoot.opmlRoot.isSelected = true
+                            }
+                            else {
+                                liStandardRoot.opmlRoot.isSelected = false
+                            }
+                        }
+
+                        Row {
+                            anchors
+                            {
+                                top: parent.top; bottom: parent.bottom; left: parent.left;
+                                leftMargin: units.gu(1); topMargin: units.gu(0.7); bottomMargin: units.gu(1);
+                            }
+                            spacing: units.gu(2)
+
+                            CheckBox {
+                                id: checkboxRoot
+                            }
+
+                            Label{
+                                id: labelRootName
+                                objectName: "labelTopicName"
+                                anchors.verticalCenter: parent.verticalCenter
+                                fontSize: "large"
+                                text: liStandardRoot.opmlRoot == undefined ? "" : liStandardRoot.opmlRoot.text
+                            }
+                        }
+
+                        Icon{
+                            id: imgArrow
+                            anchors
+                            {
+                                right: parent.right; top: parent.top; bottom: parent.bottom;
+                                topMargin: units.gu(1.5); bottomMargin: units.gu(1.5); rightMargin: units.gu(2)
+                            }
+                            name: "go-to"
+
+                            Behavior on rotation { UbuntuNumberAnimation{} }
+
+                            states: [
+                                State {
+                                    name: "expanded"
+                                    when: !liStandardRoot.isHidden
+
+                                    PropertyChanges
+                                    {
+                                        target: imgArrow
+                                        rotation: 90
+                                    }
+                                }
+                            ]
+                        }
+
+                        onClicked:
+                        {
+                            isHidden = !isHidden
+                        }
+                    }
+
+                    Repeater {
+                        id: repeaterFeedsChildren
+                        model: liStandardRoot.opmlRoot == undefined ?
+                                   "" : liStandardRoot.opmlRoot.children
+
+                        ListItem.Standard {
+                            id: listitemFeed
+                            text: opmlFeed.title
+//                            selected: false
+                            visible: !liStandardRoot.isHidden
+//                            height: visible ? units.gu(6) : 0
+
+                            property var opmlFeed: liStandardRoot.opmlRoot == undefined ?
+                                undefined : liStandardRoot.opmlRoot.children[index]
+
+                            Connections {
+                                target: liStandardRoot
+                                onIsCheckedChanged: {
+                                    if (liStandardRoot.isChecked) {
+                                        listitemFeed.selected = true
+                                        checkBoxFeed.checked = true
+                                        listitemFeed.opmlFeed.isSelected = true
+                                    }
+                                    else {
+                                        listitemFeed.selected = false
+                                        checkBoxFeed.checked = false
+                                        listitemFeed.opmlFeed.isSelected = false
+                                    }
+                                }
+                            }
+
+                            CheckBox{
+                                id: checkBoxFeed
+                                anchors
+                                {
+                                    right: parent.right; rightMargin: units.gu(5)
+                                    verticalCenter: parent.verticalCenter
+                                }
+                                enabled: liStandardRoot.isChecked
+//                                checked: parent.opmlFeed.isSelected
+
+                                MouseArea { anchors.fill: parent; onClicked: {} }
+                            }
+
+                            Rectangle {
+                                anchors.fill: parent
+                                color: "#55000000"
+                                visible: !liStandardRoot.isChecked
+                            }
+
+                            onClicked:
+                            {
+//                                importFeeds.test()
+                                if (liStandardRoot.isChecked) {
+                                    selected = !selected
+                                    //                                console.log("xmlUrl: ", opmlFeed.xmlUrl /*JSON.stringify(opmlRoot)*/)
+                                    listitemFeed.opmlFeed.isSelected = !listitemFeed.opmlFeed.isSelected
+                                    checkBoxFeed.checked = listitemFeed.opmlFeed.isSelected
+                                }
+                            }
+                        }
+                    }
+                }
+            } //repeaterFeedList
+        }
+
+    }
+    /////////////////////////////////////////////////////////////////////   opml content    end here
+
+    /////////////////////////////////////////////////////////////////////   import tips    start here
+    Label {
+        visible: repeaterFeedList.opmlList == undefined
+        anchors { left: parent.left; right: parent.right; margins: units.gu(3) }
+        anchors { top: parent.top; topMargin: units.gu(6) }
+//        horizontalAlignment: Text.AlignHCenter
+        wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+        text: i18n.tr("Attention please, before importing opml file, Shorts only support one opml structure. <br><br>
+For example: <br><br>Folder0<br>  ---feed0<br>  ---feed1<br>Folder1<br>  ---feed3<br>  ---feed4")
+    }
+
+    Column {
+//        width: childrenRect.width
+//        height: childrenRect.height
+        anchors.centerIn: parent
+        spacing: units.gu(1)
+        visible: repeaterFeedList.opmlList == undefined
+
+        Label {
+            text: i18n.tr("Please select an opml file")
+        }
+
+        Button {
+            anchors.horizontalCenter: parent.horizontalCenter
+            text: i18n.tr("Open")
+
+            onClicked: {
+                pageStack.push(contentPickerDialog)
+            }
+        }
+    }
+    /////////////////////////////////////////////////////////////////////   import tips    end here
+
+    //////////////////////////////////////////////////////////////////         Opml Parser
+    OpmlParser {
+        id: opmlParser
+        opmlPath: ""
+
+        onParseFinished: {
+            repeaterFeedList.addOpmlObjs(opml)
+        }
+    }
+}

=== added file 'shorts/qml/content/OpmlParser.qml'
--- shorts/qml/content/OpmlParser.qml	1970-01-01 00:00:00 +0000
+++ shorts/qml/content/OpmlParser.qml	2016-01-14 15:39:13 +0000
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013, 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+//import Ubuntu.Components.ListItems 1.3 as ListItem
+//import Ubuntu.Components.Popups 1.3
+
+import QtQuick.XmlListModel 2.0
+
+import "../utils/databasemodule_v2.js" as DB
+//import "../."
+
+Item {
+    id: opmlParser
+
+    property string opmlPath: ""
+
+    signal parseFinished(var opml)
+
+    XmlListModel {
+        id: modelOpml
+        property string subTitle
+
+        property var opmlObj
+        property int currentRootsIndex: 0
+        property int allRootsCount: 0
+
+        function getAllRootObj() {
+            var rootObjs = new Array
+            // push all root objects to array
+            for (var i=0; i<modelOpml.count; i++) {
+                rootObjs.push(
+                            //modelOpml.get(i)
+                            {
+                                "text": modelOpml.get(i).text
+                                ,"title": modelOpml.get(i).title
+                                ,"xmlUrl": modelOpml.get(i).xmlUrl
+                                ,"htmlUrl": modelOpml.get(i).htmlUrl
+                                ,"isSelected": false
+                            }
+                            )
+            }
+            opmlObj = rootObjs
+            // set first subTitle
+            subTitle = rootObjs[currentRootsIndex].text
+        }
+
+        function getChildObjsFromOneRoot() {
+            var rootObjs = opmlObj
+            var array = new Array
+            for (var i=0; i<modelOpml.count; i++) {
+//                rootObjs[currentRootsIndex].children.push(
+                if (modelOpml.get(i).xmlUrl != "") {
+                    array.push(
+                                {
+                                    "text": modelOpml.get(i).text
+                                    ,"title": modelOpml.get(i).title
+                                    ,"xmlUrl": modelOpml.get(i).xmlUrl
+                                    ,"htmlUrl": modelOpml.get(i).htmlUrl
+                                    ,"isSelected": false
+                                }
+                                )
+                }
+            }
+            rootObjs[currentRootsIndex].children = array
+            opmlObj = rootObjs
+            // if not end of the roots, set next subTitle
+            if (currentRootsIndex < (allRootsCount - 1)) {
+                currentRootsIndex ++
+                subTitle = rootObjs[currentRootsIndex].text
+            }
+            // else send finish signal
+            else {
+                console.log("children objs: ", JSON.stringify(opmlObj))
+                parseFinished(opmlObj)
+            }
+        }
+
+        onStatusChanged: {
+            console.log("model status:", status)
+            if (status == XmlListModel.Ready) {
+                if (!subTitle) {
+                    allRootsCount = count
+                    // get all root object
+                    getAllRootObj()
+                }
+                else {
+                    // load children object one by one
+//                    console.log("children objs: ", JSON.stringify(opmlObj))
+//                    console.log("model count: ", count)
+                    getChildObjsFromOneRoot()
+                }
+            }
+            else if (status == XmlListModel.Error) {
+                console.log("XmlListModel.Error: ", errorString())
+            }
+        }
+
+        query: subTitle ? "/opml/body/outline[@text='" + subTitle + "']/outline": "/opml/body/outline"
+        //        query: "/opml/body/outline"
+        source:   opmlPath
+
+        XmlRole { name: 'text'; query: '@text/string()' }
+        XmlRole { name: 'title'; query: '@title/string()' }
+        XmlRole { name: 'type'; query: '@type/string()' }
+        XmlRole { name: 'xmlUrl'; query: '@xmlUrl/string()' }
+        XmlRole { name: 'htmlUrl'; query: '@htmlUrl/string()' }
+    }
+}

=== modified file 'shorts/qml/pages/PageSettings.qml'
--- shorts/qml/pages/PageSettings.qml	2016-01-04 11:46:35 +0000
+++ shorts/qml/pages/PageSettings.qml	2016-01-14 15:39:13 +0000
@@ -23,32 +23,29 @@
 
     Column {
         anchors {
-            top: parent.top; topMargin: units.gu(1)
-            left: parent.left; leftMargin: units.gu(1)
-            right: parent.right; rightMargin: units.gu(1)
+            top: parent.top; topMargin: units.gu(1.5)
+            left: parent.left; leftMargin: units.gu(0)
+            right: parent.right; rightMargin: units.gu(0)
         }
         height: childrenRect.height
-        spacing: units.gu(0.8)
+//        spacing: units.gu(0.8)
 
         /////////////////////////////////////////////////////////////////////   Google RSS engine switch    start here
         Label {
             anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
             horizontalAlignment: Text.AlignHCenter
             wrapMode: Text.WrapAtWordBoundaryOrAnywhere
-            text: i18n.tr("For those who living in some special regions cannot access Google, the switch below can disable Google RSS engine, Shorts will directly get data from RSS sources.")
+            text: i18n.tr("For those who living in some special regions cannot access Google, the switch below can disable Google RSS engine, Shorts will directly gets data from RSS sources.")
         }
 
-        Item { width: 10; height: 1 } // just a separator
-
-        Item {
-            anchors { left: parent.left; right: parent.right; }
-            height: childrenRect.height
-
-            Label {
-                text: i18n.tr("Use Google Search: ")
-            }
-
-            Switch {
+        Item { width: 10; height: units.gu(1); } // just a separator
+
+        ListItem.ThinDivider{ }
+
+        ListItem.Standard {
+            text: i18n.tr("Use Google Search: ")
+            control:
+                Switch {
                 id: swUseGfa
                 anchors.right: parent.right
 
@@ -60,8 +57,37 @@
             }
         }
 
-        ListItem.ThinDivider{ }
+        ListItem.Divider { }
         /////////////////////////////////////////////////////////////////////   Google RSS engine switch    end here
+
+
+        /////////////////////////////////////////////////////////////////////   Import OPML functions    start here
+        Item { width: 10; height: units.gu(1); } // just a separator
+
+        Label {
+            anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
+            horizontalAlignment: Text.AlignHCenter
+            wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+            text: i18n.tr("For those users, who want to import their RSS feeds from other sources, please press the button below.")
+        }
+
+        Item { width: 10; height: units.gu(1); } // just a separator
+        ListItem.ThinDivider{ }
+        Item { width: 10; height: units.gu(1); } // just a separator
+
+        Button {
+            anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
+            anchors.horizontalCenter: parent.horizontalCenter
+            text: i18n.tr("Import OMPL")
+
+            onClicked: {
+                pageStack.push(Qt.resolvedUrl("../content/ImportFeeds.qml")) ;
+            }
+        }
+
+
+        /////////////////////////////////////////////////////////////////////   Import OPML functions    end here
+
     }// Column
 
 }

=== modified file 'shorts/qml/utils/databasemodule_v2.js'
--- shorts/qml/utils/databasemodule_v2.js	2015-07-04 08:38:18 +0000
+++ shorts/qml/utils/databasemodule_v2.js	2016-01-14 15:39:13 +0000
@@ -673,6 +673,82 @@
     return dbResult;
 }
 
+
+/* import opml file into database ~
+ * currently the opml file will be converted to a js object
+ * the following function parse the js object then insert data to database
+ */
+function importOPMLobject(opml) {
+    console.log("opml: ", JSON.stringify(opml))
+//    var dbResult
+    var db = openStdDataBase()
+    db.transaction(function (tx) {
+        for (var i=0; i<opml.length; i++) {
+            if (opml[i].isSelected) {
+                // check if the topic(tag) is exist or not
+                // if true, get its id
+                // if false, insert a new topic(tag)
+                var resTag = tx.executeSql("SELECT * FROM tag WHERE name=?", [opml[i].text])
+                var tagID = -1
+                if (resTag.rows.length > 0) {
+                    console.log("Database, importOPMLobject: already exist tag with name: ", opml[i].text, "ID", resTag.rows.item(0).id)
+                    tagID = resTag.rows.item(0).id
+                }
+                else {
+                    resTag = tx.executeSql('INSERT INTO tag (name) VALUES(?)',
+                                           [opml[i].text])
+                    tagID = resTag.insertId
+                    console.log("Database, importOPMLobject: tag INSERT ID: ", resTag.insertId)
+                }
+                // topic(tag) end,    tagID is the unique id of a topic
+
+                // insert feed start
+                for (var j=0; j<opml[i].children.length; j++) {
+                    if (opml[i].children[j].isSelected) {
+                        /* Check uniqueness.
+                    */
+                        var feedID = -1
+                        var feedResult = tx.executeSql("SELECT * FROM feed WHERE source=?", [opml[i].children[j].xmlUrl])
+                        if (feedResult.rows.length > 0) {
+                            console.log("Database, importOPMLobject: already exist feed with source: ", opml[i].children[j].xmlUrl, "ID", feedResult.rows.item(0).id)
+                            feedID = feedResult.rows.item(0).id
+                        }
+                        else {
+                            // insert feed
+                            feedResult = tx.executeSql('INSERT INTO feed (title, source) VALUES(?, ?)',
+                                                       [opml[i].children[j].title , opml[i].children[j].xmlUrl])
+                            feedID = feedResult.insertId
+                            console.log("Database, importOPMLobject: feed INSERT ID: ", feedID)
+                        }
+
+                        // insert feed_tag
+                        /* Check uniqueness.
+                     */
+                        var feedTagResult = tx.executeSql("SELECT * FROM feed_tag WHERE feed_id=? AND tag_id=? ", [feedID, tagID])
+                        if (feedTagResult.rows.length > 0) {
+                            console.log("Database, importOPMLobject: already exist feed_tag with source: ", feedID, tagID)
+                        }
+                        else {
+                            feedTagResult = tx.executeSql('INSERT INTO feed_tag (feed_id, tag_id) VALUES(?, ?)',
+                                                          [feedID, tagID])
+                            console.log("Database, importOPMLobject: feed_tag INSERT ID: ", feedTagResult.insertId, feedID, tagID)
+                        }
+                    }
+                }
+            }
+            else {
+                continue
+            }
+
+        }
+
+    }
+    )
+//    return dbResult;
+}
+
+
+
 /* operations for testing
  * include clear and drop operations
  * not completed yet

=== modified file 'shorts/shorts.qrc'
--- shorts/shorts.qrc	2015-12-02 17:03:41 +0000
+++ shorts/shorts.qrc	2016-01-14 15:39:13 +0000
@@ -32,10 +32,13 @@
         <file>qml/shorts-app.qml</file>
         <file>qml/content/SharePage.qml</file>
         <file>qml/components/DarkModeShader.qml</file>
-	<file>qml/nongoogle/XmlNetwork.qml</file>
-         <file>qml/nongoogle/AppendNGFeedPage.qml</file>
-         <file>qml/pages/PageSettings.qml</file>
-         <file>qml/nongoogle/Positioner.qml</file>
+        <file>qml/nongoogle/XmlNetwork.qml</file>
+        <file>qml/nongoogle/AppendNGFeedPage.qml</file>
+        <file>qml/pages/PageSettings.qml</file>
+        <file>qml/nongoogle/Positioner.qml</file>
+        <file>qml/content/OpmlParser.qml</file>
+        <file>qml/content/ImportFeeds.qml</file>
+        <file>qml/content/ContentPickerDialog.qml</file>
     </qresource>
     <qresource prefix="/img">
         <file>qml/icons/add.svg</file>


Follow ups