merge separate-activities
This commit is contained in:
commit
d361b0fec4
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.fox.ttrss"
|
||||
android:versionCode="98"
|
||||
android:versionName="0.7.6" >
|
||||
android:versionCode="100"
|
||||
android:versionName="0.8.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="8"
|
||||
@ -13,21 +13,14 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".GlobalState"
|
||||
android:allowBackup="true"
|
||||
android:backupAgent=".util.PrefsBackupAgent"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name" >
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".offline.OfflineActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name=".OnlineActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -40,6 +33,41 @@
|
||||
android:label="@string/preferences" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".FeedsActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".HeadlinesActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".CommonActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ArticleActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".offline.OfflineActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".offline.OfflineFeedsActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".offline.OfflineHeadlinesActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".offline.OfflineDownloadService"
|
||||
android:enabled="true" />
|
||||
|
Binary file not shown.
@ -7,11 +7,6 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout7"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout1"
|
||||
android:layout_width="match_parent"
|
||||
@ -100,7 +95,6 @@
|
||||
android:src="@drawable/ic_rss_bw" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
|
@ -7,11 +7,6 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout7"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout1"
|
||||
android:layout_width="match_parent"
|
||||
@ -100,7 +95,6 @@
|
||||
android:src="@drawable/ic_rss_bw" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
|
@ -7,11 +7,6 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout7"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout1"
|
||||
android:layout_width="match_parent"
|
||||
@ -100,7 +95,6 @@
|
||||
android:src="@drawable/ic_rss_bw" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
|
48
res/layout-sw600dp-port/headlines.xml
Normal file
48
res/layout-sw600dp-port/headlines.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?loadingBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="visible" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/loading_message"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0.3"
|
||||
android:background="?headlinesBackgroundSolid" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/article_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0.7"
|
||||
android:background="?articleBackground" >
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,61 +0,0 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/feeds_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.6"
|
||||
android:background="?feedlistBackground" >
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/vertical_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.4"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0.30"
|
||||
android:background="?headlinesBackgroundSolid" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/article_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="?articleBackground"
|
||||
android:layout_weight="0.70" >
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:background="?loadingBackground"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/loading_message"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
@ -1,39 +1,8 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/feeds_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.3"
|
||||
android:background="?feedlistBackground" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.4"
|
||||
android:background="?headlinesBackground" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/article_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.6"
|
||||
android:background="?articleBackground" >
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
@ -52,4 +21,28 @@
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/feeds_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.4"
|
||||
android:background="?feedlistBackground" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.6"
|
||||
android:background="?headlinesBackground" >
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
49
res/layout-sw600dp/headlines.xml
Normal file
49
res/layout-sw600dp/headlines.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/headlines"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?loadingBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="visible" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/loading_message"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.4"
|
||||
android:background="?headlinesBackground" >
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/article_fragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.6"
|
||||
android:background="?articleBackground" >
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,18 +1,9 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/article_fragment"
|
||||
android:layout_width="fill_parent"
|
||||
android:padding="5sp"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<LinearLayout
|
||||
xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/article_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="5sp" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/article_header"
|
||||
@ -24,6 +15,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:textColor="?linkColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
@ -91,6 +83,13 @@
|
||||
android:layout_weight="0"
|
||||
android:text="@string/attachment_view" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/attachment_share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:text="@string/attachment_share" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/attachment_copy"
|
||||
android:layout_width="wrap_content"
|
||||
@ -98,6 +97,5 @@
|
||||
android:layout_weight="0"
|
||||
android:text="@string/attachment_copy" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -19,7 +19,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:id="@+id/feeds_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
</FrameLayout>
|
27
res/layout/headlines.xml
Normal file
27
res/layout/headlines.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/headlines"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/loading_message" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/headlines_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
@ -4,18 +4,10 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?headlineNormalBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:gravity="center"
|
||||
android:padding="5dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loadmore_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadmore_progress"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
@ -29,6 +21,5 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?headlineTextColor"
|
||||
android:text="@string/loading_message" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
14
res/layout/login.xml
Normal file
14
res/layout/login.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:gravity="center"
|
||||
android:layout_height="fill_parent" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/loading_message" />
|
||||
|
||||
</LinearLayout>
|
@ -1,5 +1,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/browse_headlines"
|
||||
android:title="@string/category_browse_headlines"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/browse_articles"
|
||||
android:title="@string/category_browse_articles"/>
|
||||
|
@ -1,5 +1,13 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/browse_headlines"
|
||||
android:title="@string/category_browse_headlines"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/browse_articles"
|
||||
android:title="@string/category_browse_articles"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/catchup_feed"
|
||||
android:title="@string/catchup"/>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<group android:id="@+id/menu_group_logged_in" >
|
||||
|
||||
<group android:id="@+id/menu_group_feeds" >
|
||||
|
||||
<!--
|
||||
@ -17,136 +16,125 @@
|
||||
android:icon="@android:drawable/ic_menu_agenda"
|
||||
android:showAsAction=""
|
||||
android:title="@string/menu_all_feeds"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/update_feeds"
|
||||
android:icon="@android:drawable/ic_menu_rotate"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/update_feeds"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/go_offline"
|
||||
android:icon="@drawable/ic_menu_cloud"
|
||||
android:showAsAction=""
|
||||
android:title="@string/go_offline"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/logout"
|
||||
android:icon="@drawable/ic_menu_exit"
|
||||
android:showAsAction=""
|
||||
android:title="@string/logout"/>
|
||||
</group>
|
||||
|
||||
<group android:id="@+id/menu_group_headlines" >
|
||||
|
||||
<item
|
||||
android:id="@+id/go_offline"
|
||||
android:icon="@drawable/ic_menu_cloud"
|
||||
android:title="@string/go_offline"/>
|
||||
|
||||
android:id="@+id/update_headlines"
|
||||
android:icon="@android:drawable/ic_menu_rotate"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/update_headlines"/>
|
||||
<item
|
||||
android:id="@+id/search"
|
||||
android:actionViewClass="android.widget.SearchView"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:showAsAction="ifRoom|collapseActionView"
|
||||
android:title="@string/search"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/headlines_mark_as_read"
|
||||
android:icon="@drawable/ic_menu_tick"
|
||||
android:showAsAction=""
|
||||
android:title="@string/headlines_mark_as_read"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/headlines_select"
|
||||
android:icon="@drawable/ic_menu_database"
|
||||
android:showAsAction="ifRoom"
|
||||
android:showAsAction=""
|
||||
android:title="@string/headlines_select"/>
|
||||
|
||||
<!--
|
||||
<item
|
||||
android:id="@+id/close_feed"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction=""
|
||||
android:title="@string/close_feed"/>
|
||||
-->
|
||||
|
||||
</group>
|
||||
|
||||
<group android:id="@+id/menu_group_headlines_selection" >
|
||||
|
||||
<item
|
||||
android:id="@+id/selection_toggle_unread"
|
||||
android:icon="@drawable/ic_menu_tick"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/selection_toggle_unread"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/selection_toggle_marked"
|
||||
android:icon="@drawable/ic_menu_marked"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/selection_toggle_marked"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/selection_toggle_published"
|
||||
android:icon="@drawable/ic_menu_rss"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/selection_toggle_published"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/selection_select_none"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction=""
|
||||
android:title="@string/selection_select_none"/>
|
||||
</group>
|
||||
|
||||
<group android:id="@+id/menu_group_article" >
|
||||
|
||||
<item
|
||||
android:id="@+id/toggle_marked"
|
||||
android:icon="@drawable/ic_menu_marked"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/article_toggle_marked"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/toggle_published"
|
||||
android:icon="@drawable/ic_menu_rss"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/article_toggle_published"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/share_article"
|
||||
android:actionProviderClass="android.widget.ShareActionProvider"
|
||||
android:icon="@android:drawable/ic_menu_share"
|
||||
android:showAsAction=""
|
||||
android:actionProviderClass="android.widget.ShareActionProvider"
|
||||
android:title="@string/share_article"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/set_unread"
|
||||
android:icon="@android:drawable/ic_menu_recent_history"
|
||||
android:showAsAction=""
|
||||
android:title="@string/article_set_unread"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/catchup_above"
|
||||
android:icon="@drawable/ic_menu_tick"
|
||||
android:title="@string/article_mark_read_above"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/set_labels"
|
||||
android:icon="@drawable/ic_menu_marked"
|
||||
android:title="@string/article_set_labels"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/article_set_note"
|
||||
android:showAsAction=""
|
||||
android:title="@string/article_set_note"/>
|
||||
|
||||
<!--
|
||||
<item
|
||||
android:id="@+id/close_article"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction=""
|
||||
android:title="@string/close_article"/>
|
||||
-->
|
||||
|
||||
</group>
|
||||
|
||||
<item
|
||||
android:id="@+id/donate"
|
||||
android:showAsAction=""
|
||||
android:title="@string/donate"/>
|
||||
</group>
|
||||
|
||||
<item
|
||||
@ -156,7 +144,6 @@
|
||||
android:title="@string/preferences"/>
|
||||
|
||||
<group android:id="@+id/menu_group_logged_out" >
|
||||
|
||||
<item
|
||||
android:id="@+id/login"
|
||||
android:icon="@android:drawable/ic_menu_rotate"
|
||||
|
@ -23,11 +23,11 @@
|
||||
android:title="@string/menu_all_feeds"/>
|
||||
</group>
|
||||
<group android:id="@+id/menu_group_headlines" >
|
||||
<item
|
||||
<!-- <item
|
||||
android:id="@+id/go_online"
|
||||
android:icon="@drawable/ic_menu_cloud"
|
||||
android:title="@string/go_online"
|
||||
android:visible="false"/>
|
||||
android:visible="false"/> -->
|
||||
<item
|
||||
android:id="@+id/search"
|
||||
android:actionViewClass="android.widget.SearchView"
|
||||
@ -41,13 +41,13 @@
|
||||
<item
|
||||
android:id="@+id/headlines_select"
|
||||
android:icon="@drawable/ic_menu_database"
|
||||
android:showAsAction="ifRoom"
|
||||
android:showAsAction=""
|
||||
android:title="@string/headlines_select"/>
|
||||
<item
|
||||
<!-- <item
|
||||
android:id="@+id/close_feed"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction=""
|
||||
android:title="@string/close_feed"/>
|
||||
android:title="@string/close_feed"/> -->
|
||||
</group>
|
||||
<group android:id="@+id/menu_group_headlines_selection" >
|
||||
<item
|
||||
@ -97,11 +97,11 @@
|
||||
android:id="@+id/catchup_above"
|
||||
android:icon="@drawable/ic_menu_tick"
|
||||
android:title="@string/article_mark_read_above"/>
|
||||
<item
|
||||
<!-- <item
|
||||
android:id="@+id/close_article"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction=""
|
||||
android:title="@string/close_article"/>
|
||||
android:title="@string/close_article"/> -->
|
||||
</group>
|
||||
|
||||
<item
|
||||
|
@ -28,7 +28,7 @@
|
||||
<item name="feedlistBackground">@drawable/ics_divider_vertical</item>
|
||||
<item name="unreadCounterColor">#303030</item>
|
||||
<item name="headlinesBackground">@drawable/headlines_dark</item>
|
||||
<item name="headlinesBackgroundSolid">@android:color/transparent</item>
|
||||
<item name="headlinesBackgroundSolid">@drawable/headlines_dark</item>
|
||||
<item name="articleBackground">@android:color/black</item>
|
||||
<item name="headlineSelectedBackground">@drawable/headline_row_selected_dark</item>
|
||||
<item name="headlineSelectedBackgroundSolid">@color/ics_cyan</item>
|
||||
|
@ -17,5 +17,13 @@
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string-array name="pref_view_mode_names">
|
||||
<item>@string/category_browse_headlines</item>
|
||||
<item>@string/category_browse_articles</item>
|
||||
</string-array>
|
||||
<string-array name="pref_view_mode_values">
|
||||
<item>HEADLINES</item>
|
||||
<item>ARTICLES</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
@ -24,7 +24,7 @@
|
||||
<string name="loading_message">Loading, please wait…</string>
|
||||
<string name="menu_unread_feeds">Show unread feeds</string>
|
||||
<string name="menu_all_feeds">Show all feeds</string>
|
||||
<string name="update_feeds">Refresh feeds</string>
|
||||
<string name="update_feeds">Refresh</string>
|
||||
<string name="share_article">Share article</string>
|
||||
<string name="catchup">Mark read</string>
|
||||
<string name="sort_feeds_by_unread">Sort feeds by unread count</string>
|
||||
@ -128,6 +128,11 @@
|
||||
<string name="notify_article_published">Article published</string>
|
||||
<string name="notify_article_unpublished">Article unpublished</string>
|
||||
<string name="notify_article_note_set">Article note saved</string>
|
||||
<string name="force_small_tablet_ui">Force compact interface</string>
|
||||
<string name="prefs_for_tablets">Tablets</string>
|
||||
<string name="update_headlines">Refresh</string>
|
||||
<string name="attachment_share">Share</string>
|
||||
<string name="error_network_unavailable">Error: network unavailable</string>
|
||||
<string name="category_browse_headlines">Browse headlines</string>
|
||||
<string name="pref_default_view_mode">Default feed view</string>
|
||||
<string name="pref_default_view_mode_long">Which feed view to open by default on smartphones</string>
|
||||
<string name="donate_thanks">Donation found, thank you for support!</string>
|
||||
</resources>
|
@ -2,18 +2,21 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<PreferenceCategory android:title="@string/connection" >
|
||||
|
||||
<EditTextPreference
|
||||
android:key="login"
|
||||
android:singleLine="true"
|
||||
android:summary="@string/login_summary"
|
||||
android:title="@string/login" >
|
||||
</EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="password"
|
||||
android:password="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/password" >
|
||||
</EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:hint="@string/default_url"
|
||||
android:inputType="textUri"
|
||||
@ -28,13 +31,16 @@
|
||||
android:key="ssl_trust_any"
|
||||
android:title="@string/ssl_trust_any" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/http_authentication" >
|
||||
|
||||
<EditTextPreference
|
||||
android:key="http_login"
|
||||
android:singleLine="true"
|
||||
android:summary="@string/http_login_summary"
|
||||
android:title="@string/login" >
|
||||
</EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="http_password"
|
||||
android:password="true"
|
||||
@ -42,9 +48,9 @@
|
||||
android:title="@string/password" >
|
||||
</EditTextPreference>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="category_look_and_feel"
|
||||
android:title="@string/look_and_feel" >
|
||||
|
||||
<PreferenceCategory android:key="category_look_and_feel" android:title="@string/look_and_feel" >
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="THEME_DARK"
|
||||
android:entries="@array/pref_theme_names"
|
||||
@ -52,6 +58,7 @@
|
||||
android:key="theme"
|
||||
android:summary="@string/pref_theme_long"
|
||||
android:title="@string/pref_theme" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="0"
|
||||
android:entries="@array/pref_font_size_names"
|
||||
@ -63,50 +70,56 @@
|
||||
android:defaultValue="false"
|
||||
android:key="sort_feeds_by_unread"
|
||||
android:title="@string/sort_feeds_by_unread" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="download_feed_icons"
|
||||
android:title="@string/download_feed_icons" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="enable_cats"
|
||||
android:title="@string/enable_cats" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:dependency="enable_cats"
|
||||
android:key="browse_cats_like_feeds"
|
||||
android:dependency="enable_cats"
|
||||
android:summary="@string/browse_cats_like_feeds_summary"
|
||||
android:title="@string/browse_cats_like_feeds" />
|
||||
<CheckBoxPreference
|
||||
|
||||
<!-- <CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="combined_mode"
|
||||
android:summary="@string/combined_mode_summary"
|
||||
android:title="@string/combined_mode" />
|
||||
android:title="@string/combined_mode" /> -->
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="justify_article_text"
|
||||
android:title="@string/justify_article_text" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="HEADLINES"
|
||||
android:entries="@array/pref_view_mode_names"
|
||||
android:entryValues="@array/pref_view_mode_values"
|
||||
android:key="default_view_mode"
|
||||
android:summary="@string/pref_default_view_mode_long"
|
||||
android:title="@string/pref_default_view_mode" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="category_tablets"
|
||||
android:title="@string/prefs_for_tablets" >
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="tablet_article_swipe"
|
||||
android:title="@string/tablet_article_swipe" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="force_small_tablet_ui"
|
||||
android:title="@string/force_small_tablet_ui" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/offline_mode" >
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="offline_image_cache_enabled"
|
||||
android:summary="@string/offline_image_cache_enabled_summary"
|
||||
android:title="@string/offline_image_cache_enabled" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/debugging" >
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="transport_debugging"
|
||||
|
@ -4,29 +4,31 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.CharBuffer;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.protocol.ClientContext;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.fox.ttrss.util.EasySSLSocketFactory;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.http.util.CharArrayBuffer;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.http.AndroidHttpClient;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
@ -38,7 +40,8 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND,
|
||||
HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, API_UNKNOWN, LOGIN_FAILED, INVALID_URL, INCORRECT_USAGE };
|
||||
HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED,
|
||||
API_UNKNOWN, LOGIN_FAILED, INVALID_URL, INCORRECT_USAGE, NETWORK_UNAVAILABLE };
|
||||
|
||||
public static final int API_STATUS_OK = 0;
|
||||
public static final int API_STATUS_ERR = 1;
|
||||
@ -46,8 +49,10 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
private String m_api;
|
||||
private boolean m_trustAny = false;
|
||||
private boolean m_transportDebugging = false;
|
||||
protected int m_httpStatusCode = 0;
|
||||
protected int m_responseCode = 0;
|
||||
protected String m_responseMessage;
|
||||
protected int m_apiStatusCode = 0;
|
||||
protected boolean m_canUseProgress = false;
|
||||
protected Context m_context;
|
||||
private SharedPreferences m_prefs;
|
||||
|
||||
@ -97,6 +102,8 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
return R.string.error_invalid_api_url;
|
||||
case INCORRECT_USAGE:
|
||||
return R.string.error_api_incorrect_usage;
|
||||
case NETWORK_UNAVAILABLE:
|
||||
return R.string.error_network_unavailable;
|
||||
default:
|
||||
Log.d(TAG, "getErrorMessage: unknown error code=" + m_lastError);
|
||||
return R.string.error_unknown;
|
||||
@ -106,37 +113,42 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
@Override
|
||||
protected JsonElement doInBackground(HashMap<String, String>... params) {
|
||||
|
||||
if (!isNetworkAvailable()) {
|
||||
m_lastError = ApiError.NETWORK_UNAVAILABLE;
|
||||
return null;
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
|
||||
String requestStr = gson.toJson(new HashMap<String,String>(params[0]));
|
||||
byte[] postData = null;
|
||||
|
||||
try {
|
||||
postData = requestStr.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
m_lastError = ApiError.OTHER_ERROR;
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
disableConnectionReuseIfNecessary();
|
||||
|
||||
if (m_transportDebugging) Log.d(TAG, ">>> (" + requestStr + ") " + m_api);
|
||||
|
||||
AndroidHttpClient client = AndroidHttpClient.newInstance("Tiny Tiny RSS");
|
||||
if (m_trustAny) trustAllHosts();
|
||||
|
||||
if (m_trustAny) {
|
||||
client.getConnectionManager().getSchemeRegistry().register(new Scheme("https", new EasySSLSocketFactory(), 443));
|
||||
}
|
||||
URL url;
|
||||
|
||||
try {
|
||||
|
||||
HttpPost httpPost;
|
||||
|
||||
try {
|
||||
httpPost = new HttpPost(m_api + "/api/");
|
||||
} catch (IllegalArgumentException e) {
|
||||
url = new URL(m_api + "/api/");
|
||||
} catch (Exception e) {
|
||||
m_lastError = ApiError.INVALID_URL;
|
||||
e.printStackTrace();
|
||||
client.close();
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
m_lastError = ApiError.OTHER_ERROR;
|
||||
e.printStackTrace();
|
||||
client.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpContext context = null;
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
|
||||
String httpLogin = m_prefs.getString("http_login", "").trim();
|
||||
String httpPassword = m_prefs.getString("http_password", "").trim();
|
||||
@ -144,56 +156,51 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
if (httpLogin.length() > 0) {
|
||||
if (m_transportDebugging) Log.d(TAG, "Using HTTP Basic authentication.");
|
||||
|
||||
URL targetUrl;
|
||||
try {
|
||||
targetUrl = new URL(m_api);
|
||||
} catch (MalformedURLException e) {
|
||||
m_lastError = ApiError.INVALID_URL;
|
||||
e.printStackTrace();
|
||||
client.close();
|
||||
return null;
|
||||
conn.setRequestProperty("Authorization", "Basic " +
|
||||
Base64.encode((httpLogin + ":" + httpPassword).getBytes("UTF-8"), Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
HttpHost targetHost = new HttpHost(targetUrl.getHost(), targetUrl.getPort(), targetUrl.getProtocol());
|
||||
CredentialsProvider cp = new BasicCredentialsProvider();
|
||||
context = new BasicHttpContext();
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Length", Integer.toString(postData.length));
|
||||
|
||||
cp.setCredentials(
|
||||
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
|
||||
new UsernamePasswordCredentials(httpLogin, httpPassword));
|
||||
OutputStream out = conn.getOutputStream();
|
||||
out.write(postData);
|
||||
out.close();
|
||||
|
||||
context.setAttribute(ClientContext.CREDS_PROVIDER, cp);
|
||||
}
|
||||
m_responseCode = conn.getResponseCode();
|
||||
m_responseMessage = conn.getResponseMessage();
|
||||
|
||||
httpPost.setEntity(new StringEntity(requestStr, "utf-8"));
|
||||
HttpResponse execute = client.execute(httpPost, context);
|
||||
switch (m_responseCode) {
|
||||
case HttpURLConnection.HTTP_OK:
|
||||
StringBuffer response = new StringBuffer();
|
||||
InputStreamReader in = new InputStreamReader(conn.getInputStream(), "UTF-8");
|
||||
char[] buf = new char[256];
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
|
||||
m_httpStatusCode = execute.getStatusLine().getStatusCode();
|
||||
int contentLength = conn.getHeaderFieldInt("Api-Content-Length", -1);
|
||||
|
||||
switch (m_httpStatusCode) {
|
||||
case 200:
|
||||
InputStream content = execute.getEntity().getContent();
|
||||
m_canUseProgress = (contentLength != -1);
|
||||
|
||||
BufferedReader buffer = new BufferedReader(
|
||||
new InputStreamReader(content), 8192);
|
||||
|
||||
String s = "";
|
||||
String response = "";
|
||||
|
||||
while ((s = buffer.readLine()) != null) {
|
||||
response += s;
|
||||
while ((read = in.read(buf)) >= 0) {
|
||||
response.append(buf, 0, read);
|
||||
total += read;
|
||||
publishProgress(Integer.valueOf(total), Integer.valueOf(contentLength));
|
||||
}
|
||||
|
||||
if (m_transportDebugging) Log.d(TAG, "<<< " + response);
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
|
||||
JsonElement result = parser.parse(response);
|
||||
JsonElement result = parser.parse(response.toString());
|
||||
JsonObject resultObj = result.getAsJsonObject();
|
||||
|
||||
m_apiStatusCode = resultObj.get("status").getAsInt();
|
||||
|
||||
client.close();
|
||||
conn.disconnect();
|
||||
|
||||
switch (m_apiStatusCode) {
|
||||
case API_STATUS_OK:
|
||||
@ -217,16 +224,16 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
}
|
||||
|
||||
return null;
|
||||
case 401:
|
||||
case HttpURLConnection.HTTP_UNAUTHORIZED:
|
||||
m_lastError = ApiError.HTTP_UNAUTHORIZED;
|
||||
break;
|
||||
case 403:
|
||||
case HttpURLConnection.HTTP_FORBIDDEN:
|
||||
m_lastError = ApiError.HTTP_FORBIDDEN;
|
||||
break;
|
||||
case 404:
|
||||
case HttpURLConnection.HTTP_NOT_FOUND:
|
||||
m_lastError = ApiError.HTTP_NOT_FOUND;
|
||||
break;
|
||||
case 500:
|
||||
case HttpURLConnection.HTTP_INTERNAL_ERROR:
|
||||
m_lastError = ApiError.HTTP_SERVER_ERROR;
|
||||
break;
|
||||
default:
|
||||
@ -234,7 +241,7 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
break;
|
||||
}
|
||||
|
||||
client.close();
|
||||
conn.disconnect();
|
||||
return null;
|
||||
} catch (javax.net.ssl.SSLPeerUnverifiedException e) {
|
||||
m_lastError = ApiError.SSL_REJECTED;
|
||||
@ -250,7 +257,64 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
client.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void trustAllHosts() {
|
||||
X509TrustManager easyTrustManager = new X509TrustManager() {
|
||||
|
||||
public void checkClientTrusted(
|
||||
X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
// Oh, I am easy!
|
||||
}
|
||||
|
||||
public void checkServerTrusted(
|
||||
X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
// Oh, I am easy!
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {easyTrustManager};
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
try {
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void disableConnectionReuseIfNecessary() {
|
||||
// HTTP connection reuse which was buggy pre-froyo
|
||||
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isNetworkAvailable() {
|
||||
ConnectivityManager cm = (ConnectivityManager)
|
||||
m_context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
|
||||
// if no network is available networkInfo will be null
|
||||
// otherwise check if we are connected
|
||||
if (networkInfo != null && networkInfo.isConnected()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -17,34 +17,38 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ArticleFragment extends Fragment {
|
||||
@SuppressWarnings("unused")
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
private SharedPreferences m_prefs;
|
||||
private Article m_article;
|
||||
private OnlineServices m_onlineServices;
|
||||
private OnlineActivity m_activity;
|
||||
//private Article m_nextArticle;
|
||||
//private Article m_prevArticle;
|
||||
|
||||
@ -60,6 +64,28 @@ public class ArticleFragment extends Fragment {
|
||||
|
||||
private View.OnTouchListener m_gestureListener;
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
/* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo(); */
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.article_link_share:
|
||||
if (true) {
|
||||
((OnlineActivity) getActivity()).shareArticle(m_article);
|
||||
}
|
||||
return true;
|
||||
case R.id.article_link_copy:
|
||||
if (true) {
|
||||
((OnlineActivity) getActivity()).copyToClipboard(m_article.link);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
@ -75,6 +101,8 @@ public class ArticleFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
m_activity.setProgressBarVisibility(true);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
m_article = savedInstanceState.getParcelable("article");
|
||||
}
|
||||
@ -94,14 +122,37 @@ public class ArticleFragment extends Fragment {
|
||||
else
|
||||
titleStr = m_article.title;
|
||||
|
||||
title.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
title.setText(Html.fromHtml("<a href=\""+m_article.link.trim().replace("\"", "\\\"")+"\">" + titleStr + "</a>"));
|
||||
title.setText(titleStr);
|
||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
title.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(m_article.link.trim()));
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
m_activity.toast(R.string.error_other_error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(title);
|
||||
}
|
||||
|
||||
WebView web = (WebView)view.findViewById(R.id.content);
|
||||
|
||||
if (web != null) {
|
||||
web.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int progress) {
|
||||
m_activity.setProgress(Math.round(((float)progress / 100f) * 10000));
|
||||
if (progress == 100) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String content;
|
||||
String cssOverride = "";
|
||||
@ -186,9 +237,10 @@ public class ArticleFragment extends Fragment {
|
||||
|
||||
try {
|
||||
URL url = new URL(a.content_url.trim());
|
||||
String strUrl = url.toString().trim();
|
||||
|
||||
if (a.content_type.indexOf("image") != -1) {
|
||||
content += "<br/><img src=\"" + url.toString().trim().replace("\"", "\\\"") + "\">";
|
||||
if (a.content_type.indexOf("image") != -1 && !articleContent.contains(strUrl)) {
|
||||
content += "<p><img src=\"" + strUrl.replace("\"", "\\\"") + "\"></p>";
|
||||
}
|
||||
|
||||
spinnerArray.add(a);
|
||||
@ -226,11 +278,29 @@ public class ArticleFragment extends Fragment {
|
||||
Attachment attachment = (Attachment) spinner.getSelectedItem();
|
||||
|
||||
if (attachment != null) {
|
||||
m_onlineServices.copyToClipboard(attachment.content_url);
|
||||
m_activity.copyToClipboard(attachment.content_url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Button attachmentsShare = (Button) view.findViewById(R.id.attachment_share);
|
||||
|
||||
if (!m_activity.isPortrait()) {
|
||||
attachmentsShare.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Attachment attachment = (Attachment) spinner.getSelectedItem();
|
||||
|
||||
if (attachment != null) {
|
||||
m_activity.shareText(attachment.content_url);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
attachmentsShare.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} else {
|
||||
view.findViewById(R.id.attachments_holder).setVisibility(View.GONE);
|
||||
}
|
||||
@ -243,7 +313,7 @@ public class ArticleFragment extends Fragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (m_onlineServices.isSmallScreen())
|
||||
if (m_activity.isSmallScreen())
|
||||
web.setOnTouchListener(m_gestureListener);
|
||||
}
|
||||
|
||||
@ -296,7 +366,7 @@ public class ArticleFragment extends Fragment {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_onlineServices = (OnlineServices)activity;
|
||||
m_activity = (OnlineActivity)activity;
|
||||
//m_article = m_onlineServices.getSelectedArticle();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.util.HeadlinesRequest;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
@ -8,17 +13,23 @@ import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class ArticlePager extends Fragment {
|
||||
|
||||
private final String TAG = "ArticlePager";
|
||||
private PagerAdapter m_adapter;
|
||||
private OnlineServices m_onlineServices;
|
||||
private HeadlinesFragment m_hf;
|
||||
private HeadlinesEventListener m_listener;
|
||||
private Article m_article;
|
||||
private ArticleList m_articles = GlobalState.getInstance().m_loadedArticles;
|
||||
private OnlineActivity m_activity;
|
||||
private String m_searchQuery = "";
|
||||
private Feed m_feed;
|
||||
|
||||
private class PagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
@ -28,7 +39,7 @@ public class ArticlePager extends Fragment {
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
Article article = m_hf.getArticleAtPosition(position);
|
||||
Article article = m_articles.get(position);
|
||||
|
||||
if (article != null) {
|
||||
ArticleFragment af = new ArticleFragment(article);
|
||||
@ -39,7 +50,7 @@ public class ArticlePager extends Fragment {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return m_hf.getAllArticles().size();
|
||||
return m_articles.size();
|
||||
}
|
||||
|
||||
}
|
||||
@ -48,21 +59,33 @@ public class ArticlePager extends Fragment {
|
||||
super();
|
||||
}
|
||||
|
||||
public ArticlePager(Article article) {
|
||||
public ArticlePager(Article article, Feed feed) {
|
||||
super();
|
||||
|
||||
m_article = article;
|
||||
m_feed = feed;
|
||||
}
|
||||
|
||||
public void setSearchQuery(String searchQuery) {
|
||||
m_searchQuery = searchQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.article_pager, container, false);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
m_article = savedInstanceState.getParcelable("article");
|
||||
m_feed = savedInstanceState.getParcelable("feed");
|
||||
}
|
||||
|
||||
m_adapter = new PagerAdapter(getActivity().getSupportFragmentManager());
|
||||
|
||||
ViewPager pager = (ViewPager) view.findViewById(R.id.article_pager);
|
||||
|
||||
int position = m_hf.getArticlePosition(m_article);
|
||||
int position = m_articles.indexOf(m_article);
|
||||
|
||||
m_listener.onArticleSelected(m_article, false);
|
||||
|
||||
pager.setAdapter(m_adapter);
|
||||
pager.setCurrentItem(position);
|
||||
@ -78,20 +101,23 @@ public class ArticlePager extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
Article article = m_hf.getArticleAtPosition(position);
|
||||
Article article = m_articles.get(position);
|
||||
|
||||
if (article != null) {
|
||||
if (article.unread) {
|
||||
m_article = article;
|
||||
|
||||
/* if (article.unread) {
|
||||
article.unread = false;
|
||||
m_onlineServices.saveArticleUnread(article);
|
||||
}
|
||||
m_onlineServices.setSelectedArticle(article);
|
||||
m_activity.saveArticleUnread(article);
|
||||
} */
|
||||
|
||||
m_listener.onArticleSelected(article, false);
|
||||
|
||||
//Log.d(TAG, "Page #" + position + "/" + m_adapter.getCount());
|
||||
|
||||
if (position == m_adapter.getCount() - 5) {
|
||||
m_hf.refresh(true);
|
||||
m_adapter.notifyDataSetChanged();
|
||||
if (m_activity.isSmallScreen() && position == m_adapter.getCount() - 5) {
|
||||
Log.d(TAG, "loading more articles...");
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,12 +126,134 @@ public class ArticlePager extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "serial" })
|
||||
private void refresh(boolean append) {
|
||||
m_activity.setLoadingStatus(R.string.blank, true);
|
||||
|
||||
m_activity.setProgressBarVisibility(true);
|
||||
|
||||
if (!m_feed.equals(GlobalState.getInstance().m_activeFeed)) {
|
||||
append = false;
|
||||
}
|
||||
|
||||
HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity) {
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
m_activity.setProgress(progress[0] / progress[1] * 10000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement result) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result != null) {
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
if (m_article.id == 0 || m_articles.indexOf(m_article) == -1) {
|
||||
if (m_articles.size() > 0) {
|
||||
m_article = m_articles.get(0);
|
||||
m_listener.onArticleSelected(m_article, false);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_activity.login();
|
||||
} else {
|
||||
m_activity.toast(getErrorMessage());
|
||||
//setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Feed feed = m_feed;
|
||||
|
||||
final String sessionId = m_activity.getSessionId();
|
||||
final boolean showUnread = m_activity.getUnreadArticlesOnly();
|
||||
int skip = 0;
|
||||
|
||||
if (append) {
|
||||
for (Article a : m_articles) {
|
||||
if (a.unread) ++skip;
|
||||
}
|
||||
|
||||
if (skip == 0) skip = m_articles.size();
|
||||
}
|
||||
|
||||
final int fskip = skip;
|
||||
|
||||
req.setOffset(skip);
|
||||
|
||||
HashMap<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
put("op", "getHeadlines");
|
||||
put("sid", sessionId);
|
||||
put("feed_id", String.valueOf(feed.id));
|
||||
put("show_content", "true");
|
||||
put("include_attachments", "true");
|
||||
put("limit", String.valueOf(HeadlinesFragment.HEADLINES_REQUEST_SIZE));
|
||||
put("offset", String.valueOf(0));
|
||||
put("view_mode", showUnread ? "adaptive" : "all_articles");
|
||||
put("skip", String.valueOf(fskip));
|
||||
put("include_nested", "true");
|
||||
|
||||
if (feed.is_cat) put("is_cat", "true");
|
||||
|
||||
if (m_searchQuery != null && m_searchQuery.length() != 0) {
|
||||
put("search", m_searchQuery);
|
||||
put("search_mode", "");
|
||||
put("match_on", "both");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.execute(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
out.putParcelable("article", m_article);
|
||||
out.putParcelable("feed", m_feed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_hf = (HeadlinesFragment) getActivity().getSupportFragmentManager().findFragmentByTag(MainActivity.FRAG_HEADLINES);
|
||||
m_onlineServices = (OnlineServices)activity;
|
||||
m_listener = (HeadlinesEventListener)activity;
|
||||
m_activity = (OnlineActivity)activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (m_articles.size() == 0 || !m_feed.equals(GlobalState.getInstance().m_activeFeed)) {
|
||||
refresh(false);
|
||||
GlobalState.getInstance().m_activeFeed = m_feed;
|
||||
}
|
||||
|
||||
m_activity.initMenu();
|
||||
}
|
||||
|
||||
public Article getSelectedArticle() {
|
||||
return m_article;
|
||||
}
|
||||
|
||||
public void setActiveArticle(Article article) {
|
||||
if (m_article != article) {
|
||||
m_article = article;
|
||||
|
||||
int position = m_articles.indexOf(m_article);
|
||||
|
||||
ViewPager pager = (ViewPager) getView().findViewById(R.id.article_pager);
|
||||
|
||||
pager.setCurrentItem(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,15 @@ package org.fox.ttrss;
|
||||
|
||||
import org.fox.ttrss.util.DatabaseHelper;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.FloatMath;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class CommonActivity extends FragmentActivity {
|
||||
@ -21,20 +21,43 @@ public class CommonActivity extends FragmentActivity {
|
||||
public final static String FRAG_FEEDS = "feeds";
|
||||
public final static String FRAG_CATS = "cats";
|
||||
|
||||
private SharedPreferences m_prefs;
|
||||
|
||||
private SQLiteDatabase m_readableDb;
|
||||
private SQLiteDatabase m_writableDb;
|
||||
|
||||
private boolean m_smallScreenMode = true;
|
||||
private boolean m_compatMode = false;
|
||||
private boolean m_smallTablet = false;
|
||||
|
||||
protected void setSmallScreen(boolean smallScreen) {
|
||||
Log.d(TAG, "m_smallScreenMode=" + smallScreen);
|
||||
m_smallScreenMode = smallScreen;
|
||||
}
|
||||
|
||||
public boolean getUnreadArticlesOnly() {
|
||||
return GlobalState.getInstance().m_unreadArticlesOnly;
|
||||
}
|
||||
|
||||
public boolean getUnreadOnly() {
|
||||
return GlobalState.getInstance().m_unreadOnly;
|
||||
}
|
||||
|
||||
public void setUnreadOnly(boolean unread) {
|
||||
GlobalState.getInstance().m_unreadOnly = unread;
|
||||
}
|
||||
|
||||
public void setUnreadArticlesOnly(boolean unread) {
|
||||
GlobalState.getInstance().m_unreadArticlesOnly = unread;
|
||||
}
|
||||
|
||||
public void setLoadingStatus(int status, boolean showProgress) {
|
||||
TextView tv = (TextView) findViewById(R.id.loading_message);
|
||||
|
||||
if (tv != null) {
|
||||
tv.setText(status);
|
||||
}
|
||||
|
||||
setProgressBarIndeterminateVisibility(showProgress);
|
||||
}
|
||||
|
||||
public void toast(int msgId) {
|
||||
Toast toast = Toast.makeText(CommonActivity.this, msgId, Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
@ -45,23 +68,6 @@ public class CommonActivity extends FragmentActivity {
|
||||
toast.show();
|
||||
}
|
||||
|
||||
protected void detectSmallTablet() {
|
||||
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
|
||||
float inHeight = displayMetrics.heightPixels / displayMetrics.ydpi;
|
||||
float inWidth = displayMetrics.widthPixels / displayMetrics.xdpi;
|
||||
|
||||
float inDiag = FloatMath.sqrt(inHeight * inHeight + inWidth * inWidth);
|
||||
|
||||
if (inDiag < 9 || m_prefs.getBoolean("force_small_tablet_ui", false)) {
|
||||
m_smallTablet = true;
|
||||
}
|
||||
|
||||
Log.d(TAG, "m_smallTabletMode=" + m_smallTablet + " " + inDiag);
|
||||
}
|
||||
|
||||
private void initDatabase() {
|
||||
DatabaseHelper dh = new DatabaseHelper(getApplicationContext());
|
||||
|
||||
@ -83,7 +89,6 @@ public class CommonActivity extends FragmentActivity {
|
||||
|
||||
m_readableDb.close();
|
||||
m_writableDb.close();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,13 +97,8 @@ public class CommonActivity extends FragmentActivity {
|
||||
|
||||
m_compatMode = android.os.Build.VERSION.SDK_INT <= 10;
|
||||
|
||||
m_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
Log.d(TAG, "m_compatMode=" + m_compatMode);
|
||||
|
||||
detectSmallTablet();
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@ -106,14 +106,11 @@ public class CommonActivity extends FragmentActivity {
|
||||
return m_smallScreenMode;
|
||||
}
|
||||
|
||||
public boolean isSmallTablet() {
|
||||
return m_smallTablet;
|
||||
}
|
||||
|
||||
public boolean isCompatMode() {
|
||||
return m_compatMode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isPortrait() {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
|
||||
@ -123,9 +120,10 @@ public class CommonActivity extends FragmentActivity {
|
||||
return width < height;
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressWarnings("deprecation")
|
||||
public void copyToClipboard(String str) {
|
||||
if (android.os.Build.VERSION.SDK_INT < 11) {
|
||||
@SuppressWarnings("deprecation")
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
clipboard.setText(str);
|
||||
} else {
|
||||
|
@ -7,6 +7,7 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.types.FeedCategory;
|
||||
import org.fox.ttrss.types.FeedCategoryList;
|
||||
|
||||
@ -17,11 +18,14 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
@ -36,13 +40,12 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class FeedCategoriesFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener {
|
||||
@SuppressWarnings("unused")
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
private SharedPreferences m_prefs;
|
||||
private FeedCategoryListAdapter m_adapter;
|
||||
private FeedCategoryList m_cats = new FeedCategoryList();
|
||||
private FeedCategory m_selectedCat;
|
||||
private OnlineServices m_onlineServices;
|
||||
private FeedsActivity m_activity;
|
||||
|
||||
class CatUnreadComparator implements Comparator<FeedCategory> {
|
||||
@Override
|
||||
@ -82,11 +85,59 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.browse_articles:
|
||||
if (true) {
|
||||
FeedCategory cat = getCategoryAtPosition(info.position);
|
||||
if (cat != null) {
|
||||
m_activity.openFeedArticles(new Feed(cat.id, cat.title, true));
|
||||
//setSelectedCategory(cat);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.browse_headlines:
|
||||
if (true) {
|
||||
FeedCategory cat = getCategoryAtPosition(info.position);
|
||||
if (cat != null) {
|
||||
m_activity.onCatSelected(cat, true);
|
||||
//setSelectedCategory(cat);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.browse_feeds:
|
||||
if (true) {
|
||||
FeedCategory cat = getCategoryAtPosition(info.position);
|
||||
if (cat != null) {
|
||||
m_activity.onCatSelected(cat, false);
|
||||
//cf.setSelectedCategory(cat);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.catchup_category:
|
||||
if (true) {
|
||||
FeedCategory cat = getCategoryAtPosition(info.position);
|
||||
if (cat != null) {
|
||||
m_activity.catchupFeed(new Feed(cat.id, cat.title, true));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
|
||||
getActivity().getMenuInflater().inflate(R.menu.category_menu, menu);
|
||||
m_activity.getMenuInflater().inflate(R.menu.category_menu, menu);
|
||||
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||
FeedCategory cat = m_adapter.getItem(info.position);
|
||||
@ -117,11 +168,6 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
list.setOnItemClickListener(this);
|
||||
registerForContextMenu(list);
|
||||
|
||||
if (m_cats == null || m_cats.size() == 0)
|
||||
refresh(false);
|
||||
else
|
||||
getActivity().setProgressBarIndeterminateVisibility(false);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -129,13 +175,22 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_onlineServices = (OnlineServices)activity;
|
||||
m_activity = (FeedsActivity)activity;
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
refresh(false);
|
||||
|
||||
m_activity.initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState (Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
@ -153,31 +208,26 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
}
|
||||
}
|
||||
|
||||
if (getActivity() != null)
|
||||
getActivity().setProgressBarIndeterminateVisibility(showProgress);
|
||||
m_activity.setProgressBarIndeterminateVisibility(showProgress);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void refresh(boolean background) {
|
||||
CatsRequest req = new CatsRequest(getActivity().getApplicationContext());
|
||||
|
||||
final String sessionId = m_onlineServices.getSessionId();
|
||||
final boolean unreadOnly = m_onlineServices.getUnreadOnly();
|
||||
final String sessionId = m_activity.getSessionId();
|
||||
final boolean unreadOnly = m_activity.getUnreadOnly();
|
||||
|
||||
if (sessionId != null) {
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setLoadingStatus(R.string.blank, true);
|
||||
}
|
||||
});
|
||||
m_activity.setProgressBarVisibility(true);
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
HashMap<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
put("op", "getCategories");
|
||||
put("sid", sessionId);
|
||||
put("enable_nested", "true");
|
||||
if (unreadOnly) {
|
||||
put("unread_only", String.valueOf(unreadOnly));
|
||||
}
|
||||
@ -185,7 +235,6 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
};
|
||||
|
||||
req.execute(map);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +244,15 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
m_activity.setProgress(Math.round((((float)progress[0] / (float)progress[1]) * 10000)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement result) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
|
||||
if (result != null) {
|
||||
try {
|
||||
JsonArray content = result.getAsJsonArray();
|
||||
@ -205,7 +262,7 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
|
||||
m_cats.clear();
|
||||
|
||||
int apiLevel = m_onlineServices.getApiLevel();
|
||||
int apiLevel = m_activity.getApiLevel();
|
||||
|
||||
// virtual cats implemented in getCategories since api level 1
|
||||
if (apiLevel == 0) {
|
||||
@ -233,7 +290,7 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
}
|
||||
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_onlineServices.login();
|
||||
m_activity.login();
|
||||
} else {
|
||||
setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
@ -247,7 +304,7 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
if (m_prefs.getBoolean("sort_feeds_by_unread", false)) {
|
||||
cmp = new CatUnreadComparator();
|
||||
} else {
|
||||
if (m_onlineServices.getApiLevel() >= 3) {
|
||||
if (m_activity.getApiLevel() >= 3) {
|
||||
cmp = new CatOrderComparator();
|
||||
} else {
|
||||
cmp = new CatTitleComparator();
|
||||
@ -280,7 +337,7 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
public int getItemViewType(int position) {
|
||||
FeedCategory cat = items.get(position);
|
||||
|
||||
if (!m_onlineServices.isSmallScreen() && m_selectedCat != null && cat.id == m_selectedCat.id) {
|
||||
if (!m_activity.isSmallScreen() && m_selectedCat != null && cat.id == m_selectedCat.id) {
|
||||
return VIEW_SELECTED;
|
||||
} else {
|
||||
return VIEW_NORMAL;
|
||||
@ -344,9 +401,17 @@ public class FeedCategoriesFragment extends Fragment implements OnItemClickListe
|
||||
|
||||
if (list != null) {
|
||||
FeedCategory cat = (FeedCategory)list.getItemAtPosition(position);
|
||||
m_onlineServices.onCatSelected(cat);
|
||||
|
||||
if (!m_onlineServices.isSmallScreen())
|
||||
if ("ARTICLES".equals(m_prefs.getString("default_view_mode", "HEADLINES")) &&
|
||||
m_prefs.getBoolean("browse_cats_like_feeds", false)) {
|
||||
|
||||
m_activity.openFeedArticles(new Feed(cat.id, cat.title, true));
|
||||
|
||||
} else {
|
||||
m_activity.onCatSelected(cat);
|
||||
}
|
||||
|
||||
if (!m_activity.isSmallScreen())
|
||||
m_selectedCat = cat;
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
300
src/org/fox/ttrss/FeedsActivity.java
Normal file
300
src/org/fox/ttrss/FeedsActivity.java
Normal file
@ -0,0 +1,300 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.types.FeedCategory;
|
||||
import org.fox.ttrss.util.AppRater;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
public class FeedsActivity extends OnlineActivity implements HeadlinesEventListener {
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
protected SharedPreferences m_prefs;
|
||||
protected long m_lastRefresh = 0;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
m_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
} else {
|
||||
setTheme(R.style.LightTheme);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.feeds);
|
||||
|
||||
setSmallScreen(findViewById(R.id.headlines_fragment) == null);
|
||||
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
||||
if (intent.getParcelableExtra("feed") != null || intent.getParcelableExtra("category") != null ||
|
||||
intent.getParcelableExtra("article") != null) {
|
||||
|
||||
if (!isCompatMode()) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
Feed feed = (Feed) intent.getParcelableExtra("feed");
|
||||
FeedCategory cat = (FeedCategory) intent.getParcelableExtra("category");
|
||||
Article article = (Article) intent.getParcelableExtra("article");
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
if (article != null) {
|
||||
Article original = GlobalState.getInstance().m_loadedArticles.findById(article.id);
|
||||
|
||||
ArticlePager ap = new ArticlePager(original != null ? original : article, feed);
|
||||
ft.replace(R.id.feeds_fragment, ap, FRAG_ARTICLE);
|
||||
|
||||
ap.setSearchQuery(intent.getStringExtra("searchQuery"));
|
||||
|
||||
setTitle(feed.title);
|
||||
} else {
|
||||
if (feed != null) {
|
||||
HeadlinesFragment hf = new HeadlinesFragment(feed);
|
||||
ft.replace(R.id.feeds_fragment, hf, FRAG_HEADLINES);
|
||||
|
||||
setTitle(feed.title);
|
||||
}
|
||||
|
||||
if (cat != null) {
|
||||
FeedsFragment ff = new FeedsFragment(cat);
|
||||
ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS);
|
||||
|
||||
setTitle(cat.title);
|
||||
}
|
||||
}
|
||||
|
||||
ft.commit();
|
||||
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
if (m_prefs.getBoolean("enable_cats", false)) {
|
||||
ft.replace(R.id.feeds_fragment, new FeedCategoriesFragment(), FRAG_CATS);
|
||||
} else {
|
||||
ft.replace(R.id.feeds_fragment, new FeedsFragment(), FRAG_FEEDS);
|
||||
}
|
||||
|
||||
ft.commit();
|
||||
|
||||
AppRater.appLaunched(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initMenu() {
|
||||
super.initMenu();
|
||||
|
||||
if (m_menu != null && getSessionId() != null) {
|
||||
Fragment ff = getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS);
|
||||
Fragment cf = getSupportFragmentManager().findFragmentByTag(FRAG_CATS);
|
||||
ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_feeds, (ff != null && ff.isAdded()) || (cf != null && cf.isAdded()));
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_article, af != null && af.isAdded());
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded() && hf.getSelectedArticles().size() == 0);
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines_selection, hf != null && hf.isAdded() && hf.getSelectedArticles().size() != 0);
|
||||
|
||||
if (isSmallScreen()) {
|
||||
m_menu.findItem(R.id.update_headlines).setVisible(hf != null && hf.isAdded());
|
||||
} else {
|
||||
m_menu.findItem(R.id.update_headlines).setVisible(false);
|
||||
}
|
||||
|
||||
MenuItem item = m_menu.findItem(R.id.show_feeds);
|
||||
|
||||
if (getUnreadOnly()) {
|
||||
item.setTitle(R.string.menu_all_feeds);
|
||||
} else {
|
||||
item.setTitle(R.string.menu_unread_feeds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onFeedSelected(Feed feed) {
|
||||
GlobalState.getInstance().m_loadedArticles.clear();
|
||||
|
||||
if (isSmallScreen()) {
|
||||
Intent intent = new Intent(FeedsActivity.this, FeedsActivity.class);
|
||||
intent.putExtra("feed", feed);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
HeadlinesFragment hf = new HeadlinesFragment(feed);
|
||||
ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES);
|
||||
|
||||
ft.commit();
|
||||
|
||||
Date date = new Date();
|
||||
|
||||
if (date.getTime() - m_lastRefresh > 10000) {
|
||||
m_lastRefresh = date.getTime();
|
||||
refresh(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCatSelected(FeedCategory cat, boolean openAsFeed) {
|
||||
|
||||
if (!openAsFeed) {
|
||||
|
||||
if (isSmallScreen()) {
|
||||
|
||||
Intent intent = new Intent(FeedsActivity.this, FeedsActivity.class);
|
||||
intent.putExtra("category", cat);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
FeedsFragment ff = new FeedsFragment(cat);
|
||||
ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS);
|
||||
|
||||
ft.addToBackStack(null);
|
||||
ft.commit();
|
||||
}
|
||||
} else {
|
||||
Feed feed = new Feed(cat.id, cat.title, true);
|
||||
onFeedSelected(feed);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCatSelected(FeedCategory cat) {
|
||||
onCatSelected(cat, m_prefs.getBoolean("browse_cats_like_feeds", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.show_feeds:
|
||||
setUnreadOnly(!getUnreadOnly());
|
||||
initMenu();
|
||||
refresh();
|
||||
return true;
|
||||
case R.id.update_feeds:
|
||||
refresh();
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loginSuccess() {
|
||||
setLoadingStatus(R.string.blank, false);
|
||||
findViewById(R.id.loading_container).setVisibility(View.GONE);
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleListSelectionChange(ArticleList m_selectedArticles) {
|
||||
initMenu();
|
||||
}
|
||||
|
||||
public void openFeedArticles(Feed feed) {
|
||||
if (isSmallScreen()) {
|
||||
Intent intent = new Intent(FeedsActivity.this, FeedsActivity.class);
|
||||
|
||||
GlobalState.getInstance().m_activeFeed = feed;
|
||||
GlobalState.getInstance().m_loadedArticles.clear();
|
||||
|
||||
intent.putExtra("feed", feed);
|
||||
intent.putExtra("article", new Article());
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void onArticleSelected(Article article, boolean open) {
|
||||
if (article.unread) {
|
||||
article.unread = false;
|
||||
saveArticleUnread(article);
|
||||
}
|
||||
|
||||
if (open) {
|
||||
HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
if (isSmallScreen()) {
|
||||
|
||||
Intent intent = new Intent(FeedsActivity.this, FeedsActivity.class);
|
||||
intent.putExtra("feed", hf.getFeed());
|
||||
intent.putExtra("article", article);
|
||||
intent.putExtra("searchQuery", hf.getSearchQuery());
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
|
||||
} else {
|
||||
Intent intent = new Intent(FeedsActivity.this, HeadlinesActivity.class);
|
||||
intent.putExtra("feed", hf.getFeed());
|
||||
intent.putExtra("article", article);
|
||||
intent.putExtra("searchQuery", hf.getSearchQuery());
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
} else {
|
||||
initMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(Article article) {
|
||||
onArticleSelected(article, true);
|
||||
}
|
||||
|
||||
public void catchupFeed(final Feed feed) {
|
||||
super.catchupFeed(feed);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlinesLoaded(boolean appended) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
@ -5,29 +5,25 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.protocol.ClientContext;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.types.FeedCategory;
|
||||
import org.fox.ttrss.types.FeedList;
|
||||
import org.fox.ttrss.util.EasySSLSocketFactory;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
@ -37,14 +33,17 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.http.AndroidHttpClient;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
@ -65,7 +64,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
private SharedPreferences m_prefs;
|
||||
private FeedListAdapter m_adapter;
|
||||
private FeedList m_feeds = new FeedList();
|
||||
private OnlineServices m_onlineServices;
|
||||
private FeedsActivity m_activity;
|
||||
private Feed m_selectedFeed;
|
||||
private FeedCategory m_activeCategory;
|
||||
private static final String ICON_PATH = "/data/org.fox.ttrss/icons/";
|
||||
@ -98,7 +97,13 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
|
||||
@Override
|
||||
public int compare(Feed a, Feed b) {
|
||||
if (a.id >= 0 && b.id >= 0)
|
||||
if (a.is_cat && b.is_cat)
|
||||
return a.title.compareTo(b.title);
|
||||
else if (a.is_cat && !b.is_cat)
|
||||
return -1;
|
||||
else if (!a.is_cat && b.is_cat)
|
||||
return 1;
|
||||
else if (a.id >= 0 && b.id >= 0)
|
||||
return a.title.compareTo(b.title);
|
||||
else
|
||||
return a.id - b.id;
|
||||
@ -111,7 +116,13 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
@Override
|
||||
public int compare(Feed a, Feed b) {
|
||||
if (a.id >= 0 && b.id >= 0)
|
||||
if (a.order_id != 0 && b.order_id != 0)
|
||||
if (a.is_cat && b.is_cat)
|
||||
return a.title.compareTo(b.title);
|
||||
else if (a.is_cat && !b.is_cat)
|
||||
return -1;
|
||||
else if (!a.is_cat && b.is_cat)
|
||||
return 1;
|
||||
else if (a.order_id != 0 && b.order_id != 0)
|
||||
return a.order_id - b.order_id;
|
||||
else
|
||||
return a.title.compareTo(b.title);
|
||||
@ -121,6 +132,42 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
switch (item.getItemId()) {
|
||||
case R.id.browse_articles:
|
||||
if (true) {
|
||||
Feed feed = getFeedAtPosition(info.position);
|
||||
if (feed != null) {
|
||||
m_activity.openFeedArticles(feed);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.browse_headlines:
|
||||
if (true) {
|
||||
Feed feed = getFeedAtPosition(info.position);
|
||||
if (feed != null) {
|
||||
m_activity.onFeedSelected(feed);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.catchup_feed:
|
||||
if (true) {
|
||||
Feed feed = getFeedAtPosition(info.position);
|
||||
if (feed != null) {
|
||||
m_activity.catchupFeed(feed);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
@ -133,6 +180,11 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
if (feed != null)
|
||||
menu.setHeaderTitle(feed.title);
|
||||
|
||||
if (!m_activity.isSmallScreen()) {
|
||||
menu.findItem(R.id.browse_headlines).setVisible(false);
|
||||
menu.findItem(R.id.browse_feeds).setVisible(false);
|
||||
}
|
||||
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
}
|
||||
@ -159,11 +211,6 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
|
||||
m_enableFeedIcons = m_prefs.getBoolean("download_feed_icons", false);
|
||||
|
||||
if (m_feeds == null || m_feeds.size() == 0)
|
||||
refresh(false);
|
||||
else
|
||||
getActivity().setProgressBarIndeterminateVisibility(false);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -179,9 +226,16 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
m_onlineServices = (OnlineServices)activity;
|
||||
m_activity = (FeedsActivity)activity;
|
||||
}
|
||||
|
||||
//m_selectedFeed = m_onlineServices.getActiveFeed();
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
refresh(false);
|
||||
|
||||
m_activity.initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -200,9 +254,22 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
|
||||
if (list != null) {
|
||||
Feed feed = (Feed)list.getItemAtPosition(position);
|
||||
m_onlineServices.onFeedSelected(feed);
|
||||
|
||||
if (!m_onlineServices.isSmallScreen())
|
||||
if (feed.is_cat) {
|
||||
FeedCategory cat = new FeedCategory();
|
||||
cat.id = feed.id;
|
||||
cat.title = feed.title;
|
||||
|
||||
m_activity.onCatSelected(cat);
|
||||
} else {
|
||||
if ("ARTICLES".equals(m_prefs.getString("default_view_mode", "HEADLINES"))) {
|
||||
m_activity.openFeedArticles(feed);
|
||||
} else {
|
||||
m_activity.onFeedSelected(feed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_activity.isSmallScreen())
|
||||
m_selectedFeed = feed;
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
@ -213,26 +280,23 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
public void refresh(boolean background) {
|
||||
//FeedCategory cat = m_onlineServices.getActiveCategory();
|
||||
|
||||
m_activity.setProgressBarVisibility(true);
|
||||
|
||||
final int catId = (m_activeCategory != null) ? m_activeCategory.id : -4;
|
||||
|
||||
final String sessionId = m_onlineServices.getSessionId();
|
||||
final boolean unreadOnly = m_onlineServices.getUnreadOnly();
|
||||
final String sessionId = m_activity.getSessionId();
|
||||
final boolean unreadOnly = m_activity.getUnreadOnly();
|
||||
|
||||
FeedsRequest req = new FeedsRequest(getActivity().getApplicationContext(), catId);
|
||||
|
||||
if (sessionId != null) {
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setLoadingStatus(R.string.blank, true);
|
||||
}
|
||||
});
|
||||
|
||||
HashMap<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
put("op", "getFeeds");
|
||||
put("sid", sessionId);
|
||||
put("include_nested", "true");
|
||||
put("cat_id", String.valueOf(catId));
|
||||
if (unreadOnly) {
|
||||
put("unread_only", String.valueOf(unreadOnly));
|
||||
@ -292,7 +356,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
}
|
||||
};
|
||||
|
||||
final String sessionId = m_onlineServices.getSessionId();
|
||||
final String sessionId = m_activity.getSessionId();
|
||||
|
||||
HashMap<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
@ -312,7 +376,15 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
m_catId = catId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
m_activity.setProgress(Math.round((((float)progress[0] / (float)progress[1]) * 10000)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement result) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
|
||||
if (result != null) {
|
||||
try {
|
||||
JsonArray content = result.getAsJsonArray();
|
||||
@ -347,7 +419,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
}
|
||||
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_onlineServices.login();
|
||||
m_activity.login();
|
||||
} else {
|
||||
setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
@ -375,7 +447,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
public int getItemViewType(int position) {
|
||||
Feed feed = items.get(position);
|
||||
|
||||
if (!m_onlineServices.isSmallScreen() && m_selectedFeed != null && feed.id == m_selectedFeed.id) {
|
||||
if (!m_activity.isSmallScreen() && m_selectedFeed != null && feed.id == m_selectedFeed.id) {
|
||||
return VIEW_SELECTED;
|
||||
} else {
|
||||
return VIEW_NORMAL;
|
||||
@ -449,7 +521,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
if (m_prefs.getBoolean("sort_feeds_by_unread", false)) {
|
||||
cmp = new FeedUnreadComparator();
|
||||
} else {
|
||||
if (m_onlineServices.getApiLevel() >= 3) {
|
||||
if (m_activity.getApiLevel() >= 3) {
|
||||
cmp = new FeedOrderComparator();
|
||||
} else {
|
||||
cmp = new FeedTitleComparator();
|
||||
@ -478,7 +550,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
|
||||
if (iconPath.exists()) {
|
||||
for (Feed feed : params[0]) {
|
||||
if (feed.id > 0 && feed.has_icon) {
|
||||
if (feed.id > 0 && feed.has_icon && !feed.is_cat) {
|
||||
File outputFile = new File(iconPath.getAbsolutePath() + "/" + feed.id + ".ico");
|
||||
String fetchUrl = m_baseUrl + "/" + feed.id + ".ico";
|
||||
|
||||
@ -496,46 +568,73 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
||||
return null;
|
||||
}
|
||||
|
||||
private void trustAllHosts() {
|
||||
X509TrustManager easyTrustManager = new X509TrustManager() {
|
||||
|
||||
public void checkClientTrusted(
|
||||
X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
// Oh, I am easy!
|
||||
}
|
||||
|
||||
public void checkServerTrusted(
|
||||
X509Certificate[] chain,
|
||||
String authType) throws CertificateException {
|
||||
// Oh, I am easy!
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {easyTrustManager};
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
try {
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void disableConnectionReuseIfNecessary() {
|
||||
// HTTP connection reuse which was buggy pre-froyo
|
||||
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
}
|
||||
}
|
||||
|
||||
protected void downloadFile(String fetchUrl, String outputFile) {
|
||||
AndroidHttpClient client = AndroidHttpClient.newInstance("Tiny Tiny RSS");
|
||||
|
||||
disableConnectionReuseIfNecessary();
|
||||
|
||||
if (m_prefs.getBoolean("ssl_trust_any", false)) {
|
||||
client.getConnectionManager().getSchemeRegistry().register(new Scheme("https", new EasySSLSocketFactory(), 443));
|
||||
trustAllHosts();
|
||||
}
|
||||
|
||||
HttpGet httpGet = new HttpGet(fetchUrl);
|
||||
HttpContext context = null;
|
||||
try {
|
||||
URL url = new URL(fetchUrl);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
|
||||
String httpLogin = m_prefs.getString("http_login", "");
|
||||
String httpPassword = m_prefs.getString("http_password", "");
|
||||
|
||||
if (httpLogin.length() > 0) {
|
||||
|
||||
URL targetUrl;
|
||||
try {
|
||||
targetUrl = new URL(fetchUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
client.close();
|
||||
return;
|
||||
conn.setRequestProperty("Authorization", "Basic " +
|
||||
Base64.encode((httpLogin + ":" + httpPassword).getBytes("UTF-8"), Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
HttpHost targetHost = new HttpHost(targetUrl.getHost(), targetUrl.getPort(), targetUrl.getProtocol());
|
||||
CredentialsProvider cp = new BasicCredentialsProvider();
|
||||
context = new BasicHttpContext();
|
||||
|
||||
cp.setCredentials(
|
||||
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
|
||||
new UsernamePasswordCredentials(httpLogin, httpPassword));
|
||||
|
||||
context.setAttribute(ClientContext.CREDS_PROVIDER, cp);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
HttpResponse execute = client.execute(httpGet, context);
|
||||
|
||||
InputStream content = execute.getEntity().getContent();
|
||||
InputStream content = conn.getInputStream();
|
||||
|
||||
BufferedInputStream is = new BufferedInputStream(content, 1024);
|
||||
FileOutputStream fos = new FileOutputStream(outputFile);
|
||||
|
31
src/org/fox/ttrss/GlobalState.java
Normal file
31
src/org/fox/ttrss/GlobalState.java
Normal file
@ -0,0 +1,31 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class GlobalState extends Application {
|
||||
private static GlobalState m_singleton;
|
||||
|
||||
public ArticleList m_loadedArticles = new ArticleList();
|
||||
public Feed m_activeFeed;
|
||||
public Article m_activeArticle;
|
||||
public int m_selectedArticleId;
|
||||
public boolean m_unreadOnly = true;
|
||||
public boolean m_unreadArticlesOnly = true;
|
||||
public String m_sessionId;
|
||||
public int m_apiLevel;
|
||||
public boolean m_canUseProgress;
|
||||
|
||||
public static GlobalState getInstance(){
|
||||
return m_singleton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onCreate() {
|
||||
super.onCreate();
|
||||
m_singleton = this;
|
||||
}
|
||||
}
|
187
src/org/fox/ttrss/HeadlinesActivity.java
Normal file
187
src/org/fox/ttrss/HeadlinesActivity.java
Normal file
@ -0,0 +1,187 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
public class HeadlinesActivity extends OnlineActivity implements HeadlinesEventListener {
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
protected SharedPreferences m_prefs;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
m_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
} else {
|
||||
setTheme(R.style.LightTheme);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.headlines);
|
||||
|
||||
if (!isCompatMode()) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setSmallScreen(findViewById(R.id.headlines_fragment) == null);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
Intent i = getIntent();
|
||||
|
||||
if (i.getExtras() != null) {
|
||||
Feed feed = i.getParcelableExtra("feed");
|
||||
Article article = i.getParcelableExtra("article");
|
||||
String searchQuery = i.getStringExtra("searchQuery");
|
||||
|
||||
HeadlinesFragment hf = new HeadlinesFragment(feed, article);
|
||||
ArticlePager af = new ArticlePager(hf.getArticleById(article.id), feed);
|
||||
|
||||
hf.setSearchQuery(searchQuery);
|
||||
af.setSearchQuery(searchQuery);
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES);
|
||||
ft.replace(R.id.article_fragment, af, FRAG_ARTICLE);
|
||||
|
||||
ft.commit();
|
||||
|
||||
setTitle(feed.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh() {
|
||||
super.refresh();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loginSuccess() {
|
||||
Log.d(TAG, "loginSuccess");
|
||||
|
||||
setLoadingStatus(R.string.blank, false);
|
||||
findViewById(R.id.loading_container).setVisibility(View.GONE);
|
||||
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
default:
|
||||
Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initMenu() {
|
||||
super.initMenu();
|
||||
|
||||
if (m_menu != null && getSessionId() != null) {
|
||||
m_menu.setGroupVisible(R.id.menu_group_feeds, false);
|
||||
|
||||
HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.getSelectedArticles().size() == 0);
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines_selection, hf != null && hf.getSelectedArticles().size() != 0);
|
||||
|
||||
Fragment af = getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_article, af != null);
|
||||
|
||||
m_menu.findItem(R.id.search).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleListSelectionChange(ArticleList m_selectedArticles) {
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(Article article) {
|
||||
onArticleSelected(article, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(Article article, boolean open) {
|
||||
|
||||
if (article.unread) {
|
||||
article.unread = false;
|
||||
saveArticleUnread(article);
|
||||
}
|
||||
|
||||
if (open) {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
af.setActiveArticle(article);
|
||||
|
||||
ft.commit();
|
||||
} else {
|
||||
HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
hf.setActiveArticle(article);
|
||||
}
|
||||
|
||||
GlobalState.getInstance().m_activeArticle = article;
|
||||
|
||||
initMenu();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlinesLoaded(boolean appended) {
|
||||
HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
if (hf != null) {
|
||||
Article article = hf.getActiveArticle();
|
||||
|
||||
if (article == null) {
|
||||
article = hf.getAllArticles().get(0);
|
||||
|
||||
hf.setActiveArticle(article);
|
||||
|
||||
ArticlePager af = new ArticlePager(article, hf.getFeed());
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
ft.replace(R.id.article_fragment, af, FRAG_ARTICLE);
|
||||
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/org/fox/ttrss/HeadlinesEventListener.java
Normal file
11
src/org/fox/ttrss/HeadlinesEventListener.java
Normal file
@ -0,0 +1,11 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
|
||||
public interface HeadlinesEventListener {
|
||||
void onArticleListSelectionChange(ArticleList m_selectedArticles);
|
||||
void onArticleSelected(Article article);
|
||||
void onArticleSelected(Article article, boolean open);
|
||||
void onHeadlinesLoaded(boolean appended);
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
@ -8,13 +7,13 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Attachment;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.util.HeadlinesRequest;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
import android.app.Activity;
|
||||
@ -34,6 +33,7 @@ import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
@ -51,10 +51,7 @@ import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class HeadlinesFragment extends Fragment implements OnItemClickListener, OnScrollListener {
|
||||
public static enum ArticlesSelection { ALL, NONE, UNREAD };
|
||||
@ -66,28 +63,27 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
private Feed m_feed;
|
||||
private Article m_activeArticle;
|
||||
private boolean m_refreshInProgress = false;
|
||||
private boolean m_canLoadMore = false;
|
||||
private boolean m_combinedMode = true;
|
||||
private String m_searchQuery = "";
|
||||
private boolean m_refreshInProgress = false;
|
||||
|
||||
private SharedPreferences m_prefs;
|
||||
|
||||
private ArticleListAdapter m_adapter;
|
||||
private ArticleList m_articles = new ArticleList();
|
||||
private ArticleList m_articles = GlobalState.getInstance().m_loadedArticles;
|
||||
private ArticleList m_selectedArticles = new ArticleList();
|
||||
|
||||
private OnlineServices m_onlineServices;
|
||||
private HeadlinesEventListener m_listener;
|
||||
private OnlineActivity m_activity;
|
||||
|
||||
private ImageGetter m_dummyGetter = new ImageGetter() {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
return new BitmapDrawable();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public ArticleList getSelectedArticles() {
|
||||
return m_selectedArticles;
|
||||
}
|
||||
@ -96,15 +92,151 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
m_feed = feed;
|
||||
}
|
||||
|
||||
public HeadlinesFragment(Feed feed, Article activeArticle) {
|
||||
m_feed = feed;
|
||||
m_activeArticle = getArticleById(activeArticle.id);
|
||||
}
|
||||
|
||||
public HeadlinesFragment() {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.set_labels:
|
||||
if (true) {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
|
||||
if (article != null) {
|
||||
m_activity.editArticleLabels(article);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.article_set_note:
|
||||
if (true) {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
|
||||
if (article != null) {
|
||||
m_activity.editArticleNote(article);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.article_link_copy:
|
||||
if (true) {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
|
||||
if (article != null) {
|
||||
m_activity.copyToClipboard(article.link);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.selection_toggle_marked:
|
||||
if (true) {
|
||||
ArticleList selected = getSelectedArticles();
|
||||
|
||||
if (selected.size() > 0) {
|
||||
for (Article a : selected)
|
||||
a.marked = !a.marked;
|
||||
|
||||
m_activity.toggleArticlesMarked(selected);
|
||||
//updateHeadlines();
|
||||
} else {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
if (article != null) {
|
||||
article.marked = !article.marked;
|
||||
m_activity.saveArticleMarked(article);
|
||||
//updateHeadlines();
|
||||
}
|
||||
}
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
return true;
|
||||
case R.id.selection_toggle_published:
|
||||
if (true) {
|
||||
ArticleList selected = getSelectedArticles();
|
||||
|
||||
if (selected.size() > 0) {
|
||||
for (Article a : selected)
|
||||
a.published = !a.published;
|
||||
|
||||
m_activity.toggleArticlesPublished(selected);
|
||||
//updateHeadlines();
|
||||
} else {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
if (article != null) {
|
||||
article.published = !article.published;
|
||||
m_activity.saveArticlePublished(article);
|
||||
//updateHeadlines();
|
||||
}
|
||||
}
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
return true;
|
||||
case R.id.selection_toggle_unread:
|
||||
if (true) {
|
||||
ArticleList selected = getSelectedArticles();
|
||||
|
||||
if (selected.size() > 0) {
|
||||
for (Article a : selected)
|
||||
a.unread = !a.unread;
|
||||
|
||||
m_activity.toggleArticlesUnread(selected);
|
||||
//updateHeadlines();
|
||||
} else {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
if (article != null) {
|
||||
article.unread = !article.unread;
|
||||
m_activity.saveArticleUnread(article);
|
||||
//updateHeadlines();
|
||||
}
|
||||
}
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
return true;
|
||||
case R.id.share_article:
|
||||
if (true) {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
if (article != null)
|
||||
m_activity.shareArticle(article);
|
||||
}
|
||||
return true;
|
||||
case R.id.catchup_above:
|
||||
if (true) {
|
||||
Article article = getArticleAtPosition(info.position);
|
||||
if (article != null) {
|
||||
ArticleList articles = getAllArticles();
|
||||
ArticleList tmp = new ArticleList();
|
||||
for (Article a : articles) {
|
||||
a.unread = false;
|
||||
tmp.add(a);
|
||||
if (article.id == a.id)
|
||||
break;
|
||||
}
|
||||
if (tmp.size() > 0) {
|
||||
m_activity.toggleArticlesUnread(tmp);
|
||||
//updateHeadlines();
|
||||
}
|
||||
}
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
|
||||
getActivity().getMenuInflater().inflate(R.menu.headlines_menu, menu);
|
||||
getActivity().getMenuInflater().inflate(R.menu.headlines_context_menu, menu);
|
||||
|
||||
if (m_selectedArticles.size() > 0) {
|
||||
menu.setHeaderTitle(R.string.headline_context_multiple);
|
||||
@ -116,8 +248,8 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
menu.setGroupVisible(R.id.menu_group_single_article, true);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.set_labels).setEnabled(m_onlineServices.getApiLevel() >= 1);
|
||||
menu.findItem(R.id.article_set_note).setEnabled(m_onlineServices.getApiLevel() >= 1);
|
||||
menu.findItem(R.id.set_labels).setEnabled(m_activity.getApiLevel() >= 1);
|
||||
menu.findItem(R.id.article_set_note).setEnabled(m_activity.getApiLevel() >= 1);
|
||||
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
@ -128,10 +260,9 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
m_feed = savedInstanceState.getParcelable("feed");
|
||||
m_articles = savedInstanceState.getParcelable("articles");
|
||||
//m_articles = savedInstanceState.getParcelable("articles");
|
||||
m_activeArticle = savedInstanceState.getParcelable("activeArticle");
|
||||
m_selectedArticles = savedInstanceState.getParcelable("selectedArticles");
|
||||
m_canLoadMore = savedInstanceState.getBoolean("canLoadMore");
|
||||
m_combinedMode = savedInstanceState.getBoolean("combinedMode");
|
||||
m_searchQuery = (String) savedInstanceState.getCharSequence("searchQuery");
|
||||
}
|
||||
@ -146,26 +277,45 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
//list.setEmptyView(view.findViewById(R.id.no_headlines));
|
||||
registerForContextMenu(list);
|
||||
|
||||
if (m_onlineServices.isSmallScreen() || m_onlineServices.isPortrait())
|
||||
if (m_activity.isSmallScreen() || m_activity.isPortrait())
|
||||
view.findViewById(R.id.headlines_fragment).setPadding(0, 0, 0, 0);
|
||||
|
||||
Log.d(TAG, "onCreateView, feed=" + m_feed);
|
||||
|
||||
if (m_feed != null && (m_articles == null || m_articles.size() == 0))
|
||||
refresh(false);
|
||||
else
|
||||
getActivity().setProgressBarIndeterminateVisibility(false);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (GlobalState.getInstance().m_activeArticle != null) {
|
||||
m_activeArticle = GlobalState.getInstance().m_activeArticle;
|
||||
GlobalState.getInstance().m_activeArticle = null;
|
||||
}
|
||||
|
||||
if (m_activeArticle != null) {
|
||||
setActiveArticle(m_activeArticle);
|
||||
}
|
||||
|
||||
if (m_articles.size() == 0 || !m_feed.equals(GlobalState.getInstance().m_activeFeed)) {
|
||||
refresh(false);
|
||||
GlobalState.getInstance().m_activeFeed = m_feed;
|
||||
} else {
|
||||
notifyUpdated();
|
||||
}
|
||||
|
||||
m_activity.initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_onlineServices = (OnlineServices) activity;
|
||||
m_activity = (OnlineActivity) activity;
|
||||
m_listener = (HeadlinesEventListener) activity;
|
||||
|
||||
m_combinedMode = m_prefs.getBoolean("combined_mode", false);
|
||||
m_combinedMode = false; /* m_prefs.getBoolean("combined_mode", false); */
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -179,12 +329,16 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
if (article.id >= 0) {
|
||||
if (m_combinedMode) {
|
||||
article.unread = false;
|
||||
m_onlineServices.saveArticleUnread(article);
|
||||
m_activity.saveArticleUnread(article);
|
||||
} else {
|
||||
m_onlineServices.onArticleSelected(article);
|
||||
m_listener.onArticleSelected(article);
|
||||
}
|
||||
|
||||
// only set active article when it makes sense (in HeadlinesActivity)
|
||||
if (getActivity().findViewById(R.id.article_fragment) != null) {
|
||||
m_activeArticle = article;
|
||||
}
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
@ -192,13 +346,50 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
@SuppressWarnings({ "unchecked", "serial" })
|
||||
public void refresh(boolean append) {
|
||||
if (m_activity != null) {
|
||||
m_refreshInProgress = true;
|
||||
|
||||
HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext());
|
||||
m_activity.setProgressBarVisibility(true);
|
||||
|
||||
final String sessionId = m_onlineServices.getSessionId();
|
||||
final boolean showUnread = m_onlineServices.getUnreadArticlesOnly();
|
||||
if (!m_feed.equals(GlobalState.getInstance().m_activeFeed)) {
|
||||
append = false;
|
||||
}
|
||||
|
||||
final boolean fappend = append;
|
||||
final String sessionId = m_activity.getSessionId();
|
||||
final boolean showUnread = m_activity.getUnreadArticlesOnly();
|
||||
final boolean isCat = m_feed.is_cat;
|
||||
|
||||
HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity) {
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
m_activity.setProgress(Math.round((((float)progress[0] / (float)progress[1]) * 10000)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement result) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result != null) {
|
||||
m_refreshInProgress = false;
|
||||
|
||||
if (m_articles.indexOf(m_activeArticle) == -1)
|
||||
m_activeArticle = null;
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
m_listener.onHeadlinesLoaded(fappend);
|
||||
} else {
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_activity.login();
|
||||
} else {
|
||||
setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int skip = 0;
|
||||
|
||||
if (append) {
|
||||
@ -226,10 +417,11 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
put("offset", String.valueOf(0));
|
||||
put("view_mode", showUnread ? "adaptive" : "all_articles");
|
||||
put("skip", String.valueOf(fskip));
|
||||
put("include_nested", "true");
|
||||
|
||||
if (isCat) put("is_cat", "true");
|
||||
|
||||
if (m_searchQuery.length() != 0) {
|
||||
if (m_searchQuery != null && m_searchQuery.length() != 0) {
|
||||
put("search", m_searchQuery);
|
||||
put("search_mode", "");
|
||||
put("match_on", "both");
|
||||
@ -239,16 +431,16 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
req.execute(map);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState (Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
out.putParcelable("feed", m_feed);
|
||||
out.putParcelable("articles", m_articles);
|
||||
//out.putParcelable("articles", m_articles);
|
||||
out.putParcelable("activeArticle", m_activeArticle);
|
||||
out.putParcelable("selectedArticles", m_selectedArticles);
|
||||
out.putBoolean("canLoadMore", m_canLoadMore);
|
||||
out.putBoolean("combinedMode", m_combinedMode);
|
||||
out.putCharSequence("searchQuery", m_searchQuery);
|
||||
}
|
||||
@ -266,7 +458,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
getActivity().setProgressBarIndeterminateVisibility(showProgress);
|
||||
}
|
||||
|
||||
private class HeadlinesRequest extends ApiRequest {
|
||||
/* private class HeadlinesRequest extends ApiRequest {
|
||||
int m_offset = 0;
|
||||
|
||||
public HeadlinesRequest(Context context) {
|
||||
@ -319,7 +511,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
}
|
||||
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_onlineServices.login();
|
||||
m_activity.login();
|
||||
} else {
|
||||
setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
@ -329,7 +521,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
public void setOffset(int skip) {
|
||||
m_offset = skip;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
private class ArticleListAdapter extends ArrayAdapter<Article> {
|
||||
private ArrayList<Article> items;
|
||||
@ -409,7 +601,12 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
if (ft != null) {
|
||||
if (article.feed_title != null && (m_feed.is_cat || m_feed.id < 0)) {
|
||||
|
||||
if (article.feed_title.length() > 20)
|
||||
ft.setText(article.feed_title.substring(0, 20) + "...");
|
||||
else
|
||||
ft.setText(article.feed_title);
|
||||
|
||||
} else {
|
||||
ft.setVisibility(View.GONE);
|
||||
}
|
||||
@ -428,7 +625,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
article.marked = !article.marked;
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
m_onlineServices.saveArticleMarked(article);
|
||||
m_activity.saveArticleMarked(article);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -445,7 +642,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
article.published = !article.published;
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
m_onlineServices.saveArticlePublished(article);
|
||||
m_activity.saveArticlePublished(article);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -533,7 +730,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
Attachment attachment = (Attachment) spinner.getSelectedItem();
|
||||
|
||||
if (attachment != null) {
|
||||
m_onlineServices.copyToClipboard(attachment.content_url);
|
||||
m_activity.copyToClipboard(attachment.content_url);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -590,7 +787,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
m_selectedArticles.remove(article);
|
||||
}
|
||||
|
||||
m_onlineServices.onArticleListSelectionChange(m_selectedArticles);
|
||||
m_listener.onArticleListSelectionChange(m_selectedArticles);
|
||||
|
||||
Log.d(TAG, "num selected: " + m_selectedArticles.size());
|
||||
}
|
||||
@ -615,19 +812,16 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
|
||||
|
||||
/* public void notifyUpdated() {
|
||||
public void notifyUpdated() {
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
Article article = m_onlineServices.getSelectedArticle();
|
||||
|
||||
setActiveArticle(article);
|
||||
} */
|
||||
}
|
||||
|
||||
public ArticleList getAllArticles() {
|
||||
return m_articles;
|
||||
}
|
||||
|
||||
public void setActiveArticle(Article article) {
|
||||
if (article != m_activeArticle) {
|
||||
m_activeArticle = article;
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
@ -638,6 +832,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
list.setSelection(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelection(ArticlesSelection select) {
|
||||
m_selectedArticles.clear();
|
||||
@ -681,7 +876,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||
if (!m_refreshInProgress && m_canLoadMore && firstVisibleItem + visibleItemCount == m_articles.size()) {
|
||||
if (!m_refreshInProgress && m_articles.findById(-1) != null && firstVisibleItem + visibleItemCount == m_articles.size()) {
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
@ -703,6 +898,10 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
}
|
||||
}
|
||||
|
||||
public String getSearchQuery() {
|
||||
return m_searchQuery;
|
||||
}
|
||||
|
||||
public void setSearchQuery(String query) {
|
||||
if (!m_searchQuery.equals(query)) {
|
||||
m_searchQuery = query;
|
||||
@ -710,5 +909,9 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
||||
}
|
||||
}
|
||||
|
||||
public Feed getFeed() {
|
||||
return m_feed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
1283
src/org/fox/ttrss/OnlineActivity.java
Normal file
1283
src/org/fox/ttrss/OnlineActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,32 +0,0 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.types.FeedCategory;
|
||||
|
||||
public interface OnlineServices {
|
||||
public enum RelativeArticle { BEFORE, AFTER };
|
||||
|
||||
public void saveArticleUnread(final Article article);
|
||||
public void saveArticleMarked(final Article article);
|
||||
public void saveArticlePublished(final Article article);
|
||||
public void setSelectedArticle(Article article);
|
||||
public boolean getUnreadArticlesOnly();
|
||||
|
||||
public void onCatSelected(FeedCategory cat);
|
||||
public void onFeedSelected(Feed feed);
|
||||
public void onArticleSelected(Article article);
|
||||
public void onArticleListSelectionChange(ArticleList selection);
|
||||
|
||||
//public void initMainMenu();
|
||||
public void login();
|
||||
public String getSessionId();
|
||||
public boolean isSmallScreen();
|
||||
public boolean getUnreadOnly();
|
||||
public int getApiLevel();
|
||||
public boolean isPortrait();
|
||||
|
||||
public void copyToClipboard(String str);
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
package org.fox.ttrss;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class PreferencesActivity extends PreferenceActivity {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -22,12 +20,12 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
|
||||
findPreference("justify_article_text").setEnabled(!prefs.getBoolean("combined_mode", false));
|
||||
|
||||
findPreference("combined_mode").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
/* findPreference("combined_mode").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
findPreference("justify_article_text").setEnabled(!newValue.toString().equals("true"));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}); */
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,34 +10,42 @@ import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class OfflineArticleFragment extends Fragment {
|
||||
@SuppressWarnings("unused")
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
private SharedPreferences m_prefs;
|
||||
private int m_articleId;
|
||||
private boolean m_isCat = false; // FIXME use
|
||||
private Cursor m_cursor;
|
||||
private OfflineServices m_offlineServices;
|
||||
private OfflineActivity m_activity;
|
||||
|
||||
public OfflineArticleFragment() {
|
||||
super();
|
||||
@ -48,6 +56,32 @@ public class OfflineArticleFragment extends Fragment {
|
||||
m_articleId = articleId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
/* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo(); */
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.article_link_share:
|
||||
m_activity.shareArticle(m_articleId);
|
||||
return true;
|
||||
case R.id.article_link_copy:
|
||||
if (true) {
|
||||
Cursor article = m_activity.getArticleById(m_articleId);
|
||||
|
||||
if (article != null) {
|
||||
m_activity.copyToClipboard(article.getString(article.getColumnIndex("link")));
|
||||
article.close();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
@ -59,6 +93,7 @@ public class OfflineArticleFragment extends Fragment {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
@ -68,7 +103,7 @@ public class OfflineArticleFragment extends Fragment {
|
||||
|
||||
View view = inflater.inflate(R.layout.article_fragment, container, false);
|
||||
|
||||
m_cursor = m_offlineServices.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
m_cursor = m_activity.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
new String[] { "articles.*", "feeds.title AS feed_title" }, "articles." + BaseColumns._ID + "=?",
|
||||
new String[] { String.valueOf(m_articleId) }, null, null, null);
|
||||
|
||||
@ -87,8 +122,24 @@ public class OfflineArticleFragment extends Fragment {
|
||||
else
|
||||
titleStr = m_cursor.getString(m_cursor.getColumnIndex("title"));
|
||||
|
||||
title.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
title.setText(Html.fromHtml("<a href=\""+m_cursor.getString(m_cursor.getColumnIndex("link")).trim().replace("\"", "\\\"")+"\">" + titleStr + "</a>"));
|
||||
final String link = m_cursor.getString(m_cursor.getColumnIndex("link"));
|
||||
|
||||
title.setText(titleStr);
|
||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
title.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(link.trim()));
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
m_activity.toast(R.string.error_other_error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(title);
|
||||
}
|
||||
|
||||
@ -96,6 +147,16 @@ public class OfflineArticleFragment extends Fragment {
|
||||
|
||||
if (web != null) {
|
||||
|
||||
web.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int progress) {
|
||||
m_activity.setProgress(Math.round(((float)progress / 100f) * 10000));
|
||||
if (progress == 100) {
|
||||
m_activity.setProgressBarVisibility(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String content;
|
||||
String cssOverride = "";
|
||||
|
||||
@ -199,7 +260,7 @@ public class OfflineArticleFragment extends Fragment {
|
||||
if (tagv != null) {
|
||||
int feedTitleIndex = m_cursor.getColumnIndex("feed_title");
|
||||
|
||||
if (feedTitleIndex != -1 && m_offlineServices.activeFeedIsCat()) {
|
||||
if (feedTitleIndex != -1 && m_isCat) {
|
||||
tagv.setText(m_cursor.getString(feedTitleIndex));
|
||||
} else {
|
||||
String tagsStr = m_cursor.getString(m_cursor.getColumnIndex("tags"));
|
||||
@ -231,7 +292,7 @@ public class OfflineArticleFragment extends Fragment {
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
|
||||
m_offlineServices = (OfflineServices)activity;
|
||||
m_activity = (OfflineActivity) activity;
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,36 +3,77 @@ package org.fox.ttrss.offline;
|
||||
import org.fox.ttrss.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class OfflineArticlePager extends Fragment {
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
private PagerAdapter m_adapter;
|
||||
private OfflineServices m_offlineServices;
|
||||
private OfflineHeadlinesFragment m_hf;
|
||||
private OfflineActivity m_activity;
|
||||
private OfflineHeadlinesEventListener m_listener;
|
||||
private boolean m_isCat;
|
||||
private int m_feedId;
|
||||
private int m_articleId;
|
||||
private String m_searchQuery = "";
|
||||
private Cursor m_cursor;
|
||||
|
||||
public int getFeedId() {
|
||||
return m_feedId;
|
||||
}
|
||||
|
||||
public boolean getFeedIsCat() {
|
||||
return m_isCat;
|
||||
}
|
||||
|
||||
public Cursor createCursor() {
|
||||
String feedClause = null;
|
||||
|
||||
if (m_isCat) {
|
||||
feedClause = "feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)";
|
||||
} else {
|
||||
feedClause = "feed_id = ?";
|
||||
}
|
||||
|
||||
if (m_searchQuery == null || m_searchQuery.equals("")) {
|
||||
return m_activity.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
new String[] { "articles."+BaseColumns._ID, "feeds.title AS feed_title" }, feedClause,
|
||||
new String[] { String.valueOf(m_feedId) }, null, null, "updated DESC");
|
||||
} else {
|
||||
return m_activity.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
new String[] { "articles."+BaseColumns._ID },
|
||||
feedClause + " AND (articles.title LIKE '%' || ? || '%' OR content LIKE '%' || ? || '%')",
|
||||
new String[] { String.valueOf(m_feedId), m_searchQuery, m_searchQuery }, null, null, "updated DESC");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close();
|
||||
}
|
||||
|
||||
private class PagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
public PagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
int articleId = m_hf.getArticleIdAtPosition(position);
|
||||
Log.d(TAG, "getItem: " + position);
|
||||
|
||||
if (articleId != 0) {
|
||||
return new OfflineArticleFragment(articleId);
|
||||
if (m_cursor.moveToPosition(position)) {
|
||||
return new OfflineArticleFragment(m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)));
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -40,30 +81,48 @@ public class OfflineArticlePager extends Fragment {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return m_hf.getArticleCount();
|
||||
return m_cursor.getCount();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public OfflineArticlePager() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OfflineArticlePager(int articleId) {
|
||||
public OfflineArticlePager(int articleId, int feedId, boolean isCat) {
|
||||
super();
|
||||
|
||||
m_feedId = feedId;
|
||||
m_isCat = isCat;
|
||||
m_articleId = articleId;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.article_pager, container, false);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
m_articleId = savedInstanceState.getInt("articleId", 0);
|
||||
m_feedId = savedInstanceState.getInt("feedId", 0);
|
||||
m_isCat = savedInstanceState.getBoolean("isCat", false);
|
||||
}
|
||||
|
||||
m_adapter = new PagerAdapter(getActivity().getSupportFragmentManager());
|
||||
|
||||
ViewPager pager = (ViewPager) view.findViewById(R.id.article_pager);
|
||||
m_cursor.moveToFirst();
|
||||
|
||||
int position = m_hf.getArticleIdPosition(m_articleId);
|
||||
int position = 0;
|
||||
|
||||
while (!m_cursor.isLast()) {
|
||||
if (m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)) == m_articleId) {
|
||||
position = m_cursor.getPosition();
|
||||
break;
|
||||
}
|
||||
m_cursor.moveToNext();
|
||||
}
|
||||
|
||||
ViewPager pager = (ViewPager) view.findViewById(R.id.article_pager);
|
||||
|
||||
pager.setAdapter(m_adapter);
|
||||
pager.setCurrentItem(position);
|
||||
@ -79,18 +138,13 @@ public class OfflineArticlePager extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
int articleId = m_hf.getArticleIdAtPosition(position);
|
||||
if (m_cursor.moveToPosition(position)) {
|
||||
int articleId = m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID));
|
||||
|
||||
if (articleId != 0) {
|
||||
m_offlineServices.setSelectedArticleId(articleId);
|
||||
m_listener.onArticleSelected(articleId, false);
|
||||
|
||||
SQLiteStatement stmt = m_offlineServices.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 " + "WHERE " + BaseColumns._ID
|
||||
+ " = ?");
|
||||
m_articleId = articleId;
|
||||
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -102,8 +156,58 @@ public class OfflineArticlePager extends Fragment {
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_hf = (OfflineHeadlinesFragment) getActivity().getSupportFragmentManager().findFragmentByTag(OfflineActivity.FRAG_HEADLINES);
|
||||
m_offlineServices = (OfflineServices)activity;
|
||||
m_activity = (OfflineActivity)activity;
|
||||
m_listener = (OfflineHeadlinesEventListener)activity;
|
||||
m_cursor = createCursor();
|
||||
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close();
|
||||
|
||||
m_cursor = createCursor();
|
||||
|
||||
if (m_cursor != null) {
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSelectedArticleId() {
|
||||
return m_articleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
out.putInt("articleId", m_articleId);
|
||||
out.putInt("feedId", m_feedId);
|
||||
out.putBoolean("isCat", m_isCat);
|
||||
|
||||
}
|
||||
|
||||
public void setSearchQuery(String searchQuery) {
|
||||
m_searchQuery = searchQuery;
|
||||
}
|
||||
|
||||
public void setArticleId(int articleId) {
|
||||
m_articleId = articleId;
|
||||
|
||||
m_cursor.moveToFirst();
|
||||
|
||||
int position = 0;
|
||||
|
||||
while (!m_cursor.isLast()) {
|
||||
if (m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)) == m_articleId) {
|
||||
position = m_cursor.getPosition();
|
||||
break;
|
||||
}
|
||||
m_cursor.moveToNext();
|
||||
}
|
||||
|
||||
ViewPager pager = (ViewPager) getView().findViewById(R.id.article_pager);
|
||||
|
||||
pager.setCurrentItem(position);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.fox.ttrss.ApiRequest;
|
||||
import org.fox.ttrss.MainActivity;
|
||||
import org.fox.ttrss.OnlineActivity;
|
||||
import org.fox.ttrss.R;
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
@ -46,8 +46,8 @@ public class OfflineDownloadService extends Service {
|
||||
public static final String INTENT_ACTION_SUCCESS = "org.fox.ttrss.intent.action.DownloadComplete";
|
||||
public static final String INTENT_ACTION_CANCEL = "org.fox.ttrss.intent.action.Cancel";
|
||||
|
||||
private static final int OFFLINE_SYNC_SEQ = 40;
|
||||
private static final int OFFLINE_SYNC_MAX = 500;
|
||||
private static final int OFFLINE_SYNC_SEQ = 50;
|
||||
private static final int OFFLINE_SYNC_MAX = OFFLINE_SYNC_SEQ * 10;
|
||||
|
||||
private SQLiteDatabase m_writableDb;
|
||||
private SQLiteDatabase m_readableDb;
|
||||
@ -87,11 +87,12 @@ public class OfflineDownloadService extends Service {
|
||||
initDatabase();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updateNotification(String msg) {
|
||||
Notification notification = new Notification(R.drawable.icon,
|
||||
getString(R.string.notify_downloading_title), System.currentTimeMillis());
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
Intent intent = new Intent(this, OnlineActivity.class);
|
||||
intent.setAction(INTENT_ACTION_CANCEL);
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
@ -158,9 +159,9 @@ public class OfflineDownloadService extends Service {
|
||||
m_readableDb = dh.getReadableDatabase();
|
||||
}
|
||||
|
||||
private synchronized SQLiteDatabase getReadableDb() {
|
||||
/* private synchronized SQLiteDatabase getReadableDb() {
|
||||
return m_readableDb;
|
||||
}
|
||||
} */
|
||||
|
||||
private synchronized SQLiteDatabase getWritableDb() {
|
||||
return m_writableDb;
|
||||
@ -198,7 +199,9 @@ public class OfflineDownloadService extends Service {
|
||||
|
||||
ApiRequest req = new ApiRequest(getApplicationContext()) {
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
protected JsonElement doInBackground(HashMap<String, String>... params) {
|
||||
JsonElement content = super.doInBackground(params);
|
||||
|
||||
if (content != null) {
|
||||
|
||||
try {
|
||||
@ -226,18 +229,24 @@ public class OfflineDownloadService extends Service {
|
||||
m_articleOffset = 0;
|
||||
|
||||
getWritableDb().execSQL("DELETE FROM articles;");
|
||||
|
||||
if (m_canProceed) {
|
||||
downloadArticles();
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
updateNotification(R.string.offline_switch_error);
|
||||
downloadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
if (content != null) {
|
||||
if (m_canProceed) {
|
||||
downloadArticles();
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
} else {
|
||||
updateNotification(getErrorMessage());
|
||||
downloadFailed();
|
||||
@ -266,10 +275,10 @@ public class OfflineDownloadService extends Service {
|
||||
getWritableDb().execSQL("DELETE FROM categories;");
|
||||
|
||||
ApiRequest req = new ApiRequest(getApplicationContext()) {
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
if (content != null) {
|
||||
protected JsonElement doInBackground(HashMap<String, String>... params) {
|
||||
JsonElement content = super.doInBackground(params);
|
||||
|
||||
if (content != null) {
|
||||
try {
|
||||
Type listType = new TypeToken<List<FeedCategory>>() {}.getType();
|
||||
List<FeedCategory> cats = new Gson().fromJson(content, listType);
|
||||
@ -289,17 +298,23 @@ public class OfflineDownloadService extends Service {
|
||||
|
||||
Log.d(TAG, "offline: done downloading categories");
|
||||
|
||||
if (m_canProceed) {
|
||||
downloadFeeds();
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
updateNotification(R.string.offline_switch_error);
|
||||
downloadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
if (content != null) {
|
||||
if (m_canProceed) {
|
||||
downloadFeeds();
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
} else {
|
||||
updateNotification(getErrorMessage());
|
||||
downloadFailed();
|
||||
@ -335,22 +350,27 @@ public class OfflineDownloadService extends Service {
|
||||
}
|
||||
|
||||
public class OfflineArticlesRequest extends ApiRequest {
|
||||
List<Article> m_articles;
|
||||
|
||||
public OfflineArticlesRequest(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
protected JsonElement doInBackground(HashMap<String, String>... params) {
|
||||
JsonElement content = super.doInBackground(params);
|
||||
|
||||
if (content != null) {
|
||||
|
||||
try {
|
||||
Type listType = new TypeToken<List<Article>>() {}.getType();
|
||||
List<Article> articles = new Gson().fromJson(content, listType);
|
||||
m_articles = new Gson().fromJson(content, listType);
|
||||
|
||||
SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO articles " +
|
||||
"("+BaseColumns._ID+", unread, marked, published, updated, is_updated, title, link, feed_id, tags, content) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
|
||||
|
||||
for (Article article : articles) {
|
||||
for (Article article : m_articles) {
|
||||
|
||||
String tagsString = "";
|
||||
|
||||
@ -402,25 +422,12 @@ public class OfflineDownloadService extends Service {
|
||||
|
||||
}
|
||||
|
||||
m_articleOffset += m_articles.size();
|
||||
|
||||
Log.d(TAG, "offline: received " + m_articles.size() + " articles; canProc=" + m_canProceed);
|
||||
|
||||
stmtInsert.close();
|
||||
|
||||
//m_canGetMoreArticles = articles.size() == 30;
|
||||
m_articleOffset += articles.size();
|
||||
|
||||
Log.d(TAG, "offline: received " + articles.size() + " articles; canProc=" + m_canProceed);
|
||||
|
||||
if (m_canProceed) {
|
||||
if (articles.size() == OFFLINE_SYNC_SEQ && m_articleOffset < m_syncMax) {
|
||||
downloadArticles();
|
||||
} else {
|
||||
downloadComplete();
|
||||
}
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
} catch (Exception e) {
|
||||
updateNotification(R.string.offline_switch_error);
|
||||
Log.d(TAG, "offline: failed: exception when loading articles");
|
||||
@ -428,6 +435,25 @@ public class OfflineDownloadService extends Service {
|
||||
downloadFailed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JsonElement content) {
|
||||
if (content != null) {
|
||||
|
||||
if (m_canProceed && m_articles != null) {
|
||||
if (m_articles.size() == OFFLINE_SYNC_SEQ && m_articleOffset < m_syncMax) {
|
||||
downloadArticles();
|
||||
} else {
|
||||
downloadComplete();
|
||||
}
|
||||
} else {
|
||||
downloadFailed();
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.d(TAG, "offline: failed: " + getErrorMessage());
|
||||
updateNotification(getErrorMessage());
|
||||
|
@ -16,6 +16,7 @@ import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
@ -31,7 +32,7 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
private FeedCategoryListAdapter m_adapter;
|
||||
private int m_selectedCatId;
|
||||
private Cursor m_cursor;
|
||||
private OfflineServices m_offlineServices;
|
||||
private OfflineFeedsActivity m_activity;
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
@ -50,11 +51,11 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
}
|
||||
|
||||
public Cursor createCursor() {
|
||||
String unreadOnly = BaseColumns._ID + "> 0 AND " + (m_offlineServices.getUnreadOnly() ? "unread > 0" : "1");
|
||||
String unreadOnly = BaseColumns._ID + "> 0 AND " + (m_activity.getUnreadOnly() ? "unread > 0" : "1");
|
||||
|
||||
String order = m_prefs.getBoolean("sort_feeds_by_unread", false) ? "unread DESC, title" : "title";
|
||||
|
||||
return m_offlineServices.getReadableDb().query("cats_unread",
|
||||
return m_activity.getReadableDb().query("cats_unread",
|
||||
null, unreadOnly, null, null, null, order);
|
||||
}
|
||||
|
||||
@ -69,6 +70,48 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.browse_articles:
|
||||
if (true) {
|
||||
int catId = getCatIdAtPosition(info.position);
|
||||
if (catId != -10000) {
|
||||
m_activity.onCatSelected(catId, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.browse_feeds:
|
||||
if (true) {
|
||||
int catId = getCatIdAtPosition(info.position);
|
||||
if (catId != -10000) {
|
||||
m_activity.onCatSelected(catId, false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.catchup_category:
|
||||
if (true) {
|
||||
int catId = getCatIdAtPosition(info.position);
|
||||
if (catId != -10000) {
|
||||
m_activity.catchupFeed(catId, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
@ -106,7 +149,7 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_offlineServices = (OfflineServices)activity;
|
||||
m_activity = (OfflineFeedsActivity)activity;
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
@ -131,9 +174,9 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
int feedId = (int) cursor.getLong(0);
|
||||
Log.d(TAG, "clicked on feed " + feedId);
|
||||
|
||||
m_offlineServices.onCatSelected(feedId);
|
||||
m_activity.onCatSelected(feedId);
|
||||
|
||||
if (!m_offlineServices.isSmallScreen())
|
||||
if (!m_activity.isSmallScreen())
|
||||
m_selectedCatId = feedId;
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
@ -175,7 +218,7 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
public int getItemViewType(int position) {
|
||||
Cursor cursor = (Cursor) this.getItem(position);
|
||||
|
||||
if (!m_offlineServices.isSmallScreen() && cursor.getLong(0) == m_selectedCatId) {
|
||||
if (!m_activity.isSmallScreen() && cursor.getLong(0) == m_selectedCatId) {
|
||||
return VIEW_SELECTED;
|
||||
} else {
|
||||
return VIEW_NORMAL;
|
||||
@ -251,7 +294,7 @@ public class OfflineFeedCategoriesFragment extends Fragment implements OnItemCli
|
||||
return catId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return -10000;
|
||||
}
|
||||
|
||||
public void setSelectedFeedId(int feedId) {
|
||||
|
253
src/org/fox/ttrss/offline/OfflineFeedsActivity.java
Normal file
253
src/org/fox/ttrss/offline/OfflineFeedsActivity.java
Normal file
@ -0,0 +1,253 @@
|
||||
package org.fox.ttrss.offline;
|
||||
|
||||
import org.fox.ttrss.R;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
public class OfflineFeedsActivity extends OfflineActivity implements OfflineHeadlinesEventListener {
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
m_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
} else {
|
||||
setTheme(R.style.LightTheme);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.feeds);
|
||||
|
||||
setSmallScreen(findViewById(R.id.headlines_fragment) == null);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (intent.getIntExtra("feed", -10000) != -10000 || intent.getIntExtra("category", -10000) != -10000 ||
|
||||
intent.getIntExtra("article", -10000) != -10000) {
|
||||
|
||||
if (!isCompatMode()) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
int feedId = intent.getIntExtra("feed", -10000);
|
||||
int catId = intent.getIntExtra("category", -10000);
|
||||
int articleId = intent.getIntExtra("article", -10000);
|
||||
boolean isCat = intent.getBooleanExtra("isCat", false);
|
||||
|
||||
if (articleId != -10000) {
|
||||
ft.replace(R.id.feeds_fragment, new OfflineArticlePager(articleId, feedId, isCat), FRAG_ARTICLE);
|
||||
} else {
|
||||
if (feedId != -10000) {
|
||||
ft.replace(R.id.feeds_fragment, new OfflineHeadlinesFragment(feedId, isCat), FRAG_HEADLINES);
|
||||
}
|
||||
|
||||
if (catId != -10000) {
|
||||
ft.replace(R.id.feeds_fragment, new OfflineFeedsFragment(catId), FRAG_FEEDS);
|
||||
}
|
||||
}
|
||||
|
||||
ft.commit();
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
if (m_prefs.getBoolean("enable_cats", false)) {
|
||||
ft.replace(R.id.feeds_fragment, new OfflineFeedCategoriesFragment(), FRAG_CATS);
|
||||
} else {
|
||||
ft.replace(R.id.feeds_fragment, new OfflineFeedsFragment(), FRAG_FEEDS);
|
||||
}
|
||||
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
setLoadingStatus(R.string.blank, false);
|
||||
findViewById(R.id.loading_container).setVisibility(View.GONE);
|
||||
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.show_feeds:
|
||||
setUnreadOnly(!getUnreadOnly());
|
||||
initMenu();
|
||||
refresh();
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle out) {
|
||||
super.onSaveInstanceState(out);
|
||||
|
||||
}
|
||||
|
||||
public void initMenu() {
|
||||
super.initMenu();
|
||||
|
||||
if (m_menu != null) {
|
||||
Fragment ff = getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS);
|
||||
Fragment cf = getSupportFragmentManager().findFragmentByTag(FRAG_CATS);
|
||||
OfflineArticlePager af = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_feeds, (ff != null && ff.isAdded()) || (cf != null && cf.isAdded()));
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_article, af != null && af.isAdded());
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded() && getSelectedArticleCount() == 0);
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines_selection, hf != null && hf.isAdded() && getSelectedArticleCount() != 0);
|
||||
|
||||
MenuItem item = m_menu.findItem(R.id.show_feeds);
|
||||
|
||||
if (getUnreadOnly()) {
|
||||
item.setTitle(R.string.menu_all_feeds);
|
||||
} else {
|
||||
item.setTitle(R.string.menu_unread_feeds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCatSelected(int catId) {
|
||||
onCatSelected(catId, m_prefs.getBoolean("browse_cats_like_feeds", false));
|
||||
}
|
||||
|
||||
public void onCatSelected(int catId, boolean openAsFeed) {
|
||||
if (openAsFeed) {
|
||||
onFeedSelected(catId, true, true);
|
||||
} else {
|
||||
if (isSmallScreen()) {
|
||||
Intent intent = new Intent(OfflineFeedsActivity.this, OfflineFeedsActivity.class);
|
||||
intent.putExtra("category", catId);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
OfflineFeedsFragment ff = new OfflineFeedsFragment(catId);
|
||||
|
||||
ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS);
|
||||
ft.addToBackStack(null);
|
||||
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onFeedSelected(int feedId) {
|
||||
onFeedSelected(feedId, false, true);
|
||||
}
|
||||
|
||||
public void onFeedSelected(int feedId, boolean isCat, boolean open) {
|
||||
|
||||
if (open) {
|
||||
if (isSmallScreen()) {
|
||||
|
||||
Intent intent = new Intent(OfflineFeedsActivity.this, OfflineFeedsActivity.class);
|
||||
intent.putExtra("feed", feedId);
|
||||
intent.putExtra("isCat", isCat);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
} else {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
|
||||
OfflineHeadlinesFragment hf = new OfflineHeadlinesFragment(feedId, isCat);
|
||||
ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES);
|
||||
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void catchupFeed(int feedId, boolean isCat) {
|
||||
if (isCat) {
|
||||
SQLiteStatement stmt = getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 WHERE feed_id IN (SELECT "+
|
||||
BaseColumns._ID+" FROM feeds WHERE cat_id = ?)");
|
||||
stmt.bindLong(1, feedId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
} else {
|
||||
SQLiteStatement stmt = getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 WHERE feed_id = ?");
|
||||
stmt.bindLong(1, feedId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(int articleId, boolean open) {
|
||||
SQLiteStatement stmt = getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 " + "WHERE " + BaseColumns._ID
|
||||
+ " = ?");
|
||||
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
|
||||
if (open) {
|
||||
if (isSmallScreen()) {
|
||||
|
||||
OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
Intent intent = new Intent(OfflineFeedsActivity.this, OfflineFeedsActivity.class);
|
||||
intent.putExtra("feed", hf.getFeedId());
|
||||
intent.putExtra("isCat", hf.getFeedIsCat());
|
||||
intent.putExtra("article", articleId);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
} else {
|
||||
|
||||
OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
Intent intent = new Intent(OfflineFeedsActivity.this, OfflineHeadlinesActivity.class);
|
||||
intent.putExtra("feed", hf.getFeedId());
|
||||
intent.putExtra("isCat", hf.getFeedIsCat());
|
||||
intent.putExtra("article", articleId);
|
||||
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
|
||||
initMenu();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(int articleId) {
|
||||
onArticleSelected(articleId, true);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
@ -39,7 +40,7 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
private int m_catId = -1;
|
||||
private boolean m_enableFeedIcons;
|
||||
private Cursor m_cursor;
|
||||
private OfflineServices m_offlineServices;
|
||||
private OfflineFeedsActivity m_activity;
|
||||
|
||||
public OfflineFeedsFragment() {
|
||||
//
|
||||
@ -49,6 +50,29 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
m_catId = catId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
switch (item.getItemId()) {
|
||||
case R.id.catchup_feed:
|
||||
int feedId = getFeedIdAtPosition(info.position);
|
||||
if (feedId != -10000) {
|
||||
m_activity.catchupFeed(feedId, false);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
@ -66,14 +90,14 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
}
|
||||
|
||||
public Cursor createCursor() {
|
||||
String unreadOnly = m_offlineServices.getUnreadOnly() ? "unread > 0" : "1";
|
||||
String unreadOnly = m_activity.getUnreadOnly() ? "unread > 0" : "1";
|
||||
String order = m_prefs.getBoolean("sort_feeds_by_unread", false) ? "unread DESC, title" : "title";
|
||||
|
||||
if (m_catId != -1) {
|
||||
return m_offlineServices.getReadableDb().query("feeds_unread",
|
||||
return m_activity.getReadableDb().query("feeds_unread",
|
||||
null, unreadOnly + " AND cat_id = ?", new String[] { String.valueOf(m_catId) }, null, null, order);
|
||||
} else {
|
||||
return m_offlineServices.getReadableDb().query("feeds_unread",
|
||||
return m_activity.getReadableDb().query("feeds_unread",
|
||||
null, unreadOnly, null, null, null, order);
|
||||
}
|
||||
}
|
||||
@ -129,7 +153,7 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
m_offlineServices = (OfflineServices)activity;
|
||||
m_activity = (OfflineFeedsActivity)activity;
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
@ -155,9 +179,9 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
int feedId = (int) cursor.getLong(0);
|
||||
Log.d(TAG, "clicked on feed " + feedId);
|
||||
|
||||
m_offlineServices.onFeedSelected(feedId);
|
||||
m_activity.onFeedSelected(feedId);
|
||||
|
||||
if (!m_offlineServices.isSmallScreen())
|
||||
if (!m_activity.isSmallScreen())
|
||||
m_selectedFeedId = feedId;
|
||||
|
||||
m_adapter.notifyDataSetChanged();
|
||||
@ -199,7 +223,7 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
public int getItemViewType(int position) {
|
||||
Cursor cursor = (Cursor) this.getItem(position);
|
||||
|
||||
if (!m_offlineServices.isSmallScreen() && cursor.getLong(0) == m_selectedFeedId) {
|
||||
if (!m_activity.isSmallScreen() && cursor.getLong(0) == m_selectedFeedId) {
|
||||
return VIEW_SELECTED;
|
||||
} else {
|
||||
return VIEW_NORMAL;
|
||||
@ -295,7 +319,7 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
||||
return feedId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return -10000;
|
||||
}
|
||||
|
||||
public void setSelectedFeedId(int feedId) {
|
||||
|
142
src/org/fox/ttrss/offline/OfflineHeadlinesActivity.java
Normal file
142
src/org/fox/ttrss/offline/OfflineHeadlinesActivity.java
Normal file
@ -0,0 +1,142 @@
|
||||
package org.fox.ttrss.offline;
|
||||
|
||||
import org.fox.ttrss.GlobalState;
|
||||
import org.fox.ttrss.R;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
public class OfflineHeadlinesActivity extends OfflineActivity implements OfflineHeadlinesEventListener {
|
||||
@SuppressWarnings("unused")
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
protected SharedPreferences m_prefs;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
m_prefs = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
} else {
|
||||
setTheme(R.style.LightTheme);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.headlines);
|
||||
|
||||
if (!isCompatMode()) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setSmallScreen(findViewById(R.id.headlines_fragment) == null);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
Intent i = getIntent();
|
||||
|
||||
if (i.getExtras() != null) {
|
||||
int feedId = i.getIntExtra("feed", 0);
|
||||
boolean isCat = i.getBooleanExtra("isCat", false);
|
||||
int articleId = i.getIntExtra("article", 0);
|
||||
String searchQuery = i.getStringExtra("searchQuery");
|
||||
|
||||
OfflineHeadlinesFragment hf = new OfflineHeadlinesFragment(feedId, isCat);
|
||||
OfflineArticlePager af = new OfflineArticlePager(articleId, feedId, isCat);
|
||||
|
||||
hf.setActiveArticleId(articleId);
|
||||
|
||||
hf.setSearchQuery(searchQuery);
|
||||
af.setSearchQuery(searchQuery);
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES);
|
||||
ft.replace(R.id.article_fragment, af, FRAG_ARTICLE);
|
||||
|
||||
ft.commit();
|
||||
|
||||
Cursor c;
|
||||
|
||||
if (isCat) {
|
||||
c = getCatById(feedId);
|
||||
} else {
|
||||
c = getFeedById(feedId);
|
||||
}
|
||||
|
||||
if (c != null) {
|
||||
setTitle(c.getString(c.getColumnIndex("title")));
|
||||
c.close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
setLoadingStatus(R.string.blank, false);
|
||||
findViewById(R.id.loading_container).setVisibility(View.GONE);
|
||||
|
||||
initMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(int articleId, boolean open) {
|
||||
SQLiteStatement stmt = getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 " + "WHERE " + BaseColumns._ID
|
||||
+ " = ?");
|
||||
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
|
||||
if (open) {
|
||||
OfflineArticlePager af = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
|
||||
af.setArticleId(articleId);
|
||||
} else {
|
||||
OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
hf.setActiveArticleId(articleId);
|
||||
}
|
||||
|
||||
GlobalState.getInstance().m_selectedArticleId = articleId;
|
||||
|
||||
initMenu();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initMenu() {
|
||||
super.initMenu();
|
||||
|
||||
if (m_menu != null) {
|
||||
m_menu.setGroupVisible(R.id.menu_group_feeds, false);
|
||||
|
||||
OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.getSelectedArticleCount() == 0);
|
||||
m_menu.setGroupVisible(R.id.menu_group_headlines_selection, hf != null && hf.getSelectedArticleCount() != 0);
|
||||
|
||||
Fragment af = getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
|
||||
|
||||
m_menu.setGroupVisible(R.id.menu_group_article, af != null);
|
||||
|
||||
m_menu.findItem(R.id.search).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArticleSelected(int articleId) {
|
||||
onArticleSelected(articleId, true);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.fox.ttrss.offline;
|
||||
|
||||
|
||||
public interface OfflineHeadlinesEventListener {
|
||||
void onArticleSelected(int articleId, boolean open);
|
||||
void onArticleSelected(int articleId);
|
||||
}
|
@ -5,6 +5,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.fox.ttrss.GlobalState;
|
||||
import org.fox.ttrss.R;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
@ -27,6 +28,7 @@ import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
@ -55,10 +57,12 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
private Cursor m_cursor;
|
||||
private ArticleListAdapter m_adapter;
|
||||
|
||||
private OfflineServices m_offlineServices;
|
||||
private OfflineHeadlinesEventListener m_listener;
|
||||
private OfflineActivity m_activity;
|
||||
|
||||
private ImageGetter m_dummyGetter = new ImageGetter() {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
return new BitmapDrawable();
|
||||
@ -83,7 +87,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
}
|
||||
|
||||
public int getSelectedArticleCount() {
|
||||
Cursor c = m_offlineServices.getReadableDb().query("articles",
|
||||
Cursor c = m_activity.getReadableDb().query("articles",
|
||||
new String[] { "COUNT(*)" }, "selected = 1", null, null, null, null);
|
||||
c.moveToFirst();
|
||||
int selected = c.getInt(0);
|
||||
@ -92,11 +96,123 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
|
||||
.getMenuInfo();
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.article_link_copy:
|
||||
if (true) {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
|
||||
Cursor article = m_activity.getArticleById(articleId);
|
||||
|
||||
if (article != null) {
|
||||
m_activity.copyToClipboard(article.getString(article.getColumnIndex("link")));
|
||||
article.close();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.selection_toggle_marked:
|
||||
if (getSelectedArticleCount() > 0) {
|
||||
SQLiteStatement stmt = m_activity.getWritableDb()
|
||||
.compileStatement(
|
||||
"UPDATE articles SET marked = NOT marked WHERE selected = 1");
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
} else {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
|
||||
SQLiteStatement stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET marked = NOT marked WHERE "
|
||||
+ BaseColumns._ID + " = ?");
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
case R.id.selection_toggle_published:
|
||||
if (getSelectedArticleCount() > 0) {
|
||||
SQLiteStatement stmt = m_activity.getWritableDb()
|
||||
.compileStatement(
|
||||
"UPDATE articles SET published = NOT published WHERE selected = 1");
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
} else {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
|
||||
SQLiteStatement stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET published = NOT published WHERE "
|
||||
+ BaseColumns._ID + " = ?");
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
case R.id.selection_toggle_unread:
|
||||
if (getSelectedArticleCount() > 0) {
|
||||
SQLiteStatement stmt = m_activity.getWritableDb()
|
||||
.compileStatement(
|
||||
"UPDATE articles SET unread = NOT unread WHERE selected = 1");
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
} else {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
|
||||
SQLiteStatement stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = NOT unread WHERE "
|
||||
+ BaseColumns._ID + " = ?");
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
case R.id.share_article:
|
||||
if (true) {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
m_activity.shareArticle(articleId);
|
||||
}
|
||||
return true;
|
||||
case R.id.catchup_above:
|
||||
if (true) {
|
||||
int articleId = getArticleIdAtPosition(info.position);
|
||||
|
||||
SQLiteStatement stmt = null;
|
||||
|
||||
if (m_feedIsCat) {
|
||||
stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 WHERE " +
|
||||
"updated >= (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " +
|
||||
"AND feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)");
|
||||
} else {
|
||||
stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 WHERE " +
|
||||
"updated >= (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " +
|
||||
"AND feed_id = ?");
|
||||
}
|
||||
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.bindLong(2, m_feedId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
default:
|
||||
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
|
||||
getActivity().getMenuInflater().inflate(R.menu.headlines_menu, menu);
|
||||
getActivity().getMenuInflater().inflate(R.menu.headlines_context_menu, menu);
|
||||
|
||||
if (getSelectedArticleCount() > 0) {
|
||||
menu.setHeaderTitle(R.string.headline_context_multiple);
|
||||
@ -107,12 +223,31 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
menu.setHeaderTitle(c.getString(c.getColumnIndex("title")));
|
||||
//c.close();
|
||||
menu.setGroupVisible(R.id.menu_group_single_article, true);
|
||||
|
||||
menu.findItem(R.id.set_labels).setVisible(false);
|
||||
menu.findItem(R.id.article_set_note).setVisible(false);
|
||||
}
|
||||
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (GlobalState.getInstance().m_selectedArticleId != 0) {
|
||||
m_activeArticleId = GlobalState.getInstance().m_selectedArticleId;
|
||||
GlobalState.getInstance().m_selectedArticleId = 0;
|
||||
}
|
||||
|
||||
if (m_activeArticleId != 0) {
|
||||
setActiveArticleId(m_activeArticleId);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close();
|
||||
|
||||
@ -120,7 +255,6 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
|
||||
if (m_cursor != null) {
|
||||
m_adapter.changeCursor(m_cursor);
|
||||
setActiveArticleId(m_offlineServices.getSelectedArticleId());
|
||||
m_adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
@ -135,6 +269,8 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
m_combinedMode = savedInstanceState.getBoolean("combinedMode");
|
||||
m_searchQuery = (String) savedInstanceState.getCharSequence("searchQuery");
|
||||
m_feedIsCat = savedInstanceState.getBoolean("feedIsCat");
|
||||
} else {
|
||||
m_activity.getWritableDb().execSQL("UPDATE articles SET selected = 0 ");
|
||||
}
|
||||
|
||||
View view = inflater.inflate(R.layout.headlines_fragment, container, false);
|
||||
@ -150,7 +286,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
list.setEmptyView(view.findViewById(R.id.no_headlines));
|
||||
registerForContextMenu(list);
|
||||
|
||||
if (m_offlineServices.isSmallScreen() || m_offlineServices.isPortrait())
|
||||
if (m_activity.isSmallScreen() || m_activity.isPortrait())
|
||||
view.findViewById(R.id.headlines_fragment).setPadding(0, 0, 0, 0);
|
||||
|
||||
getActivity().setProgressBarIndeterminateVisibility(false);
|
||||
@ -167,12 +303,12 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
feedClause = "feed_id = ?";
|
||||
}
|
||||
|
||||
if (m_searchQuery.equals("")) {
|
||||
return m_offlineServices.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
if (m_searchQuery == null || m_searchQuery.equals("")) {
|
||||
return m_activity.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
new String[] { "articles.*", "feeds.title AS feed_title" }, feedClause,
|
||||
new String[] { String.valueOf(m_feedId) }, null, null, "updated DESC");
|
||||
} else {
|
||||
return m_offlineServices.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
return m_activity.getReadableDb().query("articles LEFT JOIN feeds ON (feed_id = feeds."+BaseColumns._ID+")",
|
||||
new String[] { "articles.*", "feeds.title AS feed_title" },
|
||||
feedClause + " AND (articles.title LIKE '%' || ? || '%' OR content LIKE '%' || ? || '%')",
|
||||
new String[] { String.valueOf(m_feedId), m_searchQuery, m_searchQuery }, null, null, "updated DESC");
|
||||
@ -182,10 +318,11 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
m_offlineServices = (OfflineServices)activity;
|
||||
m_listener = (OfflineHeadlinesEventListener) activity;
|
||||
m_activity = (OfflineActivity) activity;
|
||||
|
||||
m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
|
||||
m_combinedMode = m_prefs.getBoolean("combined_mode", false);
|
||||
m_combinedMode = false; /* m_prefs.getBoolean("combined_mode", false); */
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -197,17 +334,22 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
if (list != null) {
|
||||
Cursor cursor = (Cursor)list.getItemAtPosition(position);
|
||||
|
||||
m_activeArticleId = cursor.getInt(0);
|
||||
int articleId = cursor.getInt(0);
|
||||
|
||||
SQLiteStatement stmtUpdate = m_offlineServices.getWritableDb().compileStatement("UPDATE articles SET unread = 0 " +
|
||||
"WHERE " + BaseColumns._ID + " = ?");
|
||||
|
||||
stmtUpdate.bindLong(1, m_activeArticleId);
|
||||
stmtUpdate.execute();
|
||||
stmtUpdate.close();
|
||||
if (getActivity().findViewById(R.id.article_fragment) != null) {
|
||||
m_activeArticleId = articleId;
|
||||
}
|
||||
|
||||
if (!m_combinedMode) {
|
||||
m_offlineServices.openArticle(m_activeArticleId, 0);
|
||||
m_listener.onArticleSelected(articleId);
|
||||
} else {
|
||||
SQLiteStatement stmt = m_activity.getWritableDb().compileStatement(
|
||||
"UPDATE articles SET unread = 0 " + "WHERE " + BaseColumns._ID
|
||||
+ " = ?");
|
||||
|
||||
stmt.bindLong(1, articleId);
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
}
|
||||
|
||||
refresh();
|
||||
@ -321,7 +463,10 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
if (ft != null && feedTitleIndex != -1 && m_feedIsCat) {
|
||||
String feedTitle = article.getString(feedTitleIndex);
|
||||
|
||||
if (feedTitle != null) {
|
||||
if (feedTitle.length() > 20)
|
||||
feedTitle = feedTitle.substring(0, 20) + "...";
|
||||
|
||||
if (feedTitle.length() > 0) {
|
||||
ft.setText(feedTitle);
|
||||
} else {
|
||||
ft.setVisibility(View.GONE);
|
||||
@ -339,7 +484,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SQLiteStatement stmtUpdate = m_offlineServices.getWritableDb().compileStatement("UPDATE articles SET marked = NOT marked " +
|
||||
SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET marked = NOT marked " +
|
||||
"WHERE " + BaseColumns._ID + " = ?");
|
||||
|
||||
stmtUpdate.bindLong(1, articleId);
|
||||
@ -360,7 +505,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SQLiteStatement stmtUpdate = m_offlineServices.getWritableDb().compileStatement("UPDATE articles SET published = NOT published " +
|
||||
SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET published = NOT published " +
|
||||
"WHERE " + BaseColumns._ID + " = ?");
|
||||
|
||||
stmtUpdate.bindLong(1, articleId);
|
||||
@ -438,7 +583,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
public void onClick(View view) {
|
||||
CheckBox cb = (CheckBox)view;
|
||||
|
||||
SQLiteStatement stmtUpdate = m_offlineServices.getWritableDb().compileStatement("UPDATE articles SET selected = ? " +
|
||||
SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET selected = ? " +
|
||||
"WHERE " + BaseColumns._ID + " = ?");
|
||||
|
||||
stmtUpdate.bindLong(1, cb.isChecked() ? 1 : 0);
|
||||
@ -448,7 +593,7 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
|
||||
refresh();
|
||||
|
||||
m_offlineServices.initMainMenu();
|
||||
m_activity.initMenu();
|
||||
|
||||
}
|
||||
});
|
||||
@ -476,13 +621,19 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
|
||||
public void setActiveArticleId(int articleId) {
|
||||
m_activeArticleId = articleId;
|
||||
// m_adapter.notifyDataSetChanged();
|
||||
try {
|
||||
m_adapter.notifyDataSetChanged();
|
||||
|
||||
ListView list = (ListView)getView().findViewById(R.id.headlines);
|
||||
|
||||
Log.d(TAG, articleId + " position " + getArticleIdPosition(articleId));
|
||||
|
||||
if (list != null) {
|
||||
list.setSelection(getArticleIdPosition(articleId));
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
// invoked before view is created, nvm
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getArticleAtPosition(int position) {
|
||||
@ -520,8 +671,19 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
|
||||
public void setSearchQuery(String query) {
|
||||
if (!m_searchQuery.equals(query)) {
|
||||
m_searchQuery = query;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public int getFeedId() {
|
||||
return m_feedId;
|
||||
}
|
||||
|
||||
public boolean getFeedIsCat() {
|
||||
return m_feedIsCat;
|
||||
}
|
||||
|
||||
public String getSearchQuery() {
|
||||
return m_searchQuery;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package org.fox.ttrss.offline;
|
||||
|
||||
import org.fox.ttrss.OnlineServices;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
public interface OfflineServices {
|
||||
public int getActiveFeedId();
|
||||
public SQLiteDatabase getReadableDb();
|
||||
public SQLiteDatabase getWritableDb();
|
||||
public int getRelativeArticleId(int baseId, int feedId, OnlineServices.RelativeArticle mode);
|
||||
public void onFeedSelected(int feedId);
|
||||
public void onCatSelected(int catId);
|
||||
public void openArticle(int articleId, int compatAnimation);
|
||||
public boolean getUnreadOnly();
|
||||
public int getSelectedArticleId();
|
||||
public void initMainMenu();
|
||||
public boolean isSmallScreen();
|
||||
public void setSelectedArticleId(int articleId);
|
||||
public boolean activeFeedIsCat();
|
||||
public boolean isPortrait();
|
||||
}
|
@ -3,7 +3,7 @@ package org.fox.ttrss.offline;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.fox.ttrss.ApiRequest;
|
||||
import org.fox.ttrss.MainActivity;
|
||||
import org.fox.ttrss.OnlineActivity;
|
||||
import org.fox.ttrss.R;
|
||||
import org.fox.ttrss.util.DatabaseHelper;
|
||||
|
||||
@ -48,12 +48,13 @@ public class OfflineUploadService extends IntentService {
|
||||
m_nmgr.cancel(NOTIFY_UPLOADING);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updateNotification(String msg) {
|
||||
Notification notification = new Notification(R.drawable.icon,
|
||||
getString(R.string.notify_uploading_title), System.currentTimeMillis());
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
new Intent(this, MainActivity.class), 0);
|
||||
new Intent(this, OnlineActivity.class), 0);
|
||||
|
||||
notification.flags |= Notification.FLAG_ONGOING_EVENT;
|
||||
notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
|
||||
|
@ -21,6 +21,14 @@ public class ArticleList extends ArrayList<Article> implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
public Article findById(int id) {
|
||||
for (Article a : this) {
|
||||
if (a.id == id)
|
||||
return a;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void readFromParcel(Parcel in) {
|
||||
int length = in.readInt();
|
||||
|
||||
|
@ -28,6 +28,16 @@ public class Feed implements Comparable<Feed>, Parcelable {
|
||||
|
||||
}
|
||||
|
||||
public boolean equals(Feed feed) {
|
||||
if (feed == this)
|
||||
return true;
|
||||
|
||||
if (feed == null)
|
||||
return false;
|
||||
|
||||
return feed.id == this.id && (this.title == null || this.title.equals(feed.title)) && this.is_cat == feed.is_cat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Feed feed) {
|
||||
if (feed.unread != this.unread)
|
||||
|
@ -1,582 +0,0 @@
|
||||
// Portions copyright 2002, Google, Inc.
|
||||
//
|
||||
// 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.
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
// This code was converted from code at http://iharder.sourceforge.net/base64/
|
||||
// Lots of extraneous features were removed.
|
||||
/* The original code said:
|
||||
* <p>
|
||||
* I am placing this code in the Public Domain. Do with it as you will.
|
||||
* This software comes with no guarantees or warranties but with
|
||||
* plenty of well-wishing instead!
|
||||
* Please visit
|
||||
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
|
||||
* periodically to check for updates or to contribute improvements.
|
||||
* </p>
|
||||
*
|
||||
* @author Robert Harder
|
||||
* @author rharder@usa.net
|
||||
* @version 1.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base64 converter class. This code is not a complete MIME encoder; it simply
|
||||
* converts binary data to base64 data and back.
|
||||
*
|
||||
* <p>
|
||||
* Note {@link CharBase64} is a GWT-compatible implementation of this class.
|
||||
*/
|
||||
public class Base64 {
|
||||
/** Specify encoding (value is {@code true}). */
|
||||
public final static boolean ENCODE = true;
|
||||
|
||||
/** Specify decoding (value is {@code false}). */
|
||||
public final static boolean DECODE = false;
|
||||
|
||||
/** The equals sign (=) as a byte. */
|
||||
private final static byte EQUALS_SIGN = (byte) '=';
|
||||
|
||||
/** The new line character (\n) as a byte. */
|
||||
private final static byte NEW_LINE = (byte) '\n';
|
||||
|
||||
/**
|
||||
* The 64 valid Base64 values.
|
||||
*/
|
||||
private final static byte[] ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
|
||||
(byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q',
|
||||
(byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a',
|
||||
(byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
|
||||
(byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
|
||||
(byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
|
||||
(byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' };
|
||||
|
||||
/**
|
||||
* The 64 valid web safe Base64 values.
|
||||
*/
|
||||
private final static byte[] WEBSAFE_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
|
||||
(byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q',
|
||||
(byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a',
|
||||
(byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
|
||||
(byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
|
||||
(byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
|
||||
(byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' };
|
||||
|
||||
/**
|
||||
* Translates a Base64 value to either its 6-bit reconstruction value or a
|
||||
* negative number indicating some other meaning.
|
||||
**/
|
||||
private final static byte[] DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal
|
||||
// 0
|
||||
// -
|
||||
// 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
|
||||
// 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
|
||||
62, // Plus sign at decimal 43
|
||||
-9, -9, -9, // Decimal 44 - 46
|
||||
63, // Slash at decimal 47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through
|
||||
// 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
|
||||
// through 'Z'
|
||||
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
|
||||
// through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
|
||||
// through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/*
|
||||
* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
|
||||
*/
|
||||
};
|
||||
|
||||
/** The web safe decodabet */
|
||||
private final static byte[] WEBSAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal
|
||||
// 0
|
||||
// -
|
||||
// 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
|
||||
// 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
|
||||
62, // Dash '-' sign at decimal 45
|
||||
-9, -9, // Decimal 46-47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through
|
||||
// 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
|
||||
// through 'Z'
|
||||
-9, -9, -9, -9, // Decimal 91-94
|
||||
63, // Underscore '_' at decimal 95
|
||||
-9, // Decimal 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
|
||||
// through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
|
||||
// through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/*
|
||||
* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
* -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
|
||||
*/
|
||||
};
|
||||
|
||||
// Indicates white space in encoding
|
||||
private final static byte WHITE_SPACE_ENC = -5;
|
||||
// Indicates equals sign in encoding
|
||||
private final static byte EQUALS_SIGN_ENC = -1;
|
||||
|
||||
/** Defeats instantiation. */
|
||||
private Base64() {
|
||||
}
|
||||
|
||||
/* ******** E N C O D I N G M E T H O D S ******** */
|
||||
|
||||
/**
|
||||
* Encodes up to three bytes of the array <var>source</var> and writes the
|
||||
* resulting four Base64 bytes to <var>destination</var>. The source and
|
||||
* destination arrays can be manipulated anywhere along their length by
|
||||
* specifying <var>srcOffset</var> and <var>destOffset</var>. This method
|
||||
* does not check to make sure your arrays are large enough to accommodate
|
||||
* <var>srcOffset</var> + 3 for the <var>source</var> array or
|
||||
* <var>destOffset</var> + 4 for the <var>destination</var> array. The
|
||||
* actual number of significant bytes in your array is given by
|
||||
* <var>numSigBytes</var>.
|
||||
*
|
||||
* @param source
|
||||
* the array to convert
|
||||
* @param srcOffset
|
||||
* the index where conversion begins
|
||||
* @param numSigBytes
|
||||
* the number of significant bytes in your array
|
||||
* @param destination
|
||||
* the array to hold the conversion
|
||||
* @param destOffset
|
||||
* the index where output will be put
|
||||
* @param alphabet
|
||||
* is the encoding alphabet
|
||||
* @return the <var>destination</var> array
|
||||
* @since 1.3
|
||||
*/
|
||||
private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901 Bit position
|
||||
// --------000000001111111122222222 Array position from threeBytes
|
||||
// --------| || || || | Six bit groups to index alphabet
|
||||
// >>18 >>12 >> 6 >> 0 Right shift necessary
|
||||
// 0x3f 0x3f 0x3f Additional AND
|
||||
|
||||
// Create buffer with zero-padding if there are only one or two
|
||||
// significant bytes passed in the array.
|
||||
// We have to shift left 24 in order to flush out the 1's that appear
|
||||
// when Java treats a value as negative that is cast from a byte to an
|
||||
// int.
|
||||
int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
|
||||
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
|
||||
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
|
||||
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
|
||||
return destination;
|
||||
case 2:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
case 1:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = EQUALS_SIGN;
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
default:
|
||||
return destination;
|
||||
} // end switch
|
||||
} // end encode3to4
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation. Equivalent to calling {@code
|
||||
* encodeBytes(source, 0, source.length)}
|
||||
*
|
||||
* @param source
|
||||
* The data to convert
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return encode(source, 0, source.length, ALPHABET, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into web safe Base64 notation.
|
||||
*
|
||||
* @param source
|
||||
* The data to convert
|
||||
* @param doPadding
|
||||
* is {@code true} to pad result with '=' chars if it does not
|
||||
* fall on 3 byte boundaries
|
||||
*/
|
||||
public static String encodeWebSafe(byte[] source, boolean doPadding) {
|
||||
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source
|
||||
* the data to convert
|
||||
* @param off
|
||||
* offset in array where conversion should begin
|
||||
* @param len
|
||||
* length of data to convert
|
||||
* @param alphabet
|
||||
* the encoding alphabet
|
||||
* @param doPadding
|
||||
* is {@code true} to pad result with '=' chars if it does not
|
||||
* fall on 3 byte boundaries
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source, int off, int len, byte[] alphabet, boolean doPadding) {
|
||||
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
|
||||
int outLen = outBuff.length;
|
||||
|
||||
// If doPadding is false, set length to truncate '='
|
||||
// padding characters
|
||||
while (doPadding == false && outLen > 0) {
|
||||
if (outBuff[outLen - 1] != '=') {
|
||||
break;
|
||||
}
|
||||
outLen -= 1;
|
||||
}
|
||||
|
||||
return new String(outBuff, 0, outLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source
|
||||
* the data to convert
|
||||
* @param off
|
||||
* offset in array where conversion should begin
|
||||
* @param len
|
||||
* length of data to convert
|
||||
* @param alphabet
|
||||
* is the encoding alphabet
|
||||
* @param maxLineLength
|
||||
* maximum length of one line.
|
||||
* @return the BASE64-encoded byte array
|
||||
*/
|
||||
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, int maxLineLength) {
|
||||
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
|
||||
int len43 = lenDiv3 * 4;
|
||||
byte[] outBuff = new byte[len43 // Main 4:3
|
||||
+ (len43 / maxLineLength)]; // New lines
|
||||
|
||||
int d = 0;
|
||||
int e = 0;
|
||||
int len2 = len - 2;
|
||||
int lineLength = 0;
|
||||
for (; d < len2; d += 3, e += 4) {
|
||||
|
||||
// The following block of code is the same as
|
||||
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
|
||||
// but inlined for faster encoding (~20% improvement)
|
||||
int inBuff = ((source[d + off] << 24) >>> 8) | ((source[d + 1 + off] << 24) >>> 16) | ((source[d + 2 + off] << 24) >>> 24);
|
||||
outBuff[e] = alphabet[(inBuff >>> 18)];
|
||||
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
lineLength = 0;
|
||||
} // end if: end of line
|
||||
} // end for: each piece of array
|
||||
|
||||
if (d < len) {
|
||||
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
// Add a last newline
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
}
|
||||
e += 4;
|
||||
}
|
||||
|
||||
assert (e == outBuff.length);
|
||||
return outBuff;
|
||||
}
|
||||
|
||||
/* ******** D E C O D I N G M E T H O D S ******** */
|
||||
|
||||
/**
|
||||
* Decodes four bytes from array <var>source</var> and writes the resulting
|
||||
* bytes (up to three of them) to <var>destination</var>. The source and
|
||||
* destination arrays can be manipulated anywhere along their length by
|
||||
* specifying <var>srcOffset</var> and <var>destOffset</var>. This method
|
||||
* does not check to make sure your arrays are large enough to accommodate
|
||||
* <var>srcOffset</var> + 4 for the <var>source</var> array or
|
||||
* <var>destOffset</var> + 3 for the <var>destination</var> array. This
|
||||
* method returns the actual number of bytes that were converted from the
|
||||
* Base64 encoding.
|
||||
*
|
||||
*
|
||||
* @param source
|
||||
* the array to convert
|
||||
* @param srcOffset
|
||||
* the index where conversion begins
|
||||
* @param destination
|
||||
* the array to hold the conversion
|
||||
* @param destOffset
|
||||
* the index where output will be put
|
||||
* @param decodabet
|
||||
* the decodabet for decoding Base64 content
|
||||
* @return the number of decoded bytes converted
|
||||
* @since 1.3
|
||||
*/
|
||||
private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, byte[] decodabet) {
|
||||
// Example: Dk==
|
||||
if (source[srcOffset + 2] == EQUALS_SIGN) {
|
||||
int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
return 1;
|
||||
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >>> 8);
|
||||
return 2;
|
||||
} else {
|
||||
// Example: DkLE
|
||||
int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18) | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >> 8);
|
||||
destination[destOffset + 2] = (byte) (outBuff);
|
||||
return 3;
|
||||
}
|
||||
} // end decodeToBytes
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation.
|
||||
*
|
||||
* @param s
|
||||
* the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decode(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from web safe Base64 notation. Web safe encoding uses '-'
|
||||
* instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param s
|
||||
* the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decodeWebSafe(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns the decoded byte
|
||||
* array.
|
||||
*
|
||||
* @param source
|
||||
* The Base64 encoded data
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source) throws Base64DecoderException {
|
||||
return decode(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns the
|
||||
* decoded data. Web safe encoding uses '-' instead of '+', '_' instead of
|
||||
* '/'
|
||||
*
|
||||
* @param source
|
||||
* the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source) throws Base64DecoderException {
|
||||
return decodeWebSafe(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns the decoded byte
|
||||
* array.
|
||||
*
|
||||
* @param source
|
||||
* the Base64 encoded data
|
||||
* @param off
|
||||
* the offset of where to begin decoding
|
||||
* @param len
|
||||
* the length of characters to decode
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len) throws Base64DecoderException {
|
||||
return decode(source, off, len, DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns the
|
||||
* decoded byte array. Web safe encoding uses '-' instead of '+', '_'
|
||||
* instead of '/'
|
||||
*
|
||||
* @param source
|
||||
* the Base64 encoded data
|
||||
* @param off
|
||||
* the offset of where to begin decoding
|
||||
* @param len
|
||||
* the length of characters to decode
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source, int off, int len) throws Base64DecoderException {
|
||||
return decode(source, off, len, WEBSAFE_DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content using the supplied decodabet and returns the
|
||||
* decoded byte array.
|
||||
*
|
||||
* @param source
|
||||
* the Base64 encoded data
|
||||
* @param off
|
||||
* the offset of where to begin decoding
|
||||
* @param len
|
||||
* the length of characters to decode
|
||||
* @param decodabet
|
||||
* the decodabet for decoding Base64 content
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) throws Base64DecoderException {
|
||||
int len34 = len * 3 / 4;
|
||||
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
|
||||
int outBuffPosn = 0;
|
||||
|
||||
byte[] b4 = new byte[4];
|
||||
int b4Posn = 0;
|
||||
int i = 0;
|
||||
byte sbiCrop = 0;
|
||||
byte sbiDecode = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven
|
||||
// bits
|
||||
sbiDecode = decodabet[sbiCrop];
|
||||
|
||||
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or
|
||||
// better
|
||||
if (sbiDecode >= EQUALS_SIGN_ENC) {
|
||||
// An equals sign (for padding) must not occur at position 0
|
||||
// or 1
|
||||
// and must be the last byte[s] in the encoded value
|
||||
if (sbiCrop == EQUALS_SIGN) {
|
||||
int bytesLeft = len - i;
|
||||
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
|
||||
if (b4Posn == 0 || b4Posn == 1) {
|
||||
throw new Base64DecoderException("invalid padding byte '=' at byte offset " + i);
|
||||
} else if ((b4Posn == 3 && bytesLeft > 2) || (b4Posn == 4 && bytesLeft > 1)) {
|
||||
throw new Base64DecoderException("padding byte '=' falsely signals end of encoded value " + "at offset " + i);
|
||||
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
|
||||
throw new Base64DecoderException("encoded value has invalid trailing byte");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
b4[b4Posn++] = sbiCrop;
|
||||
if (b4Posn == 4) {
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
b4Posn = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Base64DecoderException("Bad Base64 input character at " + i + ": " + source[i + off] + "(decimal)");
|
||||
}
|
||||
}
|
||||
|
||||
// Because web safe encoding allows non padding base64 encodes, we
|
||||
// need to pad the rest of the b4 buffer with equal signs when
|
||||
// b4Posn != 0. There can be at most 2 equal signs at the end of
|
||||
// four characters, so the b4 buffer must have two or three
|
||||
// characters. This also catches the case where the input is
|
||||
// padded with EQUALS_SIGN
|
||||
if (b4Posn != 0) {
|
||||
if (b4Posn == 1) {
|
||||
// Ensure you have set your public key
|
||||
throw new Base64DecoderException("single trailing character at offset " + (len - 1));
|
||||
}
|
||||
b4[b4Posn++] = EQUALS_SIGN;
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
}
|
||||
|
||||
byte[] out = new byte[outBuffPosn];
|
||||
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// Copyright 2002, Google, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
package org.fox.ttrss.util;;
|
||||
|
||||
/**
|
||||
* Exception thrown when encountering an invalid Base64 input character.
|
||||
*
|
||||
* @author nelson
|
||||
*/
|
||||
public class Base64DecoderException extends Exception {
|
||||
public Base64DecoderException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Base64DecoderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
import org.apache.http.conn.ConnectTimeoutException;
|
||||
import org.apache.http.conn.scheme.LayeredSocketFactory;
|
||||
import org.apache.http.conn.scheme.SocketFactory;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory
|
||||
{
|
||||
private SSLContext sslcontext = null;
|
||||
|
||||
private static SSLContext createEasySSLContext() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, new TrustManager[] { new EasyX509TrustManager() }, null);
|
||||
return context;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private SSLContext getSSLContext() throws IOException
|
||||
{
|
||||
if (this.sslcontext == null)
|
||||
{
|
||||
this.sslcontext = createEasySSLContext();
|
||||
}
|
||||
return this.sslcontext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,
|
||||
* java.net.InetAddress, int, org.apache.http.params.HttpParams)
|
||||
*/
|
||||
public Socket connectSocket(Socket sock,
|
||||
String host,
|
||||
int port,
|
||||
InetAddress localAddress,
|
||||
int localPort,
|
||||
HttpParams params)
|
||||
|
||||
throws IOException, UnknownHostException, ConnectTimeoutException
|
||||
{
|
||||
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
|
||||
int soTimeout = HttpConnectionParams.getSoTimeout(params);
|
||||
InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
|
||||
SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());
|
||||
|
||||
if ((localAddress != null) || (localPort > 0))
|
||||
{
|
||||
// we need to bind explicitly
|
||||
if (localPort < 0)
|
||||
{
|
||||
localPort = 0; // indicates "any"
|
||||
}
|
||||
InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);
|
||||
sslsock.bind(isa);
|
||||
}
|
||||
|
||||
sslsock.connect(remoteAddress, connTimeout);
|
||||
sslsock.setSoTimeout(soTimeout);
|
||||
return sslsock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.http.conn.scheme.SocketFactory#createSocket()
|
||||
*/
|
||||
public Socket createSocket() throws IOException {
|
||||
return getSSLContext().getSocketFactory().createSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)
|
||||
*/
|
||||
public boolean isSecure(Socket socket) throws IllegalArgumentException {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,
|
||||
* boolean)
|
||||
*/
|
||||
public Socket createSocket(Socket socket,
|
||||
String host,
|
||||
int port,
|
||||
boolean autoClose) throws IOException,
|
||||
UnknownHostException
|
||||
{
|
||||
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// javadoc in org.apache.http.conn.scheme.SocketFactory says :
|
||||
// Both Object.equals() and Object.hashCode() must be overridden
|
||||
// for the correct operation of some connection managers
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return EasySSLSocketFactory.class.hashCode();
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
// http://stackoverflow.com/questions/6989116/httpget-not-working-due-to-not-trusted-server-certificate-but-it-works-with-ht
|
||||
|
||||
public class EasyX509TrustManager implements X509TrustManager {
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException { }
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException { }
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
}
|
82
src/org/fox/ttrss/util/HeadlinesRequest.java
Normal file
82
src/org/fox/ttrss/util/HeadlinesRequest.java
Normal file
@ -0,0 +1,82 @@
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.fox.ttrss.ApiRequest;
|
||||
import org.fox.ttrss.GlobalState;
|
||||
import org.fox.ttrss.OnlineActivity;
|
||||
import org.fox.ttrss.R;
|
||||
import org.fox.ttrss.types.Article;
|
||||
import org.fox.ttrss.types.ArticleList;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class HeadlinesRequest extends ApiRequest {
|
||||
public static final int HEADLINES_REQUEST_SIZE = 30;
|
||||
public static final int HEADLINES_BUFFER_MAX = 500;
|
||||
|
||||
private int m_offset = 0;
|
||||
private OnlineActivity m_activity;
|
||||
private ArticleList m_articles = GlobalState.getInstance().m_loadedArticles;
|
||||
|
||||
public HeadlinesRequest(Context context, OnlineActivity activity) {
|
||||
super(context);
|
||||
|
||||
m_activity = activity;
|
||||
}
|
||||
|
||||
protected void onPostExecute(JsonElement result) {
|
||||
if (result != null) {
|
||||
try {
|
||||
JsonArray content = result.getAsJsonArray();
|
||||
if (content != null) {
|
||||
Type listType = new TypeToken<List<Article>>() {}.getType();
|
||||
final List<Article> articles = new Gson().fromJson(content, listType);
|
||||
|
||||
while (m_articles.size() > HEADLINES_BUFFER_MAX)
|
||||
m_articles.remove(0);
|
||||
|
||||
if (m_offset == 0)
|
||||
m_articles.clear();
|
||||
else
|
||||
if (m_articles.get(m_articles.size()-1).id == -1)
|
||||
m_articles.remove(m_articles.size()-1); // remove previous placeholder
|
||||
|
||||
for (Article f : articles)
|
||||
m_articles.add(f);
|
||||
|
||||
if (articles.size() == HEADLINES_REQUEST_SIZE) {
|
||||
Article placeholder = new Article(-1);
|
||||
m_articles.add(placeholder);
|
||||
}
|
||||
|
||||
if (m_articles.size() == 0)
|
||||
m_activity.setLoadingStatus(R.string.no_headlines_to_display, false);
|
||||
else
|
||||
m_activity.setLoadingStatus(R.string.blank, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_lastError == ApiError.LOGIN_FAILED) {
|
||||
m_activity.login();
|
||||
} else {
|
||||
m_activity.setLoadingStatus(getErrorMessage(), false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOffset(int skip) {
|
||||
m_offset = skip;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.fox.ttrss.MainActivity;
|
||||
import org.fox.ttrss.OnlineActivity;
|
||||
import org.fox.ttrss.R;
|
||||
import org.fox.ttrss.offline.OfflineDownloadService;
|
||||
|
||||
@ -25,6 +25,7 @@ import android.os.Environment;
|
||||
|
||||
public class ImageCacheService extends IntentService {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
public static final int NOTIFY_DOWNLOADING = 1;
|
||||
@ -123,12 +124,13 @@ public class ImageCacheService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updateNotification(String msg) {
|
||||
Notification notification = new Notification(R.drawable.icon,
|
||||
getString(R.string.notify_downloading_title), System.currentTimeMillis());
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
new Intent(this, MainActivity.class), 0);
|
||||
new Intent(this, OnlineActivity.class), 0);
|
||||
|
||||
notification.flags |= Notification.FLAG_ONGOING_EVENT;
|
||||
notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
|
||||
|
Loading…
Reference in New Issue
Block a user