diff options
author | Andrew Dolgov <[email protected]> | 2014-10-17 00:06:56 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2014-10-17 00:06:56 +0400 |
commit | 97cc96839d31b6cce59ec29a6681c6fe802552ee (patch) | |
tree | 9f3b8df270095bc65c10cd7208d05b3dad4794b9 /orgfoxttrss | |
parent | 5775c0d56b7c856b508bb34e478eef53c2460624 (diff) |
initial
Diffstat (limited to 'orgfoxttrss')
207 files changed, 23109 insertions, 0 deletions
diff --git a/orgfoxttrss/build.gradle b/orgfoxttrss/build.gradle new file mode 100644 index 00000000..82b24493 --- /dev/null +++ b/orgfoxttrss/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 19 + buildToolsVersion "20.0.0" + + defaultConfig { + applicationId "org.fox.ttrss" + minSdkVersion 8 + targetSdkVersion 19 + } + + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + //compile project(':slidingMenulibrary') + compile 'com.jeremyfeinstein.slidingmenu:library:1.3@aar' + compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' + compile 'com.viewpagerindicator:library:2.4.1' + compile 'com.android.support:support-v4:19.1.0' + compile 'com.google.code.gson:gson:1.7.1' + compile 'com.android.support:appcompat-v7:18.0.0' + compile files('libs/dashclock-api-r1.1.jar') + compile files('libs/jsoup-1.6.1.jar') + compile files('libs/universal-image-loader-1.9.3.jar') +} diff --git a/orgfoxttrss/libs/dashclock-api-r1.1.jar b/orgfoxttrss/libs/dashclock-api-r1.1.jar Binary files differnew file mode 100644 index 00000000..3a4e00d0 --- /dev/null +++ b/orgfoxttrss/libs/dashclock-api-r1.1.jar diff --git a/orgfoxttrss/libs/jsoup-1.6.1.jar b/orgfoxttrss/libs/jsoup-1.6.1.jar Binary files differnew file mode 100644 index 00000000..87126a49 --- /dev/null +++ b/orgfoxttrss/libs/jsoup-1.6.1.jar diff --git a/orgfoxttrss/libs/universal-image-loader-1.9.3.jar b/orgfoxttrss/libs/universal-image-loader-1.9.3.jar Binary files differnew file mode 100644 index 00000000..e8ca33b7 --- /dev/null +++ b/orgfoxttrss/libs/universal-image-loader-1.9.3.jar diff --git a/orgfoxttrss/lint.xml b/orgfoxttrss/lint.xml new file mode 100644 index 00000000..8423c0ef --- /dev/null +++ b/orgfoxttrss/lint.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<lint> +</lint>
\ No newline at end of file diff --git a/orgfoxttrss/orgfoxttrss.iml b/orgfoxttrss/orgfoxttrss.iml new file mode 100644 index 00000000..0a72db93 --- /dev/null +++ b/orgfoxttrss/orgfoxttrss.iml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Tiny-Tiny-RSS-for-Honeycomb" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="android-gradle" name="Android-Gradle"> + <configuration> + <option name="GRADLE_PROJECT_PATH" value=":orgfoxttrss" /> + </configuration> + </facet> + <facet type="android" name="Android"> + <configuration> + <option name="SELECTED_BUILD_VARIANT" value="debug" /> + <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> + <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" /> + <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" /> + <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> + <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" /> + <option name="ALLOW_USER_CONFIGURATION" value="false" /> + <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> + <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> + <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> + <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + </content> + <orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" exported="" name="dashclock-api-r1.1" level="project" /> + <orderEntry type="library" exported="" name="systembartint-1.0.3" level="project" /> + <orderEntry type="library" exported="" name="jsoup-1.6.1" level="project" /> + <orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" /> + <orderEntry type="library" exported="" name="library-1.3" level="project" /> + <orderEntry type="library" exported="" name="appcompat-v7-18.0.0" level="project" /> + <orderEntry type="library" exported="" name="gson-1.7.1" level="project" /> + <orderEntry type="library" exported="" name="library-2.4.1" level="project" /> + <orderEntry type="library" exported="" name="universal-image-loader-1.9.3" level="project" /> + </component> +</module> + diff --git a/orgfoxttrss/src/main/AndroidManifest.xml b/orgfoxttrss/src/main/AndroidManifest.xml new file mode 100644 index 00000000..684cc438 --- /dev/null +++ b/orgfoxttrss/src/main/AndroidManifest.xml @@ -0,0 +1,256 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.fox.ttrss" + android:versionCode="239" + android:versionName="1.41" > + + <uses-sdk + android:minSdkVersion="8" + android:targetSdkVersion="19" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> + + <application + android:name=".GlobalState" + android:allowBackup="true" + android:backupAgent="org.fox.ttrss.util.PrefsBackupAgent" + android:hardwareAccelerated="true" + android:icon="@drawable/icon" + android:label="@string/app_name" > + <activity + android:name=".OnlineActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name=".PreferencesActivity" + android:label="@string/preferences" > + </activity> + <activity + android:name=".FeedsActivity" + android:label="@string/app_name" + android:uiOptions="splitActionBarWhenNarrow" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> + <activity + android:name=".HeadlinesActivity" + android:label="@string/app_name" + android:uiOptions="splitActionBarWhenNarrow" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> + <activity + android:name=".CommonActivity" + android:label="@string/app_name" > + </activity> + <activity + android:name=".tasker.TaskerSettingsActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" /> + </intent-filter> + </activity> + <activity + android:name=".offline.OfflineActivity" + android:label="@string/app_name" > + </activity> + <activity + android:name=".offline.OfflineFeedsActivity" + android:label="@string/app_name" + android:uiOptions="splitActionBarWhenNarrow" > + </activity> + <activity + android:name=".offline.OfflineHeadlinesActivity" + android:label="@string/app_name" + android:uiOptions="splitActionBarWhenNarrow" > + </activity> + <activity + android:name=".share.ShareActivity" + android:excludeFromRecents="true" + android:label="@string/app_name" + android:theme="@style/DarkDialogTheme" > + <intent-filter> + <action android:name="android.intent.action.SEND" /> + + <category android:name="android.intent.category.DEFAULT" /> + + <data android:mimeType="text/plain" /> + </intent-filter> + </activity> + <activity + android:name=".share.SubscribeActivity" + android:excludeFromRecents="true" + android:label="@string/subscribe_name" + android:theme="@style/DarkDialogTheme" > + + <intent-filter> + <action android:name="android.intent.action.SEND" /> + + <category android:name="android.intent.category.DEFAULT" /> + + <data android:mimeType="text/plain" /> + </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + + <data android:scheme="itpc" /> + <data android:scheme="pcast" /> + <data android:scheme="feed" /> + <data android:scheme="rss" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="*" + android:pathPattern=".*xml" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*rss" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*feed.*" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*podcast.*" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*Podcast.*" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*rss.*" + android:scheme="http" /> + <data + android:host="*" + android:pathPattern=".*RSS.*" + android:scheme="http" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:mimeType="text/xml" + android:scheme="http" /> + <data + android:mimeType="application/rss+xml" + android:scheme="http" /> + <data + android:mimeType="application/atom+xml" + android:scheme="http" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="http" /> + <data android:host="*" /> + <data android:pathPattern=".*\\.xml" /> + <data android:pathPattern=".*\\.rss" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="http" /> + <data android:host="feeds.feedburner.com" /> + <data android:host="feedproxy.google.com" /> + <data android:host="feeds2.feedburner.com" /> + <data android:host="feedsproxy.google.com" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="http" /> + <data android:mimeType="text/xml" /> + <data android:mimeType="application/rss+xml" /> + <data android:mimeType="application/atom+xml" /> + <data android:mimeType="application/xml" /> + </intent-filter> + </activity> + + <service + android:name=".offline.OfflineDownloadService" + android:enabled="true" /> + <service + android:name=".offline.OfflineUploadService" + android:enabled="true" /> + <service + android:name="org.fox.ttrss.util.ImageCacheService" + android:enabled="true" /> + + <meta-data + android:name="com.google.android.backup.api_key" + android:value="AEdPqrEAAAAIwG6zsGB4qo6ZhjfwIJpm9WI7AqmWaoRXm6ZJnA" /> + + <receiver android:name=".tasker.TaskerReceiver" > + <intent-filter> + <action android:name="com.twofortyfouram.locale.intent.action.QUERY_CONDITION" /> + <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" /> + </intent-filter> + </receiver> + + <receiver android:name=".widget.SmallWidgetProvider" > + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> + <action android:name="org.fox.ttrss.WIDGET_FORCE_UPDATE" /> + </intent-filter> + + <meta-data + android:name="android.appwidget.provider" + android:resource="@xml/widget_small" /> + </receiver> + + <service + android:name=".widget.WidgetUpdateService" + android:enabled="true" /> + <service + android:name=".DashClock" + android:icon="@drawable/dashclock" + android:label="@string/app_name" + android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA" > + <intent-filter> + <action android:name="com.google.android.apps.dashclock.Extension" /> + </intent-filter> + + <meta-data + android:name="protocolVersion" + android:value="1" /> + <meta-data + android:name="description" + android:value="@string/app_name" /> + </service> + </application> + +</manifest>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/android/support/v4/app/ClassloaderWorkaroundFragmentStatePagerAdapter.java b/orgfoxttrss/src/main/java/android/support/v4/app/ClassloaderWorkaroundFragmentStatePagerAdapter.java new file mode 100644 index 00000000..dd67d599 --- /dev/null +++ b/orgfoxttrss/src/main/java/android/support/v4/app/ClassloaderWorkaroundFragmentStatePagerAdapter.java @@ -0,0 +1,39 @@ +package android.support.v4.app; + +// http://code.google.com/p/android/issues/detail?id=37484 +// Thanks for your amazing code quality, Google. + +import android.os.Bundle; +import android.view.ViewGroup; + +public class ClassloaderWorkaroundFragmentStatePagerAdapter extends + FragmentStatePagerAdapter { + + public ClassloaderWorkaroundFragmentStatePagerAdapter(FragmentManager fm) { + super(fm); + // TODO Auto-generated constructor stub + } + + @Override + public Fragment getItem(int arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + Fragment f = (Fragment) super.instantiateItem(container, position); + Bundle savedFragmentState = f.mSavedFragmentState; + if (savedFragmentState != null) { + savedFragmentState.setClassLoader(f.getClass().getClassLoader()); + } + return f; + } + + @Override + public int getCount() { + // TODO Auto-generated method stub + return 0; + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/ApiRequest.java b/orgfoxttrss/src/main/java/org/fox/ttrss/ApiRequest.java new file mode 100644 index 00000000..65e97e8e --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/ApiRequest.java @@ -0,0 +1,351 @@ +package org.fox.ttrss; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.HashMap; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +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; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonElement> { + 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, SSL_HOSTNAME_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, + API_UNKNOWN, LOGIN_FAILED, INVALID_URL, API_INCORRECT_USAGE, NETWORK_UNAVAILABLE, API_UNKNOWN_METHOD }; + + public static final int API_STATUS_OK = 0; + public static final int API_STATUS_ERR = 1; + + private String m_api; + private boolean m_transportDebugging = false; + 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; + + protected ApiError m_lastError; + + public ApiRequest(Context context) { + m_context = context; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(m_context); + + m_api = m_prefs.getString("ttrss_url", "").trim(); + m_transportDebugging = m_prefs.getBoolean("transport_debugging", false); + m_lastError = ApiError.NO_ERROR; + + } + + @SuppressLint("NewApi") + @SuppressWarnings("unchecked") + public void execute(HashMap<String,String> map) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + super.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, map); + else + super.execute(map); + } + + public int getErrorMessage() { + switch (m_lastError) { + case NO_ERROR: + return R.string.error_unknown; + case HTTP_UNAUTHORIZED: + return R.string.error_http_unauthorized; + case HTTP_FORBIDDEN: + return R.string.error_http_forbidden; + case HTTP_NOT_FOUND: + return R.string.error_http_not_found; + case HTTP_SERVER_ERROR: + return R.string.error_http_server_error; + case HTTP_OTHER_ERROR: + return R.string.error_http_other_error; + case SSL_REJECTED: + return R.string.error_ssl_rejected; + case SSL_HOSTNAME_REJECTED: + return R.string.error_ssl_hostname_rejected; + case PARSE_ERROR: + return R.string.error_parse_error; + case IO_ERROR: + return R.string.error_io_error; + case OTHER_ERROR: + return R.string.error_other_error; + case API_DISABLED: + return R.string.error_api_disabled; + case API_UNKNOWN: + return R.string.error_api_unknown; + case API_UNKNOWN_METHOD: + return R.string.error_api_unknown_method; + case LOGIN_FAILED: + return R.string.error_login_failed; + case INVALID_URL: + return R.string.error_invalid_api_url; + case API_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; + } + } + + @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); + + /* ApiRequest.trustAllHosts(m_prefs.getBoolean("ssl_trust_any", false), + m_prefs.getBoolean("ssl_trust_any_host", false)); */ + + URL url; + + try { + url = new URL(m_api + "/api/"); + } catch (Exception e) { + m_lastError = ApiError.INVALID_URL; + e.printStackTrace(); + return null; + } + + try { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + String httpLogin = m_prefs.getString("http_login", "").trim(); + String httpPassword = m_prefs.getString("http_password", "").trim(); + + if (httpLogin.length() > 0) { + if (m_transportDebugging) Log.d(TAG, "Using HTTP Basic authentication."); + + conn.setRequestProperty("Authorization", "Basic " + + Base64.encodeToString((httpLogin + ":" + httpPassword).getBytes("UTF-8"), Base64.NO_WRAP)); + } + + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Length", Integer.toString(postData.length)); + + OutputStream out = conn.getOutputStream(); + out.write(postData); + out.close(); + + m_responseCode = conn.getResponseCode(); + m_responseMessage = conn.getResponseMessage(); + + 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; + + int contentLength = conn.getHeaderFieldInt("Api-Content-Length", -1); + + m_canUseProgress = (contentLength != -1); + + 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.toString()); + JsonObject resultObj = result.getAsJsonObject(); + + m_apiStatusCode = resultObj.get("status").getAsInt(); + + conn.disconnect(); + + switch (m_apiStatusCode) { + case API_STATUS_OK: + return result.getAsJsonObject().get("content"); + case API_STATUS_ERR: + JsonObject contentObj = resultObj.get("content").getAsJsonObject(); + String error = contentObj.get("error").getAsString(); + + if (error.equals("LOGIN_ERROR")) { + m_lastError = ApiError.LOGIN_FAILED; + } else if (error.equals("API_DISABLED")) { + m_lastError = ApiError.API_DISABLED; + } else if (error.equals("NOT_LOGGED_IN")) { + m_lastError = ApiError.LOGIN_FAILED; + } else if (error.equals("INCORRECT_USAGE")) { + m_lastError = ApiError.API_INCORRECT_USAGE; + } else if (error.equals("UNKNOWN_METHOD")) { + m_lastError = ApiError.API_UNKNOWN_METHOD; + } else { + Log.d(TAG, "Unknown API error: " + error); + m_lastError = ApiError.API_UNKNOWN; + } + } + + return null; + case HttpURLConnection.HTTP_UNAUTHORIZED: + m_lastError = ApiError.HTTP_UNAUTHORIZED; + break; + case HttpURLConnection.HTTP_FORBIDDEN: + m_lastError = ApiError.HTTP_FORBIDDEN; + break; + case HttpURLConnection.HTTP_NOT_FOUND: + m_lastError = ApiError.HTTP_NOT_FOUND; + break; + case HttpURLConnection.HTTP_INTERNAL_ERROR: + m_lastError = ApiError.HTTP_SERVER_ERROR; + break; + default: + Log.d(TAG, "HTTP response code: " + m_responseCode + "(" + m_responseMessage + ")"); + m_lastError = ApiError.HTTP_OTHER_ERROR; + break; + } + + conn.disconnect(); + return null; + } catch (javax.net.ssl.SSLPeerUnverifiedException e) { + m_lastError = ApiError.SSL_REJECTED; + e.printStackTrace(); + } catch (IOException e) { + m_lastError = ApiError.IO_ERROR; + + if (e.getMessage() != null) { + if (e.getMessage().matches("Hostname [^ ]+ was not verified")) { + m_lastError = ApiError.SSL_HOSTNAME_REJECTED; + } + } + + e.printStackTrace(); + } catch (com.google.gson.JsonSyntaxException e) { + m_lastError = ApiError.PARSE_ERROR; + e.printStackTrace(); + } catch (Exception e) { + m_lastError = ApiError.OTHER_ERROR; + e.printStackTrace(); + } + + return null; + } + + protected static void trustAllHosts(boolean trustAnyCert, boolean trustAnyHost) { + try { + if (trustAnyCert) { + 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 + + SSLContext sc = SSLContext.getInstance("TLS"); + + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } + + if (trustAnyHost) { + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("deprecation") + protected 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; + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/ArticleFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/ArticleFragment.java new file mode 100644 index 00000000..4a568d3c --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/ArticleFragment.java @@ -0,0 +1,449 @@ +package org.fox.ttrss; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.fox.ttrss.types.Article; +import org.fox.ttrss.types.Attachment; +import org.fox.ttrss.util.TypefaceCache; +import org.jsoup.Jsoup; +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.graphics.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.text.Html; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebView.HitTestResult; +import android.widget.TextView; + +public class ArticleFragment extends Fragment { + private final String TAG = this.getClass().getSimpleName(); + + private SharedPreferences m_prefs; + private Article m_article; + private OnlineActivity m_activity; + + public void initialize(Article article) { + m_article = article; + } + + private View.OnTouchListener m_gestureListener; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + + if (v.getId() == R.id.content) { + HitTestResult result = ((WebView)v).getHitTestResult(); + + if (result != null && (result.getType() == HitTestResult.IMAGE_TYPE || result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) { + menu.setHeaderTitle(result.getExtra()); + getActivity().getMenuInflater().inflate(R.menu.article_content_img_context_menu, menu); + + /* FIXME I have no idea how to do this correctly ;( */ + + m_activity.setLastContentImageHitTestUrl(result.getExtra()); + + } else { + menu.setHeaderTitle(m_article.title); + getActivity().getMenuInflater().inflate(R.menu.article_link_context_menu, menu); + } + } else { + menu.setHeaderTitle(m_article.title); + getActivity().getMenuInflater().inflate(R.menu.article_link_context_menu, menu); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + @SuppressLint("NewApi") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + m_activity.setProgressBarVisibility(true); + + if (savedInstanceState != null) { + m_article = savedInstanceState.getParcelable("article"); + } + + boolean useTitleWebView = m_prefs.getBoolean("article_compat_view", false); + + View view = inflater.inflate(useTitleWebView ? R.layout.article_fragment_compat : R.layout.article_fragment, container, false); + + if (m_article != null) { + + if (!useTitleWebView) { + View scroll = view.findViewById(R.id.article_scrollview); + + if (scroll != null) { + final float scale = getResources().getDisplayMetrics().density; + + if (m_activity.isSmallScreen()) { + scroll.setPadding((int)(8 * scale + 0.5f), + (int)(5 * scale + 0.5f), + (int)(8 * scale + 0.5f), + 0); + } else { + scroll.setPadding((int)(25 * scale + 0.5f), + (int)(10 * scale + 0.5f), + (int)(25 * scale + 0.5f), + 0); + + } + + } + } + + int articleFontSize = Integer.parseInt(m_prefs.getString("article_font_size_sp", "16")); + int articleSmallFontSize = Math.max(10, Math.min(18, articleFontSize - 2)); + + TextView title = (TextView)view.findViewById(R.id.title); + + if (title != null) { + + if (m_prefs.getBoolean("enable_condensed_fonts", false)) { + Typeface tf = TypefaceCache.get(m_activity, "sans-serif-condensed", Typeface.NORMAL); + + if (tf != null && !tf.equals(title.getTypeface())) { + title.setTypeface(tf); + } + + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, articleFontSize + 5)); + } else { + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, articleFontSize + 3)); + } + + String titleStr; + + if (m_article.title.length() > 200) + titleStr = m_article.title.substring(0, 200) + "..."; + else + titleStr = m_article.title; + + title.setText(Html.fromHtml(titleStr)); + //title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + title.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + try { + URL url = new URL(m_article.link.trim()); + String uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), + url.getPort(), url.getPath(), url.getQuery(), url.getRef()).toString(); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + m_activity.toast(R.string.error_other_error); + } + } + }); + + registerForContextMenu(title); + } + + TextView comments = (TextView)view.findViewById(R.id.comments); + + if (comments != null) { + if (m_activity.getApiLevel() >= 4 && m_article.comments_count > 0) { + comments.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + String commentsTitle = getString(R.string.article_comments, m_article.comments_count); + comments.setText(commentsTitle); + //comments.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + comments.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + try { + URL url = new URL((m_article.comments_link != null && m_article.comments_link.length() > 0) ? + m_article.comments_link : m_article.link); + String uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), + url.getPort(), url.getPath(), url.getQuery(), url.getRef()).toString(); + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse(uri)); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + m_activity.toast(R.string.error_other_error); + } + } + }); + + } else { + comments.setVisibility(View.GONE); + } + } + + TextView note = (TextView)view.findViewById(R.id.note); + + if (note != null) { + if (m_article.note != null && !"".equals(m_article.note)) { + note.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + note.setText(m_article.note); + } else { + note.setVisibility(View.GONE); + } + + } + + final WebView web = (WebView)view.findViewById(R.id.content); + + if (web != null) { + + web.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + HitTestResult result = ((WebView)v).getHitTestResult(); + + if (result != null && (result.getType() == HitTestResult.IMAGE_TYPE || result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) { + registerForContextMenu(web); + m_activity.openContextMenu(web); + unregisterForContextMenu(web); + return true; + } else { + if (m_activity.isCompatMode()) { + KeyEvent shiftPressEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0); + shiftPressEvent.dispatch(web); + } + + return false; + } + } + }); + + // prevent flicker in ics + if (!m_prefs.getBoolean("webview_hardware_accel", true) || useTitleWebView) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + web.setLayerType(View.LAYER_TYPE_SOFTWARE, 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 = ""; + + WebSettings ws = web.getSettings(); + ws.setSupportZoom(false); + + TypedValue tv = new TypedValue(); + getActivity().getTheme().resolveAttribute(R.attr.linkColor, tv, true); + + String theme = m_prefs.getString("theme", CommonActivity.THEME_DEFAULT); + + if (CommonActivity.THEME_HOLO.equals(theme)) { + cssOverride = "body { background : transparent; color : #e0e0e0}"; + } else if (CommonActivity.THEME_DARK.equals(theme)) { + cssOverride = "body { background : transparent; color : #e0e0e0}"; + } else { + cssOverride = "body { background : transparent; }"; + } + + if (useTitleWebView || android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { + web.setBackgroundColor(Color.TRANSPARENT); + } else { + // seriously? + web.setBackgroundColor(Color.argb(1, 0, 0, 0)); + } + + String hexColor = String.format("#%06X", (0xFFFFFF & tv.data)); + cssOverride += " a:link {color: "+hexColor+";} a:visited { color: "+hexColor+";}"; + + cssOverride += " table { width : 100%; }"; + + String articleContent = m_article.content != null ? m_article.content : ""; + + Document doc = Jsoup.parse(articleContent); + + if (doc != null) { + // thanks webview for crashing on <video> tag + Elements videos = doc.select("video"); + + for (Element video : videos) + video.remove(); + + articleContent = doc.toString(); + } + + if (m_prefs.getBoolean("justify_article_text", true)) { + cssOverride += "body { text-align : justify; } "; + } + + ws.setDefaultFontSize(articleFontSize); + + content = + "<html>" + + "<head>" + + "<meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">" + + "<meta name=\"viewport\" content=\"width=device-width, user-scalable=no\" />" + + "<style type=\"text/css\">" + + "body { padding : 0px; margin : 0px; line-height : 130%; }" + + "img { max-width : 100%; width : auto; height : auto; }" + + cssOverride + + "</style>" + + "</head>" + + "<body>" + articleContent; + + if (useTitleWebView) { + content += "<p> </p><p> </p><p> </p><p> </p>"; + } + + if (m_article.attachments != null && m_article.attachments.size() != 0) { + String flatContent = articleContent.replaceAll("[\r\n]", ""); + boolean hasImages = flatContent.matches(".*?<img[^>+].*?"); + + for (Attachment a : m_article.attachments) { + if (a.content_type != null && a.content_url != null) { + try { + if (a.content_type.indexOf("image") != -1 && + (!hasImages || m_article.always_display_attachments)) { + + URL url = new URL(a.content_url.trim()); + String strUrl = url.toString().trim(); + + content += "<p><img src=\"" + strUrl.replace("\"", "\\\"") + "\"></p>"; + } + + } catch (MalformedURLException e) { + // + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + content += "</body></html>"; + + try { + String baseUrl = null; + + try { + URL url = new URL(m_article.link); + baseUrl = url.getProtocol() + "://" + url.getHost(); + } catch (MalformedURLException e) { + // + } + + web.loadDataWithBaseURL(baseUrl, content, "text/html", "utf-8", null); + } catch (RuntimeException e) { + e.printStackTrace(); + } + + if (m_activity.isSmallScreen()) + web.setOnTouchListener(m_gestureListener); + + web.setVisibility(View.VISIBLE); + } + + TextView dv = (TextView)view.findViewById(R.id.date); + + if (dv != null) { + dv.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + Date d = new Date(m_article.updated * 1000L); + DateFormat df = new SimpleDateFormat("MMM dd, HH:mm"); + dv.setText(df.format(d)); + } + + TextView author = (TextView)view.findViewById(R.id.author); + + boolean hasAuthor = false; + + if (author != null) { + author.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + if (m_article.author != null && m_article.author.length() > 0) { + author.setText(getString(R.string.author_formatted, m_article.author)); + } else { + author.setVisibility(View.GONE); + } + hasAuthor = true; + } + + TextView tagv = (TextView)view.findViewById(R.id.tags); + + if (tagv != null) { + tagv.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + if (m_article.feed_title != null) { + String fTitle = m_article.feed_title; + + if (!hasAuthor && m_article.author != null && m_article.author.length() > 0) { + fTitle += " (" + getString(R.string.author_formatted, m_article.author) + ")"; + } + + tagv.setText(fTitle); + } else if (m_article.tags != null) { + String tagsStr = ""; + + for (String tag : m_article.tags) + tagsStr += tag + ", "; + + tagsStr = tagsStr.replaceAll(", $", ""); + + tagv.setText(tagsStr); + } else { + tagv.setVisibility(View.GONE); + } + } + + } + + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.setClassLoader(getClass().getClassLoader()); + out.putParcelable("article", m_article); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + m_activity = (OnlineActivity)activity; + //m_article = m_onlineServices.getSelectedArticle(); + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/ArticlePager.java b/orgfoxttrss/src/main/java/org/fox/ttrss/ArticlePager.java new file mode 100644 index 00000000..ee940e79 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/ArticlePager.java @@ -0,0 +1,347 @@ +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.annotation.SuppressLint; +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.BadParcelableException; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.ClassloaderWorkaroundFragmentStatePagerAdapter; +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 android.view.WindowManager; + +import com.google.gson.JsonElement; +import com.viewpagerindicator.UnderlinePageIndicator; + +public class ArticlePager extends Fragment { + + private final String TAG = "ArticlePager"; + private PagerAdapter m_adapter; + 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 SharedPreferences m_prefs; + + private class PagerAdapter extends ClassloaderWorkaroundFragmentStatePagerAdapter { + + public PagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + Article article = m_articles.get(position); + + if (article != null) { + ArticleFragment af = new ArticleFragment(); + af.initialize(article); + + if (m_prefs.getBoolean("dim_status_bar", false) && getView() != null && !m_activity.isCompatMode()) { + getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + + return af; + } + return null; + } + + @Override + public int getCount() { + return m_articles.size(); + } + + } + + public void initialize(Article article, Feed feed) { + 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_articles.indexOf(m_article); + + m_listener.onArticleSelected(m_article, false); + + m_activity.setProgressBarVisibility(true); + + pager.setAdapter(m_adapter); + + UnderlinePageIndicator indicator = (UnderlinePageIndicator)view.findViewById(R.id.article_titles); + indicator.setViewPager(pager); + + pager.setCurrentItem(position); + + indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + + @Override + public void onPageScrollStateChanged(int arg0) { + } + + @Override + public void onPageScrolled(int arg0, float arg1, int arg2) { + } + + @Override + public void onPageSelected(int position) { + Article article = m_articles.get(position); + + if (article != null) { + m_article = article; + + /* if (article.unread) { + article.unread = false; + m_activity.saveArticleUnread(article); + } */ + + m_listener.onArticleSelected(article, false); + + //Log.d(TAG, "Page #" + position + "/" + m_adapter.getCount()); + + if ((m_activity.isSmallScreen() || m_activity.isPortrait()) && position == m_adapter.getCount() - 15) { + Log.d(TAG, "loading more articles..."); + refresh(true); + } + } + } + }); + + return view; + } + + @SuppressWarnings({ "serial" }) + protected void refresh(boolean append) { + m_activity.setLoadingStatus(R.string.blank, true); + + m_activity.setProgressBarVisibility(true); + //m_activity.m_pullToRefreshAttacher.setRefreshing(true); + + if (!m_feed.equals(GlobalState.getInstance().m_activeFeed)) { + append = false; + } + + HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity, m_feed) { + @Override + protected void onProgressUpdate(Integer... progress) { + m_activity.setProgress(progress[0] / progress[1] * 10000); + } + + @Override + protected void onPostExecute(JsonElement result) { + if (isDetached()) return; + + m_activity.setProgressBarVisibility(false); + //m_activity.m_pullToRefreshAttacher.setRefreshComplete(); + + super.onPostExecute(result); + + if (result != null) { + try { + m_adapter.notifyDataSetChanged(); + } catch (BadParcelableException e) { + if (getActivity() != null) { + getActivity().finish(); + return; + } + } + + if (m_article != null) { + 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(true); + } else { + m_activity.toast(getErrorMessage()); + //setLoadingStatus(getErrorMessage(), false); + } + } + } + }; + + final Feed feed = m_feed; + + final String sessionId = m_activity.getSessionId(); + int skip = 0; + + if (append) { + // adaptive, all_articles, marked, published, unread + String viewMode = m_activity.getViewMode(); + int numUnread = 0; + int numAll = m_articles.size(); + + for (Article a : m_articles) { + if (a.unread) ++numUnread; + } + + if ("marked".equals(viewMode)) { + skip = numAll; + } else if ("published".equals(viewMode)) { + skip = numAll; + } else if ("unread".equals(viewMode)) { + skip = numUnread; + } else if (m_searchQuery != null && m_searchQuery.length() > 0) { + skip = numAll; + } else if ("adaptive".equals(viewMode)) { + skip = numUnread > 0 ? numUnread : numAll; + } else { + skip = numAll; + } + } + + 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", m_activity.getViewMode()); + put("skip", String.valueOf(fskip)); + put("include_nested", "true"); + put("order_by", m_prefs.getBoolean("oldest_first", false) ? "date_reverse" : ""); + + 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.setClassLoader(getClass().getClassLoader()); + out.putParcelable("article", m_article); + out.putParcelable("feed", m_feed); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_listener = (HeadlinesEventListener)activity; + m_activity = (OnlineActivity)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + } + + @SuppressLint("NewApi") + @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(); + + if (!m_activity.isCompatMode() && m_prefs.getBoolean("dim_status_bar", false)) { + getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + + if (m_prefs.getBoolean("full_screen_mode", false)) { + m_activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + /* if (!m_activity.isCompatMode()) { + m_activity.getSupportActionBar().hide(); + } */ + } + } + + 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); + } + } + + public void selectArticle(boolean next) { + if (m_article != null) { + int position = m_articles.indexOf(m_article); + + if (next) + position++; + else + position--; + + try { + Article tmp = m_articles.get(position); + + if (tmp != null) { + setActiveArticle(tmp); + } + + } catch (IndexOutOfBoundsException e) { + // do nothing + } + } + } + + public void notifyUpdated() { + m_adapter.notifyDataSetChanged(); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/CommonActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/CommonActivity.java new file mode 100644 index 00000000..5a64ae57 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/CommonActivity.java @@ -0,0 +1,250 @@ +package org.fox.ttrss; + + +import java.io.File; +import java.io.IOException; + +import org.fox.ttrss.util.DatabaseHelper; + +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.utils.StorageUtils; +import com.readystatesoftware.systembartint.SystemBarTintManager; + +import android.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.net.http.HttpResponseCache; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.util.TypedValue; +import android.view.Display; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +public class CommonActivity extends ActionBarActivity { + private final String TAG = this.getClass().getSimpleName(); + + public final static String FRAG_HEADLINES = "headlines"; + public final static String FRAG_ARTICLE = "article"; + public final static String FRAG_FEEDS = "feeds"; + public final static String FRAG_CATS = "cats"; + + public final static String THEME_DARK = "THEME_DARK"; + public final static String THEME_LIGHT = "THEME_LIGHT"; + public final static String THEME_SEPIA = "THEME_SEPIA"; + public final static String THEME_HOLO = "THEME_HOLO"; + public final static String THEME_DEFAULT = CommonActivity.THEME_LIGHT; + + public static final int EXCERPT_MAX_SIZE = 200; + + private SQLiteDatabase m_readableDb; + private SQLiteDatabase m_writableDb; + + private boolean m_smallScreenMode = true; + private boolean m_compatMode = false; + private String m_theme; + + protected SharedPreferences m_prefs; + + /* protected void enableHttpCaching() { + // enable resource caching + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + try { + File httpCacheDir = new File(getApplicationContext().getCacheDir(), "http"); + long httpCacheSize = 10 * 1024 * 1024; // 10 MiB + HttpResponseCache.install(httpCacheDir, httpCacheSize); + } catch (IOException e) { + e.printStackTrace(); + } + } + } */ + + protected void setSmallScreen(boolean smallScreen) { + Log.d(TAG, "m_smallScreenMode=" + smallScreen); + m_smallScreenMode = smallScreen; + } + + public boolean getUnreadOnly() { + return m_prefs.getBoolean("show_unread_only", true); + } + + public void setUnreadOnly(boolean unread) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("show_unread_only", unread); + editor.commit(); + } + + public void setLoadingStatus(int status, boolean showProgress) { + TextView tv = (TextView) findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + + findViewById(R.id.loading_container).setVisibility(status == R.string.blank ? View.GONE : View.VISIBLE); + + setProgressBarIndeterminateVisibility(showProgress); + } + + public void toast(int msgId) { + Toast toast = Toast.makeText(CommonActivity.this, msgId, Toast.LENGTH_SHORT); + toast.show(); + } + + public void toast(String msg) { + Toast toast = Toast.makeText(CommonActivity.this, msg, Toast.LENGTH_SHORT); + toast.show(); + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + public synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } + + public synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + @Override + public void onResume() { + super.onResume(); + + if (!m_theme.equals(m_prefs.getString("theme", CommonActivity.THEME_DEFAULT))) { + Log.d(TAG, "theme changed, restarting"); + + finish(); + startActivity(getIntent()); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + m_readableDb.close(); + m_writableDb.close(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + if (savedInstanceState != null) { + m_theme = savedInstanceState.getString("theme"); + } else { + m_theme = m_prefs.getString("theme", CommonActivity.THEME_DEFAULT); + } + + initDatabase(); + + m_compatMode = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB; + + Log.d(TAG, "m_compatMode=" + m_compatMode); + + super.onCreate(savedInstanceState); + } + + public void setStatusBarTint() { + if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.KITKAT) { + SystemBarTintManager tintManager = new SystemBarTintManager(this); + // enable status bar tint + tintManager.setStatusBarTintEnabled(true); + // enable navigation bar tint + tintManager.setNavigationBarTintEnabled(true); + + TypedValue tv = new TypedValue(); + getTheme().resolveAttribute(R.attr.statusBarHintColor, tv, true); + + tintManager.setStatusBarTintColor(tv.data); + } + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("theme", m_theme); + } + + public boolean isSmallScreen() { + return m_smallScreenMode; + } + + public boolean isCompatMode() { + return m_compatMode; + } + + @SuppressWarnings("deprecation") + public boolean isPortrait() { + Display display = getWindowManager().getDefaultDisplay(); + + int width = display.getWidth(); + int height = display.getHeight(); + + return width < height; + } + + @SuppressLint({ "NewApi", "ServiceCast" }) + @SuppressWarnings("deprecation") + public void copyToClipboard(String str) { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(str); + } else { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(str); + } + + Toast toast = Toast.makeText(this, R.string.text_copied_to_clipboard, Toast.LENGTH_SHORT); + toast.show(); + } + + public boolean isDarkTheme() { + String theme = m_prefs.getString("theme", THEME_DEFAULT); + + return theme.equals(THEME_DARK) || theme.equals(THEME_HOLO); + } + + protected void setAppTheme(SharedPreferences prefs) { + String theme = prefs.getString("theme", CommonActivity.THEME_DEFAULT); + + if (theme.equals(THEME_DARK)) { + setTheme(R.style.DarkTheme); + } else if (theme.equals(THEME_SEPIA)) { + setTheme(R.style.SepiaTheme); + } else if (theme.equals(THEME_HOLO)) { + setTheme(R.style.HoloTheme); + } else { + setTheme(R.style.LightTheme); + } + } + + @SuppressWarnings("deprecation") + @SuppressLint("NewApi") + protected int getScreenWidthInPixel() { + Display display = getWindowManager().getDefaultDisplay(); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { + Point size = new Point(); + display.getSize(size); + int width = size.x; + return width; + } else { + return display.getWidth(); + } + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/DashClock.java b/orgfoxttrss/src/main/java/org/fox/ttrss/DashClock.java new file mode 100644 index 00000000..b3491972 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/DashClock.java @@ -0,0 +1,107 @@ +package org.fox.ttrss; + +import java.util.HashMap; + +import org.fox.ttrss.util.SimpleLoginManager; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; + +import com.google.android.apps.dashclock.api.DashClockExtension; +import com.google.android.apps.dashclock.api.ExtensionData; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class DashClock extends DashClockExtension { + + private final String TAG = this.getClass().getSimpleName(); + + protected SharedPreferences m_prefs; + + @Override + protected void onInitialize(boolean isReconnect) { + super.onInitialize(isReconnect); + setUpdateWhenScreenOn(true); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + } + + @Override + protected void onUpdateData(int reason) { + + SimpleLoginManager loginManager = new SimpleLoginManager() { + + @Override + protected void onLoginSuccess(int requestId, String sessionId, int apiLevel) { + + ApiRequest aru = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + try { + JsonObject content = result.getAsJsonObject(); + + if (content != null) { + int unread = content.get("unread").getAsInt(); + + ExtensionData updatedData = null; // when null DashClock hides the widget + + if (unread > 0) { + updatedData = new ExtensionData(); + updatedData.visible(true); + + updatedData.icon(R.drawable.dashclock); + updatedData.status(String.valueOf(unread)); + + updatedData.expandedTitle(getString(R.string.n_unread_articles, unread)); + //updatedData.expandedBody(getString(R.string.app_name)); + + updatedData.clickIntent(new Intent().setClassName("org.fox.ttrss", + "org.fox.ttrss.OnlineActivity")); + } + + publishUpdate(updatedData); + + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + }; + + final String fSessionId = sessionId; + + HashMap<String, String> umap = new HashMap<String, String>() { + { + put("op", "getUnread"); + put("sid", fSessionId); + } + }; + + aru.execute(umap); + } + + @Override + protected void onLoginFailed(int requestId, ApiRequest ar) { + + } + + @Override + protected void onLoggingIn(int requestId) { + + + } + }; + + String login = m_prefs.getString("login", "").trim(); + String password = m_prefs.getString("password", "").trim(); + + loginManager.logIn(getApplicationContext(), 1, login, password); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/DummyFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/DummyFragment.java new file mode 100644 index 00000000..7bf799a9 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/DummyFragment.java @@ -0,0 +1,17 @@ +package org.fox.ttrss; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class DummyFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.dummy_fragment, container, false); + + return view; + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java new file mode 100644 index 00000000..4439a943 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java @@ -0,0 +1,547 @@ +package org.fox.ttrss; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +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; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +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.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +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 FeedCategoriesFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { + 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 FeedsActivity m_activity; + private SwipeRefreshLayout m_swipeLayout; + + @SuppressLint("DefaultLocale") + class CatUnreadComparator implements Comparator<FeedCategory> { + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.unread != b.unread) + return b.unread - a.unread; + else + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + } + } + + + @SuppressLint("DefaultLocale") + class CatTitleComparator implements Comparator<FeedCategory> { + + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.id >= 0 && b.id >= 0) + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + else + return a.id - b.id; + } + + } + + @SuppressLint("DefaultLocale") + class CatOrderComparator implements Comparator<FeedCategory> { + + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.id >= 0 && b.id >= 0) + if (a.order_id != 0 && b.order_id != 0) + return a.order_id - b.order_id; + else + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + else + return a.id - b.id; + } + + } + + @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.create_shortcut: + if (true) { + FeedCategory cat = getCategoryAtPosition(info.position); + if (cat != null) { + m_activity.createCategoryShortcut(cat); + //cf.setSelectedCategory(cat); + } + } + return true; + case R.id.catchup_category: + if (true) { + final FeedCategory cat = getCategoryAtPosition(info.position); + if (cat != null) { + + if (m_prefs.getBoolean("confirm_headlines_catchup", true)) { + AlertDialog.Builder builder = new AlertDialog.Builder( + m_activity) + .setMessage(getString(R.string.context_confirm_catchup, cat.title)) + .setPositiveButton(R.string.catchup, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + m_activity.catchupFeed(new Feed(cat.id, cat.title, true)); + + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } else { + 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) { + + m_activity.getMenuInflater().inflate(R.menu.category_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + FeedCategory cat = m_adapter.getItem(info.position); + + if (cat != null) + menu.setHeaderTitle(cat.title); + + if (!m_activity.isSmallScreen()) { + menu.findItem(R.id.browse_articles).setVisible(false); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + public FeedCategory getCategoryAtPosition(int position) { + return m_adapter.getItem(position); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (savedInstanceState != null) { + m_selectedCat = savedInstanceState.getParcelable("selectedCat"); + m_cats = savedInstanceState.getParcelable("cats"); + } + + View view = inflater.inflate(R.layout.cats_fragment, container, false); + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.feeds_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + refresh(false); + } + }); + + if (!m_activity.isCompatMode()) { + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + } + + + ListView list = (ListView)view.findViewById(R.id.feeds); + m_adapter = new FeedCategoryListAdapter(getActivity(), R.layout.feeds_row, (ArrayList<FeedCategory>)m_cats); + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + registerForContextMenu(list); + + //m_activity.m_pullToRefreshAttacher.addRefreshableView(list, this); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(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); + + out.setClassLoader(getClass().getClassLoader()); + out.putParcelable("selectedCat", m_selectedCat); + out.putParcelable("cats", m_cats); + } + + /* private void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + m_activity.setProgressBarIndeterminateVisibility(showProgress); + } */ + + public void refresh(boolean background) { + m_swipeLayout.setRefreshing(true); + + CatsRequest req = new CatsRequest(getActivity().getApplicationContext()); + + final String sessionId = m_activity.getSessionId(); + final boolean unreadOnly = m_activity.getUnreadOnly(); + + if (sessionId != null) { + //m_activity.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)); + } + } + }; + + req.execute(map); + } + } + + private class CatsRequest extends ApiRequest { + + public CatsRequest(Context context) { + 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) { + if (isDetached()) return; + + m_activity.setProgressBarVisibility(false); + m_swipeLayout.setRefreshing(false); + + if (getView() != null) { + ListView list = (ListView)getView().findViewById(R.id.feeds); + + if (list != null) { + list.setEmptyView(getView().findViewById(R.id.no_feeds)); + } + } + + if (result != null) { + try { + JsonArray content = result.getAsJsonArray(); + if (content != null) { + Type listType = new TypeToken<List<FeedCategory>>() {}.getType(); + final List<FeedCategory> cats = new Gson().fromJson(content, listType); + + m_cats.clear(); + + int apiLevel = m_activity.getApiLevel(); + + // virtual cats implemented in getCategories since api level 1 + if (apiLevel == 0) { + m_cats.add(new FeedCategory(-1, "Special", 0)); + m_cats.add(new FeedCategory(-2, "Labels", 0)); + m_cats.add(new FeedCategory(0, "Uncategorized", 0)); + } + + for (FeedCategory c : cats) + m_cats.add(c); + + sortCats(); + + /* if (m_cats.size() == 0) + setLoadingStatus(R.string.no_feeds_to_display, false); + else */ + + //m_adapter.notifyDataSetChanged(); (done by sortCats) + m_activity.setLoadingStatus(R.string.blank, false); + + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (m_lastError == ApiError.LOGIN_FAILED) { + m_activity.login(true); + } else { + m_activity.setLoadingStatus(getErrorMessage(), false); + } + } + + } + + public void sortCats() { + Comparator<FeedCategory> cmp; + + if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { + cmp = new CatUnreadComparator(); + } else { + if (m_activity.getApiLevel() >= 3) { + cmp = new CatOrderComparator(); + } else { + cmp = new CatTitleComparator(); + } + } + + try { + Collections.sort(m_cats, cmp); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + try { + m_adapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + // adapter missing + } + + } + + private class FeedCategoryListAdapter extends ArrayAdapter<FeedCategory> { + private ArrayList<FeedCategory> items; + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_SELECTED = 1; + + public static final int VIEW_COUNT = VIEW_SELECTED+1; + + public FeedCategoryListAdapter(Context context, int textViewResourceId, ArrayList<FeedCategory> items) { + super(context, textViewResourceId, items); + this.items = items; + } + + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + FeedCategory cat = items.get(position); + + if (!m_activity.isSmallScreen() && m_selectedCat != null && cat.id == m_selectedCat.id) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + FeedCategory cat = items.get(position); + + if (v == null) { + int layoutId = R.layout.feeds_row; + + switch (getItemViewType(position)) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + } + + TextView tt = (TextView) v.findViewById(R.id.title); + + if (tt != null) { + tt.setText(cat.title); + } + + TextView tu = (TextView) v.findViewById(R.id.unread_counter); + + if (tu != null) { + tu.setText(String.valueOf(cat.unread)); + tu.setVisibility((cat.unread > 0) ? View.VISIBLE : View.INVISIBLE); + } + + ImageView icon = (ImageView)v.findViewById(R.id.icon); + + if (icon != null) { + icon.setImageResource(cat.unread > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + ImageButton ib = (ImageButton) v.findViewById(R.id.feed_menu_button); + + if (ib != null) { + ib.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + + return v; + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + + sortCats(); + + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)av; + + Log.d(TAG, "onItemClick=" + position); + + if (list != null) { + FeedCategory cat = (FeedCategory)list.getItemAtPosition(position); + + if (cat.id < 0) { + m_activity.onCatSelected(cat, false); + } else { + 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(); + } + } + + public void setSelectedCategory(FeedCategory cat) { + m_selectedCat = cat; + + if (m_adapter != null) { + m_adapter.notifyDataSetChanged(); + } + } + + public FeedCategory getSelectedCategory() { + return m_selectedCat; + } + + /* @Override + public void onRefreshStarted(View view) { + refresh(false); + } */ +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsActivity.java new file mode 100644 index 00000000..90e2c118 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsActivity.java @@ -0,0 +1,509 @@ +package org.fox.ttrss; + + +import java.util.Date; +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.types.FeedCategory; +import org.fox.ttrss.util.AppRater; + +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.util.TypedValue; +import android.view.MenuItem; +import android.view.Window; +import android.view.WindowManager; +import android.widget.LinearLayout; + +import com.google.gson.JsonElement; +import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; +import com.readystatesoftware.systembartint.SystemBarTintManager; + +public class FeedsActivity extends OnlineActivity implements HeadlinesEventListener { + private final String TAG = this.getClass().getSimpleName(); + + private static final int HEADLINES_REQUEST = 1; + + protected SharedPreferences m_prefs; + protected long m_lastRefresh = 0; + + private boolean m_actionbarUpEnabled = false; + private int m_actionbarRevertDepth = 0; + private SlidingMenu m_slidingMenu; + private boolean m_feedIsSelected = false; + private boolean m_feedWasSelected = false; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.headlines); + + setStatusBarTint(); + + setSmallScreen(findViewById(R.id.sw600dp_anchor) == null && + findViewById(R.id.sw600dp_port_anchor) == null); + + GlobalState.getInstance().load(savedInstanceState); + + if (isSmallScreen() || findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu = new SlidingMenu(this); + +/* if (findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } */ + + m_slidingMenu.setMode(SlidingMenu.LEFT); + m_slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); + m_slidingMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); + m_slidingMenu.setMenu(R.layout.feeds); + m_slidingMenu.setSlidingEnabled(true); + + m_slidingMenu.setOnClosedListener(new SlidingMenu.OnClosedListener() { + + @Override + public void onClosed() { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + m_feedIsSelected = true; + + initMenu(); + } + }); + + m_slidingMenu.setOnOpenedListener(new SlidingMenu.OnOpenedListener() { + + @Override + public void onOpened() { + if (m_actionbarRevertDepth == 0) { + m_actionbarUpEnabled = false; + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + refresh(false); + } + + m_feedIsSelected = false; + initMenu(); + } + }); + } + + if (savedInstanceState == null) { + if (m_slidingMenu != null) + m_slidingMenu.showMenu(); + + final Intent i = getIntent(); + boolean shortcutMode = i.getBooleanExtra("shortcut_mode", false); + + Log.d(TAG, "is_shortcut_mode: " + shortcutMode); + + if (shortcutMode) { + LoginRequest lr = new LoginRequest(this, false, new OnLoginFinishedListener() { + + @Override + public void OnLoginSuccess() { + int feedId = i.getIntExtra("feed_id", 0); + boolean isCat = i.getBooleanExtra("feed_is_cat", false); + String feedTitle = i.getStringExtra("feed_title"); + + Feed tmpFeed = new Feed(feedId, feedTitle, isCat); + + onFeedSelected(tmpFeed); + } + + @Override + public void OnLoginFailed() { + login(); + } + }); + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("op", "login"); + put("user", m_prefs.getString("login", "").trim()); + put("password", m_prefs.getString("password", "").trim()); + } + }; + + lr.execute(map); + } + + //m_pullToRefreshAttacher.setRefreshing(true); + + 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); + checkTrial(true); + + } else { // savedInstanceState != null + m_actionbarUpEnabled = savedInstanceState.getBoolean("actionbarUpEnabled"); + m_actionbarRevertDepth = savedInstanceState.getInt("actionbarRevertDepth"); + m_feedIsSelected = savedInstanceState.getBoolean("feedIsSelected"); + m_feedWasSelected = savedInstanceState.getBoolean("feedWasSelected"); + + if (findViewById(R.id.sw600dp_port_anchor) != null && m_feedWasSelected && m_slidingMenu != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } + + if (m_slidingMenu != null && m_feedIsSelected == false) { + m_slidingMenu.showMenu(); + } else if (m_slidingMenu != null) { + m_actionbarUpEnabled = true; + } else { + m_actionbarUpEnabled = m_actionbarRevertDepth > 0; + } + + if (m_actionbarUpEnabled) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + if (!isSmallScreen()) { + // temporary hack because FeedsActivity doesn't track whether active feed is open + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + + if (container != null) + container.setWeightSum(3f); + } + + } + + /* if (!isCompatMode() && !isSmallScreen()) { + ((ViewGroup)findViewById(R.id.headlines_fragment)).setLayoutTransition(new LayoutTransition()); + ((ViewGroup)findViewById(R.id.feeds_fragment)).setLayoutTransition(new LayoutTransition()); + } */ + + } + + @Override + protected void initMenu() { + super.initMenu(); + + if (m_menu != null && getSessionId() != null) { + Fragment ff = getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); + Fragment cf = getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (m_slidingMenu != null) { + m_menu.setGroupVisible(R.id.menu_group_feeds, m_slidingMenu.isMenuShowing()); + m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded() && !m_slidingMenu.isMenuShowing()); + } else { + m_menu.setGroupVisible(R.id.menu_group_feeds, (ff != null && ff.isAdded()) || (cf != null && cf.isAdded())); + m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded()); + + m_menu.findItem(R.id.update_headlines).setVisible(false); + } + + m_menu.findItem(R.id.headlines_toggle_sidebar).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(); + //m_pullToRefreshAttacher.setRefreshing(true); + + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + ft.replace(R.id.headlines_fragment, new LoadingFragment(), null); + ft.commit(); + + if (!isCompatMode() && !isSmallScreen()) { + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + if (container != null) { + float wSum = container.getWeightSum(); + if (wSum <= 2.0f) { + ObjectAnimator anim = ObjectAnimator.ofFloat(container, "weightSum", wSum, 3.0f); + anim.setDuration(200); + anim.start(); + } + } + } + + final Feed fFeed = feed; + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + HeadlinesFragment hf = new HeadlinesFragment(); + hf.initialize(fFeed); + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + + ft.commit(); + + m_feedIsSelected = true; + m_feedWasSelected = true; + + if (m_slidingMenu != null) { + if (findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } + + m_slidingMenu.showContent(); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + + } + } + }, 10); + + + Date date = new Date(); + + if (date.getTime() - m_lastRefresh > 10000) { + m_lastRefresh = date.getTime(); + refresh(false); + } + } + + public void onCatSelected(FeedCategory cat, boolean openAsFeed) { + FeedCategoriesFragment fc = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + + //m_pullToRefreshAttacher.setRefreshing(true); + + if (!openAsFeed) { + + if (fc != null) { + fc.setSelectedCategory(null); + } + + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + FeedsFragment ff = new FeedsFragment(); + ff.initialize(cat); + ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); + + ft.addToBackStack(null); + ft.commit(); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + m_actionbarRevertDepth = m_actionbarRevertDepth + 1; + + } else { + + if (fc != null) { + fc.setSelectedCategory(cat); + } + + 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 void onBackPressed() { + if (m_actionbarRevertDepth > 0) { + + if (m_feedIsSelected && m_slidingMenu != null && !m_slidingMenu.isMenuShowing()) { + m_slidingMenu.showMenu(); + } else { + m_actionbarRevertDepth = m_actionbarRevertDepth - 1; + m_actionbarUpEnabled = m_actionbarRevertDepth > 0; + getSupportActionBar().setDisplayHomeAsUpEnabled(m_actionbarUpEnabled); + + onBackPressed(); + } + } else if (m_slidingMenu != null && !m_slidingMenu.isMenuShowing()) { + m_slidingMenu.showMenu(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (m_actionbarUpEnabled) + onBackPressed(); + return true; + case R.id.show_feeds: + setUnreadOnly(!getUnreadOnly()); + initMenu(); + refresh(); + return true; + case R.id.update_feeds: + //m_pullToRefreshAttacher.setRefreshing(true); + refresh(); + return true; + default: + Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void loginSuccess(boolean refresh) { + setLoadingStatus(R.string.blank, false); + //findViewById(R.id.loading_container).setVisibility(View.GONE); + initMenu(); + + if (refresh) refresh(); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putBoolean("actionbarUpEnabled", m_actionbarUpEnabled); + out.putInt("actionbarRevertDepth", m_actionbarRevertDepth); + out.putBoolean("feedIsSelected", m_feedIsSelected); + out.putBoolean("feedWasSelected", m_feedWasSelected); + + //if (m_slidingMenu != null ) + // out.putBoolean("slidingMenuVisible", m_slidingMenu.isMenuShowing()); + + GlobalState.getInstance().save(out); + } + + @Override + public void onResume() { + super.onResume(); + initMenu(); + } + + @Override + public void onArticleListSelectionChange(ArticleList m_selectedArticles) { + initMenu(); + } + + public void openFeedArticles(Feed feed) { + GlobalState.getInstance().m_loadedArticles.clear(); + + Intent intent = new Intent(FeedsActivity.this, HeadlinesActivity.class); + intent.putExtra("feed", feed); + intent.putExtra("article", (Article)null); + intent.putExtra("searchQuery", (String)null); + + startActivityForResult(intent, HEADLINES_REQUEST); + overridePendingTransition(R.anim.right_slide_in, 0); + } + + public void onArticleSelected(Article article, boolean open) { + if (article.unread) { + article.unread = false; + saveArticleUnread(article); + } + + if (open) { + HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + Intent intent = new Intent(FeedsActivity.this, HeadlinesActivity.class); + intent.putExtra("feed", hf.getFeed()); + intent.putExtra("article", article); + intent.putExtra("searchQuery", hf.getSearchQuery()); + + startActivityForResult(intent, HEADLINES_REQUEST); + overridePendingTransition(R.anim.right_slide_in, 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 + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == HEADLINES_REQUEST) { + GlobalState.getInstance().m_activeArticle = null; + } + } + + public void createFeedShortcut(Feed feed) { + final Intent shortcutIntent = new Intent(this, FeedsActivity.class); + shortcutIntent.putExtra("feed_id", feed.id); + shortcutIntent.putExtra("feed_is_cat", feed.is_cat); + shortcutIntent.putExtra("feed_title", feed.title); + shortcutIntent.putExtra("shortcut_mode", true); + + Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); + + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, feed.title); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon)); + intent.putExtra("duplicate", false); + + sendBroadcast(intent); + + toast(R.string.shortcut_has_been_placed_on_the_home_screen); + } + + public void createCategoryShortcut(FeedCategory cat) { + createFeedShortcut(new Feed(cat.id, cat.title, true)); + } + + public void unsubscribeFeed(final Feed feed) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + refresh(); + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "unsubscribeFeed"); + put("feed_id", String.valueOf(feed.id)); + } + }; + + req.execute(map); + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsFragment.java new file mode 100644 index 00000000..7c974809 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/FeedsFragment.java @@ -0,0 +1,808 @@ +package org.fox.ttrss; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +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.FeedList; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +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.support.v4.widget.SwipeRefreshLayout; +import android.util.Base64; +import android.util.Log; +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.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +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 FeedsFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { + private final String TAG = this.getClass().getSimpleName(); + private SharedPreferences m_prefs; + private FeedListAdapter m_adapter; + private FeedList m_feeds = new FeedList(); + private FeedsActivity m_activity; + private Feed m_selectedFeed; + private FeedCategory m_activeCategory; + private static final String ICON_PATH = "/icons/"; + private boolean m_enableFeedIcons; + private boolean m_feedIconsChecked = false; + private SwipeRefreshLayout m_swipeLayout; + + public void initialize(FeedCategory cat) { + m_activeCategory = cat; + } + + @SuppressLint("DefaultLocale") + class FeedUnreadComparator implements Comparator<Feed> { + + @Override + public int compare(Feed a, Feed b) { + if (a.unread != b.unread) + return b.unread - a.unread; + else + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + } + + } + + + @SuppressLint("DefaultLocale") + class FeedTitleComparator implements Comparator<Feed> { + + @Override + public int compare(Feed a, Feed b) { + if (a.is_cat && b.is_cat) + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + 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.toUpperCase().compareTo(b.title.toUpperCase()); + else + return a.id - b.id; + } + + } + + @SuppressLint("DefaultLocale") + class FeedOrderComparator implements Comparator<Feed> { + + @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.toUpperCase().compareTo(b.title.toUpperCase()); + 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.toUpperCase().compareTo(b.title.toUpperCase()); + else + return a.id - b.id; + } + + } + + @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.browse_feeds: + if (true) { + Feed feed = getFeedAtPosition(info.position); + if (feed != null) { + m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); + } + } + return true; + case R.id.unsubscribe_feed: + if (true) { + final Feed feed = getFeedAtPosition(info.position); + + AlertDialog.Builder builder = new AlertDialog.Builder( + m_activity) + .setMessage(getString(R.string.unsubscribe_from_prompt, feed.title)) + .setPositiveButton(R.string.unsubscribe, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + m_activity.unsubscribeFeed(feed); + + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } + + return true; + case R.id.create_shortcut: + if (true) { + Feed feed = getFeedAtPosition(info.position); + if (feed != null) { + m_activity.createFeedShortcut(feed); + } + } + return true; + case R.id.catchup_feed: + if (true) { + final Feed feed = getFeedAtPosition(info.position); + + if (feed != null) { + if (m_prefs.getBoolean("confirm_headlines_catchup", true)) { + AlertDialog.Builder builder = new AlertDialog.Builder( + m_activity) + .setMessage(getString(R.string.context_confirm_catchup, feed.title)) + .setPositiveButton(R.string.catchup, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + m_activity.catchupFeed(feed); + + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } else { + 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) { + + getActivity().getMenuInflater().inflate(R.menu.feed_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + Feed feed = m_adapter.getItem(info.position); + + if (feed != null) + menu.setHeaderTitle(feed.title); + + if (!m_activity.isSmallScreen()) { + menu.findItem(R.id.browse_articles).setVisible(false); + } + + if (!feed.is_cat) { + menu.findItem(R.id.browse_feeds).setVisible(false); + } + + if (feed.id <= 0) { + menu.findItem(R.id.unsubscribe_feed).setVisible(false); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + if (savedInstanceState != null) { + m_selectedFeed = savedInstanceState.getParcelable("selectedFeed"); + m_feeds = savedInstanceState.getParcelable("feeds"); + m_feedIconsChecked = savedInstanceState.getBoolean("feedIconsChecked"); + m_activeCategory = savedInstanceState.getParcelable("activeCat"); + } + + View view = inflater.inflate(R.layout.feeds_fragment, container, false); + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.feeds_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + refresh(false); + } + }); + + if (!m_activity.isCompatMode()) { + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + } + + ListView list = (ListView)view.findViewById(R.id.feeds); + m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, (ArrayList<Feed>)m_feeds); + list.setAdapter(m_adapter); + //list.setEmptyView(view.findViewById(R.id.no_feeds)); + list.setOnItemClickListener(this); + + registerForContextMenu(list); + + m_enableFeedIcons = m_prefs.getBoolean("download_feed_icons", false); + + //Log.d(TAG, "mpTRA=" + m_activity.m_pullToRefreshAttacher); + //m_activity.m_pullToRefreshAttacher.addRefreshableView(list, this); + + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + m_prefs.registerOnSharedPreferenceChangeListener(this); + + m_activity = (FeedsActivity)activity; + + } + + @Override + public void onResume() { + super.onResume(); + + refresh(false); + + m_activity.initMenu(); + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.setClassLoader(getClass().getClassLoader()); + out.putParcelable("selectedFeed", m_selectedFeed); + out.putParcelable("feeds", m_feeds); + out.putBoolean("feedIconsChecked", m_feedIconsChecked); + out.putParcelable("activeCat", m_activeCategory); + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)av; + + if (list != null) { + Feed feed = (Feed)list.getItemAtPosition(position); + + if (feed.is_cat) { + if (m_activity.isSmallScreen() && "ARTICLES".equals(m_prefs.getString("default_view_mode", "HEADLINES")) && + m_prefs.getBoolean("browse_cats_like_feeds", false)) { + + m_activity.openFeedArticles(feed); + + } else { + m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread)); + } + } 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(); + } + } + + @SuppressWarnings({ "serial" }) + public void refresh(boolean background) { + //FeedCategory cat = m_onlineServices.getActiveCategory(); + + m_swipeLayout.setRefreshing(true); + + final int catId = (m_activeCategory != null) ? m_activeCategory.id : -4; + + final String sessionId = m_activity.getSessionId(); + final boolean unreadOnly = m_activity.getUnreadOnly(); + + FeedsRequest req = new FeedsRequest(getActivity().getApplicationContext(), catId); + + if (sessionId != null) { + //m_activity.setLoadingStatus(R.string.blank, true); + //m_activity.setProgressBarVisibility(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)); + } + } + }; + + req.execute(map); + + } + } + + /* private void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + if (getActivity() != null) + getActivity().setProgressBarIndeterminateVisibility(showProgress); + } */ + + @SuppressWarnings({ "serial" }) + public void getFeedIcons() { + + ApiRequest req = new ApiRequest(getActivity().getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + if (isDetached()) return; + + if (result != null) { + + try { + JsonElement iconsUrl = result.getAsJsonObject().get("icons_dir"); + + if (iconsUrl != null) { + String iconsStr = iconsUrl.getAsString(); + String baseUrl = ""; + + if (!iconsStr.contains("://")) { + baseUrl = m_prefs.getString("ttrss_url", "").trim() + "/" + iconsStr; + } else { + baseUrl = iconsStr; + } + + GetIconsTask git = new GetIconsTask(baseUrl); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + git.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, m_feeds); + else + git.execute(m_feeds); + + m_feedIconsChecked = true; + } + } catch (Exception e) { + Log.d(TAG, "Error receiving icons configuration"); + e.printStackTrace(); + } + + } + } + }; + + final String sessionId = m_activity.getSessionId(); + + HashMap<String,String> map = new HashMap<String,String>() { + { + put("sid", sessionId); + put("op", "getConfig"); + } + }; + + req.execute(map); + } + + private class FeedsRequest extends ApiRequest { + private int m_catId; + + public FeedsRequest(Context context, int catId) { + super(context); + 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) { + if (isDetached()) return; + + if (getView() != null) { + ListView list = (ListView)getView().findViewById(R.id.feeds); + + if (list != null) { + list.setEmptyView(getView().findViewById(R.id.no_feeds)); + } + } + + m_activity.setProgressBarVisibility(false); + //m_activity.m_pullToRefreshAttacher.setRefreshComplete(); + m_swipeLayout.setRefreshing(false); + + if (result != null) { + try { + JsonArray content = result.getAsJsonArray(); + if (content != null) { + + Type listType = new TypeToken<List<Feed>>() {}.getType(); + final List<Feed> feeds = new Gson().fromJson(content, listType); + + m_feeds.clear(); + + for (Feed f : feeds) + if (f.id > -10 || m_catId != -4) // skip labels for flat feedlist for now + m_feeds.add(f); + + sortFeeds(); + + /*if (m_feeds.size() == 0) + setLoadingStatus(R.string.no_feeds_to_display, false); + else */ + + m_activity.setLoadingStatus(R.string.blank, false); + //m_adapter.notifyDataSetChanged(); (done by sortFeeds) + + if (m_enableFeedIcons && !m_feedIconsChecked && Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) + getFeedIcons(); + + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (m_lastError == ApiError.LOGIN_FAILED) { + m_activity.login(true); + } else { + m_activity.setLoadingStatus(getErrorMessage(), false); + } + } + } + + private class FeedListAdapter extends ArrayAdapter<Feed> { + private ArrayList<Feed> items; + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_SELECTED = 1; + + public static final int VIEW_COUNT = VIEW_SELECTED+1; + + public FeedListAdapter(Context context, int textViewResourceId, ArrayList<Feed> items) { + super(context, textViewResourceId, items); + this.items = items; + } + + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + Feed feed = items.get(position); + + if (!m_activity.isSmallScreen() && m_selectedFeed != null && feed.id == m_selectedFeed.id) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + Feed feed = items.get(position); + + if (v == null) { + int layoutId = R.layout.feeds_row; + + switch (getItemViewType(position)) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + } + + TextView tt = (TextView) v.findViewById(R.id.title); + + if (tt != null) { + tt.setText(feed.title); + } + + TextView tu = (TextView) v.findViewById(R.id.unread_counter); + + if (tu != null) { + tu.setText(String.valueOf(feed.unread)); + tu.setVisibility((feed.unread > 0) ? View.VISIBLE : View.INVISIBLE); + } + + ImageView icon = (ImageView)v.findViewById(R.id.icon); + + if (icon != null) { + + if (m_enableFeedIcons) { + + try { + File storage = m_activity.getExternalCacheDir(); + + File iconFile = new File(storage.getAbsolutePath() + ICON_PATH + feed.id + ".ico"); + if (iconFile.exists()) { + Bitmap bmpOrig = BitmapFactory.decodeFile(iconFile.getAbsolutePath()); + if (bmpOrig != null) { + icon.setImageBitmap(bmpOrig); + } + } else { + icon.setImageResource(feed.unread > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + } catch (NullPointerException e) { + icon.setImageResource(feed.unread > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + } else { + icon.setImageResource(feed.unread > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + } + + ImageButton ib = (ImageButton) v.findViewById(R.id.feed_menu_button); + + if (ib != null) { + ib.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + return v; + } + } + + public void sortFeeds() { + Comparator<Feed> cmp; + + if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { + cmp = new FeedUnreadComparator(); + } else { + if (m_activity.getApiLevel() >= 3) { + cmp = new FeedOrderComparator(); + } else { + cmp = new FeedTitleComparator(); + } + } + + try { + Collections.sort(m_feeds, cmp); + } catch (IllegalArgumentException e) { + // sort order got changed in prefs or something + e.printStackTrace(); + } + + try { + m_adapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + // adapter missing + } + } + + public class GetIconsTask extends AsyncTask<FeedList, Integer, Integer> { + + private String m_baseUrl; + + public GetIconsTask(String baseUrl) { + m_baseUrl = baseUrl.trim(); + } + + @Override + protected Integer doInBackground(FeedList... params) { + + FeedList localList = new FeedList(); + + try { + localList.addAll(params[0]); + + File storage = m_activity.getExternalCacheDir(); + final File iconPath = new File(storage.getAbsolutePath() + ICON_PATH); + if (!iconPath.exists()) iconPath.mkdirs(); + + if (iconPath.exists()) { + for (Feed feed : localList) { + 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"; + + if (!outputFile.exists()) { + downloadFile(fetchUrl, outputFile.getAbsolutePath()); + Thread.sleep(2000); + } + } + } + } + } catch (Exception e) { + Log.d(TAG, "Error while downloading feed icons"); + e.printStackTrace(); + } + return null; + } + + protected void downloadFile(String fetchUrl, String outputFile) { + AndroidHttpClient client = AndroidHttpClient.newInstance("Tiny Tiny RSS"); + + /* ApiRequest.disableConnectionReuseIfNecessary(); */ + + /* ApiRequest.trustAllHosts(m_prefs.getBoolean("ssl_trust_any", false), + m_prefs.getBoolean("ssl_trust_any_host", false)); */ + + try { + URL url = new URL(fetchUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setConnectTimeout(1000); + conn.setReadTimeout(5000); + + Log.d(TAG, "[downloadFile] " + url); + + String httpLogin = m_prefs.getString("http_login", ""); + String httpPassword = m_prefs.getString("http_password", ""); + + if (httpLogin.length() > 0) { + conn.setRequestProperty("Authorization", "Basic " + + Base64.encodeToString((httpLogin + ":" + httpPassword).getBytes("UTF-8"), Base64.NO_WRAP)); + } + + InputStream content = conn.getInputStream(); + + BufferedInputStream is = new BufferedInputStream(content, 1024); + FileOutputStream fos = new FileOutputStream(outputFile); + + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + + client.close(); + } + + protected void onPostExecute(Integer result) { + if (isDetached()) return; + + m_adapter.notifyDataSetChanged(); + } + + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + + sortFeeds(); + m_enableFeedIcons = m_prefs.getBoolean("download_feed_icons", false); + + } + + public Feed getFeedAtPosition(int position) { + try { + return m_adapter.getItem(position); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + public Feed getSelectedFeed() { + return m_selectedFeed; + } + + public void setSelectedFeed(Feed feed) { + m_selectedFeed = feed; + + if (m_adapter != null) { + m_adapter.notifyDataSetChanged(); + } + } + + /* @Override + public void onRefreshStarted(View view) { + refresh(false); + } */ + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/GlobalState.java b/orgfoxttrss/src/main/java/org/fox/ttrss/GlobalState.java new file mode 100644 index 00000000..6f81fc5d --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/GlobalState.java @@ -0,0 +1,63 @@ +package org.fox.ttrss; + +import java.util.ArrayList; + +import org.fox.ttrss.types.Article; +import org.fox.ttrss.types.ArticleList; +import org.fox.ttrss.types.Feed; + +import android.app.Application; +import android.os.Bundle; +import android.os.Parcelable; + +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 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; + } + + public void save(Bundle out) { + + out.setClassLoader(getClass().getClassLoader()); + out.putParcelableArrayList("gs:loadedArticles", m_loadedArticles); + out.putParcelable("gs:activeFeed", m_activeFeed); + out.putParcelable("gs:activeArticle", m_activeArticle); + out.putString("gs:sessionId", m_sessionId); + out.putInt("gs:apiLevel", m_apiLevel); + out.putBoolean("gs:canUseProgress", m_canUseProgress); + out.putInt("gs:selectedArticleId", m_selectedArticleId); + } + + public void load(Bundle in) { + if (m_loadedArticles.size() == 0 && in != null) { + ArrayList<Parcelable> list = in.getParcelableArrayList("gs:loadedArticles"); + + for (Parcelable p : list) { + m_loadedArticles.add((Article)p); + } + + m_activeFeed = (Feed) in.getParcelable("gs:activeFeed"); + m_activeArticle = (Article) in.getParcelable("gs:activeArticle"); + m_sessionId = in.getString("gs:sessionId"); + m_apiLevel = in.getInt("gs:apiLevel"); + m_canUseProgress = in.getBoolean("gs:canUseProgress"); + m_selectedArticleId = in.getInt("gs:selectedArticleId"); + } + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesActivity.java new file mode 100644 index 00000000..c4e0f0cc --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesActivity.java @@ -0,0 +1,281 @@ +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.os.Handler; +import android.preference.PreferenceManager; +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()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.headlines_articles); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setStatusBarTint(); + setSmallScreen(findViewById(R.id.sw600dp_anchor) == null); + + GlobalState.getInstance().load(savedInstanceState); + + if (isPortrait() || m_prefs.getBoolean("headlines_hide_sidebar", false)) { + findViewById(R.id.headlines_fragment).setVisibility(View.GONE); + } + + if (savedInstanceState == null) { + Intent i = getIntent(); + + if (i.getExtras() != null) { + boolean shortcutMode = i.getBooleanExtra("shortcut_mode", false); + + Log.d(TAG, "is_shortcut_mode: " + shortcutMode); + + Feed tmpFeed; + + if (shortcutMode) { + int feedId = i.getIntExtra("feed_id", 0); + boolean isCat = i.getBooleanExtra("feed_is_cat", false); + String feedTitle = i.getStringExtra("feed_title"); + + tmpFeed = new Feed(feedId, feedTitle, isCat); + + GlobalState.getInstance().m_loadedArticles.clear(); + } else { + tmpFeed = i.getParcelableExtra("feed"); + } + + final Feed feed = tmpFeed; + + final Article article = i.getParcelableExtra("article"); + final String searchQuery = i.getStringExtra("searchQuery"); + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + + ft.replace(R.id.headlines_fragment, new LoadingFragment(), null); + ft.replace(R.id.article_fragment, new LoadingFragment(), null); + + ft.commit(); + + setTitle(feed.title); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + + HeadlinesFragment hf = new HeadlinesFragment(); + hf.initialize(feed, article); + hf.setSearchQuery(searchQuery); + + ArticlePager af = new ArticlePager(); + af.initialize(article != null ? hf.getArticleById(article.id) : new Article(), feed); + af.setSearchQuery(searchQuery); + + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + ft.replace(R.id.article_fragment, af, FRAG_ARTICLE); + + ft.commit(); + } + }, 25); + + } + } + + /* if (!isCompatMode()) { + ((ViewGroup)findViewById(R.id.headlines_fragment)).setLayoutTransition(new LayoutTransition()); + ((ViewGroup)findViewById(R.id.article_fragment)).setLayoutTransition(new LayoutTransition()); + } */ + } + + @Override + protected void refresh() { + super.refresh(); + + + } + + @Override + protected void loginSuccess(boolean refresh) { + Log.d(TAG, "loginSuccess"); + + setLoadingStatus(R.string.blank, false); + findViewById(R.id.loading_container).setVisibility(View.GONE); + + initMenu(); + + if (refresh) refresh(); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + GlobalState.getInstance().save(out); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + overridePendingTransition(0, R.anim.right_slide_out); + return true; + 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, !isPortrait() && !isSmallScreen()); + m_menu.findItem(R.id.headlines_toggle_sidebar).setVisible(!isPortrait() && !isSmallScreen()); + + ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + m_menu.setGroupVisible(R.id.menu_group_article, af != null); + + if (af != null) { + if (af.getSelectedArticle() != null && af.getSelectedArticle().attachments != null && af.getSelectedArticle().attachments.size() > 0) { + if (!isCompatMode() && (isSmallScreen() || !isPortrait())) { + m_menu.findItem(R.id.toggle_attachments).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + m_menu.findItem(R.id.toggle_attachments).setVisible(true); + } else { + if (!isCompatMode()) { + m_menu.findItem(R.id.toggle_attachments).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + m_menu.findItem(R.id.toggle_attachments).setVisible(false); + } + } + + 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 == null) return; + + if (article.unread) { + article.unread = false; + saveArticleUnread(article); + } + + if (open) { + + final Article fArticle = article; + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (af != null) { + af.setActiveArticle(fArticle); + } + } + }, 10); + + } else { + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + if (hf != null) { + 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 && hf.getAllArticles().size() > 0) { + article = hf.getAllArticles().get(0); + + hf.setActiveArticle(article); + + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + ft.replace(R.id.article_fragment, new LoadingFragment(), null); + + ft.commitAllowingStateLoss(); + + final Article fArticle = article; + final Feed fFeed = hf.getFeed(); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + ArticlePager af = new ArticlePager(); + af.initialize(fArticle, fFeed); + + ft.replace(R.id.article_fragment, af, FRAG_ARTICLE); + ft.commitAllowingStateLoss(); + } + }, 10); + } + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(0, R.anim.right_slide_out); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java new file mode 100644 index 00000000..5494bb2b --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java @@ -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); +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java new file mode 100644 index 00000000..32670252 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java @@ -0,0 +1,1179 @@ +package org.fox.ttrss; + +import java.net.MalformedURLException; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.TimeZone; + +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 org.fox.ttrss.util.TypefaceCache; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources.Theme; +import android.graphics.Bitmap; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.OpenableColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.Html; +import android.util.DisplayMetrics; +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.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.google.gson.JsonElement; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +public class HeadlinesFragment extends Fragment implements OnItemClickListener, OnScrollListener { + public static enum ArticlesSelection { ALL, NONE, UNREAD }; + + public static final int HEADLINES_REQUEST_SIZE = 30; + public static final int HEADLINES_BUFFER_MAX = 500; + + private final String TAG = this.getClass().getSimpleName(); + + private Feed m_feed; + private Article m_activeArticle; + private String m_searchQuery = ""; + private boolean m_refreshInProgress = false; + private boolean m_autoCatchupDisabled = false; + + private SharedPreferences m_prefs; + + private ArticleListAdapter m_adapter; + private ArticleList m_articles = GlobalState.getInstance().m_loadedArticles; + private ArticleList m_selectedArticles = new ArticleList(); + private ArticleList m_readArticles = new ArticleList(); + private HeadlinesEventListener m_listener; + private OnlineActivity m_activity; + private SwipeRefreshLayout m_swipeLayout; + private int m_maxImageSize = 0; + + public ArticleList getSelectedArticles() { + return m_selectedArticles; + } + + public void initialize(Feed feed) { + m_feed = feed; + } + + public void initialize(Feed feed, Article activeArticle) { + m_feed = feed; + + if (activeArticle != null) { + m_activeArticle = getArticleById(activeArticle.id); + } + } + + @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) { + if (m_activity.getApiLevel() != 7) { + m_activity.editArticleLabels(article); + } else { + m_activity.toast(R.string.server_function_not_available); + } + } + } + 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.headlines_article_link_copy: + if (true) { + Article article = getArticleAtPosition(info.position); + + if (article != null) { + m_activity.copyToClipboard(article.link); + } + } + return true; + case R.id.headlines_article_link_open: + if (true) { + Article article = getArticleAtPosition(info.position); + + if (article != null) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(article.link)); + startActivity(browserIntent); + + if (article.unread) { + article.unread = false; + m_activity.saveArticleUnread(article); + } + } + } + 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.headlines_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) { + if (article.id == a.id) + break; + + if (a.unread) { + a.unread = false; + tmp.add(a); + } + } + 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_context_menu, menu); + + if (m_selectedArticles.size() > 0) { + menu.setHeaderTitle(R.string.headline_context_multiple); + menu.setGroupVisible(R.id.menu_group_single_article, false); + } else { + AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; + Article article = getArticleAtPosition(info.position); + menu.setHeaderTitle(Html.fromHtml(article.title)); + menu.setGroupVisible(R.id.menu_group_single_article, true); + } + + 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); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + if (savedInstanceState != null) { + m_feed = savedInstanceState.getParcelable("feed"); + //m_articles = savedInstanceState.getParcelable("articles"); + m_activeArticle = savedInstanceState.getParcelable("activeArticle"); + m_selectedArticles = savedInstanceState.getParcelable("selectedArticles"); + m_searchQuery = (String) savedInstanceState.getCharSequence("searchQuery"); + } + + DisplayMetrics metrics = new DisplayMetrics(); + getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); + m_maxImageSize = (int) (128 * metrics.density + 0.5); + + Log.d(TAG, "maxImageSize=" + m_maxImageSize); + + View view = inflater.inflate(R.layout.headlines_fragment, container, false); + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.headlines_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + refresh(false, true); + } + }); + + if (!m_activity.isCompatMode()) { + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + } + + + ListView list = (ListView)view.findViewById(R.id.headlines); + m_adapter = new ArticleListAdapter(getActivity(), R.layout.headlines_row, (ArrayList<Article>)m_articles); + + /* if (!m_activity.isCompatMode()) { + AnimationSet set = new AnimationSet(true); + + Animation animation = new AlphaAnimation(0.0f, 1.0f); + animation.setDuration(500); + set.addAnimation(animation); + + animation = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, 50.0f,Animation.RELATIVE_TO_SELF, 0.0f, + Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f + ); + animation.setDuration(1000); + set.addAnimation(animation); + + LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f); + + list.setLayoutAnimation(controller); + } */ + + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + list.setOnScrollListener(this); + //list.setEmptyView(view.findViewById(R.id.no_headlines)); + registerForContextMenu(list); + + //m_activity.m_pullToRefreshAttacher.addRefreshableView(list, this); + + //if (m_activity.isSmallScreen()) + //view.findViewById(R.id.headlines_fragment).setPadding(0, 0, 0, 0); + + Log.d(TAG, "onCreateView, feed=" + m_feed); + + 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)) { + if (m_activity.getSupportFragmentManager().findFragmentByTag(CommonActivity.FRAG_ARTICLE) == null) { + 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_activity = (OnlineActivity) activity; + m_listener = (HeadlinesEventListener) activity; + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)av; + + Log.d(TAG, "onItemClick=" + position); + + if (list != null) { + Article article = (Article)list.getItemAtPosition(position); + if (article.id >= 0) { + 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(); + } + } + } + + public void refresh(boolean append) { + refresh(append, false); + } + + @SuppressWarnings({ "serial" }) + public void refresh(boolean append, boolean userInitiated) { + if (m_activity != null && m_feed != null) { + m_refreshInProgress = true; + + m_swipeLayout.setRefreshing(true); + m_activity.setProgressBarVisibility(true); + + if (!m_feed.equals(GlobalState.getInstance().m_activeFeed)) { + append = false; + } + + // new stuff may appear on top, scroll back to show it + if (!append) { + if (getView() != null) { + Log.d(TAG, "scroll hack"); + ListView list = (ListView)getView().findViewById(R.id.headlines); + m_autoCatchupDisabled = true; + list.setSelection(0); + m_autoCatchupDisabled = false; + list.setEmptyView(null); + m_adapter.clear(); + m_adapter.notifyDataSetChanged(); + } + } + + final boolean fappend = append; + final String sessionId = m_activity.getSessionId(); + final boolean isCat = m_feed.is_cat; + + HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity, m_feed) { + @Override + protected void onProgressUpdate(Integer... progress) { + m_activity.setProgress(Math.round((((float)progress[0] / (float)progress[1]) * 10000))); + } + + @Override + protected void onPostExecute(JsonElement result) { + if (isDetached()) return; + + if (getView() != null) { + ListView list = (ListView)getView().findViewById(R.id.headlines); + + if (list != null) { + list.setEmptyView(getView().findViewById(R.id.no_headlines)); + } + } + + m_activity.setProgressBarVisibility(false); + + super.onPostExecute(result); + + if (isAdded()) { + m_swipeLayout.setRefreshing(false); + } + + 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(true); + } else { + m_activity.setLoadingStatus(getErrorMessage(), false); + } + } + } + }; + + int skip = 0; + + if (append) { + // adaptive, all_articles, marked, published, unread + String viewMode = m_activity.getViewMode(); + int numUnread = 0; + int numAll = m_articles.size(); + + for (Article a : m_articles) { + if (a.unread) ++numUnread; + } + + if ("marked".equals(viewMode)) { + skip = numAll; + } else if ("published".equals(viewMode)) { + skip = numAll; + } else if ("unread".equals(viewMode)) { + skip = numUnread; + } else if (m_searchQuery != null && m_searchQuery.length() > 0) { + skip = numAll; + } else if ("adaptive".equals(viewMode)) { + skip = numUnread > 0 ? numUnread : numAll; + } else { + skip = numAll; + } + + } else { + m_activity.setLoadingStatus(R.string.blank, true); + } + + final int fskip = skip; + + final boolean allowForceUpdate = m_activity.getApiLevel() >= 9 && + !m_feed.is_cat && m_feed.id > 0 && !append && userInitiated && + skip == 0; + + Log.d(TAG, "allowForceUpdate=" + allowForceUpdate + " userInitiated=" + userInitiated); + + + req.setOffset(skip); + + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getHeadlines"); + put("sid", sessionId); + put("feed_id", String.valueOf(m_feed.id)); + put("show_content", "true"); + put("include_attachments", "true"); + put("view_mode", m_activity.getViewMode()); + put("limit", String.valueOf(HEADLINES_REQUEST_SIZE)); + put("offset", String.valueOf(0)); + put("skip", String.valueOf(fskip)); + put("include_nested", "true"); + put("order_by", m_prefs.getBoolean("oldest_first", false) ? "date_reverse" : ""); + + if (isCat) put("is_cat", "true"); + + if (allowForceUpdate) { + put("force_update", "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.setClassLoader(getClass().getClassLoader()); + out.putParcelable("feed", m_feed); + //out.putParcelable("articles", m_articles); + out.putParcelable("activeArticle", m_activeArticle); + out.putParcelable("selectedArticles", m_selectedArticles); + out.putCharSequence("searchQuery", m_searchQuery); + } + + /* private void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + if (getActivity() != null) + getActivity().setProgressBarIndeterminateVisibility(showProgress); + } */ + + /* private class HeadlinesRequest extends ApiRequest { + int m_offset = 0; + + public HeadlinesRequest(Context context) { + super(context); + } + + 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 + 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); + + m_canLoadMore = true; + } else { + m_canLoadMore = false; + } + + m_adapter.notifyDataSetChanged(); + + if (m_articles.size() == 0) + setLoadingStatus(R.string.no_headlines_to_display, false); + else + setLoadingStatus(R.string.blank, false); + + m_refreshInProgress = false; + + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (m_lastError == ApiError.LOGIN_FAILED) { + m_activity.login(); + } else { + setLoadingStatus(getErrorMessage(), false); + } + m_refreshInProgress = false; + } + + public void setOffset(int skip) { + m_offset = skip; + } + } */ + + static class HeadlineViewHolder { + public TextView titleView; + public TextView feedTitleView; + public ImageView markedView; + public ImageView publishedView; + public TextView excerptView; + public ImageView flavorImageView; + public TextView authorView; + public TextView dateView; + public CheckBox selectionBoxView; + public ImageView menuButtonView; + public ViewGroup flavorImageHolder; + + } + + private class ArticleListAdapter extends ArrayAdapter<Article> { + private ArrayList<Article> items; + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_UNREAD = 1; + public static final int VIEW_SELECTED = 2; + public static final int VIEW_SELECTED_UNREAD = 3; + public static final int VIEW_LOADMORE = 4; + + public static final int VIEW_COUNT = VIEW_LOADMORE+1; + + private final Integer[] origTitleColors = new Integer[VIEW_COUNT]; + private final int titleHighScoreUnreadColor; + + public ArticleListAdapter(Context context, int textViewResourceId, ArrayList<Article> items) { + super(context, textViewResourceId, items); + this.items = items; + + Theme theme = context.getTheme(); + TypedValue tv = new TypedValue(); + theme.resolveAttribute(R.attr.headlineTitleHighScoreUnreadTextColor, tv, true); + titleHighScoreUnreadColor = tv.data; + } + + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + Article a = items.get(position); + + if (a.id == -1) { + return VIEW_LOADMORE; + } else if (m_activeArticle != null && a.id == m_activeArticle.id && a.unread) { + return VIEW_SELECTED_UNREAD; + } else if (m_activeArticle != null && a.id == m_activeArticle.id) { + return VIEW_SELECTED; + } else if (a.unread) { + return VIEW_UNREAD; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + View v = convertView; + + final Article article = items.get(position); + HeadlineViewHolder holder; + + int headlineFontSize = Integer.parseInt(m_prefs.getString("headlines_font_size_sp", "13")); + int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2)); + + if (v == null) { + int layoutId = R.layout.headlines_row; + + switch (getItemViewType(position)) { + case VIEW_LOADMORE: + layoutId = R.layout.headlines_row_loadmore; + break; + case VIEW_UNREAD: + layoutId = R.layout.headlines_row_unread; + break; + case VIEW_SELECTED: + layoutId = R.layout.headlines_row_selected; + break; + case VIEW_SELECTED_UNREAD: + layoutId = R.layout.headlines_row_selected_unread; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + holder = new HeadlineViewHolder(); + holder.titleView = (TextView)v.findViewById(R.id.title); + + holder.feedTitleView = (TextView)v.findViewById(R.id.feed_title); + holder.markedView = (ImageView)v.findViewById(R.id.marked); + holder.publishedView = (ImageView)v.findViewById(R.id.published); + holder.excerptView = (TextView)v.findViewById(R.id.excerpt); + holder.flavorImageView = (ImageView) v.findViewById(R.id.flavor_image); + holder.authorView = (TextView)v.findViewById(R.id.author); + holder.dateView = (TextView) v.findViewById(R.id.date); + holder.selectionBoxView = (CheckBox) v.findViewById(R.id.selected); + holder.menuButtonView = (ImageView) v.findViewById(R.id.article_menu_button); + holder.flavorImageHolder = (ViewGroup) v.findViewById(R.id.flavorImageHolder); + + v.setTag(holder); + + // http://code.google.com/p/android/issues/detail?id=3414 + ((ViewGroup)v).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + } else { + holder = (HeadlineViewHolder) v.getTag(); + } + + if (holder.titleView != null) { + holder.titleView.setText(Html.fromHtml(article.title)); + + if (m_prefs.getBoolean("enable_condensed_fonts", false)) { + Typeface tf = TypefaceCache.get(m_activity, "sans-serif-condensed", article.unread ? Typeface.BOLD : Typeface.NORMAL); + + if (tf != null && !tf.equals(holder.titleView.getTypeface())) { + holder.titleView.setTypeface(tf); + } + + holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 5)); + } else { + holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 3)); + } + + adjustTitleTextView(article.score, holder.titleView, position); + } + + + + if (holder.feedTitleView != 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 */ + + holder.feedTitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + holder.feedTitleView.setText(article.feed_title); + + } else { + holder.feedTitleView.setVisibility(View.GONE); + } + + } + + + + if (holder.markedView != null) { + holder.markedView.setImageResource(article.marked ? R.drawable.ic_star_full : R.drawable.ic_star_empty); + + holder.markedView.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + article.marked = !article.marked; + m_adapter.notifyDataSetChanged(); + + m_activity.saveArticleMarked(article); + } + }); + } + + + + if (holder.publishedView != null) { + holder.publishedView.setImageResource(article.published ? R.drawable.ic_published : R.drawable.ic_unpublished); + + holder.publishedView.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + article.published = !article.published; + m_adapter.notifyDataSetChanged(); + + m_activity.saveArticlePublished(article); + } + }); + } + + String articleContent = article.content != null ? article.content : ""; + + if (holder.excerptView != null) { + if (!m_prefs.getBoolean("headlines_show_content", true)) { + holder.excerptView.setVisibility(View.GONE); + } else { + String excerpt = Jsoup.parse(articleContent).text(); + + if (excerpt.length() > CommonActivity.EXCERPT_MAX_SIZE) + excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_SIZE) + "..."; + + holder.excerptView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineFontSize); + holder.excerptView.setText(excerpt); + } + } + + + + if (holder.flavorImageView != null && m_prefs.getBoolean("headlines_show_flavor_image", true)) { + holder.flavorImageView.setVisibility(View.GONE); + + Document doc = Jsoup.parse(articleContent); + + Element img = doc.select("img").first(); + if (doc != null) { + + if (img != null) { + String imgSrc = img.attr("src"); + + // retarded schema-less urls + if (imgSrc.indexOf("//") == 0) + imgSrc = "http:" + imgSrc; + + DisplayImageOptions options = new DisplayImageOptions.Builder(). + cacheInMemory(true). + cacheOnDisk(true). + build(); + + final ImageView flavorImageView = holder.flavorImageView; + + ImageLoader.getInstance().displayImage(imgSrc, holder.flavorImageView, options, new ImageLoadingListener() { + + @Override + public void onLoadingCancelled(String arg0, + View arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void onLoadingComplete(String arg0, + View arg1, Bitmap arg2) { + // TODO Auto-generated method stub + + if (!isAdded()) return; + + if (arg2.getWidth() > 128 && arg2.getHeight() > 128) { + if (arg0 != null && !arg0.equals(arg1.getTag())) { + if (!m_activity.isCompatMode() && flavorImageView.getVisibility() != View.VISIBLE) { + ObjectAnimator anim = ObjectAnimator.ofFloat(flavorImageView, "alpha", 0f, 1f); + anim.setDuration(500); + anim.start(); + } + } + + flavorImageView.setTag(arg0); + flavorImageView.setVisibility(View.VISIBLE); + } + } + + @Override + public void onLoadingFailed(String arg0, + View arg1, FailReason arg2) { + // TODO Auto-generated method stub + } + + @Override + public void onLoadingStarted(String arg0, + View arg1) { + // TODO Auto-generated method stub + + } + + }); + } + + } + } else if (holder.flavorImageHolder != null) { + holder.flavorImageHolder.setVisibility(View.GONE); + } + + String articleAuthor = article.author != null ? article.author : ""; + + + if (holder.authorView != null) { + holder.authorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + + if (articleAuthor.length() > 0) { + holder.authorView.setText(getString(R.string.author_formatted, articleAuthor)); + } else { + holder.authorView.setText(""); + } + } + + if (holder.dateView != null) { + holder.dateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + + Date d = new Date((long)article.updated * 1000); + DateFormat df = new SimpleDateFormat("MMM dd, HH:mm"); + df.setTimeZone(TimeZone.getDefault()); + holder.dateView.setText(df.format(d)); + } + + + if (holder.selectionBoxView != null) { + holder.selectionBoxView.setChecked(m_selectedArticles.contains(article)); + holder.selectionBoxView.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View view) { + CheckBox cb = (CheckBox)view; + + if (cb.isChecked()) { + if (!m_selectedArticles.contains(article)) + m_selectedArticles.add(article); + } else { + m_selectedArticles.remove(article); + } + + m_listener.onArticleListSelectionChange(m_selectedArticles); + + Log.d(TAG, "num selected: " + m_selectedArticles.size()); + } + }); + } + + if (holder.menuButtonView != null) { + //if (m_activity.isDarkTheme()) + // ib.setImageResource(R.drawable.ic_mailbox_collapsed_holo_dark); + + holder.menuButtonView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + return v; + } + + private void adjustTitleTextView(int score, TextView tv, int position) { + int viewType = getItemViewType(position); + if (origTitleColors[viewType] == null) + // store original color + origTitleColors[viewType] = Integer.valueOf(tv.getCurrentTextColor()); + + if (score < -500) { + tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else if (score > 500) { + tv.setTextColor(titleHighScoreUnreadColor); + tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); + } else { + tv.setTextColor(origTitleColors[viewType].intValue()); + tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); + } + } + } + + + public void notifyUpdated() { + m_adapter.notifyDataSetChanged(); + } + + public ArticleList getAllArticles() { + return m_articles; + } + + public void setActiveArticle(Article article) { + if (article != m_activeArticle) { + m_activeArticle = article; + m_adapter.notifyDataSetChanged(); + + ListView list = (ListView)getView().findViewById(R.id.headlines); + + if (list != null && article != null) { + int position = m_adapter.getPosition(article); + list.setSelection(position); + } + } + } + + public void setSelection(ArticlesSelection select) { + m_selectedArticles.clear(); + + if (select != ArticlesSelection.NONE) { + for (Article a : m_articles) { + if (select == ArticlesSelection.ALL || select == ArticlesSelection.UNREAD && a.unread) { + m_selectedArticles.add(a); + } + } + } + + m_adapter.notifyDataSetChanged(); + } + + public Article getArticleAtPosition(int position) { + try { + return m_adapter.getItem(position); + } catch (IndexOutOfBoundsException e) { + return null; + } catch (NullPointerException e) { + return null; + } + } + + public Article getArticleById(int id) { + for (Article a : m_articles) { + if (a.id == id) + return a; + } + return null; + } + + public ArticleList getUnreadArticles() { + ArticleList tmp = new ArticleList(); + for (Article a : m_articles) { + if (a.unread) tmp.add(a); + } + return tmp; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (!m_refreshInProgress && m_articles.findById(-1) != null && firstVisibleItem + visibleItemCount == m_articles.size()) { + refresh(true); + } + + if (m_prefs.getBoolean("headlines_mark_read_scroll", false) && firstVisibleItem > 0 && !m_autoCatchupDisabled) { + Article a = m_articles.get(firstVisibleItem - 1); + + if (a != null && a.unread) { + a.unread = false; + m_readArticles.add(a); + m_feed.unread--; + } + } + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (scrollState == SCROLL_STATE_IDLE && m_prefs.getBoolean("headlines_mark_read_scroll", false)) { + if (!m_readArticles.isEmpty()) { + m_activity.toggleArticlesUnread(m_readArticles); + m_activity.refresh(false); + m_readArticles.clear(); + } + } + } + + public Article getActiveArticle() { + return m_activeArticle; + } + + public int getArticlePosition(Article article) { + try { + return m_adapter.getPosition(article); + } catch (NullPointerException e) { + return -1; + } + } + + public String getSearchQuery() { + return m_searchQuery; + } + + public void setSearchQuery(String query) { + if (!m_searchQuery.equals(query)) { + m_searchQuery = query; + refresh(false); + } + } + + public Feed getFeed() { + return m_feed; + } + + /* class DownloadFlavorImagesTask extends AsyncTask<ImageView, Void, Bitmap> { + ImageView imageView = null; + @Override + protected Bitmap doInBackground(ImageView... imageViews) { + this.imageView = imageViews[0]; + return download((URL)imageView.getTag()); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null) { + imageView.setImageBitmap(result); + imageView.setVisibility(View.VISIBLE); + } + } + + private Bitmap download(URL url) { + try { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setDoInput(true); + conn.setUseCaches(true); + conn.connect(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] buf = new byte[256]; + int read = 0; + + while ((read = conn.getInputStream().read(buf)) >= 0) { + bos.write(buf, 0, read); + } + + final BitmapFactory.Options options = new BitmapFactory.Options(); + + byte[] bitmap = bos.toByteArray(); + + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length, options); + options.inJustDecodeBounds = false; + + int inSampleSize = CommonActivity.calculateInSampleSize(options, 128, 128); + + Bitmap decodedBitmap = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length, options); + + return decodedBitmap; + } catch (OutOfMemoryError e) { + Log.d(TAG, "OOM while trying to decode headline flavor image. :("); + e.printStackTrace(); + } catch (IOException e) { + // + } + + return null; + } + } */ +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/LoadingFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/LoadingFragment.java new file mode 100644 index 00000000..f0802b0a --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/LoadingFragment.java @@ -0,0 +1,18 @@ +package org.fox.ttrss; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class LoadingFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.loading_fragment, container, false); + + return view; + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/OnlineActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/OnlineActivity.java new file mode 100644 index 00000000..e33a02b7 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/OnlineActivity.java @@ -0,0 +1,1765 @@ +package org.fox.ttrss; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; + +import org.fox.ttrss.offline.OfflineActivity; +import org.fox.ttrss.offline.OfflineDownloadService; +import org.fox.ttrss.offline.OfflineUploadService; +import org.fox.ttrss.share.SubscribeActivity; +import org.fox.ttrss.types.Article; +import org.fox.ttrss.types.ArticleList; +import org.fox.ttrss.types.Feed; +import org.fox.ttrss.types.Label; +import org.fox.ttrss.widget.SmallWidgetProvider; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnMultiChoiceClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v7.view.ActionMode; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.EditText; +import android.widget.SearchView; +import android.widget.TextView; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; + +public class OnlineActivity extends CommonActivity { + private final String TAG = this.getClass().getSimpleName(); + + private final static int TRIAL_DAYS = 8; + + protected SharedPreferences m_prefs; + protected Menu m_menu; + + protected int m_offlineModeStatus = 0; + + private ActionMode m_headlinesActionMode; + private HeadlinesActionModeCallback m_headlinesActionModeCallback; + + private String m_lastImageHitTestUrl; + + //protected PullToRefreshAttacher m_pullToRefreshAttacher; + + protected abstract class OnLoginFinishedListener { + public abstract void OnLoginSuccess(); + public abstract void OnLoginFailed(); + }; + + private BroadcastReceiver m_broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context content, Intent intent) { + + if (intent.getAction().equals(OfflineDownloadService.INTENT_ACTION_SUCCESS)) { + + m_offlineModeStatus = 2; + + switchOffline(); + + } else if (intent.getAction().equals(OfflineUploadService.INTENT_ACTION_SUCCESS)) { + Log.d(TAG, "offline upload service reports success"); + toast(R.string.offline_sync_success); + } + } + }; + + + @TargetApi(11) + private class HeadlinesActionModeCallback implements ActionMode.Callback { + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + m_headlinesActionMode = null; + + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + ArticleList selected = hf.getSelectedArticles(); + if (selected.size() > 0) { + selected.clear(); + initMenu(); + hf.notifyUpdated(); + } + } + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.headlines_action_menu, menu); + + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + onOptionsItemSelected(item); + return false; + } + }; + + protected String getSessionId() { + return GlobalState.getInstance().m_sessionId; + } + + protected void setSessionId(String sessionId) { + GlobalState.getInstance().m_sessionId = sessionId; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + ApiRequest.disableConnectionReuseIfNecessary(); + + // we use that before parent onCreate so let's init locally + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + if (canUseProgress()) { + requestWindowFeature(Window.FEATURE_PROGRESS); + } + + //requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + + setProgressBarVisibility(false); + setProgressBarIndeterminateVisibility(false); + +// SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + + boolean isOffline = localPrefs.getBoolean("offline_mode_active", false); + + Log.d(TAG, "m_isOffline=" + isOffline); + + setContentView(R.layout.login); + + setStatusBarTint(); + + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()).build(); + ImageLoader.getInstance().init(config); + ImageLoader.getInstance().clearDiskCache(); + + //m_pullToRefreshAttacher = PullToRefreshAttacher.get(this); + + if (isOffline) { + switchOfflineSuccess(); + } else { + checkTrial(false); + + /* if (getIntent().getExtras() != null) { + Intent i = getIntent(); + } */ + + if (savedInstanceState != null) { + m_offlineModeStatus = savedInstanceState.getInt("offlineModeStatus"); + } + + m_headlinesActionModeCallback = new HeadlinesActionModeCallback(); + } + } + + protected boolean canUseProgress() { + return GlobalState.getInstance().m_canUseProgress; + } + + private void switchOffline() { + if (m_offlineModeStatus == 2) { + + AlertDialog.Builder builder = new AlertDialog.Builder( + OnlineActivity.this) + .setMessage(R.string.dialog_offline_success) + .setPositiveButton(R.string.dialog_offline_go, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + m_offlineModeStatus = 0; + + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = localPrefs.edit(); + editor.putBoolean("offline_mode_active", true); + editor.commit(); + + Intent offline = new Intent( + OnlineActivity.this, + OfflineActivity.class); + offline.putExtra("initial", true); + startActivity(offline); + finish(); + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + m_offlineModeStatus = 0; + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + + } else if (m_offlineModeStatus == 0) { + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setMessage(R.string.dialog_offline_switch_prompt) + .setPositiveButton(R.string.dialog_offline_go, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + if (getSessionId() != null) { + Log.d(TAG, "offline: starting"); + + m_offlineModeStatus = 1; + + Intent intent = new Intent( + OnlineActivity.this, + OfflineDownloadService.class); + intent.putExtra("sessionId", getSessionId()); + + startService(intent); + } + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + // + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } else if (m_offlineModeStatus == 1) { + cancelOfflineSync(); + } + } + + private boolean hasPendingOfflineData() { + try { + Cursor c = getReadableDb().query("articles", + new String[] { "COUNT(*)" }, "modified = 1", null, null, null, + null); + if (c.moveToFirst()) { + int modified = c.getInt(0); + c.close(); + + return modified > 0; + } + } catch (IllegalStateException e) { + // db is closed? ugh + } + + return false; + } + + private boolean hasOfflineData() { + try { + Cursor c = getReadableDb().query("articles", + new String[] { "COUNT(*)" }, null, null, null, null, null); + if (c.moveToFirst()) { + int modified = c.getInt(0); + c.close(); + + return modified > 0; + } + } catch (IllegalStateException e) { + // db is closed? + } + + return false; + } + + @Override + public void onStop() { + super.onStop(); + + Intent initialUpdateIntent = new Intent(SmallWidgetProvider.FORCE_UPDATE_ACTION); + sendBroadcast(initialUpdateIntent); + } + + @Override + public void onPause() { + super.onPause(); + + unregisterReceiver(m_broadcastReceiver); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + private void syncOfflineData() { + Log.d(TAG, "offlineSync: starting"); + + Intent intent = new Intent( + OnlineActivity.this, + OfflineUploadService.class); + + intent.putExtra("sessionId", getSessionId()); + + startService(intent); + } + + private void cancelOfflineSync() { + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setMessage(R.string.dialog_offline_sync_in_progress) + .setNegativeButton(R.string.dialog_offline_sync_stop, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + if (getSessionId() != null) { + Log.d(TAG, "offline: stopping"); + + m_offlineModeStatus = 0; + + Intent intent = new Intent( + OnlineActivity.this, + OfflineDownloadService.class); + + stopService(intent); + + dialog.dismiss(); + + restart(); + } + } + }) + .setPositiveButton(R.string.dialog_offline_sync_continue, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + dialog.dismiss(); + + restart(); + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } + + public void restart() { + Intent refresh = new Intent(OnlineActivity.this, OnlineActivity.class); + startActivity(refresh); + finish(); + } + + private void switchOfflineSuccess() { + logout(); + // setLoadingStatus(R.string.blank, false); + + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("offline_mode_active", true); + editor.commit(); + + Intent offline = new Intent(OnlineActivity.this, OfflineActivity.class); + offline.putExtra("initial", true); + offline.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + + startActivityForResult(offline, 0); + + finish(); + + } + + public void login() { + login(false, null); + } + + public void login(boolean refresh) { + login(refresh, null); + } + + public void login(boolean refresh, OnLoginFinishedListener listener) { + if (m_prefs.getString("ttrss_url", "").trim().length() == 0) { + + setLoadingStatus(R.string.login_need_configure, false); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.dialog_need_configure_prompt) + .setCancelable(false) + .setPositiveButton(R.string.dialog_open_preferences, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // launch preferences + + Intent intent = new Intent(OnlineActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } else { + setLoadingStatus(R.string.login_in_progress, true); + + LoginRequest ar = new LoginRequest(getApplicationContext(), refresh, listener); + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("op", "login"); + put("user", m_prefs.getString("login", "").trim()); + put("password", m_prefs.getString("password", "").trim()); + } + }; + + ar.execute(map); + + setLoadingStatus(R.string.login_in_progress, true); + } + } + + protected void loginSuccess(boolean refresh) { + setLoadingStatus(R.string.blank, false); + + initMenu(); + + Intent intent = new Intent(OnlineActivity.this, FeedsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + + startActivityForResult(intent, 0); + overridePendingTransition(0, 0); + + if (hasPendingOfflineData()) + syncOfflineData(); + + finish(); + } + + public void checkTrial(boolean notify) { + boolean isTrial = getPackageManager().checkSignatures( + getPackageName(), "org.fox.ttrss.key") != PackageManager.SIGNATURE_MATCH; + + if (isTrial) { + long firstStart = m_prefs.getLong("date_firstlaunch_trial", -1); + + if (firstStart == -1) { + firstStart = System.currentTimeMillis(); + + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putLong("date_firstlaunch_trial", firstStart); + editor.commit(); + } + + if (!notify && System.currentTimeMillis() > firstStart + (TRIAL_DAYS * 24 * 60 * 60 * 1000)) { + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.trial_expired) + .setMessage(R.string.trial_expired_message) + .setCancelable(false) + .setPositiveButton(getString(R.string.trial_purchase), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + + openUnlockUrl(); + finish(); + + } + }) + .setNegativeButton(getString(R.string.cancel), + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + + finish(); + + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + + } else { + long daysLeft = Math.round((firstStart + (TRIAL_DAYS * 24 * 60 * 60 * 1000) - System.currentTimeMillis()) / (24 * 60 * 60 * 1000)); + + if (notify) { + toast(getString(R.string.trial_mode_prompt, Long.valueOf(daysLeft))); + } + } + } else if (notify) { + //toast(R.string.trial_thanks); + } + } + + private void openUnlockUrl() { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("market://details?id=org.fox.ttrss.key")); + startActivity(intent); + } catch (ActivityNotFoundException ae) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse("https://play.google.com/store/apps/details?id=org.fox.ttrss.key")); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + toast(R.string.error_other_error); + } + } + } + + @Override + public boolean onContextItemSelected(android.view.MenuItem item) { + /* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + .getMenuInfo(); */ + + final ArticlePager ap = (ArticlePager)getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + switch (item.getItemId()) { + case R.id.article_img_open: + if (getLastContentImageHitTestUrl() != null) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse(getLastContentImageHitTestUrl())); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + toast(R.string.error_other_error); + } + } + return true; + case R.id.article_img_copy: + if (getLastContentImageHitTestUrl() != null) { + copyToClipboard(getLastContentImageHitTestUrl()); + } + return true; + case R.id.article_img_share: + if (getLastContentImageHitTestUrl() != null) { + Intent intent = new Intent(Intent.ACTION_SEND); + + intent.setType("image/png"); + intent.putExtra(Intent.EXTRA_SUBJECT, getLastContentImageHitTestUrl()); + intent.putExtra(Intent.EXTRA_TEXT, getLastContentImageHitTestUrl()); + + startActivity(Intent.createChooser(intent, getLastContentImageHitTestUrl())); + } + return true; + case R.id.article_img_view_caption: + if (getLastContentImageHitTestUrl() != null) { + + // Android doesn't give us an easy way to access title tags; + // we'll use Jsoup on the body text to grab the title text + // from the first image tag with this url. This will show + // the wrong text if an image is used multiple times. + Document doc = Jsoup.parse(ap.getSelectedArticle().content); + Elements es = doc.getElementsByAttributeValue("src", getLastContentImageHitTestUrl()); + if (es.size() > 0){ + if (es.get(0).hasAttr("title")){ + Dialog dia = new Dialog(this); + if (es.get(0).hasAttr("alt")){ + dia.setTitle(es.get(0).attr("alt")); + } else { + dia.setTitle(es.get(0).attr("title")); + } + TextView titleText = new TextView(this); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + titleText.setPaddingRelative(24, 24, 24, 24); + } else { + titleText.setPadding(24, 24, 24, 24); + } + + titleText.setTextSize(16); + titleText.setText(es.get(0).attr("title")); + dia.setContentView(titleText); + dia.show(); + } else { + toast(R.string.no_caption_to_display); + } + } else { + toast(R.string.no_caption_to_display); + } + } + return true; + case R.id.article_link_share: + if (ap != null && ap.getSelectedArticle() != null) { + shareArticle(ap.getSelectedArticle()); + } + return true; + case R.id.article_link_copy: + Log.d(TAG, "article_link_copy"); + if (ap != null && ap.getSelectedArticle() != null) { + copyToClipboard(ap.getSelectedArticle().link); + } + return true; + default: + Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); + return super.onContextItemSelected(item); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + final ArticlePager ap = (ArticlePager)getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + switch (item.getItemId()) { + /* case android.R.id.home: + finish(); + return true; */ + case R.id.headlines_toggle_sidebar: + if (true && !isSmallScreen()) { + View v = findViewById(R.id.headlines_fragment); + + if (v != null) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("headlines_hide_sidebar", !m_prefs.getBoolean("headlines_hide_sidebar", false)); + editor.commit(); + + v.setVisibility(m_prefs.getBoolean("headlines_hide_sidebar", false) ? View.GONE : View.VISIBLE); + } + } + return true; + case R.id.subscribe_to_feed: + Intent subscribe = new Intent(OnlineActivity.this, SubscribeActivity.class); + startActivityForResult(subscribe, 0); + return true; + case R.id.toggle_attachments: + if (true) { + Article article = ap.getSelectedArticle(); + + if (article != null && article.attachments != null && article.attachments.size() > 0) { + CharSequence[] items = new CharSequence[article.attachments.size()]; + final CharSequence[] itemUrls = new CharSequence[article.attachments.size()]; + + for (int i = 0; i < article.attachments.size(); i++) { + items[i] = article.attachments.get(i).title != null ? article.attachments.get(i).content_url : + article.attachments.get(i).content_url; + + itemUrls[i] = article.attachments.get(i).content_url; + } + + Dialog dialog = new Dialog(OnlineActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder(OnlineActivity.this) + .setTitle(R.string.attachments_prompt) + .setCancelable(true) + .setSingleChoiceItems(items, 0, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // + } + }).setNeutralButton(R.string.attachment_copy, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int selectedPosition = ((AlertDialog)dialog).getListView().getCheckedItemPosition(); + + copyToClipboard((String)itemUrls[selectedPosition]); + } + }).setPositiveButton(R.string.attachment_view, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + int selectedPosition = ((AlertDialog)dialog).getListView().getCheckedItemPosition(); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse((String)itemUrls[selectedPosition])); + startActivity(browserIntent); + + dialog.cancel(); + } + }).setNegativeButton(R.string.dialog_cancel, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + dialog = builder.create(); + dialog.show(); + } + } + return true; + case R.id.donate: + if (true) { + openUnlockUrl(); + } + return true; + case R.id.logout: + logout(); + return true; + case R.id.login: + login(); + return true; + case R.id.go_offline: + switchOffline(); + return true; + case R.id.article_set_note: + if (ap != null && ap.getSelectedArticle() != null) { + editArticleNote(ap.getSelectedArticle()); + } + return true; + case R.id.preferences: + Intent intent = new Intent(OnlineActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + return true; + case R.id.search: + if (hf != null && isCompatMode()) { + Dialog dialog = new Dialog(this); + + final EditText edit = new EditText(this); + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.search) + .setPositiveButton(getString(R.string.search), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + + String query = edit.getText().toString().trim(); + + hf.setSearchQuery(query); + + } + }) + .setNegativeButton(getString(R.string.cancel), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + + // + + } + }).setView(edit); + + dialog = builder.create(); + dialog.show(); + } + return true; + case R.id.headlines_mark_as_read: + if (hf != null) { + + int count = hf.getUnreadArticles().size(); + + boolean confirm = m_prefs.getBoolean("confirm_headlines_catchup", true); + + if (count > 0) { + if (confirm) { + AlertDialog.Builder builder = new AlertDialog.Builder( + OnlineActivity.this) + .setMessage(getString(R.string.mark_num_headlines_as_read, count)) + .setPositiveButton(R.string.catchup, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + catchupVisibleArticles(); + + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } else { + catchupVisibleArticles(); + } + } + } + return true; + case R.id.headlines_view_mode: + if (hf != null) { + Dialog dialog = new Dialog(this); + + String viewMode = getViewMode(); + + //Log.d(TAG, "viewMode:" + getViewMode()); + + int selectedIndex = 0; + + if (viewMode.equals("all_articles")) { + selectedIndex = 1; + } else if (viewMode.equals("marked")) { + selectedIndex = 2; + } else if (viewMode.equals("published")) { + selectedIndex = 3; + } else if (viewMode.equals("unread")) { + selectedIndex = 4; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.headlines_set_view_mode) + .setSingleChoiceItems( + new String[] { + getString(R.string.headlines_adaptive), + getString(R.string.headlines_all_articles), + getString(R.string.headlines_starred), + getString(R.string.headlines_published), + getString(R.string.headlines_unread) }, + selectedIndex, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + switch (which) { + case 0: + setViewMode("adaptive"); + break; + case 1: + setViewMode("all_articles"); + break; + case 2: + setViewMode("marked"); + break; + case 3: + setViewMode("published"); + break; + case 4: + setViewMode("unread"); + break; + } + dialog.cancel(); + + refresh(); + } + }); + + dialog = builder.create(); + dialog.show(); + + } + return true; + case R.id.headlines_select: + if (hf != null) { + Dialog dialog = new Dialog(this); + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.headlines_select_dialog) + .setSingleChoiceItems( + new String[] { + getString(R.string.headlines_select_all), + getString(R.string.headlines_select_unread), + getString(R.string.headlines_select_none) }, + 0, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + switch (which) { + case 0: + hf.setSelection(HeadlinesFragment.ArticlesSelection.ALL); + break; + case 1: + hf.setSelection(HeadlinesFragment.ArticlesSelection.UNREAD); + break; + case 2: + hf.setSelection(HeadlinesFragment.ArticlesSelection.NONE); + break; + } + dialog.cancel(); + initMenu(); + } + }); + + dialog = builder.create(); + dialog.show(); + } + return true; + case R.id.share_article: + if (ap != null) { + shareArticle(ap.getSelectedArticle()); + } + return true; + case R.id.toggle_marked: + if (ap != null & ap.getSelectedArticle() != null) { + Article a = ap.getSelectedArticle(); + a.marked = !a.marked; + saveArticleMarked(a); + if (hf != null) hf.notifyUpdated(); + } + return true; + /* case R.id.selection_select_none: + if (hf != null) { + ArticleList selected = hf.getSelectedArticles(); + if (selected.size() > 0) { + selected.clear(); + initMenu(); + hf.notifyUpdated(); + } + } + return true; */ + case R.id.selection_toggle_unread: + if (hf != null) { + ArticleList selected = hf.getSelectedArticles(); + + if (selected.size() > 0) { + for (Article a : selected) + a.unread = !a.unread; + + toggleArticlesUnread(selected); + hf.notifyUpdated(); + initMenu(); + } + } + return true; + case R.id.selection_toggle_marked: + if (hf != null) { + ArticleList selected = hf.getSelectedArticles(); + + if (selected.size() > 0) { + for (Article a : selected) + a.marked = !a.marked; + + toggleArticlesMarked(selected); + hf.notifyUpdated(); + initMenu(); + } + } + return true; + case R.id.selection_toggle_published: + if (hf != null) { + ArticleList selected = hf.getSelectedArticles(); + + if (selected.size() > 0) { + for (Article a : selected) + a.published = !a.published; + + toggleArticlesPublished(selected); + hf.notifyUpdated(); + initMenu(); + } + } + return true; + case R.id.toggle_published: + if (ap != null && ap.getSelectedArticle() != null) { + Article a = ap.getSelectedArticle(); + a.published = !a.published; + saveArticlePublished(a); + if (hf != null) hf.notifyUpdated(); + } + return true; + case R.id.catchup_above: + if (hf != null) { + if (ap != null && ap.getSelectedArticle() != null) { + Article article = ap.getSelectedArticle(); + + ArticleList articles = hf.getAllArticles(); + ArticleList tmp = new ArticleList(); + for (Article a : articles) { + if (article.id == a.id) + break; + + if (a.unread) { + a.unread = false; + tmp.add(a); + } + } + if (tmp.size() > 0) { + toggleArticlesUnread(tmp); + hf.notifyUpdated(); + initMenu(); + } + } + } + return true; + case R.id.set_unread: + if (ap != null && ap.getSelectedArticle() != null) { + Article a = ap.getSelectedArticle(); + + if (a != null) { + a.unread = !a.unread; + saveArticleUnread(a); + } + + if (hf != null) hf.notifyUpdated(); + } + return true; + case R.id.set_labels: + if (ap != null && ap.getSelectedArticle() != null) { + if (getApiLevel() != 7) { + editArticleLabels(ap.getSelectedArticle()); + } else { + toast(R.string.server_function_not_available); + } + + } + return true; + case R.id.update_headlines: + if (hf != null) { + //m_pullToRefreshAttacher.setRefreshing(true); + hf.refresh(false, true); + } + return true; + default: + Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + protected void catchupVisibleArticles() { + final HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + ArticleList articles = hf.getUnreadArticles(); + + for (Article a : articles) + a.unread = false; + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + if (hf.isAdded()) { + hf.refresh(false); + } + } + }; + + final String articleIds = articlesToIdString(articles); + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", articleIds); + put("mode", "0"); + put("field", "2"); + } + }; + req.execute(map); + } + } + + public void editArticleNote(final Article article) { + String note = ""; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(article.title); + final EditText topicEdit = new EditText(this); + topicEdit.setText(note); + builder.setView(topicEdit); + + builder.setPositiveButton(R.string.article_set_note, new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String note = topicEdit.getText().toString().trim(); + + saveArticleNote(article, note); + article.published = true; + article.note = note; + + saveArticlePublished(article); + + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + if (hf != null) hf.notifyUpdated(); + } + }); + + builder.setNegativeButton(R.string.dialog_cancel, new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + public void editArticleLabels(Article article) { + final int articleId = article.id; + + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + Type listType = new TypeToken<List<Label>>() {}.getType(); + final List<Label> labels = new Gson().fromJson(result, listType); + + CharSequence[] items = new CharSequence[labels.size()]; + final int[] itemIds = new int[labels.size()]; + boolean[] checkedItems = new boolean[labels.size()]; + + for (int i = 0; i < labels.size(); i++) { + items[i] = labels.get(i).caption; + itemIds[i] = labels.get(i).id; + checkedItems[i] = labels.get(i).checked; + } + + Dialog dialog = new Dialog(OnlineActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder(OnlineActivity.this) + .setTitle(R.string.article_set_labels) + .setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which, final boolean isChecked) { + final int labelId = itemIds[which]; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "setArticleLabel"); + put("label_id", String.valueOf(labelId)); + put("article_ids", String.valueOf(articleId)); + if (isChecked) put("assign", "true"); + } + }; + + ApiRequest req = new ApiRequest(m_context); + req.execute(map); + + } + }).setPositiveButton(R.string.dialog_close, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + dialog = builder.create(); + dialog.show(); + + } + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "getLabels"); + put("article_id", String.valueOf(articleId)); + } + }; + + req.execute(map); + } + + protected void logout() { + setSessionId(null); + + findViewById(R.id.loading_container).setVisibility(View.VISIBLE); + setLoadingStatus(R.string.login_ready, false); + + initMenu(); + } + + protected void loginFailure() { + setSessionId(null); + initMenu(); + + if (hasOfflineData()) { + + AlertDialog.Builder builder = new AlertDialog.Builder( + OnlineActivity.this) + .setMessage(R.string.dialog_offline_prompt) + .setPositiveButton(R.string.dialog_offline_go, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + switchOfflineSuccess(); + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + // + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + } + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("offlineModeStatus", m_offlineModeStatus); + } + + @Override + public void onResume() { + super.onResume(); + + ApiRequest.trustAllHosts(m_prefs.getBoolean("ssl_trust_any", false), + m_prefs.getBoolean("ssl_trust_any_host", false)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(OfflineDownloadService.INTENT_ACTION_SUCCESS); + filter.addAction(OfflineUploadService.INTENT_ACTION_SUCCESS); + filter.addCategory(Intent.CATEGORY_DEFAULT); + + registerReceiver(m_broadcastReceiver, filter); + + if (getSessionId() == null) { + login(); + } else { + loginSuccess(false); + } + } + + public Menu getMenu() { + return m_menu; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + + m_menu = menu; + + initMenu(); + + List<PackageInfo> pkgs = getPackageManager() + .getInstalledPackages(0); + + for (PackageInfo p : pkgs) { + if ("org.fox.ttrss.key".equals(p.packageName)) { + Log.d(TAG, "license apk found"); + menu.findItem(R.id.donate).setVisible(false); + break; + } + } + + return true; + } + + protected int getApiLevel() { + return GlobalState.getInstance().m_apiLevel; + } + + protected void setApiLevel(int apiLevel) { + GlobalState.getInstance().m_apiLevel = apiLevel; + } + + @SuppressWarnings({ "unchecked", "serial" }) + public void saveArticleUnread(final Article article) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + //toast(R.string.article_set_unread); + initMenu(); + } + }; + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", String.valueOf(article.id)); + put("mode", article.unread ? "1" : "0"); + put("field", "2"); + } + }; + + req.execute(map); + } + + public void saveArticleMarked(final Article article) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + //toast(article.marked ? R.string.notify_article_marked : R.string.notify_article_unmarked); + initMenu(); + } + }; + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", String.valueOf(article.id)); + put("mode", article.marked ? "1" : "0"); + put("field", "0"); + } + }; + + req.execute(map); + } + + @SuppressWarnings({ "unchecked", "serial" }) + public void saveArticlePublished(final Article article) { + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + //toast(article.published ? R.string.notify_article_published : R.string.notify_article_unpublished); + initMenu(); + } + }; + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", String.valueOf(article.id)); + put("mode", article.published ? "1" : "0"); + put("field", "1"); + } + }; + + req.execute(map); + } + + @SuppressWarnings({ "unchecked", "serial" }) + public void saveArticleNote(final Article article, final String note) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + toast(R.string.notify_article_note_set); + } + }; + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", String.valueOf(article.id)); + put("mode", "1"); + put("data", note); + put("field", "3"); + } + }; + + req.execute(map); + } + + public static String articlesToIdString(ArticleList articles) { + String tmp = ""; + + for (Article a : articles) + tmp += String.valueOf(a.id) + ","; + + return tmp.replaceAll(",$", ""); + } + + public void shareText(String text) { + + Intent intent = new Intent(Intent.ACTION_SEND); + + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, text); + + startActivity(Intent.createChooser(intent, text)); + } + + public void shareArticle(Article article) { + if (article != null) { + + Intent intent = getShareIntent(article); + + startActivity(Intent.createChooser(intent, + getString(R.string.share_article))); + } + } + + protected Intent getShareIntent(Article article) { + Intent intent = new Intent(Intent.ACTION_SEND); + + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, article.title); + intent.putExtra(Intent.EXTRA_TEXT, article.link); + + return intent; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (m_prefs.getBoolean("use_volume_keys", false)) { + ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (ap != null && ap.isAdded()) { + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + ap.selectArticle(false); + return true; + case KeyEvent.KEYCODE_VOLUME_DOWN: + ap.selectArticle(true); + return true; + } + } + } + + return super.onKeyDown(keyCode, event); + } + + // Handle onKeyUp too to suppress beep + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (m_prefs.getBoolean("use_volume_keys", false)) { + + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + return true; + } + } + + return super.onKeyUp(keyCode, event); + } + + public void catchupFeed(final Feed feed) { + Log.d(TAG, "catchupFeed=" + feed); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + // refresh? + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "catchupFeed"); + put("feed_id", String.valueOf(feed.id)); + if (feed.is_cat) + put("is_cat", "1"); + } + }; + + req.execute(map); + } + + public void toggleArticlesMarked(final ArticleList articles) { + ApiRequest req = new ApiRequest(getApplicationContext()); + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", articlesToIdString(articles)); + put("mode", "2"); + put("field", "0"); + } + }; + + req.execute(map); + } + + public void toggleArticlesUnread(final ArticleList articles) { + ApiRequest req = new ApiRequest(getApplicationContext()); + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", articlesToIdString(articles)); + put("mode", "2"); + put("field", "2"); + } + }; + + req.execute(map); + //refresh(); + } + + public void toggleArticlesPublished(final ArticleList articles) { + ApiRequest req = new ApiRequest(getApplicationContext()); + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "updateArticle"); + put("article_ids", articlesToIdString(articles)); + put("mode", "2"); + put("field", "1"); + } + }; + + req.execute(map); + } + + + protected void initMenu() { + if (m_menu != null) { + if (getSessionId() != null) { + m_menu.setGroupVisible(R.id.menu_group_logged_in, true); + m_menu.setGroupVisible(R.id.menu_group_logged_out, false); + } else { + m_menu.setGroupVisible(R.id.menu_group_logged_in, false); + m_menu.setGroupVisible(R.id.menu_group_logged_out, true); + } + + m_menu.setGroupVisible(R.id.menu_group_headlines, false); + m_menu.setGroupVisible(R.id.menu_group_article, false); + m_menu.setGroupVisible(R.id.menu_group_feeds, false); + + m_menu.findItem(R.id.set_labels).setEnabled(getApiLevel() >= 1); + m_menu.findItem(R.id.article_set_note).setEnabled(getApiLevel() >= 1); + m_menu.findItem(R.id.subscribe_to_feed).setEnabled(getApiLevel() >= 5); + + MenuItem search = m_menu.findItem(R.id.search); + search.setEnabled(getApiLevel() >= 2); + + ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (ap != null) { + Article article = ap.getSelectedArticle(); + + if (article != null) { + m_menu.findItem(R.id.toggle_marked).setIcon(article.marked ? R.drawable.ic_important_light : + R.drawable.ic_unimportant_light); + + m_menu.findItem(R.id.toggle_published).setIcon(article.published ? R.drawable.ic_menu_published_light : + R.drawable.ic_menu_unpublished_light); + + m_menu.findItem(R.id.set_unread).setIcon(article.unread ? R.drawable.ic_unread_light : + R.drawable.ic_read_light); + } + } + + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + if (hf.getSelectedArticles().size() > 0 && m_headlinesActionMode == null) { + m_headlinesActionMode = startSupportActionMode(m_headlinesActionModeCallback); + } else if (hf.getSelectedArticles().size() == 0 && m_headlinesActionMode != null) { + m_headlinesActionMode.finish(); + } + } + + if (!isCompatMode()) { + SearchView searchView = (SearchView) search.getActionView(); + + if (searchView != null) { + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + private String query = ""; + + @Override + public boolean onQueryTextSubmit(String query) { + HeadlinesFragment frag = (HeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + if (frag != null) { + frag.setSearchQuery(query); + this.query = query; + } + + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (newText.equals("") && !newText.equals(this.query)) { + HeadlinesFragment frag = (HeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + if (frag != null) { + frag.setSearchQuery(newText); + this.query = newText; + } + } + + return false; + } + }); + } + } + } + } + + protected void refresh(boolean includeHeadlines) { + FeedCategoriesFragment cf = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + + if (cf != null) { + cf.refresh(false); + } + + FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); + + if (ff != null) { + ff.refresh(false); + } + + if (includeHeadlines) { + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + hf.refresh(false); + } + + ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (af != null) { + af.refresh(false); + } + } + } + + protected void refresh() { + refresh(true); + } + + protected class LoginRequest extends ApiRequest { + boolean m_refreshAfterLogin = false; + OnLoginFinishedListener m_listener; + + public LoginRequest(Context context, boolean refresh, OnLoginFinishedListener listener) { + super(context); + m_refreshAfterLogin = refresh; + m_listener = listener; + } + + @SuppressWarnings("unchecked") + protected void onPostExecute(JsonElement result) { + if (result != null) { + try { + JsonObject content = result.getAsJsonObject(); + + if (content != null) { + setSessionId(content.get("session_id").getAsString()); + + JsonElement apiLevel = content.get("api_level"); + + GlobalState.getInstance().m_canUseProgress = m_canUseProgress; + + Log.d(TAG, "Authenticated! canUseProgress=" + m_canUseProgress); + + if (apiLevel != null) { + setApiLevel(apiLevel.getAsInt()); + Log.d(TAG, "Received API level: " + getApiLevel()); + + if (m_listener != null) { + m_listener.OnLoginSuccess(); + } else { + loginSuccess(m_refreshAfterLogin); + } + + } else { + + ApiRequest req = new ApiRequest(m_context) { + protected void onPostExecute(JsonElement result) { + setApiLevel(0); + + if (result != null) { + try { + setApiLevel(result.getAsJsonObject().get("level").getAsInt()); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (m_lastError != ApiError.API_UNKNOWN_METHOD) { + // Unknown method means old tt-rss, in that case we assume API 0 and continue + + setLoadingStatus(getErrorMessage(), false); + + if (m_listener != null) { + m_listener.OnLoginFailed(); + } else { + loginFailure(); + } + + return; + } + + Log.d(TAG, "Received API level: " + getApiLevel()); + + loginSuccess(m_refreshAfterLogin); + + return; + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", getSessionId()); + put("op", "getApiLevel"); + } + }; + + req.execute(map); + + setLoadingStatus(R.string.loading_message, true); + } + + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + setSessionId(null); + setLoadingStatus(getErrorMessage(), false); + + loginFailure(); + } + + } + + public void setViewMode(String viewMode) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putString("view_mode", viewMode); + editor.commit(); + } + + public String getViewMode() { + return m_prefs.getString("view_mode", "adaptive"); + } + + public void setLastContentImageHitTestUrl(String url) { + m_lastImageHitTestUrl = url; + } + + public String getLastContentImageHitTestUrl() { + return m_lastImageHitTestUrl; + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/PreferencesActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/PreferencesActivity.java new file mode 100644 index 00000000..f42154a7 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/PreferencesActivity.java @@ -0,0 +1,25 @@ +package org.fox.ttrss; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + +public class PreferencesActivity extends PreferenceActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.preferences); + + boolean compatMode = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB; + + if (compatMode) { + findPreference("dim_status_bar").setEnabled(false); + findPreference("webview_hardware_accel").setEnabled(false); + } + + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + findPreference("enable_condensed_fonts").setEnabled(false); + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineActivity.java new file mode 100644 index 00000000..fd05b596 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineActivity.java @@ -0,0 +1,881 @@ +package org.fox.ttrss.offline; + +import org.fox.ttrss.CommonActivity; +import org.fox.ttrss.PreferencesActivity; +import org.fox.ttrss.R; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteStatement; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v7.view.ActionMode; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.EditText; +import android.widget.SearchView; +import android.widget.TextView; + +public class OfflineActivity extends CommonActivity { + private final String TAG = this.getClass().getSimpleName(); + + protected SharedPreferences m_prefs; + protected Menu m_menu; + + private ActionMode m_headlinesActionMode; + private HeadlinesActionModeCallback m_headlinesActionModeCallback; + + private String m_lastImageHitTestUrl; + + @SuppressLint("NewApi") + private class HeadlinesActionModeCallback implements ActionMode.Callback { + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + m_headlinesActionMode = null; + deselectAllArticles(); + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.headlines_action_menu, menu); + + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + onOptionsItemSelected(item); + return false; + } + }; + + @Override + public boolean onContextItemSelected(android.view.MenuItem item) { + /* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + .getMenuInfo(); */ + + final OfflineArticlePager ap = (OfflineArticlePager)getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + switch (item.getItemId()) { + case R.id.article_img_open: + if (getLastContentImageHitTestUrl() != null) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, + Uri.parse(getLastContentImageHitTestUrl())); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + toast(R.string.error_other_error); + } + } + return true; + case R.id.article_img_copy: + if (getLastContentImageHitTestUrl() != null) { + copyToClipboard(getLastContentImageHitTestUrl()); + } + return true; + case R.id.article_img_share: + if (getLastContentImageHitTestUrl() != null) { + Intent intent = new Intent(Intent.ACTION_SEND); + + intent.setType("image/png"); + intent.putExtra(Intent.EXTRA_SUBJECT, getLastContentImageHitTestUrl()); + intent.putExtra(Intent.EXTRA_TEXT, getLastContentImageHitTestUrl()); + + startActivity(Intent.createChooser(intent, getLastContentImageHitTestUrl())); + } + return true; + case R.id.article_img_view_caption: + if (getLastContentImageHitTestUrl() != null) { + + String content = ""; + + Cursor article = getArticleById(ap.getSelectedArticleId()); + + if (article != null) { + content = article.getString(article.getColumnIndex("content")); + article.close(); + } + + // Android doesn't give us an easy way to access title tags; + // we'll use Jsoup on the body text to grab the title text + // from the first image tag with this url. This will show + // the wrong text if an image is used multiple times. + Document doc = Jsoup.parse(content); + Elements es = doc.getElementsByAttributeValue("src", getLastContentImageHitTestUrl()); + if (es.size() > 0){ + if (es.get(0).hasAttr("title")){ + Dialog dia = new Dialog(this); + if (es.get(0).hasAttr("alt")){ + dia.setTitle(es.get(0).attr("alt")); + } else { + dia.setTitle(es.get(0).attr("title")); + } + TextView titleText = new TextView(this); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + titleText.setPaddingRelative(24, 24, 24, 24); + } else { + titleText.setPadding(24, 24, 24, 24); + } + + titleText.setTextSize(16); + titleText.setText(es.get(0).attr("title")); + dia.setContentView(titleText); + dia.show(); + } else { + toast(R.string.no_caption_to_display); + } + } else { + toast(R.string.no_caption_to_display); + } + } + return true; + default: + Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); + return super.onContextItemSelected(item); + } + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_PROGRESS); + + setProgressBarVisibility(false); + + setContentView(R.layout.login); + + setLoadingStatus(R.string.blank, false); + findViewById(R.id.loading_container).setVisibility(View.GONE); + + initMenu(); + + Intent intent = getIntent(); + + if (intent.getExtras() != null) { + if (intent.getBooleanExtra("initial", false)) { + intent = new Intent(OfflineActivity.this, OfflineFeedsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + + startActivityForResult(intent, 0); + finish(); + } + } + + /* if (savedInstanceState != null) { + + } */ + + m_headlinesActionModeCallback = new HeadlinesActionModeCallback(); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + } + + protected void selectArticles(int feedId, boolean isCat, int mode) { + switch (mode) { + case 0: + SQLiteStatement stmtSelectAll = null; + + if (isCat) { + stmtSelectAll = getWritableDb().compileStatement( + "UPDATE articles SET selected = 1 WHERE feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"); + } else { + stmtSelectAll = getWritableDb().compileStatement( + "UPDATE articles SET selected = 1 WHERE feed_id = ?"); + } + + stmtSelectAll.bindLong(1, feedId); + stmtSelectAll.execute(); + stmtSelectAll.close(); + + break; + case 1: + + SQLiteStatement stmtSelectUnread = null; + + if (isCat) { + stmtSelectUnread = getWritableDb().compileStatement( + "UPDATE articles SET selected = 1 WHERE feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?) AND unread = 1"); + } else { + stmtSelectUnread = getWritableDb().compileStatement( + "UPDATE articles SET selected = 1 WHERE feed_id = ? AND unread = 1"); + } + + stmtSelectUnread.bindLong(1, feedId); + stmtSelectUnread.execute(); + stmtSelectUnread.close(); + + break; + case 2: + deselectAllArticles(); + break; + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final OfflineHeadlinesFragment ohf = (OfflineHeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + /* final OfflineFeedsFragment off = (OfflineFeedsFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_FEEDS); */ + + /* final OfflineFeedCategoriesFragment ocf = (OfflineFeedCategoriesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_CATS); */ + + final OfflineArticlePager oap = (OfflineArticlePager) getSupportFragmentManager() + .findFragmentByTag(FRAG_ARTICLE); + + switch (item.getItemId()) { + /* case android.R.id.home: + finish(); + return true; */ + case R.id.headlines_toggle_sidebar: + if (true && !isSmallScreen()) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("headlines_hide_sidebar", !m_prefs.getBoolean("headlines_hide_sidebar", false)); + editor.commit(); + + if (ohf != null && ohf.isAdded()) { + ohf.getView().setVisibility(m_prefs.getBoolean("headlines_hide_sidebar", false) ? View.GONE : View.VISIBLE); + } + } + return true; + case R.id.go_online: + switchOnline(); + return true; + case R.id.search: + if (ohf != null && isCompatMode()) { + Dialog dialog = new Dialog(this); + + final EditText edit = new EditText(this); + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.search) + .setPositiveButton(getString(R.string.search), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + + String query = edit.getText().toString().trim(); + + ohf.setSearchQuery(query); + + } + }) + .setNegativeButton(getString(R.string.cancel), + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + + // + + } + }).setView(edit); + + dialog = builder.create(); + dialog.show(); + } + + return true; + case R.id.preferences: + Intent intent = new Intent(this, PreferencesActivity.class); + startActivityForResult(intent, 0); + return true; + case R.id.headlines_view_mode: + if (ohf != null) { + Dialog dialog = new Dialog(this); + + String viewMode = getViewMode(); + + //Log.d(TAG, "viewMode:" + getViewMode()); + + int selectedIndex = 0; + + if (viewMode.equals("all_articles")) { + selectedIndex = 0; + } else if (viewMode.equals("marked")) { + selectedIndex = 1; + } else if (viewMode.equals("published")) { + selectedIndex = 2; + } else if (viewMode.equals("unread")) { + selectedIndex = 3; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.headlines_set_view_mode) + .setSingleChoiceItems( + new String[] { + /* getString(R.string.headlines_adaptive), */ + getString(R.string.headlines_all_articles), + getString(R.string.headlines_starred), + getString(R.string.headlines_published), + getString(R.string.headlines_unread) }, + selectedIndex, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + switch (which) { + /* case 0: + setViewMode("adaptive"); + break; */ + case 0: + setViewMode("all_articles"); + break; + case 1: + setViewMode("marked"); + break; + case 2: + setViewMode("published"); + break; + case 3: + setViewMode("unread"); + break; + } + dialog.cancel(); + + refresh(); + } + }); + + dialog = builder.create(); + dialog.show(); + + } + return true; + case R.id.headlines_select: + if (ohf != null) { + Dialog dialog = new Dialog(this); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.headlines_select_dialog); + + builder.setSingleChoiceItems(new String[] { + getString(R.string.headlines_select_all), + getString(R.string.headlines_select_unread), + getString(R.string.headlines_select_none) }, 0, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + + selectArticles(ohf.getFeedId(), ohf.getFeedIsCat(), which); + initMenu(); + refresh(); + + dialog.cancel(); + } + }); + + dialog = builder.create(); + dialog.show(); + } + return true; + case R.id.headlines_mark_as_read: + if (ohf != null) { + final int feedId = ohf.getFeedId(); + final boolean isCat = ohf.getFeedIsCat(); + + int count = getUnreadArticleCount(feedId, isCat); + boolean confirm = m_prefs.getBoolean("confirm_headlines_catchup", true); + + if (count > 0) { + if (confirm) { + AlertDialog.Builder builder = new AlertDialog.Builder( + OfflineActivity.this) + .setMessage(getString(R.string.mark_num_headlines_as_read, count)) + .setPositiveButton(R.string.catchup, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + catchupFeed(feedId, isCat); + + } + }) + .setNegativeButton(R.string.dialog_cancel, + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + } + }); + + AlertDialog dlg = builder.create(); + dlg.show(); + + + } else { + catchupFeed(feedId, isCat); + } + } + } + return true; + case R.id.share_article: + if (true) { + int articleId = oap.getSelectedArticleId(); + + shareArticle(articleId); + } + return true; + case R.id.toggle_marked: + if (oap != null) { + int articleId = oap.getSelectedArticleId(); + + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, marked = NOT marked WHERE " + + BaseColumns._ID + " = ?"); + stmt.bindLong(1, articleId); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + /* case R.id.selection_select_none: + deselectAllArticles(); + return true; */ + case R.id.selection_toggle_unread: + if (getSelectedArticleCount() > 0) { + SQLiteStatement stmt = getWritableDb() + .compileStatement( + "UPDATE articles SET modified = 1, unread = NOT unread WHERE selected = 1"); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + case R.id.selection_toggle_marked: + if (getSelectedArticleCount() > 0) { + SQLiteStatement stmt = getWritableDb() + .compileStatement( + "UPDATE articles SET modified = 1, marked = NOT marked WHERE selected = 1"); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + case R.id.selection_toggle_published: + if (getSelectedArticleCount() > 0) { + SQLiteStatement stmt = getWritableDb() + .compileStatement( + "UPDATE articles SET modified = 1, published = NOT published WHERE selected = 1"); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + case R.id.toggle_published: + if (oap != null) { + int articleId = oap.getSelectedArticleId(); + + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, published = NOT published WHERE " + + BaseColumns._ID + " = ?"); + stmt.bindLong(1, articleId); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + case R.id.catchup_above: + if (oap != null) { + int articleId = oap.getSelectedArticleId(); + int feedId = oap.getFeedId(); + boolean isCat = oap.getFeedIsCat(); + + SQLiteStatement stmt = null; + + if (isCat) { + stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, 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 = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, unread = 0 WHERE " + + "updated >= (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " + + "AND feed_id = ?"); + } + + stmt.bindLong(1, articleId); + stmt.bindLong(2, feedId); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + case R.id.set_unread: + if (oap != null) { + int articleId = oap.getSelectedArticleId(); + + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, unread = 1 WHERE " + + BaseColumns._ID + " = ?"); + stmt.bindLong(1, articleId); + stmt.execute(); + stmt.close(); + + refresh(); + } + return true; + default: + Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.offline_menu, menu); + + m_menu = menu; + + initMenu(); + + return true; + } + + @SuppressLint("NewApi") + protected void initMenu() { + if (m_menu != null) { + m_menu.setGroupVisible(R.id.menu_group_headlines, false); + m_menu.setGroupVisible(R.id.menu_group_article, false); + m_menu.setGroupVisible(R.id.menu_group_feeds, false); + + OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + if (hf.getSelectedArticleCount() > 0 && m_headlinesActionMode == null) { + m_headlinesActionMode = startSupportActionMode(m_headlinesActionModeCallback); + } else if (hf.getSelectedArticleCount() == 0 && m_headlinesActionMode != null) { + m_headlinesActionMode.finish(); + } + } + + OfflineArticlePager ap = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (ap != null) { + int articleId = ap.getSelectedArticleId(); + + Cursor article = getArticleById(articleId); + + if (article != null) { + boolean unread = article.getInt(article.getColumnIndex("unread")) == 1; + boolean marked = article.getInt(article.getColumnIndex("marked")) == 1; + boolean published = article.getInt(article.getColumnIndex("published")) == 1; + + m_menu.findItem(R.id.toggle_marked).setIcon(marked ? R.drawable.ic_important_light : + R.drawable.ic_unimportant_light); + + m_menu.findItem(R.id.toggle_published).setIcon(published ? R.drawable.ic_menu_published_light : + R.drawable.ic_menu_unpublished_light); + + m_menu.findItem(R.id.set_unread).setIcon(unread ? R.drawable.ic_unread_light : + R.drawable.ic_read_light); + + article.close(); + } + } + + if (!isCompatMode()) { + MenuItem search = m_menu.findItem(R.id.search); + + SearchView searchView = (SearchView) search.getActionView(); + + if (searchView != null) { + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + private String query = ""; + + @Override + public boolean onQueryTextSubmit(String query) { + OfflineHeadlinesFragment frag = (OfflineHeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + if (frag != null) { + frag.setSearchQuery(query); + this.query = query; + } + + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (newText.equals("") && !newText.equals(this.query)) { + OfflineHeadlinesFragment frag = (OfflineHeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + if (frag != null) { + frag.setSearchQuery(newText); + this.query = newText; + } + } + + return false; + } + }); + } + } + } + } + + private void switchOnline() { + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = localPrefs.edit(); + editor.putBoolean("offline_mode_active", false); + editor.commit(); + + Intent refresh = new Intent(this, org.fox.ttrss.OnlineActivity.class); + startActivity(refresh); + finish(); + } + + protected Cursor getArticleById(int articleId) { + Cursor c = getReadableDb().query("articles", null, + BaseColumns._ID + "=?", + new String[] { String.valueOf(articleId) }, null, null, null); + + c.moveToFirst(); + + return c; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (m_prefs.getBoolean("use_volume_keys", false)) { + OfflineArticlePager ap = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); + + if (ap != null && ap.isAdded()) { + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + ap.selectArticle(false); + return true; + case KeyEvent.KEYCODE_VOLUME_DOWN: + ap.selectArticle(true); + return true; + } + } + } + + return super.onKeyDown(keyCode, event); + } + + // Handle onKeyUp too to suppress beep + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (m_prefs.getBoolean("use_volume_keys", false)) { + + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + return true; + } + } + + return super.onKeyUp(keyCode, event); + } + + protected Cursor getFeedById(int feedId) { + Cursor c = getReadableDb().query("feeds", null, + BaseColumns._ID + "=?", + new String[] { String.valueOf(feedId) }, null, null, null); + + c.moveToFirst(); + + return c; + } + + protected Cursor getCatById(int catId) { + Cursor c = getReadableDb().query("categories", null, + BaseColumns._ID + "=?", + new String[] { String.valueOf(catId) }, null, null, null); + + c.moveToFirst(); + + return c; + } + + protected Intent getShareIntent(Cursor article) { + if (article != null) { + String title = article.getString(article.getColumnIndex("title")); + String link = article.getString(article.getColumnIndex("link")); + + Intent intent = new Intent(Intent.ACTION_SEND); + + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, title); + intent.putExtra(Intent.EXTRA_TEXT, link); + + return intent; + } else { + return null; + } + } + + protected void shareArticle(int articleId) { + + Cursor article = getArticleById(articleId); + + if (article != null) { + shareArticle(article); + article.close(); + } + } + + private void shareArticle(Cursor article) { + if (article != null) { + Intent intent = getShareIntent(article); + + startActivity(Intent.createChooser(intent, + getString(R.string.share_article))); + } + } + + protected int getSelectedArticleCount() { + Cursor c = getReadableDb().query("articles", + new String[] { "COUNT(*)" }, "selected = 1", null, null, null, + null); + c.moveToFirst(); + int selected = c.getInt(0); + c.close(); + + return selected; + } + + protected int getUnreadArticleCount(int feedId, boolean isCat) { + + Cursor c; + + if (isCat) { + c = getReadableDb().query("articles", + new String[] { "COUNT(*)" }, "unread = 1 AND feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)", + new String[] { String.valueOf(feedId) }, + null, null, null); + } else { + c = getReadableDb().query("articles", + new String[] { "COUNT(*)" }, "unread = 1 AND feed_id = ?", + new String[] { String.valueOf(feedId) }, + null, null, null); + } + + c.moveToFirst(); + int selected = c.getInt(0); + c.close(); + + return selected; + } + + protected void deselectAllArticles() { + getWritableDb().execSQL("UPDATE articles SET selected = 0 "); + refresh(); + } + + protected void refresh() { + OfflineFeedsFragment ff = (OfflineFeedsFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_FEEDS); + + if (ff != null) { + ff.refresh(); + } + + OfflineFeedCategoriesFragment cf = (OfflineFeedCategoriesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_CATS); + + if (cf != null) { + cf.refresh(); + } + + OfflineHeadlinesFragment ohf = (OfflineHeadlinesFragment) getSupportFragmentManager() + .findFragmentByTag(FRAG_HEADLINES); + + if (ohf != null) { + ohf.refresh(); + } + + initMenu(); + } + + public void catchupFeed(int feedId, boolean isCat) { + if (isCat) { + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, 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 modified = 1, unread = 0 WHERE feed_id = ?"); + stmt.bindLong(1, feedId); + stmt.execute(); + stmt.close(); + } + + refresh(); + } + + public void setLastContentImageHitTestUrl(String url) { + m_lastImageHitTestUrl = url; + } + + public String getLastContentImageHitTestUrl() { + return m_lastImageHitTestUrl; + } + + public void setViewMode(String viewMode) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putString("offline_view_mode", viewMode); + editor.commit(); + } + + public String getViewMode() { + return m_prefs.getString("offline_view_mode", "adaptive"); + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticleFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticleFragment.java new file mode 100644 index 00000000..985150a9 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticleFragment.java @@ -0,0 +1,439 @@ +package org.fox.ttrss.offline; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.fox.ttrss.CommonActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.util.ImageCacheService; +import org.fox.ttrss.util.TypefaceCache; +import org.jsoup.Jsoup; +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.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +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.WebView; +import android.webkit.WebView.HitTestResult; +import android.widget.TextView; + +public class OfflineArticleFragment extends Fragment { + 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 OfflineActivity m_activity; + + public void initialize(int articleId) { + 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) { + + //getActivity().getMenuInflater().inflate(R.menu.article_link_context_menu, menu); + //menu.setHeaderTitle(m_cursor.getString(m_cursor.getColumnIndex("title"))); + + String title = m_cursor.getString(m_cursor.getColumnIndex("title")); + + if (v.getId() == R.id.content) { + HitTestResult result = ((WebView)v).getHitTestResult(); + + if (result != null && (result.getType() == HitTestResult.IMAGE_TYPE || result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) { + menu.setHeaderTitle(result.getExtra()); + getActivity().getMenuInflater().inflate(R.menu.article_content_img_context_menu, menu); + + /* FIXME I have no idea how to do this correctly ;( */ + + m_activity.setLastContentImageHitTestUrl(result.getExtra()); + + } else { + menu.setHeaderTitle(title); + getActivity().getMenuInflater().inflate(R.menu.article_link_context_menu, menu); + } + } else { + menu.setHeaderTitle(title); + getActivity().getMenuInflater().inflate(R.menu.article_link_context_menu, menu); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + @SuppressLint("NewApi") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + if (savedInstanceState != null) { + m_articleId = savedInstanceState.getInt("articleId"); + } + + boolean useTitleWebView = m_prefs.getBoolean("article_compat_view", false); + + View view = inflater.inflate(useTitleWebView ? R.layout.article_fragment_compat : R.layout.article_fragment, container, false); + + 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); + + m_cursor.moveToFirst(); + + if (m_cursor.isFirst()) { + if (!useTitleWebView) { + View scroll = view.findViewById(R.id.article_scrollview); + + if (scroll != null) { + final float scale = getResources().getDisplayMetrics().density; + + if (m_activity.isSmallScreen()) { + scroll.setPadding((int)(8 * scale + 0.5f), + (int)(5 * scale + 0.5f), + (int)(8 * scale + 0.5f), + 0); + } else { + scroll.setPadding((int)(25 * scale + 0.5f), + (int)(10 * scale + 0.5f), + (int)(25 * scale + 0.5f), + 0); + + } + + } + } + + int articleFontSize = Integer.parseInt(m_prefs.getString("article_font_size_sp", "16")); + int articleSmallFontSize = Math.max(10, Math.min(18, articleFontSize - 2)); + + TextView title = (TextView)view.findViewById(R.id.title); + + final String link = m_cursor.getString(m_cursor.getColumnIndex("link")); + + if (title != null) { + + if (m_prefs.getBoolean("enable_condensed_fonts", false)) { + Typeface tf = TypefaceCache.get(m_activity, "sans-serif-condensed", Typeface.NORMAL); + + if (tf != null && !tf.equals(title.getTypeface())) { + title.setTypeface(tf); + } + + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, articleFontSize + 5)); + } else { + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, articleFontSize + 3)); + } + + String titleStr; + + if (m_cursor.getString(m_cursor.getColumnIndex("title")).length() > 200) + titleStr = m_cursor.getString(m_cursor.getColumnIndex("title")).substring(0, 200) + "..."; + else + titleStr = m_cursor.getString(m_cursor.getColumnIndex("title")); + + title.setText(titleStr); + //title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + title.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + try { + URL url = new URL(link.trim()); + String uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), + url.getPort(), url.getPath(), url.getQuery(), url.getRef()).toString(); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + m_activity.toast(R.string.error_other_error); + } + } + }); + + registerForContextMenu(title); + } + + TextView comments = (TextView)view.findViewById(R.id.comments); + + if (comments != null) { + comments.setVisibility(View.GONE); + } + + TextView note = (TextView)view.findViewById(R.id.note); + + if (note != null) { + note.setVisibility(View.GONE); + } + + final WebView web = (WebView)view.findViewById(R.id.content); + + if (web != null) { + + web.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + HitTestResult result = ((WebView)v).getHitTestResult(); + + if (result != null && (result.getType() == HitTestResult.IMAGE_TYPE || result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE)) { + registerForContextMenu(web); + m_activity.openContextMenu(web); + unregisterForContextMenu(web); + return true; + } else { + if (m_activity.isCompatMode()) { + KeyEvent shiftPressEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0); + shiftPressEvent.dispatch(web); + } + + return false; + } + } + }); + + 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 = ""; + + WebSettings ws = web.getSettings(); + ws.setSupportZoom(false); + + TypedValue tv = new TypedValue(); + getActivity().getTheme().resolveAttribute(R.attr.linkColor, tv, true); + + // prevent flicker in ics + if (!m_prefs.getBoolean("webview_hardware_accel", true) || useTitleWebView) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + web.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + } + + String theme = m_prefs.getString("theme", CommonActivity.THEME_DEFAULT); + + if (CommonActivity.THEME_HOLO.equals(theme)) { + cssOverride = "body { background : transparent; color : #e0e0e0}"; + } else if (CommonActivity.THEME_DARK.equals(theme)) { + cssOverride = "body { background : transparent; color : #e0e0e0}"; + } else { + cssOverride = "body { background : transparent; }"; + } + + if (useTitleWebView || android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { + web.setBackgroundColor(Color.TRANSPARENT); + } else { + // seriously? + web.setBackgroundColor(Color.argb(1, 0, 0, 0)); + } + + String hexColor = String.format("#%06X", (0xFFFFFF & tv.data)); + cssOverride += " a:link {color: "+hexColor+";} a:visited { color: "+hexColor+";}"; + + cssOverride += " table { width : 100%; }"; + + String articleContent = m_cursor.getString(m_cursor.getColumnIndex("content")); + Document doc = Jsoup.parse(articleContent); + + if (doc != null) { + if (m_prefs.getBoolean("offline_image_cache_enabled", false)) { + + Elements images = doc.select("img"); + + for (Element img : images) { + String url = img.attr("src"); + + if (ImageCacheService.isUrlCached(m_activity, url)) { + img.attr("src", "file://" + ImageCacheService.getCacheFileName(m_activity, url)); + } + } + } + + // thanks webview for crashing on <video> tag + Elements videos = doc.select("video"); + + for (Element video : videos) + video.remove(); + + articleContent = doc.toString(); + } + + if (m_prefs.getBoolean("justify_article_text", true)) { + cssOverride += "body { text-align : justify; } "; + } + + ws.setDefaultFontSize(articleFontSize); + + content = + "<html>" + + "<head>" + + "<meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">" + + "<meta name=\"viewport\" content=\"width=device-width, user-scalable=no\" />" + + "<style type=\"text/css\">" + + "body { padding : 0px; margin : 0px; line-height : 130%; }" + + cssOverride + + "img { max-width : 100%; width : auto; height : auto; }" + + "</style>" + + "</head>" + + "<body>" + articleContent; + + if (useTitleWebView) { + content += "<p> </p><p> </p><p> </p><p> </p>"; + } + + content += "</body></html>"; + + try { + String baseUrl = null; + + try { + URL url = new URL(link); + baseUrl = url.getProtocol() + "://" + url.getHost(); + } catch (MalformedURLException e) { + // + } + + web.loadDataWithBaseURL(baseUrl, content, "text/html", "utf-8", null); + } catch (RuntimeException e) { + e.printStackTrace(); + } + + + } + + TextView dv = (TextView)view.findViewById(R.id.date); + + if (dv != null) { + dv.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + Date d = new Date(m_cursor.getInt(m_cursor.getColumnIndex("updated")) * 1000L); + DateFormat df = new SimpleDateFormat("MMM dd, HH:mm"); + dv.setText(df.format(d)); + } + + TextView author = (TextView)view.findViewById(R.id.author); + + boolean hasAuthor = false; + + if (author != null) { + author.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + int authorIndex = m_cursor.getColumnIndex("author"); + if (authorIndex >= 0) + author.setText(m_cursor.getString(authorIndex)); + else + author.setVisibility(View.GONE); + + hasAuthor = true; + } + + TextView tagv = (TextView)view.findViewById(R.id.tags); + + if (tagv != null) { + tagv.setTextSize(TypedValue.COMPLEX_UNIT_SP, articleSmallFontSize); + + int feedTitleIndex = m_cursor.getColumnIndex("feed_title"); + + if (feedTitleIndex != -1 /* && m_isCat */) { + String fTitle = m_cursor.getString(feedTitleIndex); + + int authorIndex = m_cursor.getColumnIndex("author"); + + if (!hasAuthor && authorIndex >= 0) { + fTitle += " (" + getString(R.string.author_formatted, m_cursor.getString(authorIndex)) + ")"; + } + + tagv.setText(fTitle); + } else { + String tagsStr = m_cursor.getString(m_cursor.getColumnIndex("tags")); + tagv.setText(tagsStr); + } + } + + } + + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + m_cursor.close(); + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("articleId", m_articleId); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + + m_activity = (OfflineActivity) activity; + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticlePager.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticlePager.java new file mode 100644 index 00000000..510ab97b --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineArticlePager.java @@ -0,0 +1,298 @@ +package org.fox.ttrss.offline; + +import org.fox.ttrss.R; + +import com.viewpagerindicator.UnderlinePageIndicator; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.Bundle; +import android.preference.PreferenceManager; +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; +import android.view.WindowManager; + +public class OfflineArticlePager extends Fragment { + private final String TAG = this.getClass().getSimpleName(); + + private PagerAdapter m_adapter; + 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; + private SharedPreferences m_prefs; + + 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 = ?"; + } + + String viewMode = m_activity.getViewMode(); + + if ("adaptive".equals(viewMode)) { + // TODO: implement adaptive + } else if ("marked".equals(viewMode)) { + feedClause += "AND (marked = 1)"; + } else if ("published".equals(viewMode)) { + feedClause += "AND (published = 1)"; + } else if ("unread".equals(viewMode)) { + feedClause += "AND (unread = 1)"; + } else { // all_articles + // + } + + String orderBy = (m_prefs.getBoolean("offline_oldest_first", false)) ? "updated" : "updated DESC"; + + 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, orderBy); + } 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, orderBy); + } + } + + @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) { + Log.d(TAG, "getItem: " + position); + + if (m_cursor.moveToPosition(position)) { + + if (m_prefs.getBoolean("dim_status_bar", false) && getView() != null && !m_activity.isCompatMode()) { + getView().setSystemUiVisibility(View.STATUS_BAR_HIDDEN); + } + + OfflineArticleFragment oaf = new OfflineArticleFragment(); + oaf.initialize(m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID))); + + return oaf; + } + + return null; + } + + @Override + public int getCount() { + return m_cursor.getCount(); + } + } + + @Override + public void onResume() { + super.onResume(); + + if (!m_activity.isCompatMode() && m_prefs.getBoolean("dim_status_bar", false)) { + getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + + if (m_prefs.getBoolean("full_screen_mode", false)) { + m_activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + /* if (!m_activity.isCompatMode()) { + m_activity.getSupportActionBar().hide(); + } */ + } + } + + public void initialize(int articleId, int feedId, boolean isCat) { + 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); + } + + Log.d(TAG, "feed=" + m_feedId + "; iscat=" + m_isCat); + + m_cursor = createCursor(); + + m_adapter = new PagerAdapter(getActivity().getSupportFragmentManager()); + + int position = 0; + + Log.d(TAG, "maId=" + m_articleId); + + if (m_articleId != 0) { + if (m_cursor.moveToFirst()) { + + while (!m_cursor.isAfterLast()) { + if (m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)) == m_articleId) { + position = m_cursor.getPosition(); + break; + } + m_cursor.moveToNext(); + } + + Log.d(TAG, "(1)maId=" + m_articleId); + m_listener.onArticleSelected(m_articleId, false); + } + } else { + if (m_cursor.moveToFirst()) { + m_articleId = m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)); + m_listener.onArticleSelected(m_articleId, false); + + Log.d(TAG, "(2)maId=" + m_articleId); + } + } + + + ViewPager pager = (ViewPager) view.findViewById(R.id.article_pager); + + pager.setAdapter(m_adapter); + + UnderlinePageIndicator indicator = (UnderlinePageIndicator)view.findViewById(R.id.article_titles); + indicator.setViewPager(pager); + + pager.setCurrentItem(position); + indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + + @Override + public void onPageScrollStateChanged(int arg0) { + } + + @Override + public void onPageScrolled(int arg0, float arg1, int arg2) { + } + + @Override + public void onPageSelected(int position) { + if (m_cursor.moveToPosition(position)) { + int articleId = m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)); + + m_articleId = articleId; + m_listener.onArticleSelected(articleId, false); + } + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (OfflineActivity)activity; + m_listener = (OfflineHeadlinesEventListener)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + + } + + 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; + + int position = getArticleIdPosition(articleId); + + ViewPager pager = (ViewPager) getView().findViewById(R.id.article_pager); + + pager.setCurrentItem(position); + + } + + public int getArticleIdPosition(int articleId) { + m_cursor.moveToFirst(); + + while (!m_cursor.isAfterLast()) { + if (m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID)) == articleId) { + return m_cursor.getPosition(); + } + m_cursor.moveToNext(); + } + + return -1; + } + + public void selectArticle(boolean next) { + int position = getArticleIdPosition(m_articleId); + + if (position != -1) { + if (next) + position++; + else + position--; + + Log.d(TAG, "pos=" + position); + + if (m_cursor.moveToPosition(position)) { + setArticleId(m_cursor.getInt(m_cursor.getColumnIndex(BaseColumns._ID))); + } + } + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java new file mode 100644 index 00000000..2d65c890 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineDownloadService.java @@ -0,0 +1,500 @@ +package org.fox.ttrss.offline; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.OnlineActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.types.Article; +import org.fox.ttrss.types.Feed; +import org.fox.ttrss.types.FeedCategory; +import org.fox.ttrss.util.DatabaseHelper; +import org.fox.ttrss.util.ImageCacheService; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.os.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +public class OfflineDownloadService extends Service { + + private final String TAG = this.getClass().getSimpleName(); + + public static final int NOTIFY_DOWNLOADING = 1; + 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 = 50; + private static final int OFFLINE_SYNC_MAX = OFFLINE_SYNC_SEQ * 10; + + private SQLiteDatabase m_writableDb; + private SQLiteDatabase m_readableDb; + private int m_articleOffset = 0; + private String m_sessionId; + private NotificationManager m_nmgr; + + private boolean m_batchMode = false; + private boolean m_downloadInProgress = false; + private boolean m_downloadImages = false; + private int m_syncMax; + private SharedPreferences m_prefs; + private boolean m_canProceed = true; + + private final IBinder m_binder = new LocalBinder(); + + public class LocalBinder extends Binder { + OfflineDownloadService getService() { + return OfflineDownloadService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return m_binder; + } + + @Override + public void onCreate() { + super.onCreate(); + m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + m_downloadImages = m_prefs.getBoolean("offline_image_cache_enabled", false); + m_syncMax = Integer.parseInt(m_prefs.getString("offline_sync_max", String.valueOf(OFFLINE_SYNC_MAX))); + + 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, OnlineActivity.class); + intent.setAction(INTENT_ACTION_CANCEL); + + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + intent, 0); + + notification.flags |= Notification.FLAG_ONGOING_EVENT; + notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + + notification.setLatestEventInfo(this, getString(R.string.notify_downloading_title), msg, contentIntent); + + m_nmgr.notify(NOTIFY_DOWNLOADING, notification); + } + + private void updateNotification(int msgResId) { + updateNotification(getString(msgResId)); + } + + private void downloadFailed() { + m_readableDb.close(); + m_writableDb.close(); + + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + // TODO send notification to activity? + + m_downloadInProgress = false; + stopSelf(); + } + + private boolean isCacheServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("org.fox.ttrss.util.ImageCacheService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + + public void downloadComplete() { + m_downloadInProgress = false; + + // if cache service is running, it will send a finished intent on its own + if (!isCacheServiceRunning()) { + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + if (m_batchMode) { + + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = localPrefs.edit(); + editor.putBoolean("offline_mode_active", true); + editor.commit(); + + } else { + Intent intent = new Intent(); + intent.setAction(INTENT_ACTION_SUCCESS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + sendBroadcast(intent); + } + } else { + updateNotification(getString(R.string.notify_downloading_images, 0)); + } + + m_readableDb.close(); + m_writableDb.close(); + + stopSelf(); + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + /* private synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } */ + + private synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + @SuppressWarnings("unchecked") + private void downloadArticles() { + Log.d(TAG, "offline: downloading articles... offset=" + m_articleOffset); + + updateNotification(getString(R.string.notify_downloading_articles, m_articleOffset)); + + OfflineArticlesRequest req = new OfflineArticlesRequest(this); + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getHeadlines"); + put("sid", m_sessionId); + put("feed_id", "-4"); + put("view_mode", "unread"); + put("show_content", "true"); + put("skip", String.valueOf(m_articleOffset)); + put("limit", String.valueOf(OFFLINE_SYNC_SEQ)); + } + }; + + req.execute(map); + } + + private void downloadFeeds() { + + updateNotification(R.string.notify_downloading_feeds); + + getWritableDb().execSQL("DELETE FROM feeds;"); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected JsonElement doInBackground(HashMap<String, String>... params) { + JsonElement content = super.doInBackground(params); + + if (content != null) { + + try { + Type listType = new TypeToken<List<Feed>>() {}.getType(); + List<Feed> feeds = new Gson().fromJson(content, listType); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO feeds " + + "("+BaseColumns._ID+", title, feed_url, has_icon, cat_id) " + + "VALUES (?, ?, ?, ?, ?);"); + + for (Feed feed : feeds) { + stmtInsert.bindLong(1, feed.id); + stmtInsert.bindString(2, feed.title); + stmtInsert.bindString(3, feed.feed_url); + stmtInsert.bindLong(4, feed.has_icon ? 1 : 0); + stmtInsert.bindLong(5, feed.cat_id); + + stmtInsert.execute(); + } + + stmtInsert.close(); + + Log.d(TAG, "offline: done downloading feeds"); + + m_articleOffset = 0; + + getWritableDb().execSQL("DELETE FROM articles;"); + } 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(); + } + } + + }; + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getFeeds"); + put("sid", m_sessionId); + put("cat_id", "-3"); + put("unread_only", "true"); + } + }; + + req.execute(map); + } + + private void downloadCategories() { + + updateNotification(R.string.notify_downloading_feeds); + + getWritableDb().execSQL("DELETE FROM categories;"); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + 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); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO categories " + + "("+BaseColumns._ID+", title) " + + "VALUES (?, ?);"); + + for (FeedCategory cat : cats) { + stmtInsert.bindLong(1, cat.id); + stmtInsert.bindString(2, cat.title); + + stmtInsert.execute(); + } + + stmtInsert.close(); + + Log.d(TAG, "offline: done downloading categories"); + + } 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(); + } + } + + }; + + @SuppressWarnings("serial") + HashMap<String,String> map = new HashMap<String,String>() { + { + put("op", "getCategories"); + put("sid", m_sessionId); + //put("cat_id", "-3"); + put("unread_only", "true"); + } + }; + + req.execute(map); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + m_canProceed = false; + Log.d(TAG, "onDestroy"); + + //m_readableDb.close(); + //m_writableDb.close(); + } + + public class OfflineArticlesRequest extends ApiRequest { + List<Article> m_articles; + + public OfflineArticlesRequest(Context context) { + super(context); + } + + @Override + protected JsonElement doInBackground(HashMap<String, String>... params) { + JsonElement content = super.doInBackground(params); + + if (content != null) { + + try { + Type listType = new TypeToken<List<Article>>() {}.getType(); + m_articles = new Gson().fromJson(content, listType); + + SQLiteStatement stmtInsert = getWritableDb().compileStatement("INSERT INTO articles " + + "("+BaseColumns._ID+", unread, marked, published, score, updated, is_updated, title, link, feed_id, tags, content, author) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"); + + for (Article article : m_articles) { + + String tagsString = ""; + + for (String t : article.tags) { + tagsString += t + ", "; + } + + tagsString = tagsString.replaceAll(", $", ""); + + int index = 1; + stmtInsert.bindLong(index++, article.id); + stmtInsert.bindLong(index++, article.unread ? 1 : 0); + stmtInsert.bindLong(index++, article.marked ? 1 : 0); + stmtInsert.bindLong(index++, article.published ? 1 : 0); + stmtInsert.bindLong(index++, article.score); + stmtInsert.bindLong(index++, article.updated); + stmtInsert.bindLong(index++, article.is_updated ? 1 : 0); + stmtInsert.bindString(index++, article.title); + stmtInsert.bindString(index++, article.link); + stmtInsert.bindLong(index++, article.feed_id); + stmtInsert.bindString(index++, tagsString); // comma-separated tags + stmtInsert.bindString(index++, article.content); + stmtInsert.bindString(index++, article.author != null ? article.author : ""); + + if (m_downloadImages) { + Document doc = Jsoup.parse(article.content); + + if (doc != null) { + Elements images = doc.select("img"); + + for (Element img : images) { + String url = img.attr("src"); + + if (url.indexOf("://") != -1) { + if (!ImageCacheService.isUrlCached(OfflineDownloadService.this, url)) { + Intent intent = new Intent(OfflineDownloadService.this, + ImageCacheService.class); + + intent.putExtra("url", url); + startService(intent); + } + } + } + } + } + + try { + stmtInsert.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + m_articleOffset += m_articles.size(); + + Log.d(TAG, "offline: received " + m_articles.size() + " articles; canProc=" + m_canProceed); + + stmtInsert.close(); + + } catch (Exception e) { + updateNotification(R.string.offline_switch_error); + Log.d(TAG, "offline: failed: exception when loading articles"); + e.printStackTrace(); + 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()); + downloadFailed(); + } + } + } + + @Override + public void onStart(Intent intent, int startId) { + try { + if (getWritableDb().isDbLockedByCurrentThread() || getWritableDb().isDbLockedByOtherThreads()) { + return; + } + + m_sessionId = intent.getStringExtra("sessionId"); + m_batchMode = intent.getBooleanExtra("batchMode", false); + + if (!m_downloadInProgress) { + if (m_downloadImages) ImageCacheService.cleanupCache(this, false); + + updateNotification(R.string.notify_downloading_init); + m_downloadInProgress = true; + + downloadCategories(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedCategoriesFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedCategoriesFragment.java new file mode 100644 index 00000000..8fde8176 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedCategoriesFragment.java @@ -0,0 +1,337 @@ +package org.fox.ttrss.offline; + +import org.fox.ttrss.R; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SimpleCursorAdapter; +import android.util.Log; +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.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +public class OfflineFeedCategoriesFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { + private final String TAG = this.getClass().getSimpleName(); + private SharedPreferences m_prefs; + private FeedCategoryListAdapter m_adapter; + private int m_selectedCatId; + private Cursor m_cursor; + private OfflineFeedsActivity m_activity; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + + getActivity().getMenuInflater().inflate(R.menu.category_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + Cursor cursor = (Cursor)m_adapter.getItem(info.position); + + if (cursor != null) + menu.setHeaderTitle(cursor.getString(cursor.getColumnIndex("title"))); + + if (!m_activity.isSmallScreen()) { + menu.findItem(R.id.browse_articles).setVisible(false); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + public Cursor createCursor() { + 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_activity.getReadableDb().query("cats_unread", + null, unreadOnly, null, null, null, order); + } + + public void refresh() { + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + + m_cursor = createCursor(); + + if (m_cursor != null && m_adapter != null) { + m_adapter.changeCursor(m_cursor); + m_adapter.notifyDataSetChanged(); + } + } + + @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.openFeedArticles(catId, true); + } + } + return true; + case R.id.browse_headlines: + 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) { + + if (savedInstanceState != null) { + m_selectedCatId = savedInstanceState.getInt("selectedFeedId"); + } + + View view = inflater.inflate(R.layout.feeds_fragment, container, false); + + ListView list = (ListView)view.findViewById(R.id.feeds); + + m_cursor = createCursor(); + + m_adapter = new FeedCategoryListAdapter(getActivity(), R.layout.feeds_row, m_cursor, + new String[] { "title", "unread" }, new int[] { R.id.title, R.id.unread_counter }, 0); + + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + list.setEmptyView(view.findViewById(R.id.no_feeds)); + registerForContextMenu(list); + + view.findViewById(R.id.loading_container).setVisibility(View.GONE); + + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (OfflineFeedsActivity)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + m_prefs.registerOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("selectedFeedId", m_selectedCatId); + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)getActivity().findViewById(R.id.feeds); + + if (list != null) { + Cursor cursor = (Cursor) list.getItemAtPosition(position); + + if (cursor != null) { + int feedId = (int) cursor.getLong(0); + Log.d(TAG, "clicked on feed " + feedId); + + if (m_activity.isSmallScreen() && "ARTICLES".equals(m_prefs.getString("default_view_mode", "HEADLINES")) && + m_prefs.getBoolean("browse_cats_like_feeds", false)) { + + m_activity.openFeedArticles(feedId, true); + + } else { + m_activity.onCatSelected(feedId); + } + + /* if (!m_activity.isSmallScreen()) + m_selectedCatId = feedId; */ + + m_adapter.notifyDataSetChanged(); + } + } + } + + /* public void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + getActivity().setProgressBarIndeterminateVisibility(showProgress); + } */ + + private class FeedCategoryListAdapter extends SimpleCursorAdapter { + + + public FeedCategoryListAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + } + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_SELECTED = 1; + + public static final int VIEW_COUNT = VIEW_SELECTED+1; + + @Override + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + Cursor cursor = (Cursor) this.getItem(position); + + if (!m_activity.isSmallScreen() && cursor.getLong(0) == m_selectedCatId) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + Cursor cursor = (Cursor)getItem(position); + + if (v == null) { + int layoutId = R.layout.feeds_row; + + switch (getItemViewType(position)) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + } + + TextView tt = (TextView) v.findViewById(R.id.title); + + if (tt != null) { + tt.setText(cursor.getString(cursor.getColumnIndex("title"))); + } + + TextView tu = (TextView) v.findViewById(R.id.unread_counter); + + if (tu != null) { + tu.setText(String.valueOf(cursor.getInt(cursor.getColumnIndex("unread")))); + tu.setVisibility((cursor.getInt(cursor.getColumnIndex("unread")) > 0) ? View.VISIBLE : View.INVISIBLE); + } + + ImageView icon = (ImageView)v.findViewById(R.id.icon); + + if (icon != null) { + icon.setImageResource(cursor.getInt(cursor.getColumnIndex("unread")) > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + ImageButton ib = (ImageButton) v.findViewById(R.id.feed_menu_button); + + if (ib != null) { + ib.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + + return v; + } + } + + public void sortCategories() { + try { + refresh(); + } catch (NullPointerException e) { + // activity is gone? + } catch (IllegalStateException e) { + // we're probably closing and DB is gone already + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + + sortCategories(); + } + + public int getCatIdAtPosition(int position) { + Cursor c = (Cursor)m_adapter.getItem(position); + + if (c != null) { + int catId = c.getInt(0); + return catId; + } + + return -10000; + } + + public void setSelectedFeedId(int feedId) { + m_selectedCatId = feedId; + refresh(); + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsActivity.java new file mode 100644 index 00000000..f7263fe0 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsActivity.java @@ -0,0 +1,349 @@ +package org.fox.ttrss.offline; + +import org.fox.ttrss.GlobalState; +import org.fox.ttrss.R; + +import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; + +import android.animation.LayoutTransition; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.database.sqlite.SQLiteStatement; +import android.os.Bundle; +import android.os.Handler; +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.ViewGroup; +import android.widget.LinearLayout; + +public class OfflineFeedsActivity extends OfflineActivity implements OfflineHeadlinesEventListener { + private final String TAG = this.getClass().getSimpleName(); + + private boolean m_actionbarUpEnabled = false; + private int m_actionbarRevertDepth = 0; + private SlidingMenu m_slidingMenu; + private boolean m_feedIsSelected = false; + private boolean m_feedWasSelected = false; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.headlines); + + setStatusBarTint(); + setSmallScreen(findViewById(R.id.sw600dp_anchor) == null && + findViewById(R.id.sw600dp_port_anchor) == null); + + GlobalState.getInstance().load(savedInstanceState); + + if (isSmallScreen() || findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu = new SlidingMenu(this); + + /* if (findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } */ + + m_slidingMenu.setMode(SlidingMenu.LEFT); + m_slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); + m_slidingMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); + m_slidingMenu.setSlidingEnabled(true); + m_slidingMenu.setMenu(R.layout.feeds); + + m_slidingMenu.setOnClosedListener(new SlidingMenu.OnClosedListener() { + + @Override + public void onClosed() { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + m_feedIsSelected = true; + + initMenu(); + } + }); + + m_slidingMenu.setOnOpenedListener(new SlidingMenu.OnOpenedListener() { + + @Override + public void onOpened() { + if (m_actionbarRevertDepth == 0) { + m_actionbarUpEnabled = false; + m_feedIsSelected = false; + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + refresh(); + } + + initMenu(); + } + }); + } + + if (savedInstanceState != null) { + + m_actionbarUpEnabled = savedInstanceState.getBoolean("actionbarUpEnabled"); + m_actionbarRevertDepth = savedInstanceState.getInt("actionbarRevertDepth"); + m_feedIsSelected = savedInstanceState.getBoolean("feedIsSelected"); + m_feedWasSelected = savedInstanceState.getBoolean("feedWasSelected"); + + if (findViewById(R.id.sw600dp_port_anchor) != null && m_feedWasSelected && m_slidingMenu != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } + + if (m_slidingMenu != null && m_feedIsSelected == false) { + m_slidingMenu.showMenu(); + } else if (m_slidingMenu != null) { + m_actionbarUpEnabled = true; + } else { + m_actionbarUpEnabled = m_actionbarRevertDepth > 0; + } + + if (m_actionbarUpEnabled) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + } else { + if (m_slidingMenu != null) + m_slidingMenu.showMenu(); + + 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); + + initMenu(); + + if (!isCompatMode() && !isSmallScreen()) { + ((ViewGroup)findViewById(R.id.headlines_fragment)).setLayoutTransition(new LayoutTransition()); + ((ViewGroup)findViewById(R.id.feeds_fragment)).setLayoutTransition(new LayoutTransition()); + } + } + + public void openFeedArticles(int feedId, boolean isCat) { + if (isSmallScreen()) { + Intent intent = new Intent(OfflineFeedsActivity.this, OfflineHeadlinesActivity.class); + + intent.putExtra("feed", feedId); + intent.putExtra("isCat", isCat); + intent.putExtra("article", 0); + startActivityForResult(intent, 0); + } + } + + @Override + public void onBackPressed() { + if (m_actionbarRevertDepth > 0) { + + if (m_feedIsSelected && m_slidingMenu != null && !m_slidingMenu.isMenuShowing()) { + m_slidingMenu.showMenu(); + } else { + m_actionbarRevertDepth = m_actionbarRevertDepth - 1; + m_actionbarUpEnabled = m_actionbarRevertDepth > 0; + getSupportActionBar().setDisplayHomeAsUpEnabled(m_actionbarUpEnabled); + + onBackPressed(); + } + } else if (m_slidingMenu != null && !m_slidingMenu.isMenuShowing()) { + m_slidingMenu.showMenu(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (m_actionbarUpEnabled) + onBackPressed(); + return true; + 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); + + out.putBoolean("actionbarUpEnabled", m_actionbarUpEnabled); + out.putInt("actionbarRevertDepth", m_actionbarRevertDepth); + out.putBoolean("feedIsSelected", m_feedIsSelected); + out.putBoolean("feedWasSelected", m_feedWasSelected); + + + //if (m_slidingMenu != null ) + // out.putBoolean("slidingMenuVisible", m_slidingMenu.isMenuShowing()); + + GlobalState.getInstance().save(out); + } + + public void initMenu() { + super.initMenu(); + + if (m_menu != null) { + Fragment ff = getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); + Fragment cf = getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (m_slidingMenu != null) { + m_menu.setGroupVisible(R.id.menu_group_feeds, m_slidingMenu.isMenuShowing()); + m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded() && !m_slidingMenu.isMenuShowing()); + } else { + m_menu.setGroupVisible(R.id.menu_group_feeds, (ff != null && ff.isAdded()) || (cf != null && cf.isAdded())); + m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded()); + } + + m_menu.findItem(R.id.headlines_toggle_sidebar).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 onCatSelected(int catId) { + onCatSelected(catId, m_prefs.getBoolean("browse_cats_like_feeds", false)); + } + + public void onCatSelected(int catId, boolean openAsFeed) { + OfflineFeedCategoriesFragment fc = (OfflineFeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + + if (openAsFeed) { + if (fc != null) { + fc.setSelectedFeedId(catId); + } + + onFeedSelected(catId, true, true); + } else { + if (fc != null) { + fc.setSelectedFeedId(-1); + } + + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + OfflineFeedsFragment ff = new OfflineFeedsFragment(); + ff.initialize(catId); + + ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); + ft.addToBackStack(null); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + m_actionbarRevertDepth = m_actionbarRevertDepth + 1; + + ft.commit(); + } + } + + public void onFeedSelected(int feedId) { + onFeedSelected(feedId, false, true); + } + + public void onFeedSelected(final int feedId, final boolean isCat, boolean open) { + + if (open) { + if (!isSmallScreen()) { + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + if (container != null) { + container.setWeightSum(3f); + } + } + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + OfflineHeadlinesFragment hf = new OfflineHeadlinesFragment(); + hf.initialize(feedId, isCat); + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + + ft.commit(); + + m_feedIsSelected = true; + m_feedWasSelected = true; + + if (m_slidingMenu != null) { + if (findViewById(R.id.sw600dp_port_anchor) != null) { + m_slidingMenu.setBehindWidth(getScreenWidthInPixel() * 2/3); + } + + m_slidingMenu.showContent(); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + m_actionbarUpEnabled = true; + } + } + }, 10); + } + } + + @Override + public void onArticleSelected(int articleId, boolean open) { + + if (!open) { + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, unread = 0 " + "WHERE " + BaseColumns._ID + + " = ?"); + + stmt.bindLong(1, articleId); + stmt.execute(); + stmt.close(); + } + + initMenu(); + + if (open) { + 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); + + overridePendingTransition(R.anim.right_slide_in, 0); + + } else { + refresh(); + } + + initMenu(); + + } + + @Override + public void onArticleSelected(int articleId) { + onArticleSelected(articleId, true); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsFragment.java new file mode 100644 index 00000000..8c04d0cd --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineFeedsFragment.java @@ -0,0 +1,372 @@ +package org.fox.ttrss.offline; + +import java.io.File; + +import org.fox.ttrss.R; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SimpleCursorAdapter; +import android.util.Log; +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.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +public class OfflineFeedsFragment extends Fragment implements OnItemClickListener, OnSharedPreferenceChangeListener { + private final String TAG = this.getClass().getSimpleName(); + private SharedPreferences m_prefs; + private FeedListAdapter m_adapter; + private static final String ICON_PATH = "/data/org.fox.ttrss/icons/"; + private int m_selectedFeedId; + private int m_catId = -1; + private boolean m_enableFeedIcons; + private Cursor m_cursor; + private OfflineFeedsActivity m_activity; + + public void initialize(int catId) { + 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.browse_articles: + if (true) { + int feedId = getFeedIdAtPosition(info.position); + if (feedId != -10000) { + m_activity.openFeedArticles(feedId, false); + } + } + return true; + case R.id.browse_headlines: + if (true) { + int feedId = getFeedIdAtPosition(info.position); + if (feedId != -10000) { + m_activity.onFeedSelected(feedId); + } + } + return true; + 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) { + + getActivity().getMenuInflater().inflate(R.menu.feed_menu, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + Cursor cursor = (Cursor)m_adapter.getItem(info.position); + + if (cursor != null) + menu.setHeaderTitle(cursor.getString(cursor.getColumnIndex("title"))); + + if (!m_activity.isSmallScreen()) { + menu.findItem(R.id.browse_articles).setVisible(false); + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + public Cursor createCursor() { + 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_activity.getReadableDb().query("feeds_unread", + null, unreadOnly + " AND cat_id = ?", new String[] { String.valueOf(m_catId) }, null, null, order); + } else { + return m_activity.getReadableDb().query("feeds_unread", + null, unreadOnly, null, null, null, order); + } + } + + public void refresh() { + try { + if (!isAdded()) return; + + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + + m_cursor = createCursor(); + + if (m_cursor != null && m_adapter != null) { + m_adapter.changeCursor(m_cursor); + m_adapter.notifyDataSetChanged(); + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + if (savedInstanceState != null) { + m_selectedFeedId = savedInstanceState.getInt("selectedFeedId"); + m_catId = savedInstanceState.getInt("catId"); + } + + View view = inflater.inflate(R.layout.feeds_fragment, container, false); + + ListView list = (ListView)view.findViewById(R.id.feeds); + + m_cursor = createCursor(); + + m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, m_cursor, + new String[] { "title", "unread" }, new int[] { R.id.title, R.id.unread_counter }, 0); + + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + list.setEmptyView(view.findViewById(R.id.no_feeds)); + registerForContextMenu(list); + + view.findViewById(R.id.loading_container).setVisibility(View.GONE); + + m_enableFeedIcons = m_prefs.getBoolean("download_feed_icons", false); + + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (OfflineFeedsActivity)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + m_prefs.registerOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("selectedFeedId", m_selectedFeedId); + out.putInt("catId", m_catId); + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)getActivity().findViewById(R.id.feeds); + + if (list != null) { + Cursor cursor = (Cursor) list.getItemAtPosition(position); + + if (cursor != null) { + int feedId = (int) cursor.getLong(0); + Log.d(TAG, "clicked on feed " + feedId); + + if (!m_activity.isSmallScreen() && "ARTICLES".equals(m_prefs.getString("default_view_mode", "HEADLINES"))) { + m_activity.openFeedArticles(feedId, false); + } else { + m_activity.onFeedSelected(feedId); + } + + if (!m_activity.isSmallScreen()) + m_selectedFeedId = feedId; + + m_adapter.notifyDataSetChanged(); + } + } + } + + /* public void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + getActivity().setProgressBarIndeterminateVisibility(showProgress); + } */ + + private class FeedListAdapter extends SimpleCursorAdapter { + + + public FeedListAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + } + + public static final int VIEW_NORMAL = 0; + public static final int VIEW_SELECTED = 1; + + public static final int VIEW_COUNT = VIEW_SELECTED+1; + + @Override + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + Cursor cursor = (Cursor) this.getItem(position); + + if (!m_activity.isSmallScreen() && cursor.getLong(0) == m_selectedFeedId) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + Cursor cursor = (Cursor)getItem(position); + + if (v == null) { + int layoutId = R.layout.feeds_row; + + switch (getItemViewType(position)) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + } + + TextView tt = (TextView) v.findViewById(R.id.title); + + if (tt != null) { + tt.setText(cursor.getString(cursor.getColumnIndex("title"))); + } + + TextView tu = (TextView) v.findViewById(R.id.unread_counter); + + if (tu != null) { + tu.setText(String.valueOf(cursor.getInt(cursor.getColumnIndex("unread")))); + tu.setVisibility((cursor.getInt(cursor.getColumnIndex("unread")) > 0) ? View.VISIBLE : View.INVISIBLE); + } + + ImageView icon = (ImageView)v.findViewById(R.id.icon); + + if (icon != null) { + + if (m_enableFeedIcons) { + + try { + File storage = Environment.getExternalStorageDirectory(); + + File iconFile = new File(storage.getAbsolutePath() + ICON_PATH + cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)) + ".ico"); + if (iconFile.exists()) { + Bitmap bmpOrig = BitmapFactory.decodeFile(iconFile.getAbsolutePath()); + if (bmpOrig != null) { + icon.setImageBitmap(bmpOrig); + } + } else { + icon.setImageResource(cursor.getInt(cursor.getColumnIndex("unread")) > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + } catch (NullPointerException e) { + icon.setImageResource(cursor.getInt(cursor.getColumnIndex("unread")) > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + } else { + icon.setImageResource(cursor.getInt(cursor.getColumnIndex("unread")) > 0 ? R.drawable.ic_published : R.drawable.ic_unpublished); + } + + } + + ImageButton ib = (ImageButton) v.findViewById(R.id.feed_menu_button); + + if (ib != null) { + ib.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + return v; + } + } + + public void sortFeeds() { + try { + refresh(); + } catch (NullPointerException e) { + // activity is gone? + } catch (IllegalStateException e) { + // we're probably closing and DB is gone already + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + + sortFeeds(); + m_enableFeedIcons = m_prefs.getBoolean("download_feed_icons", false); + + } + + public int getFeedIdAtPosition(int position) { + Cursor c = (Cursor)m_adapter.getItem(position); + + if (c != null) { + int feedId = c.getInt(0); + return feedId; + } + + return -10000; + } + + public void setSelectedFeedId(int feedId) { + m_selectedFeedId = feedId; + refresh(); + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesActivity.java new file mode 100644 index 00000000..de57c985 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesActivity.java @@ -0,0 +1,167 @@ +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.util.Log; +import android.view.MenuItem; +import android.view.View; + +public class OfflineHeadlinesActivity extends OfflineActivity implements OfflineHeadlinesEventListener { + private final String TAG = this.getClass().getSimpleName(); + + protected SharedPreferences m_prefs; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setAppTheme(m_prefs); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.headlines_articles); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setStatusBarTint(); + setSmallScreen(findViewById(R.id.sw600dp_anchor) == null); + + if (isPortrait() || m_prefs.getBoolean("headlines_hide_sidebar", false)) { + findViewById(R.id.headlines_fragment).setVisibility(View.GONE); + } + + 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(); + hf.initialize(feedId, isCat); + + OfflineArticlePager af = new OfflineArticlePager(); + af.initialize(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 boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + overridePendingTransition(0, R.anim.right_slide_out); + return true; + default: + Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onArticleSelected(int articleId, boolean open) { + + if (!open) { + SQLiteStatement stmt = getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, 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, !isPortrait() && !isSmallScreen()); + m_menu.findItem(R.id.headlines_toggle_sidebar).setVisible(!isPortrait() && !isSmallScreen()); + + 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); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + overridePendingTransition(0, R.anim.right_slide_out); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesEventListener.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesEventListener.java new file mode 100644 index 00000000..0818a66b --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesEventListener.java @@ -0,0 +1,7 @@ +package org.fox.ttrss.offline; + + +public interface OfflineHeadlinesEventListener { + void onArticleSelected(int articleId, boolean open); + void onArticleSelected(int articleId); +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesFragment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesFragment.java new file mode 100644 index 00000000..7f9d73f7 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineHeadlinesFragment.java @@ -0,0 +1,774 @@ +package org.fox.ttrss.offline; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.fox.ttrss.CommonActivity; +import org.fox.ttrss.GlobalState; +import org.fox.ttrss.R; +import org.fox.ttrss.util.TypefaceCache; +import org.jsoup.Jsoup; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources.Theme; +import android.database.Cursor; +import android.database.sqlite.SQLiteStatement; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.Html; +import android.text.Html.ImageGetter; +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.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +public class OfflineHeadlinesFragment extends Fragment implements OnItemClickListener { + public static enum ArticlesSelection { ALL, NONE, UNREAD }; + + private final String TAG = this.getClass().getSimpleName(); + + private int m_feedId; + private boolean m_feedIsCat = false; + private int m_activeArticleId; + private String m_searchQuery = ""; + + private SharedPreferences m_prefs; + + private Cursor m_cursor; + private ArticleListAdapter m_adapter; + + private OfflineHeadlinesEventListener m_listener; + private OfflineActivity m_activity; + private SwipeRefreshLayout m_swipeLayout; + + public void initialize(int feedId, boolean isCat) { + m_feedId = feedId; + m_feedIsCat = isCat; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + } + + public int getSelectedArticleCount() { + Cursor c = m_activity.getReadableDb().query("articles", + new String[] { "COUNT(*)" }, "selected = 1", null, null, null, null); + c.moveToFirst(); + int selected = c.getInt(0); + c.close(); + + 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 modified = 1, 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 modified = 1, 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 modified = 1, 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 modified = 1, 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 modified = 1, 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 modified = 1, 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; + + String updatedOperator = (m_prefs.getBoolean("offline_oldest_first", false)) ? "<" : ">"; + + if (m_feedIsCat) { + stmt = m_activity.getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, unread = 0 WHERE " + + "updated "+updatedOperator+" (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " + + "AND unread = 1 AND feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"); + } else { + stmt = m_activity.getWritableDb().compileStatement( + "UPDATE articles SET modified = 1, unread = 0 WHERE " + + "updated "+updatedOperator+" (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " + + "AND unread = 1 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_context_menu, menu); + + if (getSelectedArticleCount() > 0) { + menu.setHeaderTitle(R.string.headline_context_multiple); + menu.setGroupVisible(R.id.menu_group_single_article, false); + } else { + AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; + Cursor c = getArticleAtPosition(info.position); + 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(); + + m_activity.initMenu(); + } + + public void refresh() { + try { + if (!isAdded()) return; + + m_swipeLayout.setRefreshing(true); + + if (m_cursor != null && !m_cursor.isClosed()) m_cursor.close(); + + m_cursor = createCursor(); + + if (m_cursor != null && m_adapter != null) { + m_adapter.changeCursor(m_cursor); + m_adapter.notifyDataSetChanged(); + } + + m_swipeLayout.setRefreshing(false); + + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + if (savedInstanceState != null) { + m_feedId = savedInstanceState.getInt("feedId"); + m_activeArticleId = savedInstanceState.getInt("activeArticleId"); + //m_selectedArticles = savedInstanceState.getParcelableArrayList("selectedArticles"); + 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); + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.headlines_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + refresh(); + } + }); + + if (!m_activity.isCompatMode()) { + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + } + + m_cursor = createCursor(); + + ListView list = (ListView)view.findViewById(R.id.headlines); + m_adapter = new ArticleListAdapter(getActivity(), R.layout.headlines_row, m_cursor, + new String[] { "title" }, new int[] { R.id.title }, 0); + + /* if (!m_activity.isCompatMode()) { + AnimationSet set = new AnimationSet(true); + + Animation animation = new AlphaAnimation(0.0f, 1.0f); + animation.setDuration(500); + set.addAnimation(animation); + + animation = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, 50.0f,Animation.RELATIVE_TO_SELF, 0.0f, + Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f + ); + animation.setDuration(1000); + set.addAnimation(animation); + + LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f); + + list.setLayoutAnimation(controller); + } */ + + list.setAdapter(m_adapter); + list.setOnItemClickListener(this); + list.setEmptyView(view.findViewById(R.id.no_headlines)); + registerForContextMenu(list); + + //if (m_activity.isSmallScreen()) + // view.findViewById(R.id.headlines_fragment).setPadding(0, 0, 0, 0); + + getActivity().setProgressBarIndeterminateVisibility(false); + + return view; + } + + public Cursor createCursor() { + String feedClause = null; + + if (m_feedIsCat) { + feedClause = "feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"; + } else { + feedClause = "feed_id = ?"; + } + + String viewMode = m_activity.getViewMode(); + + if ("adaptive".equals(viewMode)) { + // TODO: implement adaptive + } else if ("marked".equals(viewMode)) { + feedClause += "AND (marked = 1)"; + } else if ("published".equals(viewMode)) { + feedClause += "AND (published = 1)"; + } else if ("unread".equals(viewMode)) { + feedClause += "AND (unread = 1)"; + } else { // all_articles + // + } + + String orderBy = (m_prefs.getBoolean("offline_oldest_first", false)) ? "updated" : "updated DESC"; + + 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, orderBy); + } else { + 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, orderBy); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + m_listener = (OfflineHeadlinesEventListener) activity; + m_activity = (OfflineActivity) activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); + } + + @Override + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + ListView list = (ListView)av; + + Log.d(TAG, "onItemClick=" + position); + + if (list != null) { + /* Cursor cursor = (Cursor)list.getItemAtPosition(position); + + int articleId = cursor.getInt(0); */ + + int articleId = getArticleIdAtPosition(position); + + if (getActivity().findViewById(R.id.article_fragment) != null) { + m_activeArticleId = articleId; + } + + m_listener.onArticleSelected(articleId); + + refresh(); + } + } + + @Override + public void onSaveInstanceState (Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("feedId", m_feedId); + out.putInt("activeArticleId", m_activeArticleId); + //out.putParcelableArrayList("selectedArticles", m_selectedArticles); + out.putCharSequence("searchQuery", m_searchQuery); + out.putBoolean("feedIsCat", m_feedIsCat); + } + + /* public void setLoadingStatus(int status, boolean showProgress) { + if (getView() != null) { + TextView tv = (TextView)getView().findViewById(R.id.loading_message); + + if (tv != null) { + tv.setText(status); + } + } + + getActivity().setProgressBarIndeterminateVisibility(showProgress); + } */ + + private class ArticleListAdapter extends SimpleCursorAdapter { + public static final int VIEW_NORMAL = 0; + public static final int VIEW_UNREAD = 1; + public static final int VIEW_SELECTED = 2; + public static final int VIEW_SELECTED_UNREAD = 3; + public static final int VIEW_LOADMORE = 4; + + public static final int VIEW_COUNT = VIEW_LOADMORE+1; + + private final Integer[] origTitleColors = new Integer[VIEW_COUNT]; + private final int titleHighScoreUnreadColor; + + public ArticleListAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + + Theme theme = context.getTheme(); + TypedValue tv = new TypedValue(); + theme.resolveAttribute(R.attr.headlineTitleHighScoreUnreadTextColor, tv, true); + titleHighScoreUnreadColor = tv.data; + } + + public int getViewTypeCount() { + return VIEW_COUNT; + } + + @Override + public int getItemViewType(int position) { + Cursor c = (Cursor) getItem(position); + + //Log.d(TAG, "@gIVT " + position + " " + c.getInt(0) + " vs " + m_activeArticleId); + + if (c.getInt(0) == m_activeArticleId && c.getInt(c.getColumnIndex("unread")) == 1) { + return VIEW_SELECTED_UNREAD; + } else if (c.getInt(0) == m_activeArticleId) { + return VIEW_SELECTED; + } else if (c.getInt(c.getColumnIndex("unread")) == 1) { + return VIEW_UNREAD; + } else { + return VIEW_NORMAL; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + View v = convertView; + + Cursor article = (Cursor)getItem(position); + final int articleId = article.getInt(0); + + int headlineFontSize = Integer.parseInt(m_prefs.getString("headlines_font_size_sp", "13")); + int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2)); + + if (v == null) { + int layoutId = R.layout.headlines_row; + + switch (getItemViewType(position)) { + case VIEW_LOADMORE: + layoutId = R.layout.headlines_row_loadmore; + break; + case VIEW_UNREAD: + layoutId = R.layout.headlines_row_unread; + break; + case VIEW_SELECTED_UNREAD: + layoutId = R.layout.headlines_row_selected_unread; + break; + case VIEW_SELECTED: + layoutId = R.layout.headlines_row_selected; + break; + } + + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = vi.inflate(layoutId, null); + + // http://code.google.com/p/android/issues/detail?id=3414 + ((ViewGroup)v).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + } + + TextView tt = (TextView)v.findViewById(R.id.title); + + if (tt != null) { + + tt.setText(Html.fromHtml(article.getString(article.getColumnIndex("title")))); + + if (m_prefs.getBoolean("enable_condensed_fonts", false)) { + Typeface tf = TypefaceCache.get(m_activity, "sans-serif-condensed", article.getInt(article.getColumnIndex("unread")) == 1 ? Typeface.BOLD : Typeface.NORMAL); + + if (tf != null && !tf.equals(tt.getTypeface())) { + tt.setTypeface(tf); + } + + tt.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 5)); + } else { + tt.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 3)); + } + + int scoreIndex = article.getColumnIndex("score"); + if (scoreIndex >= 0) + adjustTitleTextView(article.getInt(scoreIndex), tt, position); + } + + TextView ft = (TextView)v.findViewById(R.id.feed_title); + + int feedTitleIndex = article.getColumnIndex("feed_title"); + + if (ft != null && feedTitleIndex != -1 && m_feedIsCat) { + String feedTitle = article.getString(feedTitleIndex); + + if (feedTitle.length() > 20) + feedTitle = feedTitle.substring(0, 20) + "..."; + + if (feedTitle.length() > 0) { + ft.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + ft.setText(feedTitle); + } else { + ft.setVisibility(View.GONE); + } + } else if (ft != null) { + ft.setVisibility(View.GONE); + } + + ImageView marked = (ImageView)v.findViewById(R.id.marked); + + if (marked != null) { + marked.setImageResource(article.getInt(article.getColumnIndex("marked")) == 1 ? R.drawable.ic_star_full : R.drawable.ic_star_empty); + + marked.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET modified = 1, marked = NOT marked " + + "WHERE " + BaseColumns._ID + " = ?"); + + stmtUpdate.bindLong(1, articleId); + stmtUpdate.execute(); + stmtUpdate.close(); + + refresh(); + } + }); + } + + ImageView published = (ImageView)v.findViewById(R.id.published); + + if (published != null) { + published.setImageResource(article.getInt(article.getColumnIndex("published")) == 1 ? R.drawable.ic_published : R.drawable.ic_unpublished); + + published.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET modified = 1, published = NOT published " + + "WHERE " + BaseColumns._ID + " = ?"); + + stmtUpdate.bindLong(1, articleId); + stmtUpdate.execute(); + stmtUpdate.close(); + + refresh(); + } + }); + } + + TextView te = (TextView)v.findViewById(R.id.excerpt); + + if (te != null) { + if (!m_prefs.getBoolean("headlines_show_content", true)) { + te.setVisibility(View.GONE); + } else { + String excerpt = Jsoup.parse(article.getString(article.getColumnIndex("content"))).text(); + + if (excerpt.length() > CommonActivity.EXCERPT_MAX_SIZE) + excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_SIZE) + "..."; + + te.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineFontSize); + te.setText(excerpt); + } + } + + TextView ta = (TextView)v.findViewById(R.id.author); + + if (ta != null) { + int authorIndex = article.getColumnIndex("author"); + if (authorIndex >= 0) { + String author = article.getString(authorIndex); + + ta.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + + if (author != null && author.length() > 0) + ta.setText(getString(R.string.author_formatted, author)); + else + ta.setText(""); + } + } + + /* ImageView separator = (ImageView)v.findViewById(R.id.headlines_separator); + + if (separator != null && m_offlineServices.isSmallScreen()) { + separator.setVisibility(View.GONE); + } */ + + TextView dv = (TextView) v.findViewById(R.id.date); + + if (dv != null) { + dv.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + + Date d = new Date((long)article.getInt(article.getColumnIndex("updated")) * 1000); + DateFormat df = new SimpleDateFormat("MMM dd, HH:mm"); + df.setTimeZone(TimeZone.getDefault()); + dv.setText(df.format(d)); + } + + CheckBox cb = (CheckBox) v.findViewById(R.id.selected); + + if (cb != null) { + cb.setChecked(article.getInt(article.getColumnIndex("selected")) == 1); + cb.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View view) { + CheckBox cb = (CheckBox)view; + + SQLiteStatement stmtUpdate = m_activity.getWritableDb().compileStatement("UPDATE articles SET selected = ? " + + "WHERE " + BaseColumns._ID + " = ?"); + + stmtUpdate.bindLong(1, cb.isChecked() ? 1 : 0); + stmtUpdate.bindLong(2, articleId); + stmtUpdate.execute(); + stmtUpdate.close(); + + refresh(); + + m_activity.initMenu(); + + } + }); + } + + ImageView ib = (ImageView) v.findViewById(R.id.article_menu_button); + + if (ib != null) { + //if (m_activity.isDarkTheme()) + // ib.setImageResource(R.drawable.ic_mailbox_collapsed_holo_dark); + + ib.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + + return v; + } + + private void adjustTitleTextView(int score, TextView tv, int position) { + int viewType = getItemViewType(position); + if (origTitleColors[viewType] == null) + // store original color + origTitleColors[viewType] = Integer.valueOf(tv.getCurrentTextColor()); + + if (score < -500) { + tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else if (score > 500) { + tv.setTextColor(titleHighScoreUnreadColor); + tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); + } else { + tv.setTextColor(origTitleColors[viewType].intValue()); + tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); + } + } + } + + public void notifyUpdated() { + m_adapter.notifyDataSetChanged(); + } + + public void setActiveArticleId(int articleId) { + m_activeArticleId = articleId; + 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) { + return (Cursor) m_adapter.getItem(position); + } + + public int getArticleIdAtPosition(int position) { + /*Cursor c = getArticleAtPosition(position); + + if (c != null) { + int id = c.getInt(0); + return id; + } */ + + return (int) m_adapter.getItemId(position); + } + + public int getActiveArticleId() { + return m_activeArticleId; + } + + public int getArticleIdPosition(int articleId) { + for (int i = 0; i < m_adapter.getCount(); i++) { + if (articleId == m_adapter.getItemId(i)) + return i; + } + + return -1; + } + + public int getArticleCount() { + return m_adapter.getCount(); + } + + public void setSearchQuery(String query) { + if (!m_searchQuery.equals(query)) { + m_searchQuery = query; + } + } + + public int getFeedId() { + return m_feedId; + } + + public boolean getFeedIsCat() { + return m_feedIsCat; + } + + public String getSearchQuery() { + return m_searchQuery; + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineUploadService.java b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineUploadService.java new file mode 100644 index 00000000..4c3349d4 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/offline/OfflineUploadService.java @@ -0,0 +1,286 @@ +package org.fox.ttrss.offline; + +import java.util.HashMap; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.OnlineActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.util.DatabaseHelper; + +import android.app.IntentService; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import com.google.gson.JsonElement; + +public class OfflineUploadService extends IntentService { + private final String TAG = this.getClass().getSimpleName(); + + public static final int NOTIFY_UPLOADING = 2; + public static final String INTENT_ACTION_SUCCESS = "org.fox.ttrss.intent.action.UploadComplete"; + + private SQLiteDatabase m_writableDb; + private SQLiteDatabase m_readableDb; + private String m_sessionId; + private NotificationManager m_nmgr; + private boolean m_uploadInProgress = false; + private boolean m_batchMode = false; + + public OfflineUploadService() { + super("OfflineUploadService"); + } + + @Override + public void onCreate() { + super.onCreate(); + m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + initDatabase(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + 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, OnlineActivity.class), 0); + + notification.flags |= Notification.FLAG_ONGOING_EVENT; + notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + + notification.setLatestEventInfo(this, getString(R.string.notify_uploading_title), msg, contentIntent); + + m_nmgr.notify(NOTIFY_UPLOADING, notification); + } + + private void updateNotification(int msgResId) { + updateNotification(getString(msgResId)); + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + private synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } + + private synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + private void uploadRead() { + Log.d(TAG, "syncing modified offline data... (read)"); + + final String ids = getModifiedIds(ModifiedCriteria.READ); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + uploadMarked(); + } else { + updateNotification(getErrorMessage()); + uploadFailed(); + } + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "0"); + put("field", "2"); + } + }; + + req.execute(map); + } else { + uploadMarked(); + } + } + + private enum ModifiedCriteria { + READ, MARKED, PUBLISHED + }; + + private String getModifiedIds(ModifiedCriteria criteria) { + + String criteriaStr = ""; + + switch (criteria) { + case READ: + criteriaStr = "unread = 0"; + break; + case MARKED: + criteriaStr = "marked = 1"; + break; + case PUBLISHED: + criteriaStr = "published = 1"; + break; + } + + Cursor c = getReadableDb().query("articles", null, + "modified = 1 AND " + criteriaStr, null, null, null, null); + + String tmp = ""; + + while (c.moveToNext()) { + tmp += c.getInt(0) + ","; + } + + tmp = tmp.replaceAll(",$", ""); + + c.close(); + + return tmp; + } + + private void uploadMarked() { + Log.d(TAG, "syncing modified offline data... (marked)"); + + final String ids = getModifiedIds(ModifiedCriteria.MARKED); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + uploadPublished(); + } else { + updateNotification(getErrorMessage()); + uploadFailed(); + } + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "1"); + put("field", "0"); + } + }; + + req.execute(map); + } else { + uploadPublished(); + } + } + + private void uploadFailed() { + m_readableDb.close(); + m_writableDb.close(); + + // TODO send notification to activity? + + m_uploadInProgress = false; + } + + private void uploadSuccess() { + getWritableDb().execSQL("UPDATE articles SET modified = 0"); + + if (m_batchMode) { + + SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = localPrefs.edit(); + editor.putBoolean("offline_mode_active", false); + editor.commit(); + + } else { + Intent intent = new Intent(); + intent.setAction(INTENT_ACTION_SUCCESS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + sendBroadcast(intent); + } + + m_readableDb.close(); + m_writableDb.close(); + + m_uploadInProgress = false; + + m_nmgr.cancel(NOTIFY_UPLOADING); + } + + private void uploadPublished() { + Log.d(TAG, "syncing modified offline data... (published)"); + + final String ids = getModifiedIds(ModifiedCriteria.MARKED); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + uploadSuccess(); + } else { + updateNotification(getErrorMessage()); + uploadFailed(); + } + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "1"); + put("field", "1"); + } + }; + + req.execute(map); + } else { + uploadSuccess(); + } + } + + + @Override + protected void onHandleIntent(Intent intent) { + try { + if (getWritableDb().isDbLockedByCurrentThread() || getWritableDb().isDbLockedByOtherThreads()) { + return; + } + + m_sessionId = intent.getStringExtra("sessionId"); + m_batchMode = intent.getBooleanExtra("batchMode", false); + + if (!m_uploadInProgress) { + m_uploadInProgress = true; + + updateNotification(R.string.notify_uploading_sending_data); + + uploadRead(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java new file mode 100644 index 00000000..63458532 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java @@ -0,0 +1,57 @@ +package org.fox.ttrss.share; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Display; +import android.widget.Toast; + +public class CommonActivity extends Activity { + private final String TAG = this.getClass().getSimpleName(); + + private boolean m_smallScreenMode = true; + private boolean m_compatMode = false; + + protected void setSmallScreen(boolean smallScreen) { + Log.d(TAG, "m_smallScreenMode=" + smallScreen); + m_smallScreenMode = smallScreen; + } + + public void toast(int msgId) { + Toast toast = Toast.makeText(CommonActivity.this, msgId, Toast.LENGTH_SHORT); + toast.show(); + } + + public void toast(String msg) { + Toast toast = Toast.makeText(CommonActivity.this, msg, Toast.LENGTH_SHORT); + toast.show(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + m_compatMode = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB; + + Log.d(TAG, "m_compatMode=" + m_compatMode); + + super.onCreate(savedInstanceState); + } + + public boolean isSmallScreen() { + return m_smallScreenMode; + } + + public boolean isCompatMode() { + return m_compatMode; + } + + public boolean isPortrait() { + Display display = getWindowManager().getDefaultDisplay(); + + int width = display.getWidth(); + int height = display.getHeight(); + + return width < height; + } + + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonShareActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonShareActivity.java new file mode 100644 index 00000000..165d38f7 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/share/CommonShareActivity.java @@ -0,0 +1,136 @@ +package org.fox.ttrss.share; + +import java.util.HashMap; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.PreferencesActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.util.SimpleLoginManager; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + + +public abstract class CommonShareActivity extends CommonActivity { + protected SharedPreferences m_prefs; + protected String m_sessionId; + protected int m_apiLevel = 0; + + private final String TAG = this.getClass().getSimpleName(); + + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + m_sessionId = savedInstanceState.getString("sessionId"); + m_apiLevel = savedInstanceState.getInt("apiLevel"); + } + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("sessionId", m_sessionId); + out.putInt("apiLevel", m_apiLevel); + } + + protected abstract void onLoggedIn(int requestId); + + protected abstract void onLoggingIn(int requestId); + + public void login(int requestId) { + + if (m_prefs.getString("ttrss_url", "").trim().length() == 0) { + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.dialog_need_configure_prompt) + .setCancelable(false) + .setPositiveButton(R.string.dialog_open_preferences, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // launch preferences + + Intent intent = new Intent(CommonShareActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } else { + + SimpleLoginManager loginManager = new SimpleLoginManager() { + + @Override + protected void onLoginSuccess(int requestId, String sessionId, int apiLevel) { + m_sessionId = sessionId; + m_apiLevel = apiLevel; + + CommonShareActivity.this.onLoggedIn(requestId); + } + + @Override + protected void onLoginFailed(int requestId, ApiRequest ar) { + toast(ar.getErrorMessage()); + setProgressBarIndeterminateVisibility(false); + } + + @Override + protected void onLoggingIn(int requestId) { + CommonShareActivity.this.onLoggingIn(requestId); + } + }; + + String login = m_prefs.getString("login", "").trim(); + String password = m_prefs.getString("password", "").trim(); + + loginManager.logIn(this, requestId, login, password); + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.preferences: + Intent intent = new Intent(CommonShareActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.share_menu, menu); + return true; + } + + + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java new file mode 100644 index 00000000..dff48502 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java @@ -0,0 +1,146 @@ +package org.fox.ttrss.share; + +import java.util.HashMap; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.R; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.EditText; + +import com.google.gson.JsonElement; + +public class ShareActivity extends CommonShareActivity { + private final String TAG = this.getClass().getSimpleName(); + + private Button m_button; + + @Override + public void onCreate(Bundle savedInstanceState) { + //setTheme(R.style.DarkTheme); + + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_LEFT_ICON); + + Intent intent = getIntent(); + + String urlValue = intent.getStringExtra(Intent.EXTRA_TEXT); + String titleValue = intent.getStringExtra(Intent.EXTRA_SUBJECT); + String contentValue = ""; + + if (savedInstanceState != null) { + urlValue = savedInstanceState.getString("url"); + titleValue = savedInstanceState.getString("title"); + contentValue = savedInstanceState.getString("content"); + } + + setContentView(R.layout.share); + + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.icon); + + setSmallScreen(false); + + EditText url = (EditText) findViewById(R.id.url); + url.setText(urlValue); + + EditText title = (EditText) findViewById(R.id.title); + title.setText(titleValue); + + EditText content = (EditText) findViewById(R.id.content); + content.setText(contentValue); + + m_button = (Button) findViewById(R.id.share_button); + + m_button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + login(0); + } + }); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + EditText url = (EditText) findViewById(R.id.url); + + if (url != null) { + out.putString("url", url.getText().toString()); + } + + EditText title = (EditText) findViewById(R.id.title); + + if (title != null) { + out.putString("title", title.getText().toString()); + } + + EditText content = (EditText) findViewById(R.id.content); + + if (content != null) { + out.putString("content", content.getText().toString()); + } + + } + + private void postData() { + m_button.setEnabled(false); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + setProgressBarIndeterminateVisibility(false); + + if (m_lastError != ApiError.NO_ERROR) { + toast(getErrorMessage()); + } else { + toast(R.string.share_article_posted); + finish(); + } + + m_button.setEnabled(true); + } + }; + + final EditText url = (EditText) findViewById(R.id.url); + final EditText title = (EditText) findViewById(R.id.title); + final EditText content = (EditText) findViewById(R.id.content); + + if (url != null && title != null && content != null) { + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "shareToPublished"); + put("title", title.getText().toString()); + put("url", url.getText().toString()); + put("content", content.getText().toString()); + } + }; + + setProgressBarIndeterminateVisibility(true); + + req.execute(map); + } + } + + + @Override + public void onLoggingIn(int requestId) { + m_button.setEnabled(false); + } + + @Override + protected void onLoggedIn(int requestId) { + m_button.setEnabled(true); + + if (m_apiLevel < 4) { + toast(R.string.api_too_low); + } else { + postData(); + } + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java new file mode 100644 index 00000000..bd7e964f --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java @@ -0,0 +1,321 @@ +package org.fox.ttrss.share; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.R; +import org.fox.ttrss.types.FeedCategory; +import org.fox.ttrss.types.FeedCategoryList; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +public class SubscribeActivity extends CommonShareActivity { + private final String TAG = this.getClass().getSimpleName(); + + private Button m_postButton; + private Button m_catButton; + private CatListAdapter m_adapter; + private FeedCategoryList m_cats = new FeedCategoryList(); + + private static final int REQ_CATS = 1; + private static final int REQ_POST = 2; + + class CatTitleComparator implements Comparator<FeedCategory> { + + @Override + public int compare(FeedCategory a, FeedCategory b) { + if (a.id >= 0 && b.id >= 0) + return a.title.compareTo(b.title); + else + return a.id - b.id; + } + + } + + public void sortCats() { + Comparator<FeedCategory> cmp = new CatTitleComparator(); + + Collections.sort(m_cats, cmp); + try { + m_adapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + // adapter missing + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_LEFT_ICON); + + String urlValue = getIntent().getDataString(); + + if (urlValue == null) + urlValue = getIntent().getStringExtra(Intent.EXTRA_TEXT); + + if (savedInstanceState != null) { + urlValue = savedInstanceState.getString("url"); + + ArrayList<FeedCategory> list = savedInstanceState.getParcelableArrayList("cats"); + + for (FeedCategory c : list) + m_cats.add(c); + } + + setContentView(R.layout.subscribe); + + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.icon); + + setSmallScreen(false); + + Spinner catList = (Spinner) findViewById(R.id.category_spinner); + + if (m_cats.size() == 0) m_cats.add(new FeedCategory(0, "Uncategorized", 0)); + + m_adapter = new CatListAdapter(this, android.R.layout.simple_spinner_dropdown_item, m_cats); + catList.setAdapter(m_adapter); + + EditText feedUrl = (EditText) findViewById(R.id.feed_url); + feedUrl.setText(urlValue); + + m_postButton = (Button) findViewById(R.id.subscribe_button); + + m_postButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + login(REQ_POST); + } + }); + + m_catButton = (Button) findViewById(R.id.cats_button); + + m_catButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + login(REQ_CATS); + } + }); + + login(REQ_CATS); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + EditText url = (EditText) findViewById(R.id.url); + + if (url != null) { + out.putString("url", url.getText().toString()); + } + + out.putParcelableArrayList("cats", m_cats); + + } + + private void subscribeToFeed() { + m_postButton.setEnabled(false); + + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + setProgressBarIndeterminateVisibility(false); + + if (m_lastError != ApiError.NO_ERROR) { + toast(getErrorMessage()); + } else { + try { + int rc = -1; + + try { + rc = result.getAsJsonObject().get("status").getAsJsonObject().get("code").getAsInt(); + } catch (Exception e) { + e.printStackTrace(); + } + + switch (rc) { + case -1: + toast(R.string.error_api_unknown); + //finish(); + break; + case 0: + toast(R.string.error_feed_already_exists_); + //finish(); + break; + case 1: + toast(R.string.subscribed_to_feed); + finish(); + break; + case 2: + toast(R.string.error_invalid_url); + break; + case 3: + toast(R.string.error_url_is_an_html_page_no_feeds_found); + break; + case 4: + toast(R.string.error_url_contains_multiple_feeds); + break; + case 5: + toast(R.string.error_could_not_download_url); + break; + } + + } catch (Exception e) { + toast(R.string.error_while_subscribing); + e.printStackTrace(); + } + } + + m_postButton.setEnabled(true); + } + }; + + Spinner catSpinner = (Spinner) findViewById(R.id.category_spinner); + + final FeedCategory cat = (FeedCategory) m_adapter.getCategory(catSpinner.getSelectedItemPosition()); + final EditText feedUrl = (EditText) findViewById(R.id.feed_url); + + if (feedUrl != null ) { + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "subscribeToFeed"); + put("feed_url", feedUrl.getText().toString()); + + if (cat != null) { + put("category_id", String.valueOf(cat.id)); + } + } + }; + + setProgressBarIndeterminateVisibility(true); + + req.execute(map); + } + } + + @Override + public void onLoggingIn(int requestId) { + switch (requestId) { + case REQ_CATS: + m_catButton.setEnabled(false); + break; + case REQ_POST: + m_postButton.setEnabled(false); + break; + } + } + + private void updateCats() { + ApiRequest req = new ApiRequest(getApplicationContext()) { + protected void onPostExecute(JsonElement result) { + setProgressBarIndeterminateVisibility(false); + + if (m_lastError != ApiError.NO_ERROR) { + toast(getErrorMessage()); + } else { + JsonArray content = result.getAsJsonArray(); + + if (content != null) { + Type listType = new TypeToken<List<FeedCategory>>() {}.getType(); + final List<FeedCategory> cats = new Gson().fromJson(content, listType); + + m_cats.clear(); + + for (FeedCategory c : cats) { + if (c.id > 0) + m_cats.add(c); + } + + sortCats(); + + m_cats.add(0, new FeedCategory(0, "Uncategorized", 0)); + + m_adapter.notifyDataSetChanged(); + + toast(R.string.category_list_updated); + } + } + + m_catButton.setEnabled(true); + } + }; + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "getCategories"); + } + }; + + setProgressBarIndeterminateVisibility(true); + + req.execute(map); + } + + @Override + protected void onLoggedIn(int requestId) { + switch (requestId) { + case REQ_CATS: + updateCats(); + break; + case REQ_POST: + m_postButton.setEnabled(true); + if (m_apiLevel < 5) { + toast(R.string.api_too_low); + } else { + subscribeToFeed(); + } + break; + } + } + + private class CatListAdapter extends ArrayAdapter<String> { + private List<FeedCategory> m_items; + + public CatListAdapter(Context context, int resource, + List<FeedCategory> items) { + super(context, resource); + + m_items = items; + } + + @Override + public String getItem(int item) { + return m_items.get(item).title; + } + + public FeedCategory getCategory(int item) { + try { + return m_items.get(item); + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + + @Override + public int getCount() { + return m_items.size(); + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerReceiver.java b/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerReceiver.java new file mode 100644 index 00000000..1b1351cb --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerReceiver.java @@ -0,0 +1,93 @@ +package org.fox.ttrss.tasker; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.CommonActivity; +import org.fox.ttrss.OnlineActivity; +import org.fox.ttrss.offline.OfflineDownloadService; +import org.fox.ttrss.offline.OfflineUploadService; +import org.fox.ttrss.util.SimpleLoginManager; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +public class TaskerReceiver extends BroadcastReceiver { + private final String TAG = this.getClass().getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Got action: " + intent.getAction()); + + final Context fContext = context; + + if (com.twofortyfouram.locale.Intent.ACTION_FIRE_SETTING.equals(intent.getAction())) { + + final Bundle settings = intent.getBundleExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE); + final int actionId = settings != null ? settings.getInt("actionId", -1) : -1; + + Log.d(TAG, "received action id=" + actionId); + + SimpleLoginManager loginMgr = new SimpleLoginManager() { + + @Override + protected void onLoginSuccess(int requestId, String sessionId, int apiLevel) { + + switch (actionId) { + case TaskerSettingsActivity.ACTION_DOWNLOAD: + if (true) { + Intent intent = new Intent(fContext, + OfflineDownloadService.class); + intent.putExtra("sessionId", sessionId); + intent.putExtra("batchMode", true); + + fContext.startService(intent); + } + break; + case TaskerSettingsActivity.ACTION_UPLOAD: + if (true) { + Intent intent = new Intent(fContext, + OfflineUploadService.class); + intent.putExtra("sessionId", sessionId); + intent.putExtra("batchMode", true); + + fContext.startService(intent); + } + break; + default: + Log.d(TAG, "unknown action id=" + actionId); + } + } + + @Override + protected void onLoginFailed(int requestId, ApiRequest ar) { + Toast toast = Toast.makeText(fContext, fContext.getString(ar.getErrorMessage()), Toast.LENGTH_SHORT); + toast.show(); + } + + @Override + protected void onLoggingIn(int requestId) { + // + } + }; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + String login = prefs.getString("login", "").trim(); + String password = prefs.getString("password", "").trim(); + String ttrssUrl = prefs.getString("ttrss_url", "").trim(); + + if (ttrssUrl.equals("")) { + Toast toast = Toast.makeText(fContext, "Could not download articles: not configured?", Toast.LENGTH_SHORT); + toast.show(); + } else { + loginMgr.logIn(context, 1, login, password); + } + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerSettingsActivity.java b/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerSettingsActivity.java new file mode 100644 index 00000000..0770fc00 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/tasker/TaskerSettingsActivity.java @@ -0,0 +1,96 @@ +package org.fox.ttrss.tasker; + +import org.fox.ttrss.R; +import org.fox.ttrss.offline.OfflineDownloadService; +import org.fox.ttrss.offline.OfflineUploadService; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; + +public class TaskerSettingsActivity extends Activity { + protected static final int ACTION_DOWNLOAD = 0; + protected static final int ACTION_UPLOAD = 1; + + private final String TAG = this.getClass().getSimpleName(); + + protected Bundle m_settings = new Bundle(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle settings = getIntent().getBundleExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE); + + int actionId = settings != null ? settings.getInt("actionId", -1) : -1; + + setContentView(R.layout.tasker_settings); + + RadioGroup radioGroup = (RadioGroup) findViewById(R.id.taskerActions); + + switch (actionId) { + case TaskerSettingsActivity.ACTION_DOWNLOAD: + radioGroup.check(R.id.actionDownload); + break; + case TaskerSettingsActivity.ACTION_UPLOAD: + radioGroup.check(R.id.actionUpload); + break; + default: + Log.d(TAG, "unknown action id=" + actionId); + } + + radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.actionDownload: + m_settings.putInt("actionId", ACTION_DOWNLOAD); + break; + case R.id.actionUpload: + m_settings.putInt("actionId", ACTION_UPLOAD); + break; + } + } + }); + + Button button = (Button)findViewById(R.id.close_button); + + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + + @Override + public void finish() { + final Intent intent = new Intent(); + + intent.putExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE, m_settings); + + String blurb = "?"; + + switch (m_settings.getInt("actionId")) { + case TaskerSettingsActivity.ACTION_DOWNLOAD: + blurb = getString(R.string.download_articles_and_go_offline); + break; + case TaskerSettingsActivity.ACTION_UPLOAD: + blurb = getString(R.string.synchronize_read_articles_and_go_online); + break; + } + + intent.putExtra(com.twofortyfouram.locale.Intent.EXTRA_STRING_BLURB, blurb); + + setResult(RESULT_OK, intent); + + super.finish(); + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/Article.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Article.java new file mode 100644 index 00000000..9beea81a --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Article.java @@ -0,0 +1,115 @@ +package org.fox.ttrss.types; +import java.util.ArrayList; +import java.util.List; + + +import android.os.Parcel; +import android.os.Parcelable; + +// TODO: serialize Labels +public class Article implements Parcelable { + public int id; + public boolean unread; + public boolean marked; + public boolean published; + public int score; + public int updated; + public boolean is_updated; + public String title; + public String link; + public int feed_id; + public List<String> tags; + public List<Attachment> attachments; + public String content; + public List<List<String>> labels; + public String feed_title; + public int comments_count; + public String comments_link; + public boolean always_display_attachments; + public String author; + public String note; + + public Article(Parcel in) { + readFromParcel(in); + } + + public Article() { + + } + + public Article(int id) { + this.id = id; + this.title = ""; + this.link = ""; + this.tags = new ArrayList<String>(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(id); + out.writeInt(unread ? 1 : 0); + out.writeInt(marked ? 1 : 0); + out.writeInt(published ? 1 : 0); + out.writeInt(score); + out.writeInt(updated); + out.writeInt(is_updated ? 1 : 0); + out.writeString(title); + out.writeString(link); + out.writeInt(feed_id); + out.writeStringList(tags); + out.writeString(content); + out.writeList(attachments); + out.writeString(feed_title); + out.writeInt(comments_count); + out.writeString(comments_link); + out.writeInt(always_display_attachments ? 1 : 0); + out.writeString(author); + out.writeString(note); + } + + public void readFromParcel(Parcel in) { + id = in.readInt(); + unread = in.readInt() == 1; + marked = in.readInt() == 1; + published = in.readInt() == 1; + score = in.readInt(); + updated = in.readInt(); + is_updated = in.readInt() == 1; + title = in.readString(); + link = in.readString(); + feed_id = in.readInt(); + + if (tags == null) tags = new ArrayList<String>(); + in.readStringList(tags); + + content = in.readString(); + + attachments = new ArrayList<Attachment>(); + in.readList(attachments, Attachment.class.getClassLoader()); + + feed_title = in.readString(); + + comments_count = in.readInt(); + comments_link = in.readString(); + always_display_attachments = in.readInt() == 1; + author = in.readString(); + note = in.readString(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public Article createFromParcel(Parcel in) { + return new Article(in); + } + + public Article[] newArray(int size) { + return new Article[size]; + } + }; +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/ArticleList.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/ArticleList.java new file mode 100644 index 00000000..c9b491ee --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/ArticleList.java @@ -0,0 +1,59 @@ +package org.fox.ttrss.types; + +import java.util.ArrayList; + + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +@SuppressWarnings("serial") +public class ArticleList extends ArrayList<Article> implements Parcelable { + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeList(this); + } + + public Article findById(int id) { + for (Article a : this) { + if (a.id == id) + return a; + } + return null; + } + + public void readFromParcel(Parcel in) { + in.readList(this, getClass().getClassLoader()); + } + + public ArticleList() { } + + public ArticleList(Parcel in) { + readFromParcel(in); + } + + public boolean containsId(int id) { + for (Article a : this) { + if (a.id == id) + return true; + } + return false; + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ArticleList createFromParcel(Parcel in) { + return new ArticleList(in); + } + + public ArticleList[] newArray(int size) { + return new ArticleList[size]; + } + }; +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/Attachment.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Attachment.java new file mode 100644 index 00000000..9e363dba --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Attachment.java @@ -0,0 +1,75 @@ +package org.fox.ttrss.types; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import android.os.Parcel; +import android.os.Parcelable; + +public class Attachment implements Parcelable { + public int id; + public String content_url; + public String content_type; + public String title; + public String duration; + public int post_id; + + public Attachment(Parcel in) { + readFromParcel(in); + } + + public Attachment() { + + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(id); + out.writeString(content_url); + out.writeString(content_type); + out.writeString(title); + out.writeString(duration); + out.writeInt(post_id); + } + + public String toString() { + if (title != null && title.length() > 0) { + return title; + } else { + try { + URL url = new URL(content_url.trim()); + return new File(url.getFile()).getName(); + } catch (MalformedURLException e) { + return content_url; + } + } + } + + public void readFromParcel(Parcel in) { + id = in.readInt(); + content_url = in.readString(); + content_type = in.readString(); + title = in.readString(); + duration = in.readString(); + post_id = in.readInt(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public Attachment createFromParcel(Parcel in) { + return new Attachment(in); + } + + public Attachment[] newArray(int size) { + return new Attachment[size]; + } + }; + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/Feed.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Feed.java new file mode 100644 index 00000000..6cf4a1b1 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Feed.java @@ -0,0 +1,90 @@ +package org.fox.ttrss.types; + +import android.os.Parcel; +import android.os.Parcelable; + +public class Feed implements Comparable<Feed>, Parcelable { + public String feed_url; + public String title; + public int id; + public int unread; + public boolean has_icon; + public int cat_id; + public int last_updated; + public int order_id; + public boolean is_cat; + + public Feed(int id, String title, boolean is_cat) { + this.id = id; + this.title = title; + this.is_cat = is_cat; + } + + public Feed(Parcel in) { + readFromParcel(in); + } + + public Feed() { + + } + + 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) + return feed.unread - this.unread; + else + return this.title.compareTo(feed.title); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(feed_url); + out.writeString(title); + out.writeInt(id); + out.writeInt(unread); + out.writeInt(has_icon ? 1 : 0); + out.writeInt(cat_id); + out.writeInt(last_updated); + out.writeInt(is_cat ? 1 : 0); + out.writeInt(order_id); + } + + public void readFromParcel(Parcel in) { + feed_url = in.readString(); + title = in.readString(); + id = in.readInt(); + unread = in.readInt(); + has_icon = in.readInt() == 1; + cat_id = in.readInt(); + last_updated = in.readInt(); + is_cat = in.readInt() == 1; + order_id = in.readInt(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public Feed createFromParcel(Parcel in) { + return new Feed(in); + } + + public Feed[] newArray(int size) { + return new Feed[size]; + } + }; +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategory.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategory.java new file mode 100644 index 00000000..c8193f94 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategory.java @@ -0,0 +1,58 @@ +package org.fox.ttrss.types; + +import android.os.Parcel; +import android.os.Parcelable; + +public class FeedCategory implements Parcelable { + public int id; + public String title; + public int unread; + public int order_id; + + public FeedCategory(Parcel in) { + readFromParcel(in); + } + + public FeedCategory(int id, String title, int unread) { + this.id = id; + this.title = title; + this.unread = unread; + this.order_id = 0; + } + + public FeedCategory() { + + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(id); + out.writeString(title); + out.writeInt(unread); + out.writeInt(order_id); + } + + public void readFromParcel(Parcel in) { + id = in.readInt(); + title = in.readString(); + unread = in.readInt(); + order_id = in.readInt(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public FeedCategory createFromParcel(Parcel in) { + return new FeedCategory(in); + } + + public FeedCategory[] newArray(int size) { + return new FeedCategory[size]; + } + }; +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java new file mode 100644 index 00000000..eb5331bc --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java @@ -0,0 +1,43 @@ +package org.fox.ttrss.types; + +import java.util.ArrayList; + + +import android.os.Parcel; +import android.os.Parcelable; + +@SuppressWarnings("serial") +public class FeedCategoryList extends ArrayList<FeedCategory> implements Parcelable { + + public FeedCategoryList() { } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeList(this); + } + + public void readFromParcel(Parcel in) { + in.readList(this, getClass().getClassLoader()); + } + + public FeedCategoryList(Parcel in) { + readFromParcel(in); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public FeedCategoryList createFromParcel(Parcel in) { + return new FeedCategoryList(in); + } + + public FeedCategoryList[] newArray(int size) { + return new FeedCategoryList[size]; + } + }; + } diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedList.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedList.java new file mode 100644 index 00000000..350f45ad --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/FeedList.java @@ -0,0 +1,43 @@ +package org.fox.ttrss.types; + +import java.util.ArrayList; + + +import android.os.Parcel; +import android.os.Parcelable; + +@SuppressWarnings("serial") +public class FeedList extends ArrayList<Feed> implements Parcelable { + + public FeedList() { } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeList(this); + } + + public void readFromParcel(Parcel in) { + in.readList(this, getClass().getClassLoader()); + } + + public FeedList(Parcel in) { + readFromParcel(in); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public FeedList createFromParcel(Parcel in) { + return new FeedList(in); + } + + public FeedList[] newArray(int size) { + return new FeedList[size]; + } + }; + } diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/types/Label.java b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Label.java new file mode 100644 index 00000000..0d4f3699 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/types/Label.java @@ -0,0 +1,13 @@ +package org.fox.ttrss.types; + +public class Label { + public int id; + public String caption; + public String fg_color; + public String bg_color; + public boolean checked; + + public Label() { + + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/AppRater.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/AppRater.java new file mode 100644 index 00000000..21dccdff --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/AppRater.java @@ -0,0 +1,105 @@ +package org.fox.ttrss.util; + +// From http://androidsnippets.com/prompt-engaged-users-to-rate-your-app-in-the-android-market-appirater + +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class AppRater { + private final static String APP_TITLE = "Tiny Tiny RSS"; + private final static String APP_PNAME = "org.fox.ttrss"; + + private final static int DAYS_UNTIL_PROMPT = 3; + private final static int LAUNCHES_UNTIL_PROMPT = 7; + + public static void appLaunched(Context mContext) { + SharedPreferences prefs = mContext.getSharedPreferences("apprater", 0); + + if (prefs.getBoolean("dontshowagain", false)) { return ; } + + SharedPreferences.Editor editor = prefs.edit(); + + // Increment launch counter + long launch_count = prefs.getLong("launch_count", 0) + 1; + editor.putLong("launch_count", launch_count); + + // Get date of first launch + Long date_firstLaunch = prefs.getLong("date_firstlaunch", 0); + if (date_firstLaunch == 0) { + date_firstLaunch = System.currentTimeMillis(); + editor.putLong("date_firstlaunch", date_firstLaunch); + } + + // Wait at least n days before opening + if (launch_count >= LAUNCHES_UNTIL_PROMPT && + !prefs.getBoolean("dontshowagain", false) && + System.currentTimeMillis() >= date_firstLaunch + (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)) { + + showRateDialog(mContext, editor); + } + + editor.commit(); + } + + public static void showRateDialog(final Context mContext, final SharedPreferences.Editor editor) { + final Dialog dialog = new Dialog(mContext); + dialog.setTitle("Rate " + APP_TITLE); + + LinearLayout ll = new LinearLayout(mContext); + ll.setOrientation(LinearLayout.VERTICAL); + + TextView tv = new TextView(mContext); + tv.setText("If you enjoy using " + APP_TITLE + ", please take a moment to rate it. Thanks for your support!"); + tv.setWidth(240); + tv.setPadding(4, 0, 4, 10); + ll.addView(tv); + + Button b1 = new Button(mContext); + b1.setText("Rate " + APP_TITLE); + b1.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + try { + mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + APP_PNAME))); + } catch (ActivityNotFoundException e) { + mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + APP_PNAME))); + } + dialog.dismiss(); + } + }); + ll.addView(b1); + + Button b2 = new Button(mContext); + b2.setText("Remind me later"); + b2.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + dialog.dismiss(); + } + }); + ll.addView(b2); + + Button b3 = new Button(mContext); + b3.setText("No, thanks"); + b3.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (editor != null) { + editor.putBoolean("dontshowagain", true); + editor.commit(); + } + dialog.dismiss(); + } + }); + ll.addView(b3); + + dialog.setContentView(ll); + dialog.show(); + } +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/DatabaseHelper.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/DatabaseHelper.java new file mode 100644 index 00000000..572ff62e --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/DatabaseHelper.java @@ -0,0 +1,89 @@ +package org.fox.ttrss.util; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.BaseColumns; + + +public class DatabaseHelper extends SQLiteOpenHelper { + + @SuppressWarnings("unused") + private final String TAG = this.getClass().getSimpleName(); + public static final String DATABASE_NAME = "OfflineStorage.db"; + public static final int DATABASE_VERSION = 4; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("DROP VIEW IF EXISTS cats_unread;"); + db.execSQL("DROP VIEW IF EXISTS feeds_unread;"); + db.execSQL("DROP TRIGGER IF EXISTS articles_set_modified;"); + db.execSQL("DROP TABLE IF EXISTS categories;"); + db.execSQL("DROP TABLE IF EXISTS feeds;"); + db.execSQL("DROP TABLE IF EXISTS articles;"); + + db.execSQL("CREATE TABLE IF NOT EXISTS feeds (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + "feed_url TEXT, " + + "title TEXT, " + + "has_icon BOOLEAN, " + + "cat_id INTEGER" + + ");"); + + db.execSQL("CREATE TABLE IF NOT EXISTS categories (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + "title TEXT" + + ");"); + + db.execSQL("CREATE TABLE IF NOT EXISTS articles (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + "unread BOOLEAN, " + + "marked BOOLEAN, " + + "published BOOLEAN, " + + "score INTEGER, " + + "updated INTEGER, " + + "is_updated BOOLEAN, " + + "title TEXT, " + + "link TEXT, " + + "feed_id INTEGER, " + + "tags TEXT, " + + "content TEXT, " + + "author TEXT, " + + "selected BOOLEAN, " + + "modified BOOLEAN" + + ");"); + + db.execSQL("CREATE TRIGGER articles_set_modified UPDATE OF marked, published, unread ON articles " + + "BEGIN " + + " UPDATE articles SET modified = 1 WHERE " + BaseColumns._ID + " = " + "OLD." + BaseColumns._ID + "; " + + "END;"); + + db.execSQL("CREATE VIEW feeds_unread AS SELECT feeds."+BaseColumns._ID+" AS "+BaseColumns._ID+", " + + "feeds.title AS title, " + + "cat_id, " + + "SUM(articles.unread) AS unread FROM feeds " + + "LEFT JOIN articles ON (articles.feed_id = feeds."+BaseColumns._ID+") " + + "GROUP BY feeds."+BaseColumns._ID+", feeds.title;"); + + //sqlite> select categories._id,categories.title,sum(articles.unread) from categories left j + //oin feeds on (feeds.cat_id = categories._id) left join articles on (articles.feed_id = fee + //ds._id) group by categories._id; + + db.execSQL("CREATE VIEW cats_unread AS SELECT categories."+BaseColumns._ID+" AS "+BaseColumns._ID+", " + + "categories.title AS title, " + + "SUM(articles.unread) AS unread FROM categories " + + "LEFT JOIN feeds ON (feeds.cat_id = categories."+BaseColumns._ID+") "+ + "LEFT JOIN articles ON (articles.feed_id = feeds."+BaseColumns._ID+") " + + "GROUP BY categories."+BaseColumns._ID+", categories.title;"); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onCreate(db); + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/EnlargingImageView.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/EnlargingImageView.java new file mode 100644 index 00000000..e3f1e6f6 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/EnlargingImageView.java @@ -0,0 +1,252 @@ +package org.fox.ttrss.util; + +/* + * Copyright (C) 2013 Tomáš Procházka + * + * 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. + */ + +import java.lang.reflect.Field; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +/** + * Special version of ImageView which allow enlarge width of image if android:adjustViewBounds is true. + * + * <p>It simulate HTML behaviour <img src="" widh="100" /></p> + * <p><a href="http://stackoverflow.com/questions/6202000/imageview-one-dimension-to-fit-free-space-and-second-evaluate-to-keep-aspect-rati">Stackoverflow question link</a></p> + * + * <p>It also allow set related view which will be used as reference for size measure.</p> + * + * @author Tomáš Procházka <<a href="mailto:[email protected]">[email protected]</a>> + * @version $Revision: 0$ ($Date: 6.6.2011 18:16:52$) + */ +public class EnlargingImageView extends android.widget.ImageView { + + private int mDrawableWidth; + private int mDrawableHeight; + private boolean mAdjustViewBoundsL; + private int mMaxWidthL = Integer.MAX_VALUE; + private int mMaxHeightL = Integer.MAX_VALUE; + private View relatedView; + + public EnlargingImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // hack for acces some private field of parent :-( + Field f; + try { + f = android.widget.ImageView.class.getDeclaredField("mAdjustViewBounds"); + f.setAccessible(true); + setAdjustViewBounds((Boolean) f.get(this)); + + f = android.widget.ImageView.class.getDeclaredField("mMaxWidth"); + f.setAccessible(true); + setMaxWidth((Integer) f.get(this)); + + f = android.widget.ImageView.class.getDeclaredField("mMaxHeight"); + f.setAccessible(true); + setMaxHeight((Integer) f.get(this)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public EnlargingImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public EnlargingImageView(Context context) { + super(context); + } + + public void setAdjustViewBounds(boolean adjustViewBounds) { + super.setAdjustViewBounds(adjustViewBounds); + mAdjustViewBoundsL = adjustViewBounds; + } + + public void setMaxWidth(int maxWidth) { + super.setMaxWidth(maxWidth); + mMaxWidthL = maxWidth; + } + + public void setMaxHeight(int maxHeight) { + super.setMaxHeight(maxHeight); + mMaxHeightL = maxHeight; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (getDrawable() == null) { + setMeasuredDimension(0, 0); + return; + } + + mDrawableWidth = getDrawable().getIntrinsicWidth(); + mDrawableHeight = getDrawable().getIntrinsicHeight(); + + int w = 0; + int h = 0; + + // Desired aspect ratio of the view's contents (not including padding) + float desiredAspect = 0.0f; + + // We are allowed to change the view's width + boolean resizeWidth = false; + + // We are allowed to change the view's height + boolean resizeHeight = false; + + if (mDrawableWidth > 0) { + w = mDrawableWidth; + h = mDrawableHeight; + if (w <= 0) w = 1; + if (h <= 0) h = 1; + + // We are supposed to adjust view bounds to match the aspect + // ratio of our drawable. See if that is possible. + if (mAdjustViewBoundsL) { + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + + resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; + resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; + + desiredAspect = (float) w / (float) h; + } + } + + int pleft = getPaddingLeft(); + int pright = getPaddingRight(); + int ptop = getPaddingTop(); + int pbottom = getPaddingBottom(); + + int widthSize; + int heightSize; + + if (resizeWidth || resizeHeight) { + /* If we get here, it means we want to resize to match the + drawables aspect ratio, and we have the freedom to change at + least one dimension. + */ + + // Get the max possible width given our constraints + widthSize = resolveAdjustedSize(w + pleft + pright, + mMaxWidthL, widthMeasureSpec); + + // Get the max possible height given our constraints + heightSize = resolveAdjustedSize(h + ptop + pbottom, + mMaxHeightL, heightMeasureSpec); + + if (desiredAspect != 0.0f) { + // See what our actual aspect ratio is + float actualAspect = (float) (widthSize - pleft - pright) / + (heightSize - ptop - pbottom); + + if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { + + // Try adjusting width to be proportional to height + if (resizeWidth) { + int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; + if (/*newWidth <= widthSize &&*/newWidth > 0) { + widthSize = Math.min(newWidth, mMaxWidthL); + heightSize = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; + } + } + + // Try adjusting height to be proportional to width + if (resizeHeight) { + int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; + if (/*newHeight <= heightSize && */newHeight > 0) { + heightSize = Math.min(newHeight, mMaxHeightL); + widthSize = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; + } + } + } + } + } else { + /* We are either don't want to preserve the drawables aspect ratio, + or we are not allowed to change view dimensions. Just measure in + the normal way. + */ + w += pleft + pright; + h += ptop + pbottom; + + w = Math.max(w, getSuggestedMinimumWidth()); + h = Math.max(h, getSuggestedMinimumHeight()); + + widthSize = resolveSize(w, widthMeasureSpec); + heightSize = resolveSize(h, heightMeasureSpec); + } + + //Log.d(Constants.LOGTAG, mDrawableWidth + ":" + mDrawableHeight + " to " + widthSize + ":" + heightSize); + + setMeasuredDimension(widthSize, heightSize); + + if (relatedView != null) { + //Log.i(Constants.LOGTAG, getTag() + " onMeasure:" + widthSize + ", " + heightSize + " update size of related view!"); + relatedView.getLayoutParams().width = widthSize; + relatedView.getLayoutParams().height = heightSize; + } + + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + //Log.d(Constants.LOGTAG, getTag() + " onLayout:" + left + ", " + top + ", " + right + ", " + bottom); + } + + /** + * Experimental. This view will be set to the same size as this image. + */ + public void setRelatedView(View view) { + relatedView = view; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + //Log.d(Constants.LOGTAG, getTag() + " onSizeChanged:" + w + ", " + h + ", " + oldw + ", " + oldh); + } + + private int resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec) { + int result = desiredSize; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + /* Parent says we can be as big as we want. Just don't be larger + than max size imposed on ourselves. + */ + result = Math.min(desiredSize, maxSize); + break; + case MeasureSpec.AT_MOST: + // Parent says we can be as big as we want, up to specSize. + // Don't be larger than specSize, and don't be larger than + // the max size imposed on ourselves. + result = Math.min(Math.min(desiredSize, specSize), maxSize); + break; + case MeasureSpec.EXACTLY: + // No choice. Do what we are told. + result = specSize; + break; + } + return result; + } +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/FontSizeDialogPreference.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/FontSizeDialogPreference.java new file mode 100644 index 00000000..ec7af2e5 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/FontSizeDialogPreference.java @@ -0,0 +1,224 @@ +package org.fox.ttrss.util; + +// http://www.lukehorvat.com/blog/android-seekbardialogpreference/ + +import org.fox.ttrss.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +/** + * A {@link DialogPreference} that provides a user with the means to select an + * integer from a {@link SeekBar}, and persist it. + * + * @author lukehorvat + * + */ +public class FontSizeDialogPreference extends DialogPreference { + private static final int DEFAULT_MIN_PROGRESS = 9; + private static final int DEFAULT_MAX_PROGRESS = 24; + private static final String DEFAULT_PROGRESS = "0"; + + private int mMinProgress = DEFAULT_MIN_PROGRESS; + private int mMaxProgress = DEFAULT_MAX_PROGRESS; + private int mProgress; + private CharSequence mProgressTextSuffix; + private TextView mProgressText; + private SeekBar mSeekBar; + + public FontSizeDialogPreference(Context context) { + this(context, null); + } + + public FontSizeDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setProgressTextSuffix(" " + context.getString(R.string.font_size_dialog_suffix)); + + // set layout + setDialogLayoutResource(R.layout.select_font_size_dialog); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + setDialogIcon(null); + } + + @Override + protected void onSetInitialValue(boolean restore, Object defaultValue) { + setProgress(restore ? Integer.valueOf(getPersistedString(DEFAULT_PROGRESS)) + : Integer.valueOf((String)defaultValue)); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getString(index); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mProgressText = (TextView) view.findViewById(R.id.text_progress); + + mSeekBar = (SeekBar) view.findViewById(R.id.seek_bar); + mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + // update text that displays the current SeekBar progress value + // note: this does not persist the progress value. that is only + // ever done in setProgress() + String progressStr = String.valueOf(progress + mMinProgress); + mProgressText.setText(mProgressTextSuffix == null ? progressStr + : progressStr.concat(mProgressTextSuffix.toString())); + mProgressText.setTextSize(TypedValue.COMPLEX_UNIT_SP, progress + mMinProgress); + } + }); + + mSeekBar.setMax(mMaxProgress - mMinProgress); + mSeekBar.setProgress(mProgress - mMinProgress); + } + + public int getMinProgress() { + return mMinProgress; + } + + public void setMinProgress(int minProgress) { + mMinProgress = minProgress; + setProgress(Math.max(mProgress, mMinProgress)); + } + + public int getMaxProgress() { + return mMaxProgress; + } + + public void setMaxProgress(int maxProgress) { + mMaxProgress = maxProgress; + setProgress(Math.min(mProgress, mMaxProgress)); + } + + public int getProgress() { + return mProgress; + } + + public void setProgress(int progress) { + progress = Math.max(Math.min(progress, mMaxProgress), mMinProgress); + + if (progress != mProgress) { + mProgress = progress; + persistString(String.valueOf(progress)); + notifyChanged(); + } + } + + public CharSequence getProgressTextSuffix() { + return mProgressTextSuffix; + } + + public void setProgressTextSuffix(CharSequence progressTextSuffix) { + mProgressTextSuffix = progressTextSuffix; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + // when the user selects "OK", persist the new value + if (positiveResult) { + int seekBarProgress = mSeekBar.getProgress() + mMinProgress; + if (callChangeListener(seekBarProgress)) { + setProgress(seekBarProgress); + } + } + } + + @Override + protected Parcelable onSaveInstanceState() { + // save the instance state so that it will survive screen orientation + // changes and other events that may temporarily destroy it + final Parcelable superState = super.onSaveInstanceState(); + + // set the state's value with the class member that holds current + // setting value + final SavedState myState = new SavedState(superState); + myState.minProgress = getMinProgress(); + myState.maxProgress = getMaxProgress(); + myState.progress = getProgress(); + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + // check whether we saved the state in onSaveInstanceState() + if (state == null || !state.getClass().equals(SavedState.class)) { + // didn't save the state, so call superclass + super.onRestoreInstanceState(state); + return; + } + + // restore the state + SavedState myState = (SavedState) state; + setMinProgress(myState.minProgress); + setMaxProgress(myState.maxProgress); + setProgress(myState.progress); + + super.onRestoreInstanceState(myState.getSuperState()); + } + + private static class SavedState extends BaseSavedState { + int minProgress; + int maxProgress; + int progress; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + + minProgress = source.readInt(); + maxProgress = source.readInt(); + progress = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + + dest.writeInt(minProgress); + dest.writeInt(maxProgress); + dest.writeInt(progress); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java new file mode 100644 index 00000000..551c0add --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java @@ -0,0 +1,101 @@ +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 org.fox.ttrss.types.Feed; + +import android.content.Context; +import android.util.Log; + +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 = 1500; + + private final String TAG = this.getClass().getSimpleName(); + + private int m_offset = 0; + private OnlineActivity m_activity; + private ArticleList m_articles = GlobalState.getInstance().m_loadedArticles; + private Feed m_feed; + + public HeadlinesRequest(Context context, OnlineActivity activity, final Feed feed) { + super(context); + + m_activity = activity; + m_feed = feed; + } + + protected void onPostExecute(JsonElement result) { + if (result != null) { + try { + + // check if we are returning results for correct feed + if (GlobalState.getInstance().m_activeFeed != null && !m_feed.equals(GlobalState.getInstance().m_activeFeed)) { + Log.d(TAG, "received results for wrong feed, bailing out."); + return; + } + + JsonArray content = result.getAsJsonArray(); + if (content != null) { + Type listType = new TypeToken<List<Article>>() {}.getType(); + final List<Article> articles = new Gson().fromJson(content, listType); + + if (m_offset == 0) { + m_articles.clear(); + } else { + while (m_articles.size() > HEADLINES_BUFFER_MAX) { + m_articles.remove(0); + } + + if (m_articles.get(m_articles.size()-1).id == -1) { + m_articles.remove(m_articles.size()-1); // remove previous placeholder + } + + } + + for (Article f : articles) + if (!m_articles.containsId(f.id)) + 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; + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/ImageCacheService.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/ImageCacheService.java new file mode 100644 index 00000000..5b029fc6 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/ImageCacheService.java @@ -0,0 +1,212 @@ +package org.fox.ttrss.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import org.fox.ttrss.OnlineActivity; +import org.fox.ttrss.R; +import org.fox.ttrss.offline.OfflineDownloadService; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.app.IntentService; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +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; + + private static final String CACHE_PATH = "/image-cache/"; + + private int m_imagesDownloaded = 0; + + private NotificationManager m_nmgr; + + public ImageCacheService() { + super("ImageCacheService"); + } + + private boolean isDownloadServiceRunning() { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if ("org.fox.ttrss.OfflineDownloadService".equals(service.service.getClassName())) { + return true; + } + } + return false; + } + + + @Override + public void onCreate() { + super.onCreate(); + m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + } + + public static boolean isUrlCached(Context context, String url) { + String hashedUrl = md5(url); + + File storage = context.getExternalCacheDir(); + + File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png"); + + return file.exists(); + } + + public static String getCacheFileName(Context context, String url) { + String hashedUrl = md5(url); + + File storage = context.getExternalCacheDir(); + + File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png"); + + return file.getAbsolutePath(); + } + + public static void cleanupCache(Context context, boolean deleteAll) { + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + File storage = context.getExternalCacheDir(); + File cachePath = new File(storage.getAbsolutePath() + CACHE_PATH); + + long now = new Date().getTime(); + + if (cachePath.isDirectory()) { + for (File file : cachePath.listFiles()) { + if (deleteAll || now - file.lastModified() > 1000*60*60*24*7) { + file.delete(); + } + } + } + } + } + + protected static String md5(String s) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i=0; i<messageDigest.length; i++) + hexString.append(Integer.toHexString(0xFF & messageDigest[i])); + + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + private InputStream getStream(String urlString) { + try { + URL url = new URL(urlString); + URLConnection urlConnection = url.openConnection(); + urlConnection.setConnectTimeout(250); + urlConnection.setReadTimeout(5*1000); + return urlConnection.getInputStream(); + } catch (Exception ex) { + return null; + } + } + + @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, OnlineActivity.class), 0); + + notification.flags |= Notification.FLAG_ONGOING_EVENT; + notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + + notification.setLatestEventInfo(this, getString(R.string.notify_downloading_title), msg, contentIntent); + + m_nmgr.notify(NOTIFY_DOWNLOADING, notification); + } + + /* private void updateNotification(int msgResId) { + updateNotification(getString(msgResId)); + } */ + + @Override + protected void onHandleIntent(Intent intent) { + String url = intent.getStringExtra("url"); + + //Log.d(TAG, "got request to download URL=" + url); + + if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) + return; + + String hashedUrl = md5(url); + + File storage = getExternalCacheDir(); + File cachePath = new File(storage.getAbsolutePath() + CACHE_PATH); + if (!cachePath.exists()) cachePath.mkdirs(); + + if (cachePath.isDirectory() && hashedUrl != null) { + File outputFile = new File(cachePath.getAbsolutePath() + "/" + hashedUrl + ".png"); + + if (!outputFile.exists()) { + + //Log.d(TAG, "downloading to " + outputFile.getAbsolutePath()); + + InputStream is = getStream(url); + + if (is != null) { + try { + FileOutputStream fos = new FileOutputStream(outputFile); + + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + + m_imagesDownloaded++; + + updateNotification(getString(R.string.notify_downloading_images, m_imagesDownloaded)); + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (!isDownloadServiceRunning()) { + m_nmgr.cancel(NOTIFY_DOWNLOADING); + + Intent success = new Intent(); + success.setAction(OfflineDownloadService.INTENT_ACTION_SUCCESS); + success.addCategory(Intent.CATEGORY_DEFAULT); + sendBroadcast(success); + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/LessBrokenWebView.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/LessBrokenWebView.java new file mode 100644 index 00000000..4a3ea826 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/LessBrokenWebView.java @@ -0,0 +1,37 @@ +package org.fox.ttrss.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.webkit.WebView; + +public class LessBrokenWebView extends WebView { + + public LessBrokenWebView(Context context) { + super(context); + // TODO Auto-generated constructor stub + } + + public LessBrokenWebView(Context context, AttributeSet attrs) { + super(context, attrs); + // TODO Auto-generated constructor stub + } + + public LessBrokenWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + int temp_ScrollY = getScrollY(); + scrollTo(getScrollX(), getScrollY() + 1); + scrollTo(getScrollX(), temp_ScrollY); + } + + return super.onTouchEvent(event); + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/NoChildFocusScrollView.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/NoChildFocusScrollView.java new file mode 100644 index 00000000..b5ec23c5 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/NoChildFocusScrollView.java @@ -0,0 +1,34 @@ +package org.fox.ttrss.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.webkit.WebView; +import android.widget.ScrollView; + +public class NoChildFocusScrollView extends ScrollView { + + public NoChildFocusScrollView(Context context) { + super(context); + // TODO Auto-generated constructor stub + } + + + public NoChildFocusScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + // TODO Auto-generated constructor stub + } + + public NoChildFocusScrollView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + public void requestChildFocus(View child, View focused) { + if (focused instanceof WebView ) + return; + super.requestChildFocus(child, focused); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/PrefsBackupAgent.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/PrefsBackupAgent.java new file mode 100644 index 00000000..2b33615f --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/PrefsBackupAgent.java @@ -0,0 +1,19 @@ +package org.fox.ttrss.util; + +import android.app.backup.BackupAgentHelper; +import android.app.backup.SharedPreferencesBackupHelper; + +public class PrefsBackupAgent extends BackupAgentHelper { + // The name of the SharedPreferences file + static final String PREFS = "org.fox.ttrss_preferences"; + + // A key to uniquely identify the set of backup data + static final String PREFS_BACKUP_KEY = "prefs"; + + // Allocate a helper and add it to the backup agent + @Override + public void onCreate() { + SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS); + addHelper(PREFS_BACKUP_KEY, helper); + } +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/SimpleLoginManager.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/SimpleLoginManager.java new file mode 100644 index 00000000..e11e574d --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/SimpleLoginManager.java @@ -0,0 +1,105 @@ +package org.fox.ttrss.util; + +import java.util.HashMap; + +import org.fox.ttrss.ApiRequest; + +import android.content.Context; +import android.util.Log; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public abstract class SimpleLoginManager { + private final String TAG = this.getClass().getSimpleName(); + + protected class LoginRequest extends ApiRequest { + private int m_requestId; + protected String m_sessionId; + protected int m_apiLevel; + protected Context m_context; + + public LoginRequest(Context context, int requestId) { + super(context); + m_context = context; + m_requestId = requestId; + } + + protected void onPostExecute(JsonElement result) { + Log.d(TAG, "onPostExecute"); + + if (result != null) { + try { + JsonObject content = result.getAsJsonObject(); + if (content != null) { + m_sessionId = content.get("session_id").getAsString(); + + Log.d(TAG, "[SLM] Authenticated!"); + + ApiRequest req = new ApiRequest(m_context) { + protected void onPostExecute(JsonElement result) { + m_apiLevel = 0; + + if (result != null) { + try { + m_apiLevel = result.getAsJsonObject() + .get("level").getAsInt(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + Log.d(TAG, "[SLM] Received API level: " + m_apiLevel); + + onLoginSuccess(m_requestId, m_sessionId, m_apiLevel); + } + }; + + @SuppressWarnings("serial") + HashMap<String, String> map = new HashMap<String, String>() { + { + put("sid", m_sessionId); + put("op", "getApiLevel"); + } + }; + + req.execute(map); + + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + m_sessionId = null; + + onLoginFailed(m_requestId, this); + } + + } + + public void logIn(Context context, int requestId, final String login, final String password) { + LoginRequest ar = new LoginRequest(context, requestId); + + HashMap<String, String> map = new HashMap<String, String>() { + { + put("op", "login"); + put("user", login.trim()); + put("password", password.trim()); + } + }; + + onLoggingIn(requestId); + + ar.execute(map); + } + + protected abstract void onLoggingIn(int requestId); + + protected abstract void onLoginSuccess(int requestId, String sessionId, int apiLevel); + + protected abstract void onLoginFailed(int requestId, ApiRequest ar); + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/TitleWebView.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/TitleWebView.java new file mode 100644 index 00000000..4d97918e --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/TitleWebView.java @@ -0,0 +1,91 @@ +package org.fox.ttrss.util; + +// http://www.techques.com/question/1-9718245/Webview-in-Scrollview + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.webkit.WebView; + +public class TitleWebView extends WebView{ + + public TitleWebView(Context context, AttributeSet attrs){ + super(context, attrs); + } + + private int titleHeight; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // determine height of title bar + View title = getChildAt(0); + titleHeight = title==null ? 0 : title.getMeasuredHeight(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev){ + return true; // don't pass our touch events to children (title bar), we send these in dispatchTouchEvent + } + + private boolean touchInTitleBar; + @Override + public boolean dispatchTouchEvent(MotionEvent me){ + + boolean wasInTitle = false; + switch(me.getActionMasked()){ + case MotionEvent.ACTION_DOWN: + touchInTitleBar = (me.getY() <= visibleTitleHeight()); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + wasInTitle = touchInTitleBar; + touchInTitleBar = false; + break; + } + if(touchInTitleBar || wasInTitle) { + View title = getChildAt(0); + if(title!=null) { + // this touch belongs to title bar, dispatch it here + me.offsetLocation(0, getScrollY()); + return title.dispatchTouchEvent(me); + } + } + // this is our touch, offset and process + me.offsetLocation(0, -titleHeight); + return super.dispatchTouchEvent(me); + } + + /** + * @return visible height of title (may return negative values) + */ + private int visibleTitleHeight(){ + return titleHeight-getScrollY(); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt){ + super.onScrollChanged(l, t, oldl, oldt); + View title = getChildAt(0); + if(title!=null) // undo horizontal scroll, so that title scrolls only vertically + title.offsetLeftAndRight(l - title.getLeft()); + } + + @Override + protected void onDraw(Canvas c){ + + c.save(); + int tH = visibleTitleHeight(); + if(tH>0) { + // clip so that it doesn't clear background under title bar + int sx = getScrollX(), sy = getScrollY(); + c.clipRect(sx, sy+tH, sx+getWidth(), sy+getHeight()); + } + c.translate(0, titleHeight); + super.onDraw(c); + c.restore(); + } + } diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/util/TypefaceCache.java b/orgfoxttrss/src/main/java/org/fox/ttrss/util/TypefaceCache.java new file mode 100644 index 00000000..752304ca --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/util/TypefaceCache.java @@ -0,0 +1,29 @@ +package org.fox.ttrss.util; + +import java.util.Hashtable; + +import android.content.Context; +import android.graphics.Typeface; +import android.util.Log; + +public class TypefaceCache { + private static final String TAG = "TypefaceCache"; + private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>(); + + public static Typeface get(Context c, String typefaceName, int style) { + synchronized (cache) { + String key = typefaceName + ":" + style; + + if (!cache.containsKey(key)) { + try { + Typeface t = Typeface.create(typefaceName, style); + cache.put(key, t); + } catch (Exception e) { + Log.e(TAG, "Could not get typeface '" + typefaceName + "' because " + e.getMessage()); + return null; + } + } + return cache.get(key); + } + } +}
\ No newline at end of file diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/widget/SmallWidgetProvider.java b/orgfoxttrss/src/main/java/org/fox/ttrss/widget/SmallWidgetProvider.java new file mode 100644 index 00000000..6162abab --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/widget/SmallWidgetProvider.java @@ -0,0 +1,65 @@ +package org.fox.ttrss.widget; + +import org.fox.ttrss.R; + +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.app.Service; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.widget.RemoteViews; + +public class SmallWidgetProvider extends AppWidgetProvider { + private final String TAG = this.getClass().getSimpleName(); + + public static final String FORCE_UPDATE_ACTION = "org.fox.ttrss.WIDGET_FORCE_UPDATE"; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + //RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_small); + + final int N = appWidgetIds.length; + + for (int i=0; i < N; i++) { + int appWidgetId = appWidgetIds[i]; + + Intent updateIntent = new Intent(context, org.fox.ttrss.widget.WidgetUpdateService.class); + PendingIntent updatePendingIntent = PendingIntent.getService(context, 0, updateIntent, 0); + + Intent intent = new Intent(context, org.fox.ttrss.OnlineActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_small); + views.setOnClickPendingIntent(R.id.widget_main, pendingIntent); + + appWidgetManager.updateAppWidget(appWidgetId, views); + + try { + updatePendingIntent.send(); + } catch (CanceledException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + + if (FORCE_UPDATE_ACTION.equals(intent.getAction())) { + + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + ComponentName thisAppWidget = new ComponentName(context.getPackageName(), SmallWidgetProvider.class.getName()); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget); + + onUpdate(context, appWidgetManager, appWidgetIds); + } + } + +} diff --git a/orgfoxttrss/src/main/java/org/fox/ttrss/widget/WidgetUpdateService.java b/orgfoxttrss/src/main/java/org/fox/ttrss/widget/WidgetUpdateService.java new file mode 100644 index 00000000..e45bd301 --- /dev/null +++ b/orgfoxttrss/src/main/java/org/fox/ttrss/widget/WidgetUpdateService.java @@ -0,0 +1,141 @@ +package org.fox.ttrss.widget; + +import java.util.HashMap; + +import org.fox.ttrss.ApiRequest; +import org.fox.ttrss.R; +import org.fox.ttrss.util.SimpleLoginManager; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import android.app.Service; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +public class WidgetUpdateService extends Service { + private final String TAG = this.getClass().getSimpleName(); + + @Override + public IBinder onBind(Intent intent) { + Log.d(TAG, "onBind"); + + // TODO Auto-generated method stub + return null; + } + + /* @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand"); + + return super.onStartCommand(intent, flags, startId); + } */ + + public void update() { + + + } + + @Override + public void onStart(Intent intent, int startId) { + final RemoteViews view = new RemoteViews(getPackageName(), R.layout.widget_small); + + final ComponentName thisWidget = new ComponentName(this, SmallWidgetProvider.class); + final AppWidgetManager manager = AppWidgetManager.getInstance(this); + + try { + view.setTextViewText(R.id.counter, String.valueOf("")); + view.setViewVisibility(R.id.progress, View.VISIBLE); + + manager.updateAppWidget(thisWidget, view); + + final SharedPreferences m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + if (m_prefs.getString("ttrss_url", "").trim().length() == 0) { + + // Toast: need configure + + } else { + + SimpleLoginManager loginManager = new SimpleLoginManager() { + + @Override + protected void onLoginSuccess(int requestId, String sessionId, int apiLevel) { + + ApiRequest aru = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + try { + JsonObject content = result.getAsJsonObject(); + + if (content != null) { + int unread = content.get("unread").getAsInt(); + + view.setViewVisibility(R.id.progress, View.GONE); + view.setTextViewText(R.id.counter, String.valueOf(unread)); + manager.updateAppWidget(thisWidget, view); + + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + view.setViewVisibility(R.id.progress, View.GONE); + view.setTextViewText(R.id.counter, "?"); + manager.updateAppWidget(thisWidget, view); + } + }; + + final String fSessionId = sessionId; + + HashMap<String, String> umap = new HashMap<String, String>() { + { + put("op", "getUnread"); + put("sid", fSessionId); + } + }; + + aru.execute(umap); + } + + @Override + protected void onLoginFailed(int requestId, ApiRequest ar) { + + view.setViewVisibility(R.id.progress, View.GONE); + view.setTextViewText(R.id.counter, "?"); + manager.updateAppWidget(thisWidget, view); + } + + @Override + protected void onLoggingIn(int requestId) { + + + } + }; + + String login = m_prefs.getString("login", "").trim(); + String password = m_prefs.getString("password", "").trim(); + + loginManager.logIn(getApplicationContext(), 1, login, password); + } + } catch (Exception e) { + e.printStackTrace(); + + view.setViewVisibility(R.id.progress, View.GONE); + view.setTextViewText(R.id.counter, getString(R.string.app_name)); + manager.updateAppWidget(thisWidget, view); + + } + } +} diff --git a/orgfoxttrss/src/main/res/anim/feed_item.xml b/orgfoxttrss/src/main/res/anim/feed_item.xml new file mode 100644 index 00000000..9f445523 --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/feed_item.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> + <alpha + android:fromAlpha="0" + android:toAlpha="1" + android:duration="150" + /> +</set> + diff --git a/orgfoxttrss/src/main/res/anim/headline_item.xml b/orgfoxttrss/src/main/res/anim/headline_item.xml new file mode 100644 index 00000000..97c6d40f --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/headline_item.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> + <alpha + android:fromAlpha="0" + android:toAlpha="1" + android:duration="250" + /> + <translate + android:fromXDelta="100%p" + android:toXDelta="0" + android:duration="250" + /> +</set> + diff --git a/orgfoxttrss/src/main/res/anim/layout_feeds.xml b/orgfoxttrss/src/main/res/anim/layout_feeds.xml new file mode 100644 index 00000000..379508c3 --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/layout_feeds.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" + android:delay="20%" + android:animation="@anim/feed_item" +/>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/anim/layout_headline.xml b/orgfoxttrss/src/main/res/anim/layout_headline.xml new file mode 100644 index 00000000..ae937110 --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/layout_headline.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" + android:delay="20%" + android:animation="@anim/headline_item" +/>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/anim/right_slide_in.xml b/orgfoxttrss/src/main/res/anim/right_slide_in.xml new file mode 100644 index 00000000..2bb5acc1 --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/right_slide_in.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator"> + <translate + android:fromXDelta="100%p" + android:toXDelta="0" + android:duration="200" + /> +</set> + diff --git a/orgfoxttrss/src/main/res/anim/right_slide_out.xml b/orgfoxttrss/src/main/res/anim/right_slide_out.xml new file mode 100644 index 00000000..134467f5 --- /dev/null +++ b/orgfoxttrss/src/main/res/anim/right_slide_out.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator"> + <translate + android:fromXDelta="0" + android:toXDelta="100%p" + android:duration="200" + /> +</set> + diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/dashclock.png b/orgfoxttrss/src/main/res/drawable-hdpi/dashclock.png Binary files differnew file mode 100644 index 00000000..99ffa932 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/dashclock.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_accept_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_accept_light.png Binary files differnew file mode 100644 index 00000000..53cf6877 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_accept_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_action_overflow.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_action_overflow.png Binary files differnew file mode 100644 index 00000000..5845b648 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_action_overflow.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_cloud_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_cloud_light.png Binary files differnew file mode 100644 index 00000000..a1d27cec --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_cloud_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_important_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_important_light.png Binary files differnew file mode 100644 index 00000000..11f86414 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_important_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_labels_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_labels_light.png Binary files differnew file mode 100644 index 00000000..432e7c00 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_labels_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_list_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_list_light.png Binary files differnew file mode 100644 index 00000000..e45ea1fd --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_list_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_attaches_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_attaches_light.png Binary files differnew file mode 100644 index 00000000..8bf8cb78 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_attaches_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_published_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_published_light.png Binary files differnew file mode 100644 index 00000000..599ca764 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_published_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_unpublished_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_unpublished_light.png Binary files differnew file mode 100644 index 00000000..6d10cb92 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_menu_unpublished_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_new_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_new_light.png Binary files differnew file mode 100644 index 00000000..ad8ada6b --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_new_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_published.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_published.png Binary files differnew file mode 100644 index 00000000..6f790827 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_published.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_read_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_read_light.png Binary files differnew file mode 100644 index 00000000..9ef52959 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_read_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_refresh_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_refresh_light.png Binary files differnew file mode 100644 index 00000000..bb9d855f --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_refresh_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_rotate_left_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_rotate_left_light.png Binary files differnew file mode 100644 index 00000000..97ab27f0 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_rotate_left_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_search_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_search_light.png Binary files differnew file mode 100644 index 00000000..f12e005e --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_search_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_select_all_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_select_all_light.png Binary files differnew file mode 100644 index 00000000..26a270b3 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_select_all_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_share_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_share_light.png Binary files differnew file mode 100644 index 00000000..c329f58d --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_share_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_empty.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_empty.png Binary files differnew file mode 100644 index 00000000..057c6c66 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_empty.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_full.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_full.png Binary files differnew file mode 100644 index 00000000..2ad21203 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_star_full.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_undo_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_undo_light.png Binary files differnew file mode 100644 index 00000000..9e719c9c --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_undo_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_unimportant_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unimportant_light.png Binary files differnew file mode 100644 index 00000000..7259b06b --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unimportant_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_unpublished.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unpublished.png Binary files differnew file mode 100644 index 00000000..5d84ec52 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unpublished.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ic_unread_light.png b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unread_light.png Binary files differnew file mode 100644 index 00000000..d516f770 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ic_unread_light.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/icon.png b/orgfoxttrss/src/main/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 00000000..ac0388e9 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/icon.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/ics_divider_vertical_bitmap.png b/orgfoxttrss/src/main/res/drawable-hdpi/ics_divider_vertical_bitmap.png Binary files differnew file mode 100644 index 00000000..3391f40c --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/ics_divider_vertical_bitmap.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/paper_sepia_bitmap.png b/orgfoxttrss/src/main/res/drawable-hdpi/paper_sepia_bitmap.png Binary files differnew file mode 100644 index 00000000..68ff3d66 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/paper_sepia_bitmap.png diff --git a/orgfoxttrss/src/main/res/drawable-hdpi/shadow_bitmap.png b/orgfoxttrss/src/main/res/drawable-hdpi/shadow_bitmap.png Binary files differnew file mode 100644 index 00000000..6fb8a250 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-hdpi/shadow_bitmap.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/dashclock.png b/orgfoxttrss/src/main/res/drawable-xhdpi/dashclock.png Binary files differnew file mode 100644 index 00000000..65ebe3c1 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/dashclock.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_accept_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_accept_light.png Binary files differnew file mode 100644 index 00000000..b52dc370 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_accept_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_cloud_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_cloud_light.png Binary files differnew file mode 100644 index 00000000..37d98e5d --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_cloud_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_important_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_important_light.png Binary files differnew file mode 100644 index 00000000..7576cc1e --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_important_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_labels_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_labels_light.png Binary files differnew file mode 100644 index 00000000..8fdcd1a2 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_labels_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_list_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_list_light.png Binary files differnew file mode 100644 index 00000000..95708234 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_list_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_attaches_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_attaches_light.png Binary files differnew file mode 100644 index 00000000..a3e253fa --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_attaches_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_published_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_published_light.png Binary files differnew file mode 100644 index 00000000..938ec3e3 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_published_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_unpublished_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_unpublished_light.png Binary files differnew file mode 100644 index 00000000..7b32106b --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_menu_unpublished_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_new_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_new_light.png Binary files differnew file mode 100644 index 00000000..23b9a1c1 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_new_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_read_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_read_light.png Binary files differnew file mode 100644 index 00000000..62e3d1ad --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_read_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_refresh_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_refresh_light.png Binary files differnew file mode 100644 index 00000000..a7fdc0df --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_refresh_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_rotate_left_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_rotate_left_light.png Binary files differnew file mode 100644 index 00000000..46d04a53 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_rotate_left_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_search_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_search_light.png Binary files differnew file mode 100644 index 00000000..3549f84d --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_search_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_select_all_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_select_all_light.png Binary files differnew file mode 100644 index 00000000..52d1155d --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_select_all_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_share_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_share_light.png Binary files differnew file mode 100644 index 00000000..15549b04 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_share_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_undo_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_undo_light.png Binary files differnew file mode 100644 index 00000000..99967f2f --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_undo_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unimportant_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unimportant_light.png Binary files differnew file mode 100644 index 00000000..3c618a12 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unimportant_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unread_light.png b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unread_light.png Binary files differnew file mode 100644 index 00000000..606c902c --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/ic_unread_light.png diff --git a/orgfoxttrss/src/main/res/drawable-xhdpi/icon.png b/orgfoxttrss/src/main/res/drawable-xhdpi/icon.png Binary files differnew file mode 100644 index 00000000..c39421b8 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xhdpi/icon.png diff --git a/orgfoxttrss/src/main/res/drawable-xxhdpi/icon.png b/orgfoxttrss/src/main/res/drawable-xxhdpi/icon.png Binary files differnew file mode 100644 index 00000000..30baa7a5 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable-xxhdpi/icon.png diff --git a/orgfoxttrss/src/main/res/drawable/counter_background.xml b/orgfoxttrss/src/main/res/drawable/counter_background.xml new file mode 100644 index 00000000..1c2c4094 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/counter_background.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + + <solid android:color="@color/unread_counter_background" /> + + <corners + android:bottomLeftRadius="4dp" + android:bottomRightRadius="4dp" + android:topLeftRadius="4dp" + android:topRightRadius="4dp" /> + +</shape>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/counter_background_dark.xml b/orgfoxttrss/src/main/res/drawable/counter_background_dark.xml new file mode 100644 index 00000000..3cc971c6 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/counter_background_dark.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + + <solid android:color="@color/unread_counter_background_dark" /> + + <corners + android:bottomLeftRadius="4dp" + android:bottomRightRadius="4dp" + android:topLeftRadius="4dp" + android:topRightRadius="4dp" /> + +</shape>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/counter_background_selected_light.xml b/orgfoxttrss/src/main/res/drawable/counter_background_selected_light.xml new file mode 100644 index 00000000..7485ea00 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/counter_background_selected_light.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + + <solid android:color="@color/unread_counter_background_selected_light" /> + + <corners + android:bottomLeftRadius="4dp" + android:bottomRightRadius="4dp" + android:topLeftRadius="4dp" + android:topRightRadius="4dp" /> + +</shape>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/counter_background_sepia.xml b/orgfoxttrss/src/main/res/drawable/counter_background_sepia.xml new file mode 100644 index 00000000..daebd494 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/counter_background_sepia.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + + <solid android:color="@color/unread_counter_background_sepia" /> + + <corners + android:bottomLeftRadius="4dp" + android:bottomRightRadius="4dp" + android:topLeftRadius="4dp" + android:topRightRadius="4dp" /> + +</shape>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/flavor_image_border.xml b/orgfoxttrss/src/main/res/drawable/flavor_image_border.xml new file mode 100644 index 00000000..f59d75d8 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/flavor_image_border.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <corners android:radius="0dp" /> + + <stroke + android:width="1dp" + android:color="#cccccc" /> + +</shape>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row.xml b/orgfoxttrss/src/main/res/drawable/headline_row.xml new file mode 100644 index 00000000..ccfb8703 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#e0e0e0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#f5f5f5" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row_selected.xml b/orgfoxttrss/src/main/res/drawable/headline_row_selected.xml new file mode 100644 index 00000000..3f005c8a --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row_selected.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#e0e0e0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#88b0f0" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row_selected_sepia.xml b/orgfoxttrss/src/main/res/drawable/headline_row_selected_sepia.xml new file mode 100644 index 00000000..17d096e1 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row_selected_sepia.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#d0d0d0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#E5B0A0" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row_sepia.xml b/orgfoxttrss/src/main/res/drawable/headline_row_sepia.xml new file mode 100644 index 00000000..facd2b6a --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row_sepia.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#d0d0d0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#f5f5f5" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row_unread.xml b/orgfoxttrss/src/main/res/drawable/headline_row_unread.xml new file mode 100644 index 00000000..6813164f --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row_unread.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#e0e0e0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#ffffff" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/headline_row_unread_sepia.xml b/orgfoxttrss/src/main/res/drawable/headline_row_unread_sepia.xml new file mode 100644 index 00000000..6c649257 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/headline_row_unread_sepia.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item> + <shape android:shape="rectangle" > + <solid android:color="#d0d0d0" /> + <corners android:radius="2dp"/> + </shape> + </item> + + <item + android:bottom="2dp"> + <shape android:shape="rectangle" > + <solid android:color="#ffffff" /> + <corners android:radius="2dp"/> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/ics_divider_vertical.xml b/orgfoxttrss/src/main/res/drawable/ics_divider_vertical.xml new file mode 100644 index 00000000..14c1f642 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/ics_divider_vertical.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<bitmap + xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/ics_divider_vertical_bitmap" + android:gravity="fill_vertical|right" /> diff --git a/orgfoxttrss/src/main/res/drawable/ics_divider_vertical_gray.xml b/orgfoxttrss/src/main/res/drawable/ics_divider_vertical_gray.xml new file mode 100644 index 00000000..14c1f642 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/ics_divider_vertical_gray.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<bitmap + xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/ics_divider_vertical_bitmap" + android:gravity="fill_vertical|right" /> diff --git a/orgfoxttrss/src/main/res/drawable/paper_sepia.xml b/orgfoxttrss/src/main/res/drawable/paper_sepia.xml new file mode 100644 index 00000000..3951b4c8 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/paper_sepia.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> + <bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/paper_sepia_bitmap" + android:tileMode="repeat" />
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/s_dashclock.svg b/orgfoxttrss/src/main/res/drawable/s_dashclock.svg new file mode 100644 index 00000000..f4be84f9 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_dashclock.svg @@ -0,0 +1,1071 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="72" + height="72" + id="svg2985" + version="1.1" + inkscape:version="0.48.4 r9939" + inkscape:export-filename="C:\Users\fox\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-xhdpi\dashclock.png" + inkscape:export-xdpi="120" + inkscape:export-ydpi="120" + sodipodi:docname="s_dashclock.svg"> + <defs + id="defs2987"> + <filter + id="filter4035" + inkscape:label="Glow" + inkscape:menu="Shadows and Glows" + inkscape:menu-tooltip="Glow of object's own color at the edges" + color-interpolation-filters="sRGB"> + <feGaussianBlur + id="feGaussianBlur4037" + stdDeviation="5" + result="result91" /> + <feComposite + id="feComposite4039" + in2="result91" + in="SourceGraphic" + operator="over" /> + </filter> + <filter + id="filter4044" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur4046" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix4048" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /> + <feOffset + id="feOffset4050" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge4052"> + <feMergeNode + id="feMergeNode4054" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode4056" + in="SourceGraphic" /> + </feMerge> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient3890" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="RSSg" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3838" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3840" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3842" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3844" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3846" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3848" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3850" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5363" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="linearGradient3277" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3279" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3281" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3283" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3285" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3287" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3289" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3291" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5668" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3294" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3296" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3298" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3300" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3302" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3304" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3306" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3308" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5670" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3311" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3313" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3315" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3317" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3319" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3321" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3323" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3325" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5652" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3328" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3330" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3332" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3334" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3336" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3338" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3340" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3342" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5654" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3345" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3347" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3349" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3351" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3353" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3355" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3357" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3359" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5656" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3362" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3364" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3366" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3368" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3370" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3372" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3374" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3376" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5658" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3379" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3381" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3383" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3385" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3387" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3389" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3391" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3393" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5664" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3396" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3398" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3400" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3402" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3404" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3406" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3408" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3410" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5666" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3413" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3415" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3417" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3419" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3421" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3423" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3425" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3427" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5660" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3430" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3432" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3434" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3436" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3438" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3440" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3442" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3444" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5662" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3447" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3449" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3451" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3453" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3455" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3457" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3459" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3461" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + y2="211.27248" + x2="217.63582" + y1="39.288776" + x1="46.536098" + gradientUnits="userSpaceOnUse" + id="linearGradient3469" + xlink:href="#RSSg" + inkscape:collect="always" /> + <linearGradient + y2="125.5" + x2="220.00627" + y1="125.5" + x1="42.993729" + gradientUnits="userSpaceOnUse" + id="linearGradient3471" + xlink:href="#RSSg" + inkscape:collect="always" /> + <filter + color-interpolation-filters="sRGB" + id="filter5699" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-0.25" + y="-0.25"> + <feGaussianBlur + id="feGaussianBlur5701" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix5703" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /> + <feOffset + id="feOffset5705" + in="bluralpha" + dx="2" + dy="2" + result="offsetBlur" /> + <feMerge + id="feMerge5707"> + <feMergeNode + id="feMergeNode5709" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode5711" + in="SourceGraphic" /> + </feMerge> + </filter> + <filter + id="filter3293" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur3295" + in="SourceAlpha" + stdDeviation="3" + result="blur" /> + <feColorMatrix + id="feColorMatrix3297" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0 " /> + <feOffset + id="feOffset3299" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge3301"> + <feMergeNode + id="feMergeNode3303" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode3305" + in="SourceGraphic" /> + </feMerge> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4512" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4514" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4516" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4518" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4520" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4522" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4524" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4526" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4528" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4530" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4532" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4534" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4549" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4551" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4553" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4555" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4557" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4559" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4561" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4563" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4565" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4567" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4572" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4574" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4577" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4579" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4581" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4583" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4585" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4587" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4590" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-15.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4592" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-15.505653)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4676" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient4678" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-7.505653)" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="7.7781746" + inkscape:cx="49.788136" + inkscape:cy="31.375346" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:snap-page="false" + inkscape:window-width="1280" + inkscape:window-height="961" + inkscape:window-x="1592" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata2990"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer" + transform="translate(0,8)"> + <path + style="fill:#5693fd;fill-opacity:1;stroke:none" + d="m 10.457912,43.891242 c -5.3345941,0 -9.68712703,4.316148 -9.68712703,9.650846 0,5.334697 4.35253293,9.687127 9.68712703,9.687127 3.621444,0 6.763753,-2.021498 8.417279,-4.970548 -1.332101,-1.326573 -2.33102,-2.941902 -2.90251,-4.716579 -0.552565,-1.715836 -0.798196,-3.532842 -0.79819,-5.442206 -1.2e-5,-0.630425 0.02748,-1.310797 0.108844,-2.031757 0.02951,-0.261496 0.07199,-0.533671 0.108844,-0.798191 -1.447954,-0.866201 -3.124315,-1.378692 -4.934267,-1.378692 z" + id="circle26" + inkscape:connector-curvature="0" /> + <path + style="fill:#5693fd;fill-opacity:1;stroke:none" + d="m 0.77078497,16.535086 0,13.678078 A 33.002051,33.002704 0 0 1 17.750468,34.929743 l 2.35829,-9.505721 -4.861704,0 -3.845826,0 1.015878,-3.7007 0.979597,-3.410449 A 46.685828,46.686752 0 0 0 0.77078497,16.535086 z M 39.990951,37.904815 39.337886,40.517074 c -0.08277,0.45381 -0.244611,0.952443 -0.362813,1.451255 -0.09989,0.421337 -0.134677,0.849362 -0.217689,1.269848 -0.08663,0.438515 -0.2221,0.845213 -0.290251,1.233567 -0.05306,0.302702 0,0.488934 0,0.507939 -1.1e-5,0.297438 0.0012,0.391421 0,0.435377 -0.055,-0.05926 0.482688,0.217695 1.741506,0.217688 0.228753,0 0.649652,-0.02893 1.233567,-0.108844 0.588536,-0.08053 1.214979,-0.189057 1.85035,-0.326532 0.164221,-0.03553 0.310568,-0.07416 0.471658,-0.108844 a 46.685828,46.686752 0 0 0 -3.773263,-7.183713 z m 7.365119,21.913951 -0.07256,0.253969 -1.523818,0.50794 c -0.770442,0.252811 -1.647848,0.498277 -2.648541,0.725627 -1.006346,0.228619 -2.097133,0.425988 -3.265323,0.616784 -1.18131,0.192927 -2.400678,0.344324 -3.664419,0.471657 -0.825434,0.08317 -1.630649,0.144805 -2.430852,0.181407 a 33.002051,33.002704 0 0 1 0.03628,0.653065 l 13.678078,0 A 46.685828,46.686752 0 0 0 47.35607,59.818766 z" + id="path28" + inkscape:connector-curvature="0" /> + <path + style="fill:#5693fd;fill-opacity:1;stroke:none" + d="m 0.77078497,-7.229215 0,14.1134548 A 56.344965,56.346082 0 0 1 15.9364,8.9522782 l 0.108844,-0.3990952 2.213164,0 5.913864,0 2.249445,-8.78009268 0.435377,-1.77778732 0.217688,-0.036281 A 70.431206,70.432601 0 0 0 0.77078497,-7.229215 z M 57.11576,21.070257 l -0.507939,1.814069 -0.616783,2.10432 -2.176883,0 -10.666724,0 -0.217688,0.907034 A 56.344965,56.346082 0 0 1 57.11576,63.229215 l 14.113455,0 A 70.431206,70.432601 0 0 0 57.11576,21.070257 z" + id="path30" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4547" + style="font-size:87.69078827px;font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#5693fd;fill-opacity:1;stroke:none;font-family:Segoe UI;-inkscape-font-specification:Segoe UI Bold Oblique" + d="m 53.804356,22.086365 -12.933871,0 -4.437563,17.489747 c -0.07218,0.395786 -0.162374,0.821991 -0.270583,1.278625 -0.108252,0.45667 -0.207472,0.913316 -0.297641,1.36995 -0.09022,0.456669 -0.171393,0.89049 -0.243525,1.301462 -0.07218,0.410996 -0.108252,0.753492 -0.108229,1.027466 -2.5e-5,1.217754 0.360753,2.115825 1.082332,2.694237 0.721529,0.578436 1.912093,0.867653 3.571696,0.867642 0.432905,1.1e-5 0.98309,-0.04563 1.650558,-0.136999 0.667406,-0.09137 1.352883,-0.21309 2.05643,-0.365321 0.703484,-0.152196 1.379941,-0.312032 2.029374,-0.479483 0.649365,-0.167429 1.172492,-0.312032 1.569382,-0.433809 l -2.651715,10.685637 c -0.649432,0.213102 -1.443141,0.426216 -2.381131,0.639306 -0.938051,0.213114 -1.966266,0.410996 -3.084647,0.593657 -1.118437,0.182649 -2.281943,0.33488 -3.490522,0.456646 -1.208628,0.121777 -2.390173,0.18266 -3.544639,0.18266 -2.741927,0 -5.032861,-0.296822 -6.87281,-0.890478 -1.839977,-0.593645 -3.310144,-1.415612 -4.410505,-2.465914 -1.100379,-1.050291 -1.87605,-2.275638 -2.327014,-3.676041 -0.450979,-1.400391 -0.676465,-2.922561 -0.676458,-4.566507 -7e-6,-0.487088 0.03607,-1.050291 0.10824,-1.689609 0.07215,-0.639307 0.171363,-1.293835 0.297641,-1.963605 0.126262,-0.669737 0.270577,-1.347103 0.432933,-2.032095 0.162343,-0.684969 0.315673,-1.316672 0.459992,-1.895107 l 4.437563,-17.992067 -8.550427,0 3.030532,-10.639977 8.171609,0 2.814064,-10.95963491 18.020835,-3.74454209 -3.625814,14.704177 13.150339,0 z" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_headline_published.svg b/orgfoxttrss/src/main/res/drawable/s_headline_published.svg new file mode 100644 index 00000000..b07448a0 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_headline_published.svg @@ -0,0 +1,905 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="16" + height="16" + id="RSSicon" + viewBox="0 0 32 32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_headline_published.svg" + inkscape:export-filename="C:\Users\Andrew\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-hdpi\ic_published.png" + inkscape:export-xdpi="208.25" + inkscape:export-ydpi="208.25"> + <metadata + id="metadata34"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#171717" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1137" + id="namedview32" + showgrid="false" + inkscape:zoom="23.953242" + inkscape:cx="5.9329089" + inkscape:cy="7.0884269" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="RSSicon" /> + <defs + id="defs3"> + <linearGradient + x1="30.059999" + y1="30.059999" + x2="225.94" + y2="225.94" + id="RSSg" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,-224)"> + <stop + offset="0.0" + stop-color="#E3702D" + id="stop6" /> + <stop + offset="0.1071" + stop-color="#EA7D31" + id="stop8" /> + <stop + offset="0.3503" + stop-color="#F69537" + id="stop10" /> + <stop + offset="0.5" + stop-color="#FB9E3A" + id="stop12" /> + <stop + offset="0.7016" + stop-color="#EA7C31" + id="stop14" /> + <stop + offset="0.8866" + stop-color="#DE642B" + id="stop16" /> + <stop + offset="1.0" + stop-color="#D95B29" + id="stop18" /> + </linearGradient> + <filter + id="filter3031" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033" + type="saturate" + values="0" /> + </filter> + <filter + id="filter3031-1" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-7" + type="saturate" + values="0" /> + </filter> + <linearGradient + id="RSSg-9" + y2="225.94001" + x2="225.94001" + y1="30.06" + x1="30.06" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3126" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3128" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3130" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3132" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3134" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3136" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3138" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <filter + id="filter3031-4" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-8" + type="saturate" + values="0" /> + </filter> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient3890" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="RSSg-8" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3838" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3840" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3842" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3844" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3846" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3848" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3850" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5363" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="linearGradient4849" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4851" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4853" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4855" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4857" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4859" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4861" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4863" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <filter + color-interpolation-filters="sRGB" + id="filter3293" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-0.25" + y="-0.25"> + <feGaussianBlur + id="feGaussianBlur3295" + in="SourceAlpha" + stdDeviation="3" + result="blur" /> + <feColorMatrix + id="feColorMatrix3297" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0 " /> + <feOffset + id="feOffset3299" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge3301"> + <feMergeNode + id="feMergeNode3303" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode3305" + in="SourceGraphic" /> + </feMerge> + </filter> + <linearGradient + y2="211.27248" + x2="217.63582" + y1="39.288776" + x1="46.536098" + gradientUnits="userSpaceOnUse" + id="linearGradient3469" + xlink:href="#RSSg-8" + inkscape:collect="always" /> + <linearGradient + id="linearGradient4873" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4875" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4877" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4879" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4881" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4883" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4885" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4887" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + y2="125.5" + x2="220.00627" + y1="125.5" + x1="42.993729" + gradientUnits="userSpaceOnUse" + id="linearGradient3471" + xlink:href="#RSSg-8" + inkscape:collect="always" /> + <linearGradient + id="linearGradient4890" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4892" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4894" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4896" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4898" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4900" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4902" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4904" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5652" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient4907" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4909" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4911" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4913" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4915" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4917" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4919" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4921" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5654" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient4924" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4926" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4928" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4930" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4932" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4934" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4936" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4938" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5656" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient4941" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4943" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4945" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4947" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4949" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4951" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4953" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4955" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5658" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient4958" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4960" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4962" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4964" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4966" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4968" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4970" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4972" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5664" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient4975" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4977" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4979" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4981" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop4983" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop4985" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop4987" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop4989" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5666" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient4992" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop4994" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop4996" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop4998" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop5000" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop5002" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop5004" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop5006" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5660" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient5009" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop5011" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop5013" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop5015" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop5017" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop5019" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop5021" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop5023" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5662" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient5026" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop5028" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop5030" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop5032" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop5034" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop5036" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop5038" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop5040" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + y2="211.27248" + x2="217.63582" + y1="39.288776" + x1="46.536098" + gradientUnits="userSpaceOnUse" + id="linearGradient5048" + xlink:href="#RSSg-8" + inkscape:collect="always" /> + <linearGradient + y2="125.5" + x2="220.00627" + y1="125.5" + x1="42.993729" + gradientUnits="userSpaceOnUse" + id="linearGradient5050" + xlink:href="#RSSg-8" + inkscape:collect="always" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5554" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5556" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5558" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5560" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5562" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5564" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5566" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5568" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5570" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5572" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5574" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg-8" + id="linearGradient5576" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + </defs> + <path + style="opacity:0.80000000000000004;fill:#f69538;fill-opacity:1" + d="m 9.0929935,3.8989242 c -2.8816759,0 -5.1940693,2.3123934 -5.1940693,5.1940693 l 0,13.8140145 c 0,2.881674 2.3123934,5.194069 5.1940693,5.194069 l 13.8140145,0 c 2.881674,0 5.194069,-2.312395 5.194069,-5.194069 l 0,-13.8140145 c 0,-2.8816759 -2.312395,-5.1940693 -5.194069,-5.1940693 l -13.8140145,0 z" + id="path3999" /> + <path + style="opacity:1;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="M 8.0431284,7.4905678 A 16.550485,16.550485 0 0 1 24.619945,24.012129 l -3.315363,0 A 13.240388,13.240388 0 0 0 8.0431284,10.805931 l 0,-3.3153632 z" + id="path3997" /> + <path + style="opacity:1;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="M 8.0431284,13.07143 A 10.970607,10.970607 0 0 1 19.039084,24.012129 l -3.204852,0 A 7.7550842,7.7550842 0 0 0 8.0431284,16.276281 l 0,-3.204851 z" + id="path3995" /> + <path + style="opacity:1;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="m 10.308627,19.481132 c 1.253565,0 2.265498,1.011934 2.265498,2.265498 0,1.253565 -1.011933,2.265499 -2.265498,2.265499 -1.2535655,0 -2.2654986,-1.011934 -2.2654986,-2.265499 0,-1.253564 1.0119331,-2.265498 2.2654986,-2.265498 z" + id="rect20" /> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_headline_unpublished.svg b/orgfoxttrss/src/main/res/drawable/s_headline_unpublished.svg new file mode 100644 index 00000000..3bcbf2a6 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_headline_unpublished.svg @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="16" + height="16" + id="RSSicon" + viewBox="0 0 32 32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_headline_unpublished.svg" + inkscape:export-filename="C:\Users\Andrew\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-hdpi\ic_unpublished.png" + inkscape:export-xdpi="208.25" + inkscape:export-ydpi="208.25"> + <metadata + id="metadata34"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#171717" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1137" + id="namedview32" + showgrid="false" + inkscape:zoom="23.953242" + inkscape:cx="-5.7565318" + inkscape:cy="8.340867" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="RSSicon" /> + <defs + id="defs3"> + <linearGradient + x1="30.059999" + y1="30.059999" + x2="225.94" + y2="225.94" + id="RSSg" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,-224)"> + <stop + offset="0.0" + stop-color="#E3702D" + id="stop6" /> + <stop + offset="0.1071" + stop-color="#EA7D31" + id="stop8" /> + <stop + offset="0.3503" + stop-color="#F69537" + id="stop10" /> + <stop + offset="0.5" + stop-color="#FB9E3A" + id="stop12" /> + <stop + offset="0.7016" + stop-color="#EA7C31" + id="stop14" /> + <stop + offset="0.8866" + stop-color="#DE642B" + id="stop16" /> + <stop + offset="1.0" + stop-color="#D95B29" + id="stop18" /> + </linearGradient> + <filter + id="filter3031" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033" + type="saturate" + values="0" /> + </filter> + <filter + id="filter3031-1" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-7" + type="saturate" + values="0" /> + </filter> + <linearGradient + id="RSSg-9" + y2="225.94001" + x2="225.94001" + y1="30.06" + x1="30.06" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3126" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3128" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3130" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3132" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3134" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3136" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3138" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <filter + id="filter3031-4" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-8" + type="saturate" + values="0" /> + </filter> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + </defs> + <path + style="opacity:0.80000000000000004;fill:#939393;fill-opacity:1;filter:url(#filter3031)" + d="M 4.53125 1.9375 C 3.0904121 1.9375 1.9375 3.090412 1.9375 4.53125 L 1.9375 11.46875 C 1.9375 12.909587 3.0904121 14.0625 4.53125 14.0625 L 11.46875 14.0625 C 12.909587 14.0625 14.0625 12.909587 14.0625 11.46875 L 14.0625 4.53125 C 14.0625 3.0904121 12.909587 1.9375 11.46875 1.9375 L 4.53125 1.9375 z M 4.03125 3.75 A 8.2752425 8.2752425 0 0 1 12.3125 12 L 10.65625 12 A 6.620194 6.620194 0 0 0 4.03125 5.40625 L 4.03125 3.75 z M 4.03125 6.53125 A 5.4853035 5.4853035 0 0 1 9.53125 12 L 7.90625 12 A 3.8775421 3.8775421 0 0 0 4.03125 8.125 L 4.03125 6.53125 z M 5.15625 9.75 C 5.7830325 9.75 6.28125 10.248218 6.28125 10.875 C 6.28125 11.501782 5.7830325 12 5.15625 12 C 4.5294672 12 4.03125 11.501782 4.03125 10.875 C 4.03125 10.248218 4.5294672 9.75 5.15625 9.75 z " + transform="scale(2,2)" + id="path3999" /> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_icon.svg b/orgfoxttrss/src/main/res/drawable/s_icon.svg new file mode 100644 index 00000000..82cd4812 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_icon.svg @@ -0,0 +1,789 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="72" + height="72" + id="svg2985" + version="1.1" + inkscape:version="0.48.4 r9939" + inkscape:export-filename="C:\Users\Andrew\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-xxhdpi\icon.png" + inkscape:export-xdpi="180.02" + inkscape:export-ydpi="180.02" + sodipodi:docname="s_icon.svg"> + <defs + id="defs2987"> + <filter + id="filter4035" + inkscape:label="Glow" + inkscape:menu="Shadows and Glows" + inkscape:menu-tooltip="Glow of object's own color at the edges" + color-interpolation-filters="sRGB"> + <feGaussianBlur + id="feGaussianBlur4037" + stdDeviation="5" + result="result91" /> + <feComposite + id="feComposite4039" + in2="result91" + in="SourceGraphic" + operator="over" /> + </filter> + <filter + id="filter4044" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur4046" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix4048" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /> + <feOffset + id="feOffset4050" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge4052"> + <feMergeNode + id="feMergeNode4054" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode4056" + in="SourceGraphic" /> + </feMerge> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient3890" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="RSSg" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3838" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3840" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3842" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3844" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3846" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3848" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3850" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5363" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="linearGradient3277" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3279" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3281" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3283" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3285" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3287" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3289" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3291" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5668" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3294" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3296" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3298" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3300" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3302" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3304" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3306" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3308" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5670" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3311" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3313" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3315" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3317" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3319" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3321" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3323" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3325" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5652" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3328" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3330" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3332" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3334" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3336" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3338" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3340" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3342" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5654" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3345" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3347" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3349" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3351" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3353" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3355" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3357" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3359" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5656" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3362" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3364" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3366" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3368" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3370" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3372" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3374" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3376" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5658" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3379" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3381" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3383" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3385" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3387" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3389" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3391" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3393" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5664" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3396" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3398" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3400" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3402" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3404" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3406" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3408" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3410" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5666" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3413" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3415" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3417" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3419" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3421" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3423" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3425" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3427" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5660" + gradientUnits="userSpaceOnUse" + x1="46.536098" + y1="39.288776" + x2="217.63582" + y2="211.27248" /> + <linearGradient + id="linearGradient3430" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3432" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3434" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3436" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3438" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3440" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3442" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3444" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#RSSg" + id="linearGradient5662" + gradientUnits="userSpaceOnUse" + x1="42.993729" + y1="125.5" + x2="220.00627" + y2="125.5" /> + <linearGradient + id="linearGradient3447" + y2="225.94" + x2="225.94" + y1="30.059999" + x1="30.059999" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3449" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3451" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3453" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3455" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3457" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3459" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3461" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <linearGradient + y2="211.27248" + x2="217.63582" + y1="39.288776" + x1="46.536098" + gradientUnits="userSpaceOnUse" + id="linearGradient3469" + xlink:href="#RSSg" + inkscape:collect="always" /> + <linearGradient + y2="125.5" + x2="220.00627" + y1="125.5" + x1="42.993729" + gradientUnits="userSpaceOnUse" + id="linearGradient3471" + xlink:href="#RSSg" + inkscape:collect="always" /> + <filter + color-interpolation-filters="sRGB" + id="filter5699" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-0.25" + y="-0.25"> + <feGaussianBlur + id="feGaussianBlur5701" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix5703" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /> + <feOffset + id="feOffset5705" + in="bluralpha" + dx="2" + dy="2" + result="offsetBlur" /> + <feMerge + id="feMerge5707"> + <feMergeNode + id="feMergeNode5709" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode5711" + in="SourceGraphic" /> + </feMerge> + </filter> + <filter + id="filter3293" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur3295" + in="SourceAlpha" + stdDeviation="3" + result="blur" /> + <feColorMatrix + id="feColorMatrix3297" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0 " /> + <feOffset + id="feOffset3299" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge3301"> + <feMergeNode + id="feMergeNode3303" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode3305" + in="SourceGraphic" /> + </feMerge> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="5.5" + inkscape:cx="83.010172" + inkscape:cy="24.824208" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:snap-page="false" + inkscape:window-width="1920" + inkscape:window-height="1137" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata2990"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer" + transform="translate(0,8)"> + <g + transform="matrix(0.34665172,0,0,0.34665859,-9.5847012,-15.505653)" + id="g3022" + style="fill:url(#linearGradient3890);fill-opacity:1;stroke:url(#linearGradient5363);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:1;filter:url(#filter3293)" + inkscape:export-xdpi="105.9" + inkscape:export-ydpi="105.9"> + <g + id="g3776" + style="fill:url(#linearGradient3469);fill-opacity:1;stroke:url(#linearGradient3471);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"> + <circle + cx="68" + cy="189" + r="24" + id="circle26" + d="m 92,189 c 0,13.25483 -10.745166,24 -24,24 -13.254834,0 -24,-10.74517 -24,-24 0,-13.25483 10.745166,-24 24,-24 13.254834,0 24,10.74517 24,24 z" + sodipodi:cx="68" + sodipodi:cy="189" + sodipodi:rx="24" + sodipodi:ry="24" + style="fill:url(#linearGradient5652);fill-opacity:1;stroke:url(#linearGradient5654);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="M 160,213 H 126 A 82,82 0 0 0 44,131 V 97 a 116,116 0 0 1 116,116 z" + id="path28" + inkscape:connector-curvature="0" + style="fill:url(#linearGradient5656);fill-opacity:1;stroke:url(#linearGradient5658);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + <g + style="fill:url(#linearGradient5664);fill-opacity:1;stroke:url(#linearGradient5666);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="g3773"> + <path + style="fill:url(#linearGradient5660);fill-opacity:1;stroke:url(#linearGradient5662);stroke-width:2.01254654000000020;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + inkscape:connector-curvature="0" + id="path30" + d="M 184,213 A 140,140 0 0 0 44,73 V 38 a 175,175 0 0 1 175,175 z" /> + </g> + </g> + </g> + <text + xml:space="preserve" + style="font-size:504.83898926000006000px;font-style:oblique;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#2b4265;stroke-width:17.2711068;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter5699);font-family:Sans;-inkscape-font-specification:Verdana Bold Oblique" + x="124.97492" + y="-45.951401" + id="text3780" + sodipodi:linespacing="125%" + inkscape:export-filename="C:\Users\fox\Desktop\ttrss_logo_big.png" + inkscape:export-xdpi="23.129999" + inkscape:export-ydpi="23.129999" + transform="matrix(0.18909237,0,0,0.15956152,-8.46733,61.350497)"><tspan + sodipodi:role="line" + id="tspan3782" + x="124.97492" + y="-45.951401" + style="font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;fill-opacity:1;stroke:#2b4265;stroke-width:17.2711068;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Segoe UI;-inkscape-font-specification:Segoe UI Bold Oblique">t</tspan></text> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_marked.svg b/orgfoxttrss/src/main/res/drawable/s_marked.svg new file mode 100644 index 00000000..326c9691 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_marked.svg @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.000000px" + height="16.000000px" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.0 r9654" + sodipodi:docname="s_marked.svg" + inkscape:export-filename="ic_marked.png" + inkscape:export-xdpi="176.53999" + inkscape:export-ydpi="176.53999" + version="1.1"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0000000" + inkscape:pageshadow="2" + inkscape:zoom="31.678384" + inkscape:cx="6.9004349" + inkscape:cy="7.415554" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1600" + inkscape:window-height="1131" + inkscape:window-x="0" + inkscape:window-y="25" + showguides="true" + inkscape:guide-bbox="true" + showgrid="false" + inkscape:window-maximized="0" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="star" + style="opacity:1.0000000;fill:#a8cdfd;fill-opacity:1.0000000;stroke:#4f9dfd;stroke-width:0.99999938;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000" + id="path1306" + sodipodi:sides="5" + sodipodi:cx="7.3551731" + sodipodi:cy="1.6684607" + sodipodi:r1="6.3745561" + sodipodi:r2="3.1872780" + sodipodi:arg1="0.78539816" + sodipodi:arg2="1.4137167" + inkscape:flatsided="false" + inkscape:rounded="0.0000000" + inkscape:randomized="0.0000000" + d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z " + transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_marked_bw.svg b/orgfoxttrss/src/main/res/drawable/s_marked_bw.svg new file mode 100644 index 00000000..df88f4a5 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_marked_bw.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.000000px" + height="16.000000px" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_menu_marked.svg" + inkscape:export-filename="C:\Users\Andrew\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-hdpi\ic_star_empty.png" + inkscape:export-xdpi="176.53999" + inkscape:export-ydpi="176.53999" + version="1.1"> + <defs + id="defs4"> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0000000" + inkscape:pageshadow="2" + inkscape:zoom="31.678384" + inkscape:cx="1.9759413" + inkscape:cy="7.415554" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1920" + inkscape:window-height="1137" + inkscape:window-x="-8" + inkscape:window-y="-8" + showguides="true" + inkscape:guide-bbox="true" + showgrid="false" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="star" + style="opacity:1;fill:none;fill-opacity:1;stroke:#4f9dfd;stroke-width:0.99999969000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2997)" + id="path1306" + sodipodi:sides="5" + sodipodi:cx="7.3551731" + sodipodi:cy="1.6684607" + sodipodi:r1="6.3745561" + sodipodi:r2="3.1872780" + sodipodi:arg1="0.78539816" + sodipodi:arg2="1.4137167" + inkscape:flatsided="false" + inkscape:rounded="0.0000000" + inkscape:randomized="0.0000000" + d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z " + transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_marked_bw_full.svg b/orgfoxttrss/src/main/res/drawable/s_marked_bw_full.svg new file mode 100644 index 00000000..3ccf34c1 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_marked_bw_full.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.000000px" + height="16.000000px" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_marked_bw.svg" + inkscape:export-filename="C:\Users\Andrew\workspace\Tiny-Tiny-RSS-for-Honeycomb\res\drawable-hdpi\ic_star_full.png" + inkscape:export-xdpi="176.53999" + inkscape:export-ydpi="176.53999" + version="1.1"> + <defs + id="defs4"> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0000000" + inkscape:pageshadow="2" + inkscape:zoom="31.678384" + inkscape:cx="-2.9485523" + inkscape:cy="7.415554" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1920" + inkscape:window-height="1137" + inkscape:window-x="-8" + inkscape:window-y="-8" + showguides="true" + inkscape:guide-bbox="true" + showgrid="false" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="star" + style="opacity:1;fill:#939393;fill-opacity:1;stroke:#4f9dfd;stroke-width:0.99999969000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2997)" + id="path1306" + sodipodi:sides="5" + sodipodi:cx="7.3551731" + sodipodi:cy="1.6684607" + sodipodi:r1="6.3745561" + sodipodi:r2="3.1872780" + sodipodi:arg1="0.78539816" + sodipodi:arg2="1.4137167" + inkscape:flatsided="false" + inkscape:rounded="0.0000000" + inkscape:randomized="0.0000000" + d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z " + transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_menu_attaches_light.svg b/orgfoxttrss/src/main/res/drawable/s_menu_attaches_light.svg new file mode 100644 index 00000000..0396f73f --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_menu_attaches_light.svg @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.000000px" + height="16.000000px" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_menu_attaches_light.svg" + inkscape:export-filename="C:\Users\fox\workspace\org.fox.ttrss\res\drawable-hdpi\ic_menu_attaches_light.png" + inkscape:export-xdpi="270" + inkscape:export-ydpi="270" + version="1.1"> + <defs + id="defs4"> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="29.85923" + x2="20.604948" + y1="5.7753429" + x1="23.505953" + id="linearGradient5789" + xlink:href="#linearGradient5783" + inkscape:collect="always" + gradientTransform="matrix(0.42042845,0,0,0.42042845,13.623381,13.232492)" /> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.977282,3.554943e-8,-8.305337e-10,0.651376,-0.794430,15.82896)" + r="15.571428" + fy="23.07144" + fx="21.761711" + cy="23.07144" + cx="21.761711" + id="radialGradient3564" + xlink:href="#linearGradient3558" + inkscape:collect="always" /> + <linearGradient + id="linearGradient3558" + inkscape:collect="always"> + <stop + id="stop3560" + offset="0" + style="stop-color:#000000;stop-opacity:1;" /> + <stop + id="stop3562" + offset="1" + style="stop-color:#000000;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient5783"> + <stop + id="stop5785" + offset="0" + style="stop-color:#d3d7cf;stop-opacity:1;" /> + <stop + style="stop-color:#f5f5f5;stop-opacity:1;" + offset="0.5" + id="stop5791" /> + <stop + id="stop5787" + offset="1" + style="stop-color:#bebebe;stop-opacity:1;" /> + </linearGradient> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3558" + id="radialGradient3808" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.977282,3.554943e-8,0,0.651376,-0.79443,15.82896)" + cx="21.761711" + cy="23.07144" + fx="21.761711" + fy="23.07144" + r="15.571428" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#454545" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="22.4" + inkscape:cx="6.7689862" + inkscape:cy="8.4534142" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1600" + inkscape:window-height="1137" + inkscape:window-x="-8" + inkscape:window-y="-8" + showguides="true" + inkscape:guide-bbox="true" + showgrid="false" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:0.91846919;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + d="M 7.2696546,3.9045172 C 6.5854893,4.9968981 5.9013237,6.089279 5.2171584,7.1816599 4.5329929,8.274041 3.7726219,9.3283183 3.0884565,10.420698 c -0.1294185,0.40488 0.8824391,1.576607 1.4709363,1.997356 0.6126586,0.412697 2.3969867,0.892836 2.7292147,0.778128 0.926183,-1.425517 1.9285718,-2.81293 2.8547545,-4.238445 C 11.069544,7.5322205 11.995728,6.1067037 12.921911,4.6811871 12.953651,4.2699709 12.176585,3.5406145 11.645037,3.1943024 11.065166,2.8318829 10.229025,2.7110765 9.9779075,2.8318829 9.2383431,4.0091148 8.4987784,5.1863463 7.7592138,6.363578 7.0196493,7.5408098 6.2800847,8.7180415 5.5405199,9.8952727 c -0.052078,0.2008543 0.288791,0.4615083 0.4331863,0.5769393 0.1622057,0.09665 0.6063076,0.321425 0.8197671,0.238681 C 7.2378124,10.054393 7.6821511,9.3978941 8.12649,8.7413931 8.5708291,8.0848927 9.0151679,7.4283922 9.4595067,6.7718918" + id="path3814" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccccccccc" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_menu_marked.svg b/orgfoxttrss/src/main/res/drawable/s_menu_marked.svg new file mode 100644 index 00000000..1be0b7f9 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_menu_marked.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.000000px" + height="16.000000px" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.2 r9819" + sodipodi:docname="s_menu_marked.svg" + inkscape:export-filename="C:\Users\fox\workspace\org.fox.ttrss\res\drawable-hdpi\ic_menu_marked.png" + inkscape:export-xdpi="270" + inkscape:export-ydpi="270" + version="1.1"> + <defs + id="defs4"> + <filter + id="filter2997" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix2999" + type="saturate" + values="0" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0000000" + inkscape:pageshadow="2" + inkscape:zoom="31.678384" + inkscape:cx="1.9759413" + inkscape:cy="7.415554" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1920" + inkscape:window-height="1138" + inkscape:window-x="-8" + inkscape:window-y="-8" + showguides="true" + inkscape:guide-bbox="true" + showgrid="false" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:type="star" + style="opacity:1;fill:none;fill-opacity:1;stroke:#4f9dfd;stroke-width:0.99999969000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2997)" + id="path1306" + sodipodi:sides="5" + sodipodi:cx="7.3551731" + sodipodi:cy="1.6684607" + sodipodi:r1="6.3745561" + sodipodi:r2="3.1872780" + sodipodi:arg1="0.78539816" + sodipodi:arg2="1.4137167" + inkscape:flatsided="false" + inkscape:rounded="0.0000000" + inkscape:randomized="0.0000000" + d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z " + transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_menu_published_light.svg b/orgfoxttrss/src/main/res/drawable/s_menu_published_light.svg new file mode 100644 index 00000000..752fe0f6 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_menu_published_light.svg @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="16" + height="16" + id="RSSicon" + viewBox="0 0 32 32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_menu_published_light.svg" + inkscape:export-filename="C:\Users\fox\workspace\org.fox.ttrss\res\drawable-hdpi\ic_menu_published_light.png" + inkscape:export-xdpi="270" + inkscape:export-ydpi="270"> + <metadata + id="metadata34"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#171717" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1600" + inkscape:window-height="1137" + id="namedview32" + showgrid="false" + inkscape:zoom="23.953242" + inkscape:cx="0.75615658" + inkscape:cy="8.340867" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="RSSicon" /> + <defs + id="defs3"> + <linearGradient + x1="30.059999" + y1="30.059999" + x2="225.94" + y2="225.94" + id="RSSg" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,-224)"> + <stop + offset="0.0" + stop-color="#E3702D" + id="stop6" /> + <stop + offset="0.1071" + stop-color="#EA7D31" + id="stop8" /> + <stop + offset="0.3503" + stop-color="#F69537" + id="stop10" /> + <stop + offset="0.5" + stop-color="#FB9E3A" + id="stop12" /> + <stop + offset="0.7016" + stop-color="#EA7C31" + id="stop14" /> + <stop + offset="0.8866" + stop-color="#DE642B" + id="stop16" /> + <stop + offset="1.0" + stop-color="#D95B29" + id="stop18" /> + </linearGradient> + <filter + id="filter3031" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033" + type="saturate" + values="0" /> + </filter> + <filter + id="filter3031-1" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-7" + type="saturate" + values="0" /> + </filter> + <linearGradient + id="RSSg-9" + y2="225.94001" + x2="225.94001" + y1="30.06" + x1="30.06" + gradientUnits="userSpaceOnUse"> + <stop + id="stop3126" + stop-color="#E3702D" + offset="0.0" /> + <stop + id="stop3128" + stop-color="#EA7D31" + offset="0.1071" /> + <stop + id="stop3130" + stop-color="#F69537" + offset="0.3503" /> + <stop + id="stop3132" + stop-color="#FB9E3A" + offset="0.5" /> + <stop + id="stop3134" + stop-color="#EA7C31" + offset="0.7016" /> + <stop + id="stop3136" + stop-color="#DE642B" + offset="0.8866" /> + <stop + id="stop3138" + stop-color="#D95B29" + offset="1.0" /> + </linearGradient> + <filter + id="filter3031-4" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033-8" + type="saturate" + values="0" /> + </filter> + </defs> + <path + style="opacity:0.8;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="m 9.0929935,3.8989242 c -2.8816759,0 -5.1940693,2.3123934 -5.1940693,5.1940693 l 0,13.8140145 c 0,2.881674 2.3123934,5.194069 5.1940693,5.194069 l 13.8140145,0 c 2.881674,0 5.194069,-2.312395 5.194069,-5.194069 l 0,-13.8140145 c 0,-2.8816759 -2.312395,-5.1940693 -5.194069,-5.1940693 l -13.8140145,0 z" + id="path3999" /> + <path + style="opacity:0.8;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="M 8.0431284,7.4905678 A 16.550485,16.550485 0 0 1 24.619945,24.012129 l -3.315363,0 A 13.240388,13.240388 0 0 0 8.0431284,10.805931 l 0,-3.3153632 z" + id="path3997" /> + <path + style="opacity:0.8;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="M 8.0431284,13.07143 A 10.970607,10.970607 0 0 1 19.039084,24.012129 l -3.204852,0 A 7.7550842,7.7550842 0 0 0 8.0431284,16.276281 l 0,-3.204851 z" + id="path3995" /> + <path + style="opacity:0.8;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="m 10.308627,19.481132 c 1.253565,0 2.265498,1.011934 2.265498,2.265498 0,1.253565 -1.011933,2.265499 -2.265498,2.265499 -1.2535655,0 -2.2654986,-1.011934 -2.2654986,-2.265499 0,-1.253564 1.0119331,-2.265498 2.2654986,-2.265498 z" + id="rect20" /> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_menu_unpublished_light.svg b/orgfoxttrss/src/main/res/drawable/s_menu_unpublished_light.svg new file mode 100644 index 00000000..6981beaf --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_menu_unpublished_light.svg @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + width="16" + height="16" + id="RSSicon" + viewBox="0 0 32 32" + inkscape:version="0.48.4 r9939" + sodipodi:docname="s_menu_unpublished_light.svg" + inkscape:export-filename="C:\Users\fox\workspace\org.fox.ttrss\res\drawable-hdpi\ic_menu_unpublished_light.png" + inkscape:export-xdpi="270" + inkscape:export-ydpi="270"> + <metadata + id="metadata34"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#171717" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1600" + inkscape:window-height="1137" + id="namedview32" + showgrid="false" + inkscape:zoom="23.953242" + inkscape:cx="6.9218983" + inkscape:cy="7.6051808" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="RSSicon" /> + <defs + id="defs3"> + <linearGradient + x1="30.059999" + y1="30.059999" + x2="225.94" + y2="225.94" + id="RSSg" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,-224)"> + <stop + offset="0.0" + stop-color="#E3702D" + id="stop6" /> + <stop + offset="0.1071" + stop-color="#EA7D31" + id="stop8" /> + <stop + offset="0.3503" + stop-color="#F69537" + id="stop10" /> + <stop + offset="0.5" + stop-color="#FB9E3A" + id="stop12" /> + <stop + offset="0.7016" + stop-color="#EA7C31" + id="stop14" /> + <stop + offset="0.8866" + stop-color="#DE642B" + id="stop16" /> + <stop + offset="1.0" + stop-color="#D95B29" + id="stop18" /> + </linearGradient> + <filter + id="filter3031" + inkscape:label="Desaturate" + x="0" + y="0" + width="1" + height="1" + inkscape:menu="Color" + inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix3033" + type="saturate" + values="0" /> + </filter> + </defs> + <path + style="opacity:0.8;fill:#ffffff;fill-opacity:1;filter:url(#filter3031)" + d="m 4.09375,1.15625 c -1.6297285,0 -2.9375,1.3077715 -2.9375,2.9375 l 0,7.8125 c 0,1.629728 1.3077715,2.9375 2.9375,2.9375 l 7.8125,0 c 1.629728,0 2.9375,-1.307772 2.9375,-2.9375 l 0,-7.8125 c 0,-1.6297285 -1.307772,-2.9375 -2.9375,-2.9375 l -7.8125,0 z M 3.5,3.1875 a 9.3601078,9.3601078 0 0 1 9.375,9.34375 l -1.875,0 A 7.4880862,7.4880862 0 0 0 3.5,5.0625 l 0,-1.875 z m 0,3.15625 a 6.2044143,6.2044143 0 0 1 6.21875,6.1875 l -1.8125,0 A 4.3858791,4.3858791 0 0 0 3.5,8.15625 l 0,-1.8125 z m 1.28125,3.625 c 0.7089524,0 1.28125,0.572298 1.28125,1.28125 0,0.708952 -0.5722976,1.28125 -1.28125,1.28125 C 4.0722976,12.53125 3.5,11.958952 3.5,11.25 3.5,10.541048 4.0722976,9.96875 4.78125,9.96875 z" + transform="matrix(1.7681938,0,0,1.7681938,1.8544501,1.8544501)" + id="rect20" + inkscape:connector-curvature="0" /> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/s_prev_article.svg b/orgfoxttrss/src/main/res/drawable/s_prev_article.svg new file mode 100644 index 00000000..5c83a6b5 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/s_prev_article.svg @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="32px" + height="32px" + id="svg2985" + version="1.1" + inkscape:version="0.48.0 r9654" + sodipodi:docname="s_prev_article.svg" + inkscape:export-filename="C:\Users\fox\workspace\org.fox.ttrss\res\drawable-hdpi\s_prev_article.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <defs + id="defs2987" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="15.836083" + inkscape:cx="13.700373" + inkscape:cy="14.630233" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1600" + inkscape:window-height="1138" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:snap-global="true"> + <inkscape:grid + type="xygrid" + id="grid2997" /> + </sodipodi:namedview> + <metadata + id="metadata2990"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <path + style="fill:#898989;fill-opacity:1;stroke:none" + d="m 23.5,1 -10,15 10,15 -5,0 -10,-15 10,-15 z" + id="path2999" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + </g> +</svg> diff --git a/orgfoxttrss/src/main/res/drawable/shadow.xml b/orgfoxttrss/src/main/res/drawable/shadow.xml new file mode 100644 index 00000000..a293378f --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<bitmap + xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/shadow_bitmap" + android:gravity="fill_vertical|right" /> diff --git a/orgfoxttrss/src/main/res/drawable/shadow_feeds.xml b/orgfoxttrss/src/main/res/drawable/shadow_feeds.xml new file mode 100644 index 00000000..ab71b98d --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_feeds.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@color/feeds_light"/> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/shadow_feeds_gray.xml b/orgfoxttrss/src/main/res/drawable/shadow_feeds_gray.xml new file mode 100644 index 00000000..35167a92 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_feeds_gray.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@color/feeds_dark_gray"/> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/shadow_feeds_sepia.xml b/orgfoxttrss/src/main/res/drawable/shadow_feeds_sepia.xml new file mode 100644 index 00000000..30424e15 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_feeds_sepia.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@color/feeds_sepia"/> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/shadow_headlines.xml b/orgfoxttrss/src/main/res/drawable/shadow_headlines.xml new file mode 100644 index 00000000..899a8687 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_headlines.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <!-- <item android:drawable="@color/headlines_light"/> --> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/shadow_headlines_gray.xml b/orgfoxttrss/src/main/res/drawable/shadow_headlines_gray.xml new file mode 100644 index 00000000..35167a92 --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_headlines_gray.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@color/feeds_dark_gray"/> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/drawable/shadow_headlines_sepia.xml b/orgfoxttrss/src/main/res/drawable/shadow_headlines_sepia.xml new file mode 100644 index 00000000..be7d496c --- /dev/null +++ b/orgfoxttrss/src/main/res/drawable/shadow_headlines_sepia.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:drawable="@drawable/paper_sepia"/> + <item android:drawable="@drawable/shadow"/> + +</layer-list>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines.xml b/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines.xml new file mode 100644 index 00000000..2162232e --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines.xml @@ -0,0 +1,55 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:fitsSystemWindows="true" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <FrameLayout + android:id="@+id/sw600dp_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" > + + </FrameLayout> + + <LinearLayout + android:id="@+id/loading_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:visibility="gone" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + 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:weightSum="1.1" + android:orientation="horizontal" > + + <FrameLayout + android:id="@+id/feeds_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="?feedlistBackground" > + </FrameLayout> + + <FrameLayout + android:id="@+id/headlines_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="2" + android:background="?headlinesBackground" > + </FrameLayout> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines_articles.xml b/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines_articles.xml new file mode 100644 index 00000000..367f492a --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw600dp-land/headlines_articles.xml @@ -0,0 +1,57 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines" + android:fitsSystemWindows="true" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <FrameLayout + android:id="@+id/sw600dp_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" > + </FrameLayout> + + <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="?feedlistBackground" > + </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>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines.xml b/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines.xml new file mode 100644 index 00000000..e6daa46c --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines.xml @@ -0,0 +1,35 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines" + android:fitsSystemWindows="true" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + + <FrameLayout + android:id="@+id/sw600dp_port_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + </FrameLayout> + + <LinearLayout + android:id="@+id/loading_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:visibility="gone" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </LinearLayout> + + <FrameLayout + android:id="@+id/headlines_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </FrameLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines_articles.xml b/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines_articles.xml new file mode 100644 index 00000000..36b29751 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw600dp-port/headlines_articles.xml @@ -0,0 +1,56 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:fitsSystemWindows="true" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/sw600dp_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" > + </FrameLayout> + + <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>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw700dp/headlines.xml b/orgfoxttrss/src/main/res/layout-sw700dp/headlines.xml new file mode 100644 index 00000000..2162232e --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw700dp/headlines.xml @@ -0,0 +1,55 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:fitsSystemWindows="true" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <FrameLayout + android:id="@+id/sw600dp_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" > + + </FrameLayout> + + <LinearLayout + android:id="@+id/loading_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:visibility="gone" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + 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:weightSum="1.1" + android:orientation="horizontal" > + + <FrameLayout + android:id="@+id/feeds_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="?feedlistBackground" > + </FrameLayout> + + <FrameLayout + android:id="@+id/headlines_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="2" + android:background="?headlinesBackground" > + </FrameLayout> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout-sw700dp/headlines_articles.xml b/orgfoxttrss/src/main/res/layout-sw700dp/headlines_articles.xml new file mode 100644 index 00000000..367f492a --- /dev/null +++ b/orgfoxttrss/src/main/res/layout-sw700dp/headlines_articles.xml @@ -0,0 +1,57 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines" + android:fitsSystemWindows="true" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <FrameLayout + android:id="@+id/sw600dp_anchor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" > + </FrameLayout> + + <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="?feedlistBackground" > + </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>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/article_fragment.xml b/orgfoxttrss/src/main/res/layout/article_fragment.xml new file mode 100644 index 00000000..56d42d89 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/article_fragment.xml @@ -0,0 +1,103 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/article_fragment" + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:background="?articleBackground"> + + <org.fox.ttrss.util.NoChildFocusScrollView + android:id="@+id/article_scrollview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="false" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <LinearLayout + android:id="@+id/article_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="6dp" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="4dp" + android:text="My simple headline" + android:textColor="?linkColor" + android:textSize="18sp" /> + + <TextView + android:id="@+id/comments" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:fontFamily="sans-serif-light" + android:paddingTop="4dp" + android:text="24 comments" + android:textColor="?linkColor" + android:textSize="12sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="4dp" + android:paddingBottom="4dp" > + + <TextView + android:id="@+id/tags" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="middle" + android:singleLine="true" + android:fontFamily="sans-serif-light" + android:text="Example Feed" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:gravity="right" + android:layout_marginLeft="10dp" + android:fontFamily="sans-serif-light" + android:text="Jan 01, 12:00" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + </LinearLayout> + + </LinearLayout> + + <TextView + android:id="@+id/note" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="?articleNoteBackground" + android:textColor="?articleNoteTextColor" + android:textSize="13sp" + android:padding="2dp" + android:layout_marginBottom="6dp" + android:text="[Article note]" /> + + <org.fox.ttrss.util.LessBrokenWebView + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + </org.fox.ttrss.util.NoChildFocusScrollView> + + +</RelativeLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/article_fragment_compat.xml b/orgfoxttrss/src/main/res/layout/article_fragment_compat.xml new file mode 100644 index 00000000..01264703 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/article_fragment_compat.xml @@ -0,0 +1,79 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/article_fragment" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="?articleBackground" + android:orientation="vertical" + android:padding="5sp" > + + <org.fox.ttrss.util.TitleWebView + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1"> + + <LinearLayout + android:id="@+id/article_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="0" + android:orientation="vertical" + android:paddingBottom="2dp" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingBottom="4dp" + android:text="My simple headline" + android:textColor="?linkColor" + android:textSize="18sp" /> + + <TextView + android:id="@+id/comments" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="right" + android:text="24 comments" + android:fontFamily="sans-serif-light" + android:textColor="?linkColor" + android:textSize="12sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="4dp" + android:layout_weight="1" > + + <TextView + android:id="@+id/tags" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:ellipsize="end" + android:singleLine="true" + android:text="Example Feed" + android:fontFamily="sans-serif-light" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:gravity="right" + android:text="Jan 01, 12:00" + android:fontFamily="sans-serif-light" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + </LinearLayout> + </LinearLayout> + + + </org.fox.ttrss.util.TitleWebView> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/article_pager.xml b/orgfoxttrss/src/main/res/layout/article_pager.xml new file mode 100644 index 00000000..fd5fa057 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/article_pager.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/article_pager_container" + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + + <android.support.v4.view.ViewPager + android:id="@+id/article_pager" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_alignParentTop="true" > + + </android.support.v4.view.ViewPager> + + <com.viewpagerindicator.UnderlinePageIndicator + android:id="@+id/article_titles" + android:layout_width="fill_parent" + android:layout_height="2dp" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/cats_fragment.xml b/orgfoxttrss/src/main/res/layout/cats_fragment.xml new file mode 100644 index 00000000..4216ebb2 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/cats_fragment.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/cats_fragment.xml" + android:layout_width="match_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" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" > + </TextView> + </LinearLayout> + + <android.support.v4.widget.SwipeRefreshLayout + android:id="@+id/feeds_swipe_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <ListView + android:id="@+id/feeds" + android:layoutAnimation="@anim/layout_feeds" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </ListView> + </android.support.v4.widget.SwipeRefreshLayout> + + <TextView + android:id="@+id/no_feeds" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/no_feeds" + android:textAppearance="?android:attr/textAppearanceLarge" + android:visibility="invisible" > + </TextView> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/dummy_fragment.xml b/orgfoxttrss/src/main/res/layout/dummy_fragment.xml new file mode 100644 index 00000000..dd1e9876 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/dummy_fragment.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/dummy_fragment" + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/feeds.xml b/orgfoxttrss/src/main/res/layout/feeds.xml new file mode 100644 index 00000000..8af9c50d --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/feeds.xml @@ -0,0 +1,29 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:fitsSystemWindows="true" + android:orientation="vertical" + 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:visibility="gone" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </LinearLayout> + + <FrameLayout + android:id="@+id/feeds_fragment" + android:background="?smallScreenBackground" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </FrameLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/feeds_fragment.xml b/orgfoxttrss/src/main/res/layout/feeds_fragment.xml new file mode 100644 index 00000000..b57f0bac --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/feeds_fragment.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/feeds_fragment" + android:layout_width="match_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" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" > + </TextView> + </LinearLayout> + + <android.support.v4.widget.SwipeRefreshLayout + android:id="@+id/feeds_swipe_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <ListView + android:id="@+id/feeds" + android:layoutAnimation="@anim/layout_feeds" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </ListView> + </android.support.v4.widget.SwipeRefreshLayout> + + <TextView + android:id="@+id/no_feeds" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/no_feeds" + android:textAppearance="?android:attr/textAppearanceLarge" + android:visibility="invisible" > + </TextView> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/feeds_row.xml b/orgfoxttrss/src/main/res/layout/feeds_row.xml new file mode 100644 index 00000000..9424fde8 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/feeds_row.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/feeds_row" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:descendantFocusability="blocksDescendants" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingBottom="10dip" + android:paddingLeft="8dip" + android:paddingRight="8dip" + android:paddingTop="10dip" > + + <ImageView + android:id="@+id/icon" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_weight="0" + android:scaleType="fitXY" + android:src="@drawable/ic_unpublished" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:ellipsize="end" + android:paddingLeft="8dip" + android:singleLine="true" + android:text="{FEED}" + android:textColor="?feedlistTextColor" + android:textSize="18dip" /> + + <TextView + android:id="@+id/unread_counter" + android:layout_width="45dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:background="?attr/unreadCounterBackground" + android:gravity="center" + android:paddingBottom="4dp" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:paddingTop="4dp" + android:singleLine="true" + android:text="3200" + android:textColor="?unreadCounterColor" + android:textSize="12sp" + android:textStyle="bold" /> + + <ImageButton + android:id="@+id/feed_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:background="@null" + android:paddingLeft="8dp" + android:paddingRight="4dp" + android:src="@drawable/ic_action_overflow" /> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/feeds_row_selected.xml b/orgfoxttrss/src/main/res/layout/feeds_row_selected.xml new file mode 100644 index 00000000..674e9f23 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/feeds_row_selected.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/feeds_row" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:background="?feedsSelectedBackground" + android:descendantFocusability="blocksDescendants" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingBottom="10dip" + android:paddingLeft="8dip" + android:paddingRight="8dip" + android:paddingTop="10dip" > + + <ImageView + android:id="@+id/icon" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_weight="0" + android:scaleType="fitXY" + android:src="@drawable/ic_unpublished" /> + + <TextView + android:id="@+id/title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:ellipsize="end" + android:paddingLeft="8dip" + android:singleLine="true" + android:text="{FEED}" + android:textColor="?feedlistSelectedTextColor" + android:textSize="18dip" /> + + <TextView + android:id="@+id/unread_counter" + android:layout_width="45dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:background="?attr/unreadSelectedCounterBackground" + android:gravity="center" + android:paddingBottom="4dp" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:paddingTop="4dp" + android:singleLine="true" + android:text="3200" + android:textColor="?unreadCounterColor" + android:textSize="12sp" + android:textStyle="bold" /> + + <ImageButton + android:id="@+id/feed_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:background="@null" + android:paddingLeft="8dp" + android:paddingRight="4dp" + android:src="@drawable/ic_action_overflow" /> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines.xml b/orgfoxttrss/src/main/res/layout/headlines.xml new file mode 100644 index 00000000..4e9bb566 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines.xml @@ -0,0 +1,29 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines" + android:fitsSystemWindows="true" + android:orientation="vertical" + 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:visibility="gone" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </LinearLayout> + + <FrameLayout + android:id="@+id/headlines_fragment" + android:background="?smallScreenBackground" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </FrameLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_articles.xml b/orgfoxttrss/src/main/res/layout/headlines_articles.xml new file mode 100644 index 00000000..27895b38 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_articles.xml @@ -0,0 +1,50 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines" + android:fitsSystemWindows="true" + 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" + android:background="?feedlistBackground" > + </FrameLayout> + + <FrameLayout + android:id="@+id/article_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="?articleBackground" > + </FrameLayout> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_fragment.xml b/orgfoxttrss/src/main/res/layout/headlines_fragment.xml new file mode 100644 index 00000000..63f7f856 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_fragment.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines_fragment" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <android.support.v4.widget.SwipeRefreshLayout + android:id="@+id/headlines_swipe_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <ListView + android:id="@+id/headlines" + android:layout_width="match_parent" + android:layoutAnimation="@anim/layout_headline" + android:dividerHeight="0dp" + android:divider="@null" + android:paddingTop="3dp" + android:layout_height="match_parent" > + </ListView> + </android.support.v4.widget.SwipeRefreshLayout> + + <LinearLayout + android:id="@+id/loading_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" > + + <TextView + android:id="@+id/loading_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" > + </TextView> + </LinearLayout> + + <TextView + android:id="@+id/no_headlines" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/no_headlines" + android:textAppearance="?android:attr/textAppearanceLarge" + android:visibility="invisible" > + </TextView> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_row.xml b/orgfoxttrss/src/main/res/layout/headlines_row.xml new file mode 100644 index 00000000..52f17ba3 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_row.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/headlines_row" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="3dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="3dp" + tools:ignore="HardcodedText" > + + <LinearLayout + android:id="@+id/inner_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?headlineNormalBackground" + android:orientation="vertical" + android:paddingBottom="2dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="6dp" > + + <LinearLayout + android:id="@+id/linearLayout6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:singleLine="false" + android:text="Sample entry title" + android:textColor="?headlineTextColor" + android:textSize="18sp" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingTop="3dp" > + + <TextView + android:id="@+id/feed_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="Example Feed" + android:fontFamily="sans-serif-light" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="right|center_vertical" + android:text="Jan 01, 12:00" + android:fontFamily="sans-serif-light" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + </LinearLayout> + + <FrameLayout + android:paddingTop="3dp" + android:id="@+id/flavorImageHolder" + android:layout_width="match_parent" + android:layout_gravity="center" + android:layout_height="wrap_content" > + + <org.fox.ttrss.util.EnlargingImageView + android:id="@+id/flavor_image" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:adjustViewBounds="true" + android:background="@drawable/flavor_image_border" + android:scaleType="fitCenter" + android:cropToPadding="true" + android:padding="2dp" + android:visibility="gone" /> + </FrameLayout> + + <TextView + android:id="@+id/excerpt" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="3dp" + android:lineSpacingExtra="2sp" + android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + android:textColor="?headlineExcerptTextColor" + android:textSize="13sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + <CheckBox + android:id="@+id/selected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:focusable="false" /> + + <TextView + android:id="@+id/author" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="by Author" + android:fontFamily="sans-serif-light" + android:textStyle="italic" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + <ImageView + android:id="@+id/marked" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_star_empty" /> + + <ImageView + android:id="@+id/published" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_unpublished" /> + + <ImageView + android:id="@+id/article_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:src="@drawable/ic_action_overflow" /> + </LinearLayout> + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_row_loadmore.xml b/orgfoxttrss/src/main/res/layout/headlines_row_loadmore.xml new file mode 100644 index 00000000..c8f41688 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_row_loadmore.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines_row_loadmore" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="?headlineNormalBackground" + android:gravity="center" + android:padding="5dp" + android:orientation="horizontal" > + + + <ProgressBar + android:id="@+id/loadmore_progress" + style="?android:attr/progressBarStyleSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:paddingLeft="6dp" + android:id="@+id/loadmore_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?headlineTextColor" + android:text="@string/loading_message" /> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_row_selected.xml b/orgfoxttrss/src/main/res/layout/headlines_row_selected.xml new file mode 100644 index 00000000..149ff555 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_row_selected.xml @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines_row" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="3dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="3dp" > + + <LinearLayout + android:id="@+id/inner_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?headlineSelectedBackground" + android:orientation="vertical" + android:paddingBottom="2dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="6dp" > + + <LinearLayout + android:id="@+id/linearLayout6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:singleLine="false" + android:text="Sample entry title" + android:textColor="?attr/headlineSelectedTextColor" + android:textSize="18sp" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingTop="3dp" > + + <TextView + android:id="@+id/feed_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:fontFamily="sans-serif-light" + android:text="Example Feed" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="right|center_vertical" + android:text="Jan 01, 12:00" + android:fontFamily="sans-serif-light" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + </LinearLayout> + + <FrameLayout + android:paddingTop="3dp" + android:id="@+id/flavorImageHolder" + android:layout_width="match_parent" + android:layout_gravity="center" + android:layout_height="wrap_content" > + + <org.fox.ttrss.util.EnlargingImageView + android:id="@+id/flavor_image" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:adjustViewBounds="true" + android:background="@drawable/flavor_image_border" + android:scaleType="fitCenter" + android:cropToPadding="true" + android:padding="2dp" + android:visibility="gone" /> + </FrameLayout> + + <TextView + android:id="@+id/excerpt" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="3dp" + android:lineSpacingExtra="2sp" + android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + android:textColor="?headlineSelectedExcerptTextColor" + android:textSize="13sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + <CheckBox + android:id="@+id/selected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:focusable="false" /> + + <TextView + android:id="@+id/author" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="by Author" + android:fontFamily="sans-serif-light" + android:textStyle="italic" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + + <ImageView + android:id="@+id/marked" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_star_empty" /> + + <ImageView + android:id="@+id/published" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_unpublished" /> + + <ImageView + android:id="@+id/article_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:src="@drawable/ic_action_overflow" /> + </LinearLayout> + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/headlines_row_selected_unread.xml b/orgfoxttrss/src/main/res/layout/headlines_row_selected_unread.xml new file mode 100644 index 00000000..833c09e2 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_row_selected_unread.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines_row" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="3dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="3dp" > + + <LinearLayout + android:id="@+id/inner_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?headlineSelectedBackground" + android:orientation="vertical" + android:paddingBottom="2dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="6dp" > + + <LinearLayout + android:id="@+id/linearLayout6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:singleLine="false" + android:text="Sample entry title" + android:textColor="?headlineSelectedTextColor" + android:textSize="18sp" + android:textStyle="bold" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingTop="3dp" > + + <TextView + android:id="@+id/feed_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="Example Feed" + android:fontFamily="sans-serif-light" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="right|center_vertical" + android:fontFamily="sans-serif-light" + android:text="Jan 01, 12:00" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + </LinearLayout> + + <FrameLayout + android:paddingTop="3dp" + android:id="@+id/flavorImageHolder" + android:layout_width="match_parent" + android:layout_gravity="center" + android:layout_height="wrap_content" > + + <org.fox.ttrss.util.EnlargingImageView + android:id="@+id/flavor_image" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:adjustViewBounds="true" + android:background="@drawable/flavor_image_border" + android:scaleType="fitCenter" + android:cropToPadding="true" + android:padding="2dp" + android:visibility="gone" /> + </FrameLayout> + + <TextView + android:id="@+id/excerpt" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="3dp" + android:lineSpacingExtra="2sp" + android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + android:textColor="?headlineSelectedExcerptTextColor" + android:textSize="13sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + <CheckBox + android:id="@+id/selected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:focusable="false" /> + + <TextView + android:id="@+id/author" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:singleLine="true" + android:text="by Author" + android:fontFamily="sans-serif-light" + android:textStyle="italic" + android:textColor="?headlineSelectedSecondaryTextColor" + android:textSize="12sp" /> + + <ImageView + android:id="@+id/marked" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_star_empty" /> + + <ImageView + android:id="@+id/published" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_unpublished" /> + + <ImageView + android:id="@+id/article_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:src="@drawable/ic_action_overflow" /> + </LinearLayout> + </LinearLayout> + +</FrameLayout> diff --git a/orgfoxttrss/src/main/res/layout/headlines_row_unread.xml b/orgfoxttrss/src/main/res/layout/headlines_row_unread.xml new file mode 100644 index 00000000..b5e9d3f6 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/headlines_row_unread.xml @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/headlines_row" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="3dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="3dp" > + + <LinearLayout + android:id="@+id/inner_row" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?headlineUnreadBackground" + android:orientation="vertical" + android:paddingBottom="2dp" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:paddingTop="6dp" > + + <LinearLayout + android:id="@+id/linearLayout6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" > + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:singleLine="false" + android:text="Sample entry title" + android:textColor="?headlineUnreadTextColor" + android:textSize="18sp" + android:textStyle="bold" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingTop="3dp" > + + <TextView + android:id="@+id/feed_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="end" + android:fontFamily="sans-serif-light" + android:gravity="center_vertical" + android:singleLine="true" + android:text="Example Feed" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:fontFamily="sans-serif-light" + android:gravity="right|center_vertical" + android:text="Jan 01, 12:00" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" /> + </LinearLayout> + + <FrameLayout + android:id="@+id/flavorImageHolder" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp" > + + <org.fox.ttrss.util.EnlargingImageView + android:id="@+id/flavor_image" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:adjustViewBounds="true" + android:background="@drawable/flavor_image_border" + android:scaleType="fitCenter" + android:cropToPadding="true" + android:padding="2dp" + android:visibility="gone" /> + </FrameLayout> + + <TextView + android:id="@+id/excerpt" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:lineSpacingExtra="2sp" + android:paddingTop="3dp" + android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + android:textColor="?headlineExcerptTextColor" + android:textSize="13sp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + <CheckBox + android:id="@+id/selected" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:focusable="false" /> + + <TextView + android:id="@+id/author" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:ellipsize="end" + android:fontFamily="sans-serif-light" + android:gravity="center_vertical" + android:singleLine="true" + android:text="by Author" + android:textColor="?headlineSecondaryTextColor" + android:textSize="12sp" + android:textStyle="italic" /> + + <ImageView + android:id="@+id/marked" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_star_empty" /> + + <ImageView + android:id="@+id/published" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dp" + android:layout_weight="0" + android:clickable="true" + android:src="@drawable/ic_unpublished" /> + + <ImageView + android:id="@+id/article_menu_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:paddingLeft="6dp" + android:paddingRight="6dp" + android:src="@drawable/ic_action_overflow" /> + </LinearLayout> + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/loading_fragment.xml b/orgfoxttrss/src/main/res/layout/loading_fragment.xml new file mode 100644 index 00000000..b9b3e977 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/loading_fragment.xml @@ -0,0 +1,14 @@ + + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + + + <ProgressBar + android:id="@+id/progress" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + + </FrameLayout> diff --git a/orgfoxttrss/src/main/res/layout/login.xml b/orgfoxttrss/src/main/res/layout/login.xml new file mode 100644 index 00000000..2cfc44b1 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/login.xml @@ -0,0 +1,15 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/loading_container" + android:layout_width="fill_parent" + android:fitsSystemWindows="true" + 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>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/select_font_size_dialog.xml b/orgfoxttrss/src/main/res/layout/select_font_size_dialog.xml new file mode 100644 index 00000000..f77cc151 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/select_font_size_dialog.xml @@ -0,0 +1,22 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <TextView + android:id="@+id/text_progress" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dip" + android:gravity="center_horizontal" > + </TextView> + + <SeekBar + android:id="@+id/seek_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="6dip" + android:layout_marginTop="6dip" /> + +</LinearLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/share.xml b/orgfoxttrss/src/main/res/layout/share.xml new file mode 100644 index 00000000..dfd09003 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/share.xml @@ -0,0 +1,55 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="5dp" > + + <EditText + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" + android:ems="10" + android:hint="@string/share_title_hint" + android:singleLine="true" /> + + <EditText + android:id="@+id/url" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@+id/title" + android:layout_alignParentRight="true" + android:layout_below="@+id/title" + android:ems="10" + android:hint="@string/share_url_hint" + android:singleLine="true" /> + + + <EditText + android:id="@+id/content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignRight="@+id/url" + android:layout_below="@+id/url" + android:ems="10" + android:hint="@string/share_content_hint" + android:inputType="textMultiLine" + android:maxLines="3" > + + <requestFocus /> + </EditText> + + + <Button + android:id="@+id/share_button" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignRight="@+id/content" + android:layout_below="@+id/content" + android:text="@string/share_share_button" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/subscribe.xml b/orgfoxttrss/src/main/res/layout/subscribe.xml new file mode 100644 index 00000000..8daa1169 --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/subscribe.xml @@ -0,0 +1,51 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="5dp" > + + <EditText + android:id="@+id/feed_url" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:ems="10" + android:hint="@string/feed_url" + android:inputType="textUri" + android:maxLines="3" > + + <requestFocus /> + </EditText> + + <Spinner + android:id="@+id/category_spinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@+id/feed_url" + android:layout_alignParentRight="true" + android:layout_below="@+id/feed_url" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@+id/category_spinner" + android:layout_alignRight="@+id/category_spinner" + android:layout_below="@+id/category_spinner" > + + <Button + android:id="@+id/cats_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:text="Update categories" /> + + <Button + android:id="@+id/subscribe_button" + android:layout_width="wrap_content" + android:layout_weight="0.5" + android:layout_height="wrap_content" + android:text="@string/subscribe_to_feed" /> + </LinearLayout> + +</RelativeLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/tasker_settings.xml b/orgfoxttrss/src/main/res/layout/tasker_settings.xml new file mode 100644 index 00000000..efdd76ab --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/tasker_settings.xml @@ -0,0 +1,36 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/main" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="5dp" > + + <Button + android:id="@+id/close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:text="@string/tasker_save_and_close" /> + + <RadioGroup + android:id="@+id/taskerActions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" > + + <RadioButton + android:id="@+id/actionDownload" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="true" + android:text="@string/download_articles_and_go_offline" /> + + <RadioButton + android:id="@+id/actionUpload" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/synchronize_read_articles_and_go_online" /> + </RadioGroup> + +</RelativeLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/layout/widget_small.xml b/orgfoxttrss/src/main/res/layout/widget_small.xml new file mode 100644 index 00000000..e8e68a4e --- /dev/null +++ b/orgfoxttrss/src/main/res/layout/widget_small.xml @@ -0,0 +1,42 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/widget_main" + android:layout_width="match_parent" + android:layout_height="fill_parent" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ImageView + android:id="@+id/imageView1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="2dp" + android:src="@drawable/icon" /> + + <TextView + android:id="@+id/counter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center" + android:text="-1" + android:shadowColor="#cc000000" + android:shadowDx="0" + android:shadowDy="3" + android:shadowRadius="3" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="@android:color/primary_text_dark" /> + + </LinearLayout> + + <ProgressBar + android:id="@+id/progress" + style="?android:attr/progressBarStyleLarge" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + +</FrameLayout>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/article_content_img_context_menu.xml b/orgfoxttrss/src/main/res/menu/article_content_img_context_menu.xml new file mode 100644 index 00000000..691a1401 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/article_content_img_context_menu.xml @@ -0,0 +1,27 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto" > + + <item + android:id="@+id/article_img_open" + ugh:showAsAction="" + android:title="@string/article_img_open"/> + <item + android:id="@+id/article_img_copy" + ugh:showAsAction="" + android:title="@string/article_link_copy"/> + <item + android:id="@+id/article_img_share" + ugh:showAsAction="" + android:title="@string/article_img_share"/> + <item + android:id="@+id/article_img_view_caption" + ugh:showAsAction="" + android:title="@string/article_img_view_caption"/> + + <!-- + <item + android:id="@+id/article_img_save" + ugh:showAsAction="" + android:title="Save image to file"/> + --> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/article_link_context_menu.xml b/orgfoxttrss/src/main/res/menu/article_link_context_menu.xml new file mode 100644 index 00000000..8317d5e0 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/article_link_context_menu.xml @@ -0,0 +1,13 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/article_link_share" + ugh:showAsAction="" + android:title="@string/share_article"/> + + <item + android:id="@+id/article_link_copy" + ugh:showAsAction="" + android:title="@string/article_link_copy"/> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/category_menu.xml b/orgfoxttrss/src/main/res/menu/category_menu.xml new file mode 100644 index 00000000..28216d41 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/category_menu.xml @@ -0,0 +1,22 @@ +<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/browse_feeds" + android:title="@string/category_browse_feeds"/> + + <item + android:id="@+id/catchup_category" + android:title="@string/catchup"/> + + <item + android:id="@+id/create_shortcut" + android:title="@string/place_shortcut"/> +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/feed_menu.xml b/orgfoxttrss/src/main/res/menu/feed_menu.xml new file mode 100644 index 00000000..fa7dac1f --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/feed_menu.xml @@ -0,0 +1,27 @@ +<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/browse_feeds" + android:title="@string/category_browse_feeds"/> + + <item + android:id="@+id/catchup_feed" + android:title="@string/catchup"/> + + <item + android:id="@+id/create_shortcut" + android:title="@string/place_shortcut"/> + + <item + android:id="@+id/unsubscribe_feed" + android:title="@string/unsubscribe"/> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/headlines_action_menu.xml b/orgfoxttrss/src/main/res/menu/headlines_action_menu.xml new file mode 100644 index 00000000..462fac40 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/headlines_action_menu.xml @@ -0,0 +1,21 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/selection_toggle_unread" + ugh:showAsAction="ifRoom" + android:icon="@drawable/ic_unread_light" + android:title="@string/selection_toggle_unread"/> + + <item + android:id="@+id/selection_toggle_marked" + android:icon="@drawable/ic_unimportant_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_marked"/> + + <item + android:id="@+id/selection_toggle_published" + android:icon="@drawable/ic_menu_unpublished_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_published"/> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/headlines_context_menu.xml b/orgfoxttrss/src/main/res/menu/headlines_context_menu.xml new file mode 100644 index 00000000..de930a23 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/headlines_context_menu.xml @@ -0,0 +1,42 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/selection_toggle_unread" + ugh:showAsAction="" + android:title="@string/context_selection_toggle_unread"/> + <item + android:id="@+id/selection_toggle_marked" + ugh:showAsAction="" + android:title="@string/context_selection_toggle_marked"/> + <item + android:id="@+id/selection_toggle_published" + ugh:showAsAction="" + android:title="@string/context_selection_toggle_published"/> + + <group android:id="@+id/menu_group_single_article" > + <item + android:id="@+id/headlines_share_article" + ugh:showAsAction="" + android:title="@string/share_article"/> + <item + android:id="@+id/headlines_article_link_open" + ugh:showAsAction="" + android:title="@string/open_article_in_web_browser"/> + <item + android:id="@+id/headlines_article_link_copy" + ugh:showAsAction="" + android:title="@string/article_link_copy"/> + <item + android:id="@+id/catchup_above" + ugh:showAsAction="" + android:title="@string/article_mark_read_above"/> + <item + android:id="@+id/set_labels" + android:title="@string/article_set_labels"/> + <item + android:id="@+id/article_set_note" + ugh:showAsAction="" + android:title="@string/article_set_note"/> + </group> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/main_menu.xml b/orgfoxttrss/src/main/res/menu/main_menu.xml new file mode 100644 index 00000000..e4a9776b --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/main_menu.xml @@ -0,0 +1,166 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto"> + + <group android:id="@+id/menu_group_logged_in" > + <group android:id="@+id/menu_group_feeds" > + + <!-- + <item + android:id="@+id/back_to_categories" + android:icon="@android:drawable/ic_menu_close_clear_cancel" + ugh:showAsAction="" + android:title="@string/back_to_categories"/> + --> + + <item + android:id="@+id/subscribe_to_feed" + android:icon="@drawable/ic_new_light" + ugh:showAsAction="ifRoom" + android:title="@string/subscribe_to_feed"/> + + <item + android:id="@+id/show_feeds" + android:icon="@drawable/ic_list_light" + ugh:showAsAction="ifRoom" + android:title="@string/menu_all_feeds"/> + <item + android:id="@+id/go_offline" + android:icon="@drawable/ic_cloud_light" + ugh:showAsAction="ifRoom" + android:title="@string/go_offline"/> + <item + android:id="@+id/update_feeds" + android:icon="@drawable/ic_refresh_light" + ugh:showAsAction="" + android:title="@string/update_feeds"/> + <item + android:id="@+id/logout" + ugh:showAsAction="" + android:title="@string/logout"/> + </group> + <group android:id="@+id/menu_group_headlines" > + <item + android:id="@+id/update_headlines" + android:icon="@drawable/ic_refresh_light" + ugh:showAsAction="" + android:title="@string/update_headlines"/> + <item + android:id="@+id/search" + ugh:actionViewClass="android.widget.SearchView" + android:icon="@drawable/ic_search_light" + ugh:showAsAction="ifRoom|collapseActionView" + android:title="@string/search"/> + <item + android:id="@+id/headlines_mark_as_read" + android:icon="@drawable/ic_accept_light" + ugh:showAsAction="ifRoom" + android:title="@string/headlines_mark_as_read"/> + <item + android:id="@+id/headlines_select" + ugh:showAsAction="ifRoom" + android:icon="@drawable/ic_select_all_light" + android:title="@string/headlines_select"/> + + <item + android:id="@+id/headlines_view_mode" + ugh:showAsAction="" + android:title="@string/headlines_view_mode"/> + + <item + android:id="@+id/headlines_toggle_sidebar" + ugh:showAsAction="" + android:title="@string/toggle_sidebar"/> + + <!-- + <item + android:id="@+id/close_feed" + android:icon="@android:drawable/ic_menu_close_clear_cancel" + ugh:showAsAction="" + android:title="@string/close_feed"/> + --> + + </group> + <!-- <group android:id="@+id/menu_group_headlines_selection" > + <item + android:id="@+id/selection_toggle_unread" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_unread"/> + <item + android:id="@+id/selection_toggle_marked" + android:icon="@drawable/ic_unimportant_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_marked"/> + <item + android:id="@+id/selection_toggle_published" + android:icon="@drawable/ic_menu_unpublished_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_published"/> + <item + android:id="@+id/selection_select_none" + ugh: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_unimportant_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_toggle_marked"/> + <item + android:id="@+id/toggle_published" + android:icon="@drawable/ic_menu_unpublished_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_toggle_published"/> + <item + android:id="@+id/toggle_attachments" + android:icon="@drawable/ic_menu_attaches_light" + ugh:showAsAction="" + android:title="@string/attachments_prompt"/> + <item + android:id="@+id/share_article" + android:icon="@drawable/ic_share_light" + ugh:showAsAction="ifRoom" + android:title="@string/share_article"/> + <!-- android:actionProviderClass="android.widget.ShareActionProvider" --> + <item + android:id="@+id/set_labels" + ugh:showAsAction="" + android:icon="@drawable/ic_labels_light" + android:title="@string/article_set_labels"/> + <item + android:id="@+id/article_set_note" + ugh:showAsAction="" + android:title="@string/article_set_note"/> + + <item + android:id="@+id/catchup_above" + android:title="@string/article_mark_read_above"/> + + <item + android:id="@+id/set_unread" + android:icon="@drawable/ic_read_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_set_unread"/> + + </group> + + <item + android:id="@+id/donate" + ugh:showAsAction="" + android:title="@string/trial_purchase"/> + </group> + + <item + android:id="@+id/preferences" + android:icon="@android:drawable/ic_menu_preferences" + ugh:showAsAction="" + android:title="@string/preferences"/> + + <group android:id="@+id/menu_group_logged_out" > + <item + android:id="@+id/login" + android:icon="@android:drawable/ic_menu_rotate" + ugh:showAsAction="ifRoom|withText" + android:title="@string/login_login"/> + </group> + +</menu> diff --git a/orgfoxttrss/src/main/res/menu/offline_menu.xml b/orgfoxttrss/src/main/res/menu/offline_menu.xml new file mode 100644 index 00000000..a1bbf222 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/offline_menu.xml @@ -0,0 +1,96 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto" > + + <group android:id="@+id/menu_group_feeds" > + <item + android:id="@+id/go_online" + android:icon="@drawable/ic_cloud_light" + ugh:showAsAction="ifRoom|withText" + android:title="@string/go_online" + android:visible="false"/> + <item + android:id="@+id/show_feeds" + android:icon="@drawable/ic_list_light" + ugh:showAsAction="ifRoom" + android:title="@string/menu_all_feeds"/> + </group> + <group android:id="@+id/menu_group_headlines" > + <item + android:id="@+id/search" + ugh:actionViewClass="android.widget.SearchView" + android:icon="@drawable/ic_search_light" + ugh:showAsAction="ifRoom|collapseActionView" + android:title="@string/search"/> + <item + android:id="@+id/headlines_mark_as_read" + android:icon="@drawable/ic_accept_light" + ugh:showAsAction="ifRoom" + android:title="@string/headlines_mark_as_read"/> + <item + android:id="@+id/headlines_select" + android:icon="@drawable/ic_select_all_light" + ugh:showAsAction="ifRoom" + android:title="@string/headlines_select"/> + <item + android:id="@+id/headlines_view_mode" + ugh:showAsAction="" + android:title="@string/headlines_view_mode"/> + + <item + android:id="@+id/headlines_toggle_sidebar" + ugh:showAsAction="" + android:title="@string/toggle_sidebar"/> + </group> + <!-- + <group android:id="@+id/menu_group_headlines_selection" > + <item + android:id="@+id/selection_toggle_unread" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_unread"/> + <item + android:id="@+id/selection_toggle_marked" + android:icon="@drawable/ic_unimportant_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_marked"/> + <item + android:id="@+id/selection_toggle_published" + android:icon="@drawable/ic_menu_unpublished_light" + ugh:showAsAction="ifRoom" + android:title="@string/selection_toggle_published"/> + <item + android:id="@+id/selection_select_none" + ugh: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_unimportant_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_toggle_marked"/> + <item + android:id="@+id/toggle_published" + android:icon="@drawable/ic_menu_unpublished_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_toggle_published"/> + <item + android:id="@+id/share_article" + android:icon="@drawable/ic_share_light" + ugh:showAsAction="ifRoom" + android:title="@string/share_article"/> + <item + android:id="@+id/set_unread" + android:icon="@drawable/ic_read_light" + ugh:showAsAction="ifRoom" + android:title="@string/article_set_unread"/> + <item + android:id="@+id/catchup_above" + android:title="@string/article_mark_read_above"/> + </group> + + <item + android:id="@+id/preferences" + ugh:showAsAction="" + android:title="@string/preferences"/> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/menu/share_menu.xml b/orgfoxttrss/src/main/res/menu/share_menu.xml new file mode 100644 index 00000000..f2cb8145 --- /dev/null +++ b/orgfoxttrss/src/main/res/menu/share_menu.xml @@ -0,0 +1,9 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ugh="http://schemas.android.com/apk/res-auto" > + + <item + android:id="@+id/preferences" + android:icon="@android:drawable/ic_menu_preferences" + ugh:showAsAction="" + android:title="@string/preferences"/> + +</menu>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values-cs/strings.xml b/orgfoxttrss/src/main/res/values-cs/strings.xml new file mode 100644 index 00000000..fcaf790e --- /dev/null +++ b/orgfoxttrss/src/main/res/values-cs/strings.xml @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="login_in_progress">Přihlašení k serveru</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Prosím, nejprve nastavte aplikaci.</string> + <string name="login_ready">Připraven na přihlášení.</string> + <string name="login_login">Přihlásit</string> + <string name="logout">Odhlásit </string> + <string name="login">Jméno</string> + <string name="login_summary">Přihlašovací jméno. (Není nutné pokud se jedná o jednouživatelskou instalaci serveru.)</string> + <string name="http_login_summary">Vyplňuje se pouze pokud je tt-rss server chráněn HTTP autentizací</string> + <string name="debugging">Debugging</string> + <string name="password">Heslo</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Rozhraní</string> + <string name="pref_theme">Vzhled</string> + <string name="pref_theme_long">Změna barvy aplikace</string> + <string name="ttrss_url">Tiny Tiny RSS URL (adresa serveru)</string> + <string name="theme_dark">Tmavý</string> + <string name="preferences">Nastavení</string> + <string name="theme_light">Světlý</string> + <string name="connection">Připojení</string> + <string name="headline_context_multiple">Vybrané články</string> + <string name="http_authentication">HTTP autentizace</string> + <string name="loading_message">Přihlašování, čekejte prosí…</string> + <string name="menu_unread_feeds">Zobrazit nepřečtené zdroje</string> + <string name="menu_all_feeds">Zobrazit všechny zdroje</string> + <string name="update_feeds">Obnovit</string> + <string name="share_article">Sdílet článek</string> + <string name="catchup">Označit jako přečtené</string> + <string name="sort_feeds_by_unread">Řadit zdroje podle počtu nepřečtených článků</string> + <string name="ssl_trust_any">Přijmout jakýkoliv certifikát</string> + <string name="category_browse_feeds">Procházet zdroje</string> + <string name="category_browse_articles">Procházet články</string> + <string name="blank"/> + <string name="transport_debugging">Logovat odeslaná a přijatá data</string> + <string name="article_toggle_marked">Hvězdička</string> + <string name="article_toggle_published">Publikován</string> + <string name="headlines_select">Vybrat články</string> + <string name="headlines_select_dialog">Vybrat články</string> + <string name="headlines_select_all">Vše</string> + <string name="headlines_select_unread">Nepřečtené</string> + <string name="headlines_select_none">Odoznačit vše</string> + <string name="selection_toggle_marked">Hvězdička</string> + <string name="selection_toggle_published">Publikovaný</string> + <string name="selection_toggle_unread">Nepřečtené</string> + <string name="selection_select_none">Odoznačit vše</string> + <string name="context_selection_toggle_marked">Hvězdička</string> + <string name="context_selection_toggle_published">Publikovaný</string> + <string name="context_selection_toggle_unread">Nepřečtené</string> + <string name="article_set_unread">Nastavit nepřečtené</string> + <string name="article_mark_read_above">Nastavit jako přečtené</string> + <string name="ttrss_url_summary">Adresa tt-rss serveru. Například: http://muj_server.cz/tt-rss/</string> + <string name="download_feed_icons">Povolit ikony zdrojů</string> + <string name="enable_cats">Povolit kategorie</string> + <string name="no_feeds_to_display">Žádné zdroje k zobrazení</string> + <string name="no_headlines_to_display">Žádné titulky k zobrazení</string> + <string name="browse_cats_like_feeds">Procházet kategorie jako zdroje</string> + <string name="browse_cats_like_feeds_summary">Použij kontextové menu pro procházení kategorie</string> + <string name="headlines_mark_as_read">Označit za přečtené</string> + <string name="error_unknown">Chyba: Neznámá chyba (více v logu)</string> + <string name="error_http_unauthorized">Chyba: 401 neoprávněný přístup k serveru</string> + <string name="error_http_forbidden">Chyba: 403 obecná chyba serveru</string> + <string name="error_http_not_found">Chyba: 404 stránka nenalezena</string> + <string name="error_http_server_error">Chyba: 500 chyba serveru</string> + <string name="error_http_other_error">Chyba: jiná HTTP chyba (více v logu)</string> + <string name="error_ssl_rejected">Chyba: SSL certifikát zamítnut</string> + <string name="error_parse_error">Chyba: Nepodařilo se dekódovat JSON</string> + <string name="error_io_error">Chyba: I/O (Je server zapnut?)</string> + <string name="error_other_error">Chyba: Neznámá chyba (více v logu)</string> + <string name="error_api_disabled">Chyba: Prosím, povolte na serveru externí API. Nastavení - Obecné - Enable external API</string> + <string name="error_api_unknown">Chyba: neznámá chyba API (více v logu)</string> + <string name="error_api_incorrect_usage">Chyba: nekorektní použití API</string> + <string name="error_login_failed">Chyba: chybné uživatelské jméno nebo heslo</string> + <string name="error_invalid_api_url">Chyba: chybna API URL</string> + <string name="combined_mode_summary">Zobrazí celý článek, v samostatném panelu</string> + <string name="combined_mode">Kombinovaný mód</string> + <string name="go_offline">Pracovat offline</string> + <string name="go_online">Pracovat online</string> + <string name="offline_switch_error">Nepodařilo se přepnout do offline režimu (více log)</string> + <string name="no_feeds">Žádné zdroje k zobrazení</string> + <string name="no_headlines">Žádné články k zobrazení</string> + <string name="dialog_offline_prompt">Přihlášení se nezdařilo! Mate však uložena offline data. Chcete přejít do offline režimu?</string> + <string name="dialog_offline_success">Offline režim je připraven</string> + <string name="dialog_offline_go">Přejít do offline režimu</string> + <string name="dialog_cancel">Konec</string> + <string name="dialog_offline_switch_prompt">Stáhnout nepřečtené články a přejít do offline módu?</string> + <string name="notify_downloading_articles">Stahování článku (%1$d)…</string> + <string name="notify_downloading_init">Startuje stahování…</string> + <string name="notify_downloading_feeds">Stahování zdrojů…</string> + <string name="notify_uploading_sending_data">Odesílání dat na server…</string> + <string name="notify_downloading_title">Příprava offline módu</string> + <string name="notify_uploading_title">Synchronizace offline dat</string> + <string name="offline_sync_success">Synchronizace offline dat je hotova</string> + <string name="offline_mode">Offline mód</string> + <string name="offline_image_cache_enabled">Cache obrázků</string> + <string name="offline_image_cache_enabled_summary">Stahovat obrázky na sd kartu. Může výrazně zvýšit čas potřebný pro přejití do offline módu.</string> + <string name="notify_downloading_images">Stahuji obrázek (%1$d)…</string> + <string name="article_set_labels">Nastavit štítek</string> + <string name="search">Hledat</string> + <string name="cancel">Konec</string> + <string name="font_size_small">Malý</string> + <string name="font_size_medium">Medium</string> + <string name="font_size_large">Velký</string> + <string name="pref_font_size">Velikost textu</string> + <string name="donate">Darovat</string> + <string name="dialog_close">Zavřít</string> + <string name="donate_select">Prosím vyberte dar</string> + <string name="donate_do">Darovat!</string> + <string name="tablet_article_swipe">Přepínat mezi články</string> + <string name="article_link_copy">Kopírovat odkaz do schránky</string> + <string name="text_copied_to_clipboard">Text byl zkopírován do schránky</string> + <string name="attachments_prompt">Vyberte přílohu</string> + <string name="attachment_view">Zobrazit</string> + <string name="attachment_copy">Kopírovat URL</string> + <string name="justify_article_text">Zarovnání textu článku</string> + <string name="dialog_offline_sync_in_progress">Probíhá Offline synchronizace</string> + <string name="dialog_offline_sync_stop">Zastavit synchronizaci</string> + <string name="dialog_offline_sync_continue">Pokračovat</string> + <string name="article_set_note">Publikovat s poznámkou</string> + <string name="close_feed">Zavřít zdroj</string> + <string name="close_article">Zavřít článek</string> + <string name="dialog_open_preferences">Nastavení</string> + <string name="dialog_need_configure_prompt">Prosím, vyplňte informace o tt-rss serveru, jako jsou adresy URL, přihlašovací jméno a heslo.</string> + <string name="notify_article_marked">Článku byla přidána hvězdička</string> + <string name="notify_article_unmarked">Článku byla odebrána hvězdička</string> + <string name="notify_article_published">Článek byl publikován</string> + <string name="notify_article_unpublished">Publikování článku bylo zrušeno</string> + <string name="notify_article_note_set">Poznámky ke článku jsou uloženy</string> + <string name="update_headlines">Obnovit</string> + <string name="attachment_share">Sdílet</string> + <string name="error_network_unavailable">Chyba: síť je nedostupná</string> + <string name="category_browse_headlines">Procházet titulky</string> + <string name="pref_default_view_mode">Zobrazení zdrojů</string> + <string name="pref_default_view_mode_long">Určuje jakým způsobem se ve výchozím stavu budou zdroje procházet.</string> + <string name="donate_thanks">Dar byl ověřen, děkujeme za podporu!</string> + <string name="use_volume_keys">Použít ovladač hlasitosti</string> + <string name="use_volume_keys_long">Přepínání mezi články pomocí hardwarových tlačítek pro ovládání hlasitosti.</string> + <string name="ssl_trust_any_host">Neověřovat jméno serveru</string> + <string name="ssl_trust_any_host_long">Nebude se ověřovat pravost jména serveru</string> + <string name="error_api_unknown_method">Chyba: neznáma API funkce</string> + <string name="ssl_trust_any_long">Akceptovat jakýkoliv SSL certifikát bez validace</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Chyba: SSL hostname nelze ověřit</string> + <string name="offline_oldest_first">Zobrazit nejstarší články jako první</string> + <string name="prefs_dim_status_bar">Matný stavový proužek</string> + <string name="prefs_dim_status_bar_long">Schová stavový proužek při čtení</string> + <string name="article_comments">%1$d komentáře</string> + <string name="trial_mode_prompt">Trial verze, %1$d dní zbývá.</string> + <string name="trial_purchase">Odemknout plnou verzi</string> + <string name="trial_expired">Trial verze vypršela</string> + <string name="trial_expired_message">Chcete-li pokračovat v používání aplikace Tiny Tiny RSS prosím odemkněte ji zakoupeným klíčem.</string> + <string name="theme_sepia">Sépie</string> + <string name="trial_thanks">Plná verze, děkujeme za podporu!</string> + <string name="prefs_fullscreen_mode">Celá obrazovka</string> + <string name="reading">Čtení</string> + <string name="theme_dark_gray">Tmavošedý</string> + <string name="offline_articles_to_download">Počet článků ke stažení</string> + <string name="offline_articles_to_download_long">Počet článků ke stažení pro offline mód (nejnovější nejdříve).</string> + <string name="pref_headlines_show_content_long">Zobrazit obsahu náhledů v seznamu titulků</string> + <string name="pref_headlines_show_content">Náhled obsahu článku</string> + <string name="api_too_low">Tato akce vyžaduje novější verzi Tiny Tiny RSS serveru</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL článku</string> + <string name="share_content_hint">Obsah článku</string> + <string name="share_title_prompt">Nadpis:</string> + <string name="share_title_hint">Nadpis článku</string> + <string name="share_share_button">Sdílet</string> + <string name="share_article_posted">Článek odeslán</string> + <string name="subscribe_name">Přihlásit se k Tiny Tiny RSS</string> + <string name="feed_url">URL zdroje</string> + <string name="error_while_subscribing">Chyba při přihlášení.</string> + <string name="category_list_updated">Seznam kategorii načten</string> + <string name="subscribed_to_feed">Zdroj přihlášen k odběru</string> + <string name="error_feed_already_exists_">Chyba: zdroj již existuje</string> + <string name="error_invalid_url">Chyba: Nesprávná URL.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Chyba: Zdroj nenalezen.</string> + <string name="error_url_contains_multiple_feeds">Chyba: URL obsahuje několik zdrojů</string> + <string name="error_could_not_download_url">Chyba: Nelze stahovat</string> + <string name="headlines_view_mode">Nastavit mód</string> + <string name="headlines_set_view_mode">Nastavit mód</string> + <string name="headlines_adaptive">Adaptivní</string> + <string name="headlines_all_articles">Všechny články</string> + <string name="headlines_starred">S hvězdičkou</string> + <string name="headlines_published">Publikované</string> + <string name="headlines_unread">Nepřečtené</string> + <string name="subscribe_to_feed">Přidat zdroj</string> + <string name="labels">Štítky</string> + <string name="prefs_confirm_headlines_catchup">Potvrdit označení článků jako přečtených</string> + <string name="article_img_open">Otevřít obrázek</string> + <string name="n_unread_articles">%1$d nepřečtených článků</string> + <string name="pref_headline_font_size">Velikost textu nadpisu</string> + <string name="requires_api5">Vyžaduje verzi 1.7.6</string> + <string name="pref_headlines_mark_read_scroll_long">Nadpisy budou při posunu označeny jako přečtené</string> + <string name="no_caption_to_display">Bez popisky</string> + <string name="article_img_view_caption">Zobrazit popisku</string> + <string name="light_theme_is_not_supported_on_honeycomb">Světlé téma není na Honeycombu podporováno</string> + <string name="mark_num_headlines_as_read">Označit %1$d článků jako přečtené?</string> + <string name="pref_headlines_mark_read_scroll">Při posunu označit jako přečtené</string> + <string name="article_img_share">Sdílet obrázek</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-de/strings.xml b/orgfoxttrss/src/main/res/values-de/strings.xml new file mode 100644 index 00000000..7e8c2992 --- /dev/null +++ b/orgfoxttrss/src/main/res/values-de/strings.xml @@ -0,0 +1,220 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="login_in_progress">Anmelden…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Bitte erst Einstellungen überprüfen.</string> + <string name="login_ready">Bereit zur Anmeldung</string> + <string name="login_login">Anmelden</string> + <string name="logout">Abmelden</string> + <string name="login">Benutzername</string> + <string name="debugging">Debugging</string> + <string name="password">Passwort</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Oberfläche</string> + <string name="pref_theme">Theme</string> + <string name="pref_theme_long">Ändert die Farbeinstellungen</string> + <string name="ttrss_url">Tiny Tiny RSS URL</string> + <string name="theme_dark">Dunkel</string> + <string name="preferences">Einstellungen</string> + <string name="theme_light">Hell</string> + <string name="connection">Verbindung</string> + <string name="headline_context_multiple">Ausgewählte Artikel</string> + <string name="http_authentication">HTTP Authentifizierung</string> + <string name="loading_message">Lade, bitte warten…</string> + <string name="menu_unread_feeds">Zeige ungelesene Feeds</string> + <string name="menu_all_feeds">Zeige alle Feeds</string> + <string name="update_feeds">Feeds neu laden</string> + <string name="share_article">Artikel teilen</string> + <string name="catchup">Als gelesen markieren</string> + <string name="sort_feeds_by_unread">Sortiere Feeds nach ungelesenen Artikeln</string> + <string name="ssl_trust_any">Alle SSL Zertifikate akzeptieren</string> + <string name="category_browse_feeds">Feeds anzeigen</string> + <string name="category_browse_articles">Artikel anzeigen</string> + <string name="blank"></string> + <string name="transport_debugging">Gesendete und empfangene Daten aufzeichnen</string> + <string name="article_toggle_marked">Markieren (rückgängig machen)</string> + <string name="article_toggle_published">Veröffentlichen (rückgängig machen)</string> + <string name="headlines_select">Artikel auswählen</string> + <string name="headlines_select_dialog">Artikel auswählen</string> + <string name="headlines_select_all">Alle</string> + <string name="headlines_select_unread">Ungelesen</string> + <string name="headlines_select_none">Alle abwählen</string> + <string name="selection_toggle_marked">Markieren (rückgängig machen)</string> + <string name="selection_toggle_published">Veröffentlichen (rückgängig machen)</string> + <string name="selection_toggle_unread">Als (Un)gelesen markieren</string> + <string name="selection_select_none">Alle abwählen</string> + <string name="context_selection_toggle_marked">Markieren (rückgängig machen)</string> + <string name="context_selection_toggle_published">Veröffentlichen (rückgängig machen)</string> + <string name="context_selection_toggle_unread">Als (Un)gelesen markieren</string> + <string name="article_set_unread">Als ungelesen markieren</string> + <string name="article_mark_read_above">Obige als gelesen markieren.</string> + <string name="http_login_summary">Optional. Bitte ausfüllen wenn die TinyTiny RSS Installation durch HTTP Basic Authentication geschützt ist.</string> + <string name="login_summary">Dein tt-rss Benutzername. Nicht notwendig im Einbenutzermodus</string> + <string name="ttrss_url_summary">URL deiner tt-rss Installation, Bsp.: http://site.com/tt-rss/</string> + <string name="download_feed_icons">Feedsymbole herunterladen</string> + <string name="enable_cats">Feedkategorien anzeigen</string> + <string name="no_feeds_to_display">Keine Feeds zum Anzeigen</string> + <string name="no_headlines_to_display">Keine Überschriften zum Anzeigen.</string> + <string name="browse_cats_like_feeds">Kategorien als Feeds anzeigen</string> + <string name="browse_cats_like_feeds_summary">Kann im Kontextmenü jeder Kategorie angepasst werden.</string> + <string name="headlines_mark_as_read">Als gelesen markieren</string> + <string name="error_unknown">Fehler: Unbekannter Fehler (siehe Log)</string> + <string name="error_http_unauthorized">Fehler: 401 Nicht autorisiert</string> + <string name="error_http_forbidden">Fehler: 403 Zugriff verweigert</string> + <string name="error_http_not_found">Fehler: 404 Nicht gefunden</string> + <string name="error_http_server_error">Fehler: 500 Serverfehler</string> + <string name="error_http_other_error">Fehler: anderer HTTP Fehler (siehe Log)</string> + <string name="error_ssl_rejected">Fehler: SSL Zertifikat zurückgewiesen</string> + <string name="error_parse_error">Fehler: JSON parsen fehlgeschlagen</string> + <string name="error_io_error">Fehler: E/A Fehler (Server nicht erreichbar?)</string> + <string name="error_other_error">Fehler: Unbekannter Fehler (siehe Log)</string> + <string name="error_api_disabled">Fehler: Bitte API-Zugang in tt-rss unter Einstellungen - Erweitert aktivieren</string> + <string name="error_api_unknown">Fehler: unbekannter API Fehler (siehe Log)</string> + <string name="error_api_incorrect_usage">Fehler: Fehlerhafte Benutzung der API</string> + <string name="error_login_failed">Fehler: Benutzername und Passwort falsch</string> + <string name="error_invalid_api_url">Fehler: API URL ungültig</string> + <string name="combined_mode_summary">Zeigt Artikelinhalt in der gleichen Spalte statt in einer eigenen.</string> + <string name="combined_mode">Kombinierte Anzeige</string> + <string name="go_offline">Offline gehen</string> + <string name="go_online">Online gehen</string> + <string name="offline_switch_error">Vorbereitungen für den Offlinemodus fehlgeschlagen</string> + <string name="no_feeds">Keine Feeds zum Anzeigen</string> + <string name="no_headlines">Keine Artikel zum Anzeigen</string> + <string name="dialog_offline_prompt">Anmeldung fehlgeschlagen, Offlinedaten vorhanden. In den Offlinemodus wechseln?</string> + <string name="dialog_offline_success">Offlinemodus bereit</string> + <string name="dialog_offline_go">Offline gehen</string> + <string name="dialog_cancel">Abbrechen</string> + <string name="dialog_offline_switch_prompt">Ungelesene Artikel herunterladen und Offline gehen?</string> + <string name="notify_downloading_articles">Lade Artikel herunter (%1$d)…</string> + <string name="notify_downloading_init">Starte Download…</string> + <string name="notify_downloading_feeds">Lade Feeds herunter…</string> + <string name="notify_uploading_sending_data">Sende Daten an Server…</string> + <string name="notify_downloading_title">Bereite Offlinemodus vor</string> + <string name="notify_uploading_title">Synchronisiere Offlinedaten…</string> + <string name="offline_sync_success">Synchronisierung der Offlinedaten abgeschlossen</string> + <string name="offline_mode">Offlinemodus</string> + <string name="offline_image_cache_enabled">Bilder herunterladen</string> + <string name="offline_image_cache_enabled_summary">Bilder auf die SD-Karte herunterladen. Dies kann den Wechsel in den Offlinemodus deutlich verlängern.</string> + <string name="notify_downloading_images">Lade Bilder herunter (%1$d)…</string> + <string name="article_set_labels">Setze Label</string> + <string name="search">Suchen</string> + <string name="cancel">Abbrechen</string> + <string name="font_size_small">klein</string> + <string name="font_size_medium">mittel</string> + <string name="font_size_large">groß</string> + <string name="pref_font_size">Schriftgröße</string> + <string name="donate">Spenden</string> + <string name="dialog_close">Schließen</string> + <string name="donate_select">Bitte Spendenbetrag auswählen</string> + <string name="donate_do">Spenden!</string> + <string name="tablet_article_swipe">Swipe benutzen</string> + <string name="article_link_copy">Link in die Zwischenablage kopieren</string> + <string name="text_copied_to_clipboard">Text in die Zwischenablage kopiert</string> + <string name="attachments_prompt">Anhang auswählen</string> + <string name="attachment_view">Ansicht</string> + <string name="attachment_copy">Kopiere URL</string> + <string name="justify_article_text">Artikeltext als Blocksatz</string> + <string name="dialog_offline_sync_in_progress">Offlinesynchronisation läuft</string> + <string name="dialog_offline_sync_stop">Synchronisation abbrechen</string> + <string name="dialog_offline_sync_continue">Weiter</string> + <string name="article_set_note">Mit Notiz veröffentlichen</string> + <string name="close_feed">Feed schließen</string> + <string name="close_article">Artikel schließen</string> + <string name="dialog_open_preferences">Einstellungen öffnen</string> + <string name="dialog_need_configure_prompt">Bitte die tt-rss Serverdaten wie URL, Benutzername und Passwort eintragen.</string> + <string name="notify_article_marked">Artikel markiert</string> + <string name="notify_article_unmarked">Markierung entfernt</string> + <string name="notify_article_published">Artikel veröffentlicht</string> + <string name="notify_article_unpublished">Artikel nicht mehr veröffentlicht</string> + <string name="notify_article_note_set">Artikelnotiz gespeichert</string> + <string name="update_headlines">Aktualisieren</string> + <string name="attachment_share">Teilen</string> + <string name="error_network_unavailable">Fehler: Keine Netzwerkverbindung</string> + <string name="category_browse_headlines">Überschriften anzeigen</string> + <string name="pref_default_view_mode">Standard Feedansicht</string> + <string name="pref_default_view_mode_long">Welche Feedansicht soll standardmäßig auf einem Smartphone angezeigt werden?</string> + <string name="donate_thanks">Spende erhalten. Danke für deine Unterstützung!</string> + <string name="use_volume_keys">Benutze Lautstärkeknöpfe</string> + <string name="use_volume_keys_long">Mit den Hardware-Lautstärketasten zwischen einzelnen Artikeln wechseln.</string> + <string name="ssl_trust_any_host">Keine Überprüfung des Hostnamens</string> + <string name="error_api_unknown_method">Fehler: Unbekannte API-Methode</string> + <string name="ssl_trust_any_long">Akzeptiert jedes SSL Zertifikat ohne Überprüfung!</string> + <string name="ssl_trust_any_host_long">Server-Hostname nicht überprüfen</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Fehler: SSL-Hostname nicht korrekt</string> + <string name="offline_oldest_first">Älteste Artikel zuerst anzeigen</string> + <string name="prefs_dim_status_bar">Statusleiste abdunkeln</string> + <string name="prefs_dim_status_bar_long">Statusleiste beim Lesen dunkler machen</string> + <string name="article_comments">%1$d Kommentare</string> + <string name="trial_mode_prompt">Testversion, %1$d Tag(e) übrig.</string> + <string name="trial_purchase">Vollversion freischalten</string> + <string name="trial_expired">Testzeitraum abgelaufen</string> + <string name="trial_expired_message">Um Tiny Tiny RSS weiterhin nutzen zu können kaufen Sie bitte den Schlüssel.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Vollversion, Vielen Dank für die Unterstützung!</string> + <string name="prefs_fullscreen_mode">Vollbild</string> + <string name="reading">Lesen</string> + <string name="theme_dark_gray">Dunkelgrau</string> + <string name="offline_articles_to_download">Anzahl der Artikel</string> + <string name="offline_articles_to_download_long">Anzahl der Artikel die für den Offlinemodus heruntergeladen werden. (Neueste zuerst)</string> + <string name="pref_headlines_show_content_long">Zeige Artikelvorschau in der Listenansicht</string> + <string name="pref_headlines_show_content">Zeige Artikelvorschau</string> + <string name="api_too_low">Dafür benötigen Sie eine neuere Version von Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">Artikel-URL</string> + <string name="share_content_hint">Artikelinhalt</string> + <string name="share_title_prompt">Titel:</string> + <string name="share_title_hint">Artikelüberschrift</string> + <string name="share_share_button">Teilen</string> + <string name="share_article_posted">Artikel abgeschickt.</string> + <string name="subscribe_name">Mit Tiny Tiny RSS abonnieren</string> + <string name="feed_url">Feed-URL</string> + <string name="subscribe_to_feed">Feed abonnieren</string> + <string name="error_while_subscribing">Fehler beim Abonnieren.</string> + <string name="category_list_updated">Kategorienliste aktualisiert</string> + <string name="subscribed_to_feed">Feed abonniert</string> + <string name="error_feed_already_exists_">Fehler: Feed existiert bereits.</string> + <string name="error_invalid_url">Fehler: Ungültige URL.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Fehler: URL ist eine HTML-Seite, keine Feeds gefunden.</string> + <string name="error_url_contains_multiple_feeds">Fehler: URL enthält mehrere Feeds</string> + <string name="error_could_not_download_url">Fehler: Konnte URL nicht downloaden</string> + <string name="headlines_view_mode">Setze Anzeigemodus</string> + <string name="headlines_set_view_mode">Setze Anzeigemodus</string> + <string name="headlines_adaptive">Adaptiv</string> + <string name="headlines_all_articles">Alle Artikel</string> + <string name="headlines_starred">Markiert</string> + <string name="headlines_published">Veröffentlicht</string> + <string name="headlines_unread">Ungelesen</string> + <string name="article_img_open">Bild öffnen</string> + <string name="article_img_share">Bild teilen</string> + <string name="requires_api5">Benötigt Version 1.7.6</string> + <string name="labels">Labels</string> + <string name="article_img_view_caption">Bildunterschrift zeigen</string> + <string name="light_theme_is_not_supported_on_honeycomb">Helle Oberfläche wird auf Honeycomb nicht unterstützt</string> + <string name="pref_headlines_mark_read_scroll">Beim scrollen als gelesen markieren</string> + <string name="pref_headlines_mark_read_scroll_long">Überschriften werden beim Vorbeiscrollen als gelesen markiert</string> + <string name="mark_num_headlines_as_read">%1$d Artikel als gelesen markieren?</string> + <string name="prefs_confirm_headlines_catchup">Nachfragen, bevor Artikel als gelesen markiert werden</string> + <string name="author_formatted">von %1$s</string> + <string name="n_unread_articles">%1$d ungelesene Artikel</string> + <string name="pref_headline_font_size">Schriftgröße Überschriften</string> + <string name="context_confirm_catchup">Alle Artikel in %1$s als gelesen markieren?</string> + <string name="theme_system">Voreinstellung des Systems</string> + <string name="accel_webview_summary">Deaktivieren, falls es zu Flackern oder anderen sichtbaren Störungen kommt.</string> + <string name="accel_webview_title">Webansicht beschleunigen (3.0+)</string> + <string name="place_shortcut">Verknüpfung erstellen</string> + <string name="shortcut_has_been_placed_on_the_home_screen">Eine Verknüpfung wurde auf dem Startbildschirm erstellt</string> + <string name="download_articles_and_go_offline">Artikel herunterladen und offline gehen</string> + <string name="tasker_save_and_close">Speichern und schließen</string> + <string name="synchronize_read_articles_and_go_online">Gelesene Artikel synchronisieren und online gehen</string> + <string name="prefs_compatible_article_layout">Kompatibles Artikel-Layout</string> + <string name="prefs_compatible_layout_summary">Aktivieren, falls der Inhalt von Artikeln nicht richtig dargestellt wird</string> + <string name="pref_headlines_use_condensed_fonts">Schmale Schriftart benutzen</string> + <string name="pref_headlines_use_condensed_fonts_long">Benutzt einen schmaleren Schriftstil für Überschriften und einige andere Elemente.</string> + <string name="font_size_dialog_suffix">sp</string> + <string name="server_function_not_available">Diese Funktion ist für deine Version von tt-rss leider nicht verfügbar.</string> + <string name="unsubscribe">Nicht mehr abonnieren</string> + <string name="unsubscribe_from_prompt">Feed %1$s wirklich abbestellen?</string> + <string name="toggle_sidebar">Seitenleiste umschalten</string> + <string name="open_article_in_web_browser">In Webbrowser öffnen</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-es/strings.xml b/orgfoxttrss/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..6a111fea --- /dev/null +++ b/orgfoxttrss/src/main/res/values-es/strings.xml @@ -0,0 +1,204 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">Iniciando sesión…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Por favor, configure primero la aplicación.</string> + <string name="login_ready">Listo para iniciar sesión.</string> + <string name="login_login">Iniciar sesión</string> + <string name="logout">Cerrar sesión</string> + <string name="login">Credenciales</string> + <string name="debugging">Debugging</string> + <string name="password">Contraseña</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Apariencia</string> + <string name="pref_theme">Tema</string> + <string name="pref_theme_long">Cambiar el tema de colores de la aplicación</string> + <string name="ttrss_url">URL do Tiny Tiny RSS</string> + <string name="theme_dark">Oscuro</string> + <string name="preferences">Preferencias</string> + <string name="theme_light">Claro</string> + <string name="connection">Conexión</string> + <string name="headline_context_multiple">Artículos seleccionados</string> + <string name="http_authentication">Autenticación HTTP</string> + <string name="loading_message">Cargando, espere por favor…</string> + <string name="menu_unread_feeds">Mostrar fuentes sin leer</string> + <string name="menu_all_feeds">Mostrar todas las fuentes</string> + <string name="update_feeds">Actualizar</string> + <string name="share_article">Compartir artículo</string> + <string name="catchup">Marcar como leído</string> + <string name="sort_feeds_by_unread">Ordenar fuentes por número de artículos sin leer</string> + <string name="ssl_trust_any">Aceptar cualquier certificado</string> + <string name="category_browse_feeds">Ver fuentes</string> + <string name="category_browse_articles">Ver artículos</string> + <string name="blank"></string> + <string name="transport_debugging">Registrar datos enviados y recibidos</string> + <string name="article_toggle_marked">Marcar/desmarcar como favorito</string> + <string name="article_toggle_published">Marcar/desmarcar como publicado</string> + <string name="headlines_select">Seleccionar artículos</string> + <string name="headlines_select_dialog">Seleccionar artículos</string> + <string name="headlines_select_all">Todo</string> + <string name="headlines_select_unread">Sin leer</string> + <string name="headlines_select_none">Deseleccionar todo</string> + <string name="selection_toggle_marked">Marcar/desmarcar como favorito</string> + <string name="selection_toggle_published">Marcar/desmarcar como publicado</string> + <string name="selection_toggle_unread">Marcar/desmarcar como leído</string> + <string name="selection_select_none">Deseleccionar todo</string> + <string name="context_selection_toggle_marked">Marcar/desmarcar como favorito</string> + <string name="context_selection_toggle_published">Marcar/desmarcar como publicado</string> + <string name="context_selection_toggle_unread">Marcar/desmarcar como leído</string> + <string name="article_set_unread">Artículo marcado como no leído</string> + <string name="article_mark_read_above">Marcar artículos anteriores como leídos</string> + <string name="http_login_summary">Opcional. Complete este campo si su instalación de Tiny Tiny RSS usa autenticación básica por HTTP</string> + <string name="login_summary">Sus credenciales para Tiny Tiny RSS. No son necesarias en modo de usuario único</string> + <string name="ttrss_url_summary">URL de su instalación de Tiny Tiny RSS, por ejemplo http://site.com/tt-rss/</string> + <string name="download_feed_icons">Habilitar iconos de las fuentes</string> + <string name="enable_cats">Habilitar categorías de fuentes</string> + <string name="no_feeds_to_display">Ninguna fuente para mostrar</string> + <string name="no_headlines_to_display">Ningún titular para mostrar</string> + <string name="no_caption_to_display">Ninguna leyenda para mostrar</string> + <string name="browse_cats_like_feeds">Ver categorías como si fuesen fuentes</string> + <string name="browse_cats_like_feeds_summary">Use el menú contextual de categoría para reemplazar esta preferencia</string> + <string name="headlines_mark_as_read">Marcar como leído</string> + <string name="error_unknown">Error: Error desconocido (ver log)</string> + <string name="error_http_unauthorized">Error: 401 No autorizado</string> + <string name="error_http_forbidden">Error: 403 Acceso denegado</string> + <string name="error_http_not_found">Error: 404 No encontrado</string> + <string name="error_http_server_error">Error: 500 Error del servidor</string> + <string name="error_http_other_error">Error: Otro error HTTP (ver log)</string> + <string name="error_ssl_rejected">Error: Certificado SSL rechazado</string> + <string name="error_parse_error">Error: Fallo al interpretar JSON</string> + <string name="error_io_error">Error: Fallo de I/O (¿servidor caído?)</string> + <string name="error_other_error">Error: Error desconocido (ver log)</string> + <string name="error_api_disabled">Error: Habilite el acceso por API externa en las preferencias avanzadas de Tiny Tiny RSS</string> + <string name="error_api_unknown">Error: Error desconocido de la API (ver log)</string> + <string name="error_api_incorrect_usage">Error: Uso incorrecto da API</string> + <string name="error_login_failed">Error: Usuario o contraseña incorrectos</string> + <string name="error_invalid_api_url">Error: URL de la API inválida</string> + <string name="combined_mode_summary">Mostrar texto del artículo con el titular, en vez de usar un panel separado</string> + <string name="combined_mode">Modo combinado</string> + <string name="go_offline">Entrar en modo offline</string> + <string name="go_online">Entrar en modo online</string> + <string name="offline_switch_error">Fallo al preparar el modo offline (ver log)</string> + <string name="no_feeds">Ninguna fuente para mostrar</string> + <string name="no_headlines">Ningún artículo para mostrar</string> + <string name="dialog_offline_prompt">El inicio de sesión ha fallado, pero tiene datos offline almacenados. ¿Quiere entrar en modo offline?</string> + <string name="dialog_offline_success">Modo offline está listo</string> + <string name="dialog_offline_go">Entrar en modo offline</string> + <string name="dialog_cancel">Cancelar</string> + <string name="dialog_offline_switch_prompt">¿Descargar artículos sin leer y entrar a modo offline?</string> + <string name="notify_downloading_articles">Descargando artículos (%1$d)…</string> + <string name="notify_downloading_init">Iniciando descarga…</string> + <string name="notify_downloading_feeds">Descargando fuentes…</string> + <string name="notify_uploading_sending_data">Enviando datos al servidor…</string> + <string name="notify_downloading_title">Preparando modo offline</string> + <string name="notify_uploading_title">Sincronizando datos offline</string> + <string name="offline_sync_success">Sincronización de datos offline completa</string> + <string name="offline_mode">Modo offline</string> + <string name="offline_image_cache_enabled">Guardar imágenes en caché</string> + <string name="offline_image_cache_enabled_summary">Descargar imágenes a la tarjeta SD. Esto podría prolongar significativamente la espera para entrar al modo offline.</string> + <string name="notify_downloading_images">Descargando imágenes (%1$d)…</string> + <string name="article_set_labels">Definir marcadores</string> + <string name="search">Buscar</string> + <string name="cancel">Cancelar</string> + <string name="font_size_small">Pequeño</string> + <string name="font_size_medium">Medio</string> + <string name="font_size_large">Grande</string> + <string name="pref_font_size">Tamaño de texto de los artículos</string> + <string name="donate">Donar</string> + <string name="dialog_close">Cerrar</string> + <string name="donate_select">Por favor, seleccione la donación</string> + <string name="donate_do">¡Donar!</string> + <string name="tablet_article_swipe">Deslizar el dedo para moverse entre artículos</string> + <string name="article_link_copy">Copiar enlace al portapapeles</string> + <string name="text_copied_to_clipboard">Texto copiado al portapapeles</string> + <string name="attachments_prompt">Seleccione adjunto</string> + <string name="attachment_view">Ver</string> + <string name="attachment_copy">Copiar URL</string> + <string name="justify_article_text">Justificar texto de artículos</string> + <string name="dialog_offline_sync_in_progress">Sincronización offline en progreso</string> + <string name="dialog_offline_sync_stop">Detener sincronización</string> + <string name="dialog_offline_sync_continue">Continuar</string> + <string name="article_set_note">Publicar con anotación</string> + <string name="close_feed">Cerrar fuente</string> + <string name="close_article">Cerrar artículo</string> + <string name="dialog_open_preferences">Preferencias</string> + <string name="dialog_need_configure_prompt">Por favor, complete la información de su servidor de Tiny Tiny RSS, como URL, nombre de usuario y contraseña.</string> + <string name="notify_article_marked">Artículo favorito</string> + <string name="notify_article_unmarked">Artículo no favorito</string> + <string name="notify_article_published">Artículo publicado</string> + <string name="notify_article_unpublished">Artículo no publicado</string> + <string name="notify_article_note_set">Nota del artículo guardada</string> + <string name="update_headlines">Actualizar</string> + <string name="attachment_share">Compartir</string> + <string name="error_network_unavailable">Error: Red no disponible</string> + <string name="category_browse_headlines">Ver titulares</string> + <string name="pref_default_view_mode">Vista de fuentes por defecto</string> + <string name="pref_default_view_mode_long">Qué vista de fuentes abrir por defecto en smartphones</string> + <string name="donate_thanks">Donación encontrada, ¡gracias por su apoyo!</string> + <string name="use_volume_keys">Usar los botones de volumen</string> + <string name="use_volume_keys_long">Moverse entre artículos usando los botones de volumen</string> + <string name="ssl_trust_any_host">No verificar hostname</string> + <string name="error_api_unknown_method">Error: método API desconocido</string> + <string name="ssl_trust_any_long">Aceptar cualquier certificado SSL sin verificación</string> + <string name="ssl_trust_any_host_long">No verificar el nombre del servidor</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Error: Hostname SSL no verificado</string> + <string name="offline_oldest_first">Mostrar artículos más antiguos primero</string> + <string name="prefs_dim_status_bar">Oscurecer barra de estado</string> + <string name="prefs_dim_status_bar_long">Oscurecer barra de estado durante lectura</string> + <string name="article_comments">%1$d comentarios</string> + <string name="trial_mode_prompt">Período de prueba, queda(n) %1$d día(s).</string> + <string name="trial_purchase">Desbloquear versión completa</string> + <string name="trial_expired">Período de prueba expirado</string> + <string name="trial_expired_message">Para continuar usando Tiny Tiny RSS, por favor desbloquee la versión completa adquiriendo una clave.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Versión completa, ¡gracias por su apoyo!</string> + <string name="prefs_fullscreen_mode">Modo de pantalla completa</string> + <string name="reading">Leyendo</string> + <string name="theme_dark_gray">Gris oscuro</string> + <string name="offline_articles_to_download">Número de artículos a descargar</string> + <string name="offline_articles_to_download_long">Número de artículos a descargar para modo offline (primero los más recientes).</string> + <string name="pref_headlines_show_content_long">Previsualizar texto de los artículos en la lista de titulares</string> + <string name="pref_headlines_show_content">Previsualización de artículos</string> + <string name="api_too_low">Esta acción requiere una versión más reciente de Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL del artículo</string> + <string name="share_content_hint">Contenido del artículo</string> + <string name="share_title_prompt">Título:</string> + <string name="share_title_hint">Título del artículo</string> + <string name="share_share_button">Compartir</string> + <string name="share_article_posted">Artículo compartido.</string> + <string name="subscribe_name">Suscribirse con Tiny Tiny RSS</string> + <string name="feed_url">URL de la fuente</string> + <string name="subscribe_to_feed">Suscribirse a fuente</string> + <string name="error_while_subscribing">Error al suscribirse.</string> + <string name="category_list_updated">Lista de categorías actualizada</string> + <string name="subscribed_to_feed">Fuente suscrita</string> + <string name="error_feed_already_exists_">Error: la fuente ya existe.</string> + <string name="error_invalid_url">Error: URL inválida.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Error: la URL es de una página HTML, no se han encontrado fuentes.</string> + <string name="error_url_contains_multiple_feeds">Error: la URL contiene múltiples fuentes</string> + <string name="error_could_not_download_url">Error: no se pudo descargar la URL</string> + <string name="headlines_view_mode">Definir modo de vista</string> + <string name="headlines_set_view_mode">Definir modo de vista</string> + <string name="headlines_adaptive">Adaptable</string> + <string name="headlines_all_articles">Todos los artículos</string> + <string name="headlines_starred">Favoritos</string> + <string name="headlines_published">Publicados</string> + <string name="headlines_unread">Sin leer</string> + <string name="article_img_open">Abrir imagen</string> + <string name="article_img_share">Compartir imagen</string> + <string name="requires_api5">Requiere la versión 1.7.6</string> + <string name="labels">Marcadores</string> + <string name="article_img_view_caption">Ver leyenda</string> + <string name="light_theme_is_not_supported_on_honeycomb">Tema claro no está soportado en Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Marcar como leído al desplazarse por titulares</string> + <string name="pref_headlines_mark_read_scroll_long">Los artículos se marcarán como leídos al desplazarse por la lista de titulares</string> + <string name="mark_num_headlines_as_read">Marcar %1$d artículo(s) como leído(s)?</string> + <string name="prefs_confirm_headlines_catchup">Confirme para marcar artículos como leídos</string> + <string name="author_formatted">por %1$s</string> + <string name="n_unread_articles">%1$d artículos sin leer</string> + <string name="pref_headline_font_size">Tamaño de texto para los titulares</string> + <string name="context_confirm_catchup">¿Marcar todos los artículos en %1$s como leídos?</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-fr/strings.xml b/orgfoxttrss/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..25e24927 --- /dev/null +++ b/orgfoxttrss/src/main/res/values-fr/strings.xml @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">Connexion…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Configurer l\'application d\'abord.</string> + <string name="login_ready">Prêt à se connecter.</string> + <string name="login_login">Connexion</string> + <string name="logout">Déconnexion</string> + <string name="login">Identifiant</string> + <string name="debugging">Débogage</string> + <string name="password">Mot de passe</string> + <string name="default_url">http://exemple.domaine/tt-rss/</string> + <string name="look_and_feel">Interface</string> + <string name="pref_theme">Thème</string> + <string name="pref_theme_long">Change la couleur du thème de l\'application</string> + <string name="ttrss_url">Tiny Tiny RSS URL</string> + <string name="theme_dark">Sombre</string> + <string name="preferences">Préférences</string> + <string name="theme_light">Clair</string> + <string name="connection">Connexion</string> + <string name="headline_context_multiple">Articles sélectionnés</string> + <string name="http_authentication">Authentification HTTP</string> + <string name="loading_message">Chargement, patientez s\'il-vous-plaît…</string> + <string name="menu_unread_feeds">Afficher les flux non-lus</string> + <string name="menu_all_feeds">Afficher tous les flux</string> + <string name="update_feeds">Rafraichir</string> + <string name="share_article">Partager l\'article</string> + <string name="catchup">Marquer comme lu</string> + <string name="sort_feeds_by_unread">Trier flux par quantité de non lus</string> + <string name="ssl_trust_any">Accepter n\'importe quel certificat</string> + <string name="category_browse_feeds">Parcourir les flux</string> + <string name="category_browse_articles">Parcourir les articles</string> + <string name="blank"></string> + <string name="transport_debugging">Enregistrement des données envoyées et reçues</string> + <string name="article_toggle_marked">Basculer Marqué</string> + <string name="article_toggle_published">Basculer Publié</string> + <string name="headlines_select">Sélectionner des articles</string> + <string name="headlines_select_dialog">Sélectionner les articles</string> + <string name="headlines_select_all">Tous</string> + <string name="headlines_select_unread">Non lus</string> + <string name="headlines_select_none">Aucun</string> + <string name="selection_toggle_marked">Basculer Marqué</string> + <string name="selection_toggle_published">Basculer Publié</string> + <string name="selection_toggle_unread">Basculer Non lu</string> + <string name="selection_select_none">Desélectionner tout</string> + <string name="context_selection_toggle_marked">Basculer Marqué</string> + <string name="context_selection_toggle_published">Basculer Publié</string> + <string name="context_selection_toggle_unread">Basculer Non lu</string> + <string name="article_set_unread">Marquer Non lu</string> + <string name="article_mark_read_above">Marquer comme lu jusqu\'ici</string> + <string name="http_login_summary">Optionnel. Remplissez ceci si votre installation tt-rss est protégée par une authentification HTTP Basique.</string> + <string name="login_summary">Votre identifiant tt-rss. Inutile en mode utilisateur unique.</string> + <string name="ttrss_url_summary">L\'URL de votre répertoire d\'installation tt-rss, ex: http://site.com/tt-rss/</string> + <string name="download_feed_icons">Activer les icônes des flux</string> + <string name="enable_cats">Activer les catégories des flux</string> + <string name="no_feeds_to_display">Aucun flux à afficher</string> + <string name="no_caption_to_display">Aucun sous-titre trouvé</string> + <string name="no_headlines_to_display">Aucun titre à afficher</string> + <string name="browse_cats_like_feeds">Parcourir les catégories comme les flux</string> + <string name="browse_cats_like_feeds_summary">Utilisez le menu contexte des catégories pour redéfinir ce paramètre.</string> + <string name="headlines_mark_as_read">Marquer comme lu</string> + <string name="error_unknown">Erreur: Erreur inconnue (voir journal)</string> + <string name="error_http_unauthorized">Erreur: 401 non-authorisé</string> + <string name="error_http_forbidden">Erreur: 403 interdit</string> + <string name="error_http_not_found">Erreur: 404 pas trouvé</string> + <string name="error_http_server_error">Erreur: 500 erreur du serveur</string> + <string name="error_http_other_error">Erreur: autre erreur HTTP (voir journal)</string> + <string name="error_ssl_rejected">Erreur: certificat SSL rejeté</string> + <string name="error_parse_error">Erreur: échec de l\'analyse JSON</string> + <string name="error_io_error">Erreur: échec I/O (serveur en panne?)</string> + <string name="error_other_error">Erreur: erreur inconnue (voir journal)</string> + <string name="error_api_disabled">Erreur: Merci d\'activer les API externes dans les paramètres tt-rss: Configuration - Avancé</string> + <string name="error_api_unknown">Erreur: erreur inconnue d\'API (voir journal)</string> + <string name="error_api_incorrect_usage">Erreur: utilisation incorrecte de l\'API</string> + <string name="error_login_failed">Erreur: identifiant ou mot de passe incorrect</string> + <string name="error_invalid_api_url">Erreur: API URL invalide</string> + <string name="combined_mode_summary">Afficher l\'article complet en un seul bloc au lieu d\'un panneau séparé</string> + <string name="combined_mode">Mode combiné</string> + <string name="go_offline">Passer hors connexion</string> + <string name="go_online">Passer en-connexion</string> + <string name="offline_switch_error">Échec de la préparation en mode hors connexion (voir journal)</string> + <string name="no_feeds">Aucun flux à afficher</string> + <string name="no_headlines">Aucun article à afficher</string> + <string name="dialog_offline_prompt">Échec de la connexion, mais vous avec conserver des données hors connexion. Voulez-vous passer hors connexion ?</string> + <string name="dialog_offline_success">Le mode hors connexion est prêt</string> + <string name="dialog_offline_go">Passer hors connexion</string> + <string name="dialog_cancel">Annuler</string> + <string name="dialog_offline_switch_prompt">Télécharger les articles non lus et passer hors connexion ?</string> + <string name="notify_downloading_articles">Téléchargement des articles (%1$d)…</string> + <string name="notify_downloading_init">Démarrage du téléchargement…</string> + <string name="notify_downloading_feeds">Téléchargement des flux…</string> + <string name="notify_uploading_sending_data">Envoi des données au serveur…</string> + <string name="notify_downloading_title">Préparation du mode hors connexion</string> + <string name="notify_uploading_title">Synchronisation des données hors connexion</string> + <string name="offline_sync_success">Synchronisation de vos données hors connexion terminée</string> + <string name="offline_mode">Mode hors connexion</string> + <string name="offline_image_cache_enabled">Pré-chargement des images</string> + <string name="offline_image_cache_enabled_summary">Télécharger les images sur la carte SD. Cela pourrait augmenter de manière significative le temps de passage hors connexion.</string> + <string name="notify_downloading_images">Téléchargement des images (%1$d)…</string> + <string name="article_set_labels">Modifier les tags</string> + <string name="search">Rechercher</string> + <string name="cancel">Annuler</string> + <string name="font_size_small">Petit</string> + <string name="font_size_medium">Moyen</string> + <string name="font_size_large">Grand</string> + <string name="pref_font_size">Taille du texte des articles</string> + <string name="donate">Faire un don</string> + <string name="dialog_close">Fermer</string> + <string name="donate_select">Merci de choisir votre don</string> + <string name="donate_do">Faire un don !</string> + <string name="tablet_article_swipe">Basculer entre les articles</string> + <string name="article_link_copy">Copier le lien dans le presse-papier</string> + <string name="text_copied_to_clipboard">Texte copié dans le presse-papier</string> + <string name="attachments_prompt">Sélectionner une pièce jointe</string> + <string name="attachment_view">Voir</string> + <string name="attachment_copy">Copier l\'URL</string> + <string name="justify_article_text">Justifier le texte des articles</string> + <string name="dialog_offline_sync_in_progress">Synchronisation hors connexion en cours</string> + <string name="dialog_offline_sync_stop">Arrêter la synchronisation</string> + <string name="dialog_offline_sync_continue">Continuer</string> + <string name="article_set_note">Publier avec une note</string> + <string name="close_feed">Fermer le flux</string> + <string name="close_article">Fermer l\'article</string> + <string name="dialog_open_preferences">Ouvrir les préférences</string> + <string name="dialog_need_configure_prompt">Merci d\'indiquer les paramètres de votre serveur tt-rss tels que URL, identifiant et mot de passe.</string> + <string name="notify_article_marked">Article marqué</string> + <string name="notify_article_unmarked">Article non marqué</string> + <string name="notify_article_published">Article publié</string> + <string name="notify_article_unpublished">Article non publié</string> + <string name="notify_article_note_set">Note enregistrée</string> + <string name="update_headlines">Rafraichir</string> + <string name="attachment_share">Partager</string> + <string name="error_network_unavailable">Erreur: réseau indisponible</string> + <string name="category_browse_headlines">Parcourir les titres</string> + <string name="pref_default_view_mode">Vue par défaut</string> + <string name="pref_default_view_mode_long">Quel flux afficher par défaut sur les téléphones</string> + <string name="donate_thanks">Don trouvé, merci de votre support!</string> + <string name="use_volume_keys">Utiliser les boutons de volume</string> + <string name="use_volume_keys_long">Faire défiler les articles avec les boutons de volume</string> + <string name="ssl_trust_any_host">Ne pas vérifier les noms d\'hôte</string> + <string name="error_api_unknown_method">Erreur: méthode API inconnue</string> + <string name="ssl_trust_any_long">Accepte les certificats SSL sans validation</string> + <string name="ssl_trust_any_host_long">Ne pas vérifier si les noms des serveurs concordent</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Erreur: nom d\'hôte SSL refusé</string> + <string name="offline_oldest_first">Afficher les plus anciens d\'abord</string> + <string name="prefs_dim_status_bar">Assombrir la barre de statut</string> + <string name="prefs_dim_status_bar_long">Assombrir la barre de satut pendant la lecture</string> + <string name="article_comments">%1$d commentaires</string> + <string name="trial_mode_prompt">Mode évaluation, %1$d jour(s) restant(s).</string> + <string name="trial_purchase">Débloquer la version complète</string> + <string name="trial_expired">La période d\'évaluation a expiré</string> + <string name="trial_expired_message">Pour continuer à utiliser Tiny Tiny RSS merci de débloquer la version complète en achetant la clé.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Version complète, merci de votre support!</string> + <string name="prefs_fullscreen_mode">Mode plein écran</string> + <string name="reading">Lecture</string> + <string name="theme_dark_gray">Gris sombre</string> + <string name="offline_articles_to_download">Quantité d\'articles à télécharger</string> + <string name="offline_articles_to_download_long">Combien d\'articles à télécharger pour la lecture Hors Ligne. (les plus récents)</string> + <string name="pref_headlines_show_content_long">Afficher un aperçu du contenu dans les titres</string> + <string name="pref_headlines_show_content">Afficher un aperçu</string> + <string name="api_too_low">Vous devez utiliser une version plus récente de Tiny Tiny RSS pour réaliser cette action</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL de l\'article</string> + <string name="share_content_hint">Contenu de l\'article</string> + <string name="share_title_prompt">Titre:</string> + <string name="share_title_hint">Titre de l\'article</string> + <string name="share_share_button">Partager</string> + <string name="share_article_posted">Article publié.</string> + <string name="subscribe_name">S\'abonner avec Tiny Tiny RSS</string> + <string name="feed_url">URL du flux</string> + <string name="subscribe_to_feed">S\'abonner au flux</string> + <string name="error_while_subscribing">Une erreur s\'est produite lors de l\'abonnement.</string> + <string name="category_list_updated">Liste des catégories mise à jour</string> + <string name="subscribed_to_feed">Abonné au flux</string> + <string name="error_feed_already_exists_">Erreur: le flux existe déjà.</string> + <string name="error_invalid_url">Erreur: URL incorrecte.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Erreur: l\'URL est une page HTML, aucun flux trouvé.</string> + <string name="error_url_contains_multiple_feeds">Erreur: l\'URL contient plusieurs flux.</string> + <string name="error_could_not_download_url">Erreur: Impossible de télécharger l\'URL</string> + <string name="headlines_view_mode">Changer la vue</string> + <string name="headlines_set_view_mode">Définir la vue</string> + <string name="headlines_adaptive">Adaptatif</string> + <string name="headlines_all_articles">Tous les articles</string> + <string name="headlines_starred">Articles marqués</string> + <string name="headlines_published">Articles publiés</string> + <string name="headlines_unread">Articles non lus</string> + <string name="article_img_open">Ouvrir l\'image</string> + <string name="article_img_share">Partager l\'image</string> + <string name="requires_api5">Nécessite la version 1.7.6 ou plus récent de tt-rss</string> + <string name="article_img_view_caption">Montrer le sous-titre</string> + <string name="labels">Tags</string> + <string name="light_theme_is_not_supported_on_honeycomb">Le thème clair n\'est pas supporté sur Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Automatiquement marquer les articles comme lus</string> + <string name="pref_headlines_mark_read_scroll_long">Cette option permet de marquer automatiquement les articles comme lus lorsque vous naviguez dans la liste d\'articles.</string> + <string name="mark_num_headlines_as_read">Marquer %1$d article(s) comme lu(s) ?</string> + <string name="prefs_confirm_headlines_catchup">Confirmer marquer comme lu</string> + <string name="author_formatted">par %1$s</string> + <string name="n_unread_articles">%1$d articles non lus</string> + <string name="pref_headline_font_size">Taille du texte des titres</string> + + <string name="context_confirm_catchup">Marquer tous les articles de %1$s comme lus ?</string> + <string name="theme_system">Réglage système</string> + <string name="accel_webview_summary">Désactivez si vous voyez des défauts visuels ou des clignotements.</string> + <string name="accel_webview_title">Utiliser l\'accélération web (3.0+)</string> + <string name="place_shortcut">Faire un raccourci</string> + <string name="shortcut_has_been_placed_on_the_home_screen">Un raccourci a été placé sur votre écran d\'accueil</string> + <string name="download_articles_and_go_offline">Télécharger les articles et passer hors connexion</string> + <string name="tasker_save_and_close">Sauver et fermer</string> + <string name="synchronize_read_articles_and_go_online">Synchroniser les articles lus et passer en connexion</string> + <string name="prefs_compatible_article_layout">Mise en page compatible</string> + <string name="prefs_compatible_layout_summary">Activez si vous voyez des erreurs de mise en page dans le contenu des articles</string> + <string name="font_size_dialog_suffix">sp</string> + <string name="server_function_not_available">Désolé, cette fonction n\'est pas disponible pour votre version de tt-rss.</string> + <string name="unsubscribe">Se désabonner</string> + <string name="unsubscribe_from_prompt">Se désabonner de %1$s ?</string> + <string name="toggle_sidebar">Barre latérale</string> + <string name="open_article_in_web_browser">Ouvrir dans le navigateur</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-it/strings.xml b/orgfoxttrss/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..4fa8945c --- /dev/null +++ b/orgfoxttrss/src/main/res/values-it/strings.xml @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">Accesso…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Si prega di configurare l\'app prima.</string> + <string name="login_ready">Pronto per l\'accesso.</string> + <string name="login_login">Accedi</string> + <string name="logout">Esci</string> + <string name="login">Accedi</string> + <string name="debugging">Debugging</string> + <string name="password">Password</string> + <string name="default_url">http://esempio.dominio/tt-rss/</string> + <string name="look_and_feel">Interfaccia</string> + <string name="pref_theme">Tema</string> + <string name="pref_theme_long">Cambia il colore dell\'applicazione</string> + <string name="ttrss_url">Tiny Tiny RSS URL</string> + <string name="theme_dark">Scuro</string> + <string name="preferences">Impostazioni</string> + <string name="theme_light">Chiaro</string> + <string name="connection">Connessione</string> + <string name="headline_context_multiple">Articoli selezionati</string> + <string name="http_authentication">Autenticazione HTTP</string> + <string name="loading_message">Caricamento, attendere prego…</string> + <string name="menu_unread_feeds">Mostra feed non letti</string> + <string name="menu_all_feeds">Mostra tutti i feed</string> + <string name="update_feeds">Aggiorna</string> + <string name="share_article">Condividi articolo</string> + <string name="catchup">Segna come letto</string> + <string name="sort_feeds_by_unread">Ordina feed per numero di articoli non letti</string> + <string name="ssl_trust_any">Accetta qualsiasi certificato</string> + <string name="category_browse_feeds">Naviga feed</string> + <string name="category_browse_articles">Naviga articoli</string> + <string name="blank"></string> + <string name="transport_debugging">Scrivi su log i dati inviati e ricevuti</string> + <string name="article_toggle_marked">(non)Speciale</string> + <string name="article_toggle_published">(de)Pubblica</string> + <string name="headlines_select">Seleziona articoli</string> + <string name="headlines_select_dialog">Seleziona articoli</string> + <string name="headlines_select_all">Tutto</string> + <string name="headlines_select_unread">Non letto</string> + <string name="headlines_select_none">Deseleziona tutto</string> + <string name="selection_toggle_marked">(Non)speciale</string> + <string name="selection_toggle_published">(De)Pubblica</string> + <string name="selection_toggle_unread">(Non)Letto</string> + <string name="selection_select_none">Deseleziona tutti</string> + <string name="context_selection_toggle_marked">(Non)Speciale</string> + <string name="context_selection_toggle_published">(De)Pubblica</string> + <string name="context_selection_toggle_unread">(Non)Letto</string> + <string name="article_set_unread">Imposta come Non letto</string> + <string name="article_mark_read_above">Segna gli articoli sopra questo come letti</string> + <string name="http_login_summary">Opzionale. Compila questo solo se la tua installazione di tt-rss è protetta con autenticazione HTTP Basic</string> + <string name="login_summary">Il tuo nome utente per tt-rss. Non è necessario per la modalità utente singolo.</string> + <string name="ttrss_url_summary">URL della tua installazione di tt-rss, es. http://sito.it/tt-rss/</string> + <string name="download_feed_icons">Abilita icone feed</string> + <string name="enable_cats">Abilita categorie feed</string> + <string name="no_feeds_to_display">Nessun feed da mostrare</string> + <string name="no_headlines_to_display">Nessun titolo da mostrare</string> + <string name="browse_cats_like_feeds">Sfoglia le categorie come feed</string> + <string name="browse_cats_like_feeds_summary">Usa il menu contestuale delle categorie per sovrascrivere questa impostazione</string> + <string name="headlines_mark_as_read">Segna come letto</string> + <string name="error_unknown">Errore: errore sconosciuto (Vedi log)</string> + <string name="error_http_unauthorized">Errore: 401 non autorizzato</string> + <string name="error_http_forbidden">Errore: 403 proibit</string> + <string name="error_http_not_found">Errore: 404 non trovato</string> + <string name="error_http_server_error">Errore: 500 errore del server</string> + <string name="error_http_other_error">Errore: altro errore HTTP (Vedi log)</string> + <string name="error_ssl_rejected">Errore: certificato SSL rifiutato</string> + <string name="error_parse_error">Errore: parsing del JSON fallito</string> + <string name="error_io_error">Errore: I/O failure (server non raggiungibile?)</string> + <string name="error_other_error">Errore: errore sconosciuto (Vedi log)</string> + <string name="error_api_disabled">Errore: si prega di abilitare l\'accesso esterno all\'API nelle impostazioni avanzate di tt-rss</string> + <string name="error_api_unknown">Errore: errore API sconosciuto (Vedi log)</string> + <string name="error_api_incorrect_usage">Errore: uso API scorretto</string> + <string name="error_login_failed">Errore: nome utente o password errati</string> + <string name="error_invalid_api_url">Errore: URL API errato</string> + <string name="combined_mode_summary">Mostra testo completo dell\'articolo in linea invece che in un pannello separato</string> + <string name="combined_mode">Modalità combinata</string> + <string name="go_offline">Vai offline</string> + <string name="go_online">Vai in linea</string> + <string name="offline_switch_error">Preparazione modalità offline fallita (Vedi log)</string> + <string name="no_feeds">Nessun feed da mostrare</string> + <string name="no_headlines">Nessun articolo da mostrare</string> + <string name="dialog_offline_prompt">Accesso non riuscito, ma hai dei contenuti salvati offline. Vuoi andare in modalità offline?</string> + <string name="dialog_offline_success">Modalità offline pronta</string> + <string name="dialog_offline_go">Vai offline</string> + <string name="dialog_cancel">Annulla</string> + <string name="dialog_offline_switch_prompt">Scaricare gli articoli non letti e cambiare nella modalità offline?</string> + <string name="notify_downloading_articles">Scaricamento articoli (%1$d)…</string> + <string name="notify_downloading_init">Scaricamento in corso…</string> + <string name="notify_downloading_feeds">Scaricamento feed…</string> + <string name="notify_uploading_sending_data">Invio dati al server…</string> + <string name="notify_downloading_title">Preparazione modalità offline</string> + <string name="notify_uploading_title">Sincronizzazione dati offline</string> + <string name="offline_sync_success">Terminata la sincronizzazione dei dati offline</string> + <string name="offline_mode">Modalità offline</string> + <string name="offline_image_cache_enabled">Salva immagini nella cache</string> + <string name="offline_image_cache_enabled_summary">Scarica immagini sulla sdcard. Questo potrebbe aumentare il tempo per passare alla modalità offline.</string> + <string name="notify_downloading_images">Scaricamento immagini (%1$d)…</string> + <string name="article_set_labels">Imposta etichetta</string> + <string name="search">Cerca</string> + <string name="cancel">Annulla</string> + <string name="font_size_small">Piccolo</string> + <string name="font_size_medium">Medio</string> + <string name="font_size_large">Grande</string> + <string name="pref_font_size">Dimensione testo articolo</string> + <string name="donate">Fai una donazione</string> + <string name="dialog_close">Chiudi</string> + <string name="donate_select">Si prega di selezionare la donazione</string> + <string name="donate_do">Dona!</string> + <string name="tablet_article_swipe">Scorri tra gli articoli</string> + <string name="article_link_copy">Copia il link negli appunti</string> + <string name="text_copied_to_clipboard">Testo copiato negli appunti</string> + <string name="attachments_prompt">Seleziona allegato</string> + <string name="attachment_view">Mostra</string> + <string name="attachment_copy">Copia URL</string> + <string name="justify_article_text">Giustifica testo articoli</string> + <string name="dialog_offline_sync_in_progress">Sincronizzazione offline in corso</string> + <string name="dialog_offline_sync_stop">Ferma sincronizzazione</string> + <string name="dialog_offline_sync_continue">Continua</string> + <string name="article_set_note">Pubblica con nota</string> + <string name="close_feed">Chiudi feed</string> + <string name="close_article">Chiudi articolo</string> + <string name="dialog_open_preferences">Impostazioni</string> + <string name="dialog_need_configure_prompt">Compila le informazioni per l\'accesso al server tt-rss:URL, login, e password.</string> + <string name="notify_article_marked">Articolo reso speciale</string> + <string name="notify_article_unmarked">Articolo reso non-speciale</string> + <string name="notify_article_published">Articolo publicato</string> + <string name="notify_article_unpublished">Articolo non pubblicato</string> + <string name="notify_article_note_set">Nota articolo salvata</string> + <string name="update_headlines">Aggiorna</string> + <string name="attachment_share">Condividi</string> + <string name="error_network_unavailable">Errore: rete non disponibile</string> + <string name="category_browse_headlines">Naviga titoli</string> + <string name="pref_default_view_mode">Modalità visualizzazione default</string> + <string name="pref_default_view_mode_long">Quale modalità di visualizzazione utilizzare come predefinita sugli smartphones</string> + <string name="donate_thanks">Donazione trovata, Grazie per il supporto!</string> + <string name="use_volume_keys">Usa pulsanti volume</string> + <string name="use_volume_keys_long">Sfoglia gli articoli usando i tasti volume</string> + <string name="ssl_trust_any_host">Nessuna validazione sull\'hostname</string> + <string name="error_api_unknown_method">Errore: metodo API sconosciuto</string> + <string name="ssl_trust_any_long">Accetta qualsiasi certificato SSL senza validazione</string> + <string name="ssl_trust_any_host_long">Non verificare l\'hostname del server</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Errore: SSL dell\'hostname non verificato</string> + <string name="offline_oldest_first">Mostra prima gli articoli più vecchi</string> + <string name="prefs_dim_status_bar">Oscura la barra di stato</string> + <string name="prefs_dim_status_bar_long">Oscura la barra di stato durante la lettura</string> + <string name="article_comments">%1$d commenti</string> + <string name="trial_mode_prompt">Modalità demo, %1$d giorno/i rimanenti.</string> + <string name="trial_purchase">Sblocca versione completa</string> + <string name="trial_expired">Demo scaduta</string> + <string name="trial_expired_message">Per continuare ad usare Tiny Tiny RSS si prega di sbloccare la versione Full acquistando l\'app chiave.</string> + <string name="theme_sepia">Seppia</string> + <string name="trial_thanks">Versione completa, Grazie per il supporto!</string> + <string name="prefs_fullscreen_mode">Modalità Fullscreen</string> + <string name="reading">Lettura</string> + <string name="theme_dark_gray">Grigio scuro</string> + <string name="offline_articles_to_download">Numero di articoli da scaricare</string> + <string name="offline_articles_to_download_long">Numero di articoli da scaricare per la modalità offine (i più recenti per primi).</string> + <string name="pref_headlines_show_content_long">Mostra anteprime contenuti nella lista titoli</string> + <string name="pref_headlines_show_content">Anteprima contenuti articoli</string> + <string name="api_too_low">Questa operazione chiede una versione più recente di Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL articolo</string> + <string name="share_content_hint">Contenuto articolo</string> + <string name="share_title_prompt">Titolo:</string> + <string name="share_title_hint">Titolo articolo</string> + <string name="share_share_button">Condividi</string> + <string name="share_article_posted">Articolo pubblicato.</string> + <string name="subscribe_name">Iscriviti con Tiny Tiny RSS</string> + <string name="feed_url">Feed URL</string> + <string name="subscribe_to_feed">Iscriviti al feed</string> + <string name="error_while_subscribing">Errore durante l\'iscrizione.</string> + <string name="category_list_updated">Lista categorie aggiornata</string> + <string name="subscribed_to_feed">Iscriviti al feed</string> + <string name="error_feed_already_exists_">Errore: feed già presente.</string> + <string name="error_invalid_url">Errore: URL non valido.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Errore: l\'URL è una pagnia HTML, nessun feed trovato.</string> + <string name="error_url_contains_multiple_feeds">Errore: l\'URL contiene feed multipli</string> + <string name="error_could_not_download_url">Errore: impossibile scaricare l\'URL</string> + <string name="headlines_view_mode">Imposta modalità visualizzazione</string> + <string name="headlines_set_view_mode">Imposta modalità visualizzazione</string> + <string name="headlines_adaptive">Adattivo</string> + <string name="headlines_all_articles">Tutti gli articoli</string> + <string name="headlines_starred">Speciali</string> + <string name="headlines_published">Pubblicati</string> + <string name="headlines_unread">Non letti</string> + <string name="article_img_open">Apri immagine</string> + <string name="article_img_share">Condividi immagine</string> + <string name="requires_api5">Richiede versione 1.7.6</string> + <string name="labels">Etichette</string> + <string name="article_img_view_caption">Visualizza didascalia</string> + <string name="light_theme_is_not_supported_on_honeycomb">Il tema chiaro non è supportata su Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Segna come letto quando si scorre</string> + <string name="pref_headlines_mark_read_scroll_long">Gli articoli verranno segnati come letti quando si scorre oltre</string> + <string name="mark_num_headlines_as_read">Segni %1$d articolo/i come letti?</string> + <string name="prefs_confirm_headlines_catchup">Confermi la marcatura degli articoli come letti</string> + <string name="author_formatted">di %1$s</string> + <string name="n_unread_articles">%1$d articoli non letti</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-ja/strings.xml b/orgfoxttrss/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..cd965fb6 --- /dev/null +++ b/orgfoxttrss/src/main/res/values-ja/strings.xml @@ -0,0 +1,220 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">ログイン中…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">まずはじめにアプリケーションの設定をしてください。</string> + <string name="login_ready">Ready to login.</string> + <string name="login_login">ログイン</string> + <string name="logout">ログアウト</string> + <string name="login">ログイン</string> + <string name="debugging">デバッグ</string> + <string name="password">パスワード</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">インタフェース</string> + <string name="pref_theme">テーマ</string> + <string name="pref_theme_long">アプリケーションのテーマ色を変更する</string> + <string name="ttrss_url">Tiny Tiny RSS URL</string> + <string name="theme_dark">ダーク</string> + <string name="preferences">設定</string> + <string name="theme_light">ライト</string> + <string name="connection">接続</string> + <string name="headline_context_multiple">選択した記事</string> + <string name="http_authentication">HTTP認証</string> + <string name="loading_message">読込中, お待ちください…</string> + <string name="menu_unread_feeds">未読フィードを表示</string> + <string name="menu_all_feeds">すべてのフィードを表示</string> + <string name="update_feeds">更新</string> + <string name="share_article">記事をシェア</string> + <string name="catchup">全て既読にする</string> + <string name="sort_feeds_by_unread">フィードを未読記事数順に並びかえ</string> + <string name="ssl_trust_any">すべての証明書を受け入れる</string> + <string name="category_browse_feeds">フィードを表示</string> + <string name="category_browse_articles">記事を表示</string> + <string name="blank"></string> + <string name="transport_debugging">送受信データをロギングする</string> + <string name="article_toggle_marked">スターを付ける/外す</string> + <string name="article_toggle_published">配信/解除</string> + <string name="headlines_select">記事を選択</string> + <string name="headlines_select_dialog">記事を選択</string> + <string name="headlines_select_all">全て選択</string> + <string name="headlines_select_unread">未読記事を選択</string> + <string name="headlines_select_none">全て選択解除</string> + <string name="selection_toggle_marked">スターを付ける/外す</string> + <string name="selection_toggle_published">配信/解除</string> + <string name="selection_toggle_unread">既読/未読にする</string> + <string name="selection_select_none">全て選択解除</string> + <string name="context_selection_toggle_marked">スターを付ける/外す</string> + <string name="context_selection_toggle_published">配信/解除</string> + <string name="context_selection_toggle_unread">既読/未読にする</string> + <string name="article_set_unread">記事を未読にする</string> + <string name="article_mark_read_above">これより上を既読にする</string> + <string name="http_login_summary">tt-rssサーバにBasic認証を使用している場合は記入してください。(オプション)</string> + <string name="login_summary">tt-rssサーバのログイン名。 シングルユーザモードの場合は不要。</string> + <string name="ttrss_url_summary">tt-rssサーバのURL。例 http://site.com/tt-rss/</string> + <string name="download_feed_icons">フィードアイコンを表示する</string> + <string name="enable_cats">フィードのカテゴリを有効にする</string> + <string name="no_feeds_to_display">表示するフィードがありません</string> + <string name="no_headlines_to_display">表示するヘッドラインがありません</string> + <string name="no_caption_to_display">表示するキャプションがありません</string> + <string name="browse_cats_like_feeds">カテゴリをフィードのように扱う</string> + <string name="browse_cats_like_feeds_summary">カテゴリ内のフィードをまとめて表示する</string> + <string name="headlines_mark_as_read">全て既読にする</string> + <string name="error_unknown">エラー: 不明なエラー(ログを確認)</string> + <string name="error_http_unauthorized">エラー: 401 unauthorized</string> + <string name="error_http_forbidden">エラー: 403 forbidden</string> + <string name="error_http_not_found">エラー: 404 not found</string> + <string name="error_http_server_error">エラー: 500 server error</string> + <string name="error_http_other_error">エラー: その他のHTTPエラー(ログを確認)</string> + <string name="error_ssl_rejected">エラー: SSL証明書が拒否されました</string> + <string name="error_parse_error">エラー: JSONのパースに失敗しました</string> + <string name="error_io_error">エラー: I/O失敗(サーバダウン?)</string> + <string name="error_other_error">エラー: 不明なエラー(ログを確認)</string> + <string name="error_api_disabled">エラー: tt-rssサーバ側の設定で外部APIアクセスを有効にしてください</string> + <string name="error_api_unknown">エラー: 不明なAPIエラー(ログを確認)</string> + <string name="error_api_incorrect_usage">エラー: APIの使い方が違います</string> + <string name="error_login_failed">エラー: ユーザー名またはパスワードが違います</string> + <string name="error_invalid_api_url">エラー: APIのURLが無効です</string> + <string name="combined_mode_summary">記事全文を別パネルに表示せずインライン表示します</string> + <string name="combined_mode">コンバインモード</string> + <string name="go_offline">オフラインにする</string> + <string name="go_online">オンラインにする</string> + <string name="offline_switch_error">オフラインモードの準備に失敗しました(ログを確認)</string> + <string name="no_feeds">表示するフィードがありません</string> + <string name="no_headlines">表示するヘッドラインがありません</string> + <string name="dialog_offline_prompt">ログインに失敗しましたが端末内にオフラインデータがあります。オフラインにしますか?</string> + <string name="dialog_offline_success">オフライン準備完了</string> + <string name="dialog_offline_go">オフラインにする</string> + <string name="dialog_cancel">キャンセル</string> + <string name="dialog_offline_switch_prompt">未読記事をダウンロードしてオフラインにしますか?</string> + <string name="notify_downloading_articles">記事をダウンロード中 (%1$d)…</string> + <string name="notify_downloading_init">ダウンロード開始…</string> + <string name="notify_downloading_feeds">フィードをダウンロード中…</string> + <string name="notify_uploading_sending_data">サーバにデータを送信中…</string> + <string name="notify_downloading_title">オフライン準備中</string> + <string name="notify_uploading_title">オフラインデータを同期中</string> + <string name="offline_sync_success">オフラインデータ同期完了</string> + <string name="offline_mode">オフラインモード</string> + <string name="offline_image_cache_enabled">イメージをキャッシュする</string> + <string name="offline_image_cache_enabled_summary">イメージをSDカードにダウンロードします。有効にするとオフライン準備時間が長くなる可能性があります。</string> + <string name="notify_downloading_images">イメージをダウンロード中 (%1$d)…</string> + <string name="article_set_labels">ラベルをセットする</string> + <string name="search">検索</string> + <string name="cancel">キャンセル</string> + <string name="font_size_small">小</string> + <string name="font_size_medium">中</string> + <string name="font_size_large">大</string> + <string name="pref_font_size">記事内容の文字サイズ</string> + <string name="donate">寄付</string> + <string name="dialog_close">閉じる</string> + <string name="donate_select">寄付金額を選択してください。</string> + <string name="donate_do">寄付完了!</string> + <string name="tablet_article_swipe">記事をスワイプで切り替える</string> + <string name="article_link_copy">リンクをクリップボードにコピー</string> + <string name="text_copied_to_clipboard">テキストをクリップボードにコピー</string> + <string name="attachments_prompt">Select attachment</string> + <string name="attachment_view">View</string> + <string name="attachment_copy">Copy URL</string> + <string name="justify_article_text">記事の文字を均等割り付けする</string> + <string name="dialog_offline_sync_in_progress">オフライン同期中</string> + <string name="dialog_offline_sync_stop">同期の停止</string> + <string name="dialog_offline_sync_continue">同期の継続</string> + <string name="article_set_note">注釈を付けて配信</string> + <string name="close_feed">フィードを閉じる</string> + <string name="close_article">記事を閉じる</string> + <string name="dialog_open_preferences">設定</string> + <string name="dialog_need_configure_prompt">URL、ユーザー名、パスワードなどのtt-rssサーバ情報を設定してください。</string> + <string name="notify_article_marked">スターを付けました</string> + <string name="notify_article_unmarked">スターを外しました</string> + <string name="notify_article_published">配信しました</string> + <string name="notify_article_unpublished">配信を解除しました</string> + <string name="notify_article_note_set">注釈を保存しました</string> + <string name="update_headlines">更新</string> + <string name="attachment_share">シェア</string> + <string name="error_network_unavailable">エラー: ネットワークが利用できません</string> + <string name="category_browse_headlines">ヘッドラインを表示</string> + <string name="pref_default_view_mode">フィードの表示方法</string> + <string name="pref_default_view_mode_long">フィードの表示方法</string> + <string name="donate_thanks">寄付を確認。あなたの支援に感謝します!</string> + <string name="use_volume_keys">ボリュームボタンを使う</string> + <string name="use_volume_keys_long">ボリュームボタンで記事を切り替える</string> + <string name="ssl_trust_any_host">ホスト名の確認をしない</string> + <string name="error_api_unknown_method">エラー: 不明なAPIメソッド</string> + <string name="ssl_trust_any_long">証明書を確認せずに受け入れる</string> + <string name="ssl_trust_any_host_long">ホスト名の確認をしない</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">エラー: SSLホスト名が確認できません</string> + <string name="offline_oldest_first">古い記事を先頭に表示する</string> + <string name="prefs_dim_status_bar">下部ソフトキーをぼかす</string> + <string name="prefs_dim_status_bar_long">記事表示中は下部ソフトキーをぼかす</string> + <string name="article_comments">%1$d 件のコメント</string> + <string name="trial_mode_prompt">試用期間中。残り %1$d 日間。</string> + <string name="trial_purchase">フルバージョンを購入</string> + <string name="trial_expired">試用期限終了</string> + <string name="trial_expired_message">Tiny Tiny RSSを継続して使用する場合はTiny Tiny RSS Unlockerを購入してください。</string> + <string name="theme_sepia">セピア</string> + <string name="trial_thanks">フルバージョン。あなたの支援に感謝します!</string> + <string name="prefs_fullscreen_mode">フルスクリーンモード</string> + <string name="reading">Reading</string> + <string name="theme_dark_gray">ダークグレイ</string> + <string name="offline_articles_to_download">ダウンロードする記事数</string> + <string name="offline_articles_to_download_long">オフラインモードにする際にダウンロードする記事数(新しい記事から)</string> + <string name="pref_headlines_show_content_long">記事内容をヘッドライン一覧に表示する</string> + <string name="pref_headlines_show_content">記事内容をプレビュー</string> + <string name="api_too_low">この動作には新しいバージョンのTiny Tiny RSSが必要です。</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">記事URL</string> + <string name="share_content_hint">記事内容</string> + <string name="share_title_prompt">タイトル:</string> + <string name="share_title_hint">記事タイトル</string> + <string name="share_share_button">シェア</string> + <string name="share_article_posted">記事を投稿しました。</string> + <string name="subscribe_name">フィードを購読する</string> + <string name="feed_url">フィードのURL</string> + <string name="subscribe_to_feed">フィードを購読する</string> + <string name="error_while_subscribing">購読に失敗しました</string> + <string name="category_list_updated">カテゴリ一覧を更新しました</string> + <string name="subscribed_to_feed">フィードを購読しました</string> + <string name="error_feed_already_exists_">エラー: フィードがすでに存在します</string> + <string name="error_invalid_url">エラー: 無効なURL.</string> + <string name="error_url_is_an_html_page_no_feeds_found">エラー: URLはHTMLページです。フィードが見つかりません。</string> + <string name="error_url_contains_multiple_feeds">エラー: URLに複数のフィードが含まれています</string> + <string name="error_could_not_download_url">エラー: URLをダウンロードできませんでした</string> + <string name="headlines_view_mode">表示モード設定</string> + <string name="headlines_set_view_mode">表示モード設定</string> + <string name="headlines_adaptive">最適</string> + <string name="headlines_all_articles">すべての記事</string> + <string name="headlines_starred">スター付きの記事</string> + <string name="headlines_published">配信した記事</string> + <string name="headlines_unread">未読記事</string> + <string name="article_img_open">イメージを開く</string> + <string name="article_img_share">イメージをシェア</string> + <string name="requires_api5">tt-rss1.7.6以上が必要です</string> + <string name="labels">ラベル</string> + <string name="article_img_view_caption">キャプションを表示</string> + <string name="light_theme_is_not_supported_on_honeycomb">ライトテーマはHoneycombではサポートされていません</string> + <string name="pref_headlines_mark_read_scroll">スクロールしたら既読にする</string> + <string name="pref_headlines_mark_read_scroll_long">ヘッドラインをスクロールしたら既読にする</string> + <string name="mark_num_headlines_as_read">%1$d 件の記事を既読にしますか?</string> + <string name="prefs_confirm_headlines_catchup">記事を既読にする際に確認する</string> + <string name="author_formatted">by %1$s</string> + <string name="n_unread_articles">%1$d 件の未読記事</string> + <string name="pref_headline_font_size">ヘッドラインの文字サイズ</string> + <string name="context_confirm_catchup">%1$s の全ての記事を既読にしますか?</string> + <string name="theme_system">端末のデフォルト</string> + <string name="accel_webview_summary">ちらつきや表示化けが起こる場合は無効にしてください。</string> + <string name="accel_webview_title">WebViewのハードウェアアクセラレーション(Android3.0以降)</string> + <string name="place_shortcut">ショートカットの配置</string> + <string name="shortcut_has_been_placed_on_the_home_screen">ショートカットがホームスクリーンに配置されました</string> + <string name="download_articles_and_go_offline">記事をダウンロードしてオフラインにする</string> + <string name="tasker_save_and_close">保存して閉じる</string> + <string name="synchronize_read_articles_and_go_online">既読記事を同期してオフラインにする</string> + <string name="prefs_compatible_article_layout">記事レイアウトの互換表示</string> + <string name="prefs_compatible_layout_summary">記事表示が崩れる場合は有効にしてください。</string> + <string name="font_size_dialog_suffix">sp</string> + <string name="server_function_not_available">接続中のtt-rssサーバのバージョンではこの機能を使用できません。</string> + <string name="unsubscribe">購読解除</string> + <string name="unsubscribe_from_prompt">%1$s の購読を解除しますか?</string> + <string name="toggle_sidebar">サイドバーの表示/非表示</string> + <string name="open_article_in_web_browser">Webブラウザで開く</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-pl/strings.xml b/orgfoxttrss/src/main/res/values-pl/strings.xml new file mode 100644 index 00000000..9e60b96f --- /dev/null +++ b/orgfoxttrss/src/main/res/values-pl/strings.xml @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- ########################################################################## --> + <!-- # # --> + <!-- # Polish translation for Tiny Tiny RSS android client # --> + <!-- # Polskie tłumaczenie dla androidowego klienta Tiny Tiny RSS # --> + <!-- # # --> + <!-- # # --> + <!-- # Please see entries marked with FIXME comments, maybe you'll have # --> + <!-- # clever idea for better translation. # --> + <!-- # Zapoznaj się proszę z tłumaczeniami oznaczonymi komentarzem FIXME # --> + <!-- # może Ty będziesz miał pomysł na lepsze tłumaczenie. # --> + <!-- # # --> + <!-- # # --> + <!-- # Created by: Mirosław Lach, 2013-07-16 # --> + <!-- # Updated by: # --> + <!-- # # --> + <!-- ########################################################################## --> + + <string name="login_in_progress">Logowanie…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Najpierw skonfiguruj aplikację.</string> + <string name="login_ready">Gotowy do zalogowania.</string> + <string name="login_login">Zaloguj</string> + <string name="logout">Wyloguj</string> + <string name="login">Uzytkownik</string> + <string name="debugging">Debugowanie</string> + <string name="password">Hasło</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Wygląd</string> + <string name="pref_theme">Styl</string> + <string name="pref_theme_long">Pozwala na zmianę kolorystyki aplikacji</string> + <string name="ttrss_url">Adres Tiny Tiny RSS</string> + <string name="theme_dark">Ciemny</string> + <string name="preferences">Ustawienia</string> + <string name="theme_light">Jasny</string> + <string name="connection">Połączenie</string> + <string name="headline_context_multiple">Wybrane artykuły</string> + <string name="http_authentication">Uwierzytelnianie HTTP</string> + <string name="loading_message">Ładowanie, proszę czekać…</string> + <string name="menu_unread_feeds">Pokaż nieprzeczytane kanały</string> + <string name="menu_all_feeds">Pokaż wszystkie kanały</string> + <string name="update_feeds">Odśwież</string> + <string name="share_article">Udostępnij artykuł</string> + <string name="catchup">Oznacz jako przeczytany</string> + <string name="sort_feeds_by_unread">Sortuj według liczby nieprzeczytanych</string> + <string name="ssl_trust_any">Akceptuj dowolny certyfikat</string> + <string name="category_browse_feeds">Przeglądaj kanały</string> + <string name="category_browse_articles">Przeglądaj artykuły</string> + <string name="blank"></string> + <string name="transport_debugging">Rejestruj wysyłane i odbierane dane</string> + <string name="article_toggle_marked">O(d)gwiazdkuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="article_toggle_published">O(d)publikuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="headlines_select">Wybierz artykuły</string> + <string name="headlines_select_dialog">Wybierz artykuły</string> + <string name="headlines_select_all">Wszystko</string> + <string name="headlines_select_unread">Nieprzeczytane</string> + <string name="headlines_select_none">Odznacz wszystko</string> + <string name="selection_toggle_marked">O(d)gwiazdkuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="selection_toggle_published">O(d)publikuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="selection_toggle_unread">(Nie)Przeczytane</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="selection_select_none">Odznacz wszystko</string> + <string name="context_selection_toggle_marked">O(d)gwiazdkuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="context_selection_toggle_published">O(d)publikuj</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="context_selection_toggle_unread">(Nie)Przeczytane</string> <!-- FIXME: mile widziane krótkie polskie odpowiedniki :D, short polish translation really welcomed :D --> + <string name="article_set_unread">Oznacz jako nieprzeczytany</string> + <string name="article_mark_read_above">Oznacz art. powyżej jako przeczytane</string> + <string name="http_login_summary">Opcjonalne. Wypełnij jedynie gdy Twoja instalacja tt-rss jest chroniona uwierzytelnianiem HTTP Basic</string> + <string name="login_summary">Twoja nazwa użytkownika tt-rss. Nie jest wymagana dla trybu pojedynczego użytkownika</string> + <string name="ttrss_url_summary">Adres URL Twojej instalacji tt-rss, np. http://strona.pl/tt-rss/</string> + <string name="download_feed_icons">Włącz ikony kanałów</string> + <string name="enable_cats">Włącz kategorie kanałów</string> + <string name="no_feeds_to_display">Brak kanałów do wyświetlenia</string> + <string name="no_headlines_to_display">Brak nagłówków do wyświetlenia</string> + <string name="no_caption_to_display">Brak opisu do wyświetlenia</string> + <string name="browse_cats_like_feeds">Przeglądaj kategorie jak kanały</string> + <string name="browse_cats_like_feeds_summary">Użyj menu kontekstowego kategorii aby nadpisać to ustawienia</string> + <string name="headlines_mark_as_read">Oznacz jako przeczytane</string> + <string name="error_unknown">Błąd: Nieznany błąd (sprawdź log)</string> + <string name="error_http_unauthorized">Błąd: 401 unauthorized</string> + <string name="error_http_forbidden">Błąd: 403 odmowa dostępu</string> + <string name="error_http_not_found">Błąd: 404 nie znaleziono</string> + <string name="error_http_server_error">Błąd: 500 błąd wewn. serwera</string> + <string name="error_http_other_error">Błąd: inny błąd HTTP (sprawdź log)</string> + <string name="error_ssl_rejected">Błąd: certyfikat SSL odrzucony</string> + <string name="error_parse_error">Błąd: niepowodzenie przetwarzania danych JSON</string> + <string name="error_io_error">Błąd: niepowodzenie we/wy (niedostępny serwer?)</string> + <string name="error_other_error">Błąd: nieznany błąd (sprawdź log)</string> + <string name="error_api_disabled">Błąd: Włącz proszę zdalny dostęp do API w zaawansowanych ustawieniach tt-rss (Ustawienia -> Zaawansowane)</string> + <string name="error_api_unknown">Błąd: nieznany błąd API (sprawdź log)</string> + <string name="error_api_incorrect_usage">Błąd: nieprawidłowe wywołanie API</string> + <string name="error_login_failed">Błąd: nieprawidłowa nazwa użytkownika lub hasło</string> + <string name="error_invalid_api_url">Błąd: nieprawidłowy adres URL API</string> + <string name="combined_mode_summary">Wyświetla pełną treść artykułu razem z tytułem zamiast w osobnym panelu</string> + <string name="combined_mode">Tryb scalony</string> + <string name="go_offline">Przełącz w tryb offline</string> + <string name="go_online">Wróć do trybu online</string> + <string name="offline_switch_error">Przygotowywanie trybu offline niepowiodło się (sprawdź log)</string> + <string name="no_feeds">Brak kanałów do wyświetlenia</string> + <string name="no_headlines">Brak artykułów do wyświetlenia</string> + <string name="dialog_offline_prompt">Logowanie niepowiodło się, ale posiadasz zapisane dane trybu offline. Czy chcesz pracować w trybie offline?</string> + <string name="dialog_offline_success">Tryb offline gotowy do użycia</string> + <string name="dialog_offline_go">Przełącz w tryb offline</string> + <string name="dialog_cancel">Anuluj</string> + <string name="dialog_offline_switch_prompt">Pobrać nieprzeczytane artykuły i prześć w tryb offline?</string> + <string name="notify_downloading_articles">Pobieranie artykułów (%1$d)…</string> + <string name="notify_downloading_init">Rozpoczynam pobieranie…</string> + <string name="notify_downloading_feeds">Pobieranie kanałów…</string> + <string name="notify_uploading_sending_data">Wysyłanie danych na serwer…</string> + <string name="notify_downloading_title">Przygotowywanie trybu offline</string> + <string name="notify_uploading_title">Synchronizacja danych trybu offline</string> + <string name="offline_sync_success">Zakończono synchronizacje danych offline</string> + <string name="offline_mode">Tryb offline</string> + <string name="offline_image_cache_enabled">Lokalna kopia obrazków</string> + <string name="offline_image_cache_enabled_summary">Pobiera obrazki na kartę SD. Może znacznie wydłużyć czas przechodzenia do trybu offline.</string> + <string name="notify_downloading_images">Pobieranie obrazków (%1$d)…</string> + <string name="article_set_labels">Ustaw etykiety</string> + <string name="search">Szukaj</string> + <string name="cancel">Anuluj</string> + <string name="font_size_small">Mała</string> + <string name="font_size_medium">Średnia</string> + <string name="font_size_large">Duża</string> + <string name="pref_font_size">Rozmiar czcionki artykułu</string> + <string name="donate">Wesprzyj</string> + <string name="dialog_close">Zamknij</string> + <string name="donate_select">Wybierz kwote wsparcia</string> + <string name="donate_do">Wesprzyj!</string> + <string name="tablet_article_swipe">Przeciągnij aby przejść pomiędzy artykułami</string> + <string name="article_link_copy">Skopiuj adres do schowka</string> + <string name="text_copied_to_clipboard">Tekst skopiowany do schowka</string> + <string name="attachments_prompt">Wybierz załącznik</string> + <string name="attachment_view">Podgląd</string> + <string name="attachment_copy">Skopiuj adres</string> + <string name="justify_article_text">Wyjustuj treść artykułu</string> + <string name="dialog_offline_sync_in_progress">Trwa synchronizacja trybu offline</string> + <string name="dialog_offline_sync_stop">Zatrzymaj synchronizację</string> + <string name="dialog_offline_sync_continue">Kontynuuj</string> + <string name="article_set_note">Opublikuj z komentarzem</string> + <string name="close_feed">Zamknij kanał</string> + <string name="close_article">Zamknij artykuł</string> + <string name="dialog_open_preferences">Ustawienia</string> + <string name="dialog_need_configure_prompt">Wprowadz informacje o używanym serwerze tt-rss takie jak adres URL, nazwę użytkownika i hasło.</string> + <string name="notify_article_marked">Oznaczony gwiazdką</string> + <string name="notify_article_unmarked">Oznaczenie gwiazdką usunięte</string> + <string name="notify_article_published">Artykuł opublikowany</string> + <string name="notify_article_unpublished">Wycowano z publikacji</string> + <string name="notify_article_note_set">Komentarz do artykułu zapisany</string> + <string name="update_headlines">Odśwież</string> + <string name="attachment_share">Udostępnij</string> + <string name="error_network_unavailable">Błąd: sieć jest niedostępna</string> + <string name="category_browse_headlines">Przeglądaj nagłówki</string> + <string name="pref_default_view_mode">Domyślny widok kanału</string> + <string name="pref_default_view_mode_long">Który sposób prezentacji kanału otwierać domyślnie na smartfonach</string> + <string name="donate_thanks">Darowizna odnaleziona, dziekujemy za wsparcie!</string> + <string name="use_volume_keys">Użyj przycisków głośności</string> + <string name="use_volume_keys_long">Przełączaj pomiędzy artykułami korzystając z przycisków regulacji głośności</string> + <string name="ssl_trust_any_host">Wyłącz weryfikację zgodności nazwy serwera</string> + <string name="error_api_unknown_method">Błąd: nieznana metoda API</string> + <string name="ssl_trust_any_long">Akceptuj bez sprawdzania dowolny certyfikat SSL</string> + <string name="ssl_trust_any_host_long">Nie weryfikuj zgodności nazwy serwera</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Błąd: nazwa serwera nie zweryfikowana z cert. SSL</string> + <string name="offline_oldest_first">Wyświetl najpierw najstarsze artykuły</string> + <string name="prefs_dim_status_bar">Przyciemnij pasek statusu</string> + <string name="prefs_dim_status_bar_long">Przyciemnij pasek statusu podczas czytania</string> + <string name="article_comments">%1$d komentarzy</string> + <string name="trial_mode_prompt">Tryb wersji testowej, pozostało dni: %1$d.</string> + <string name="trial_purchase">Odblokuj pełną wersję</string> + <string name="trial_expired">Wersja testowa wygasła</string> + <string name="trial_expired_message">Aby nadal używać Tiny Tiny RSS odblokuj pełną wersję kupując klucz aktywacyjny.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Używasz pełnej wersji, dziękujemy za wsparcie!</string> + <string name="prefs_fullscreen_mode">Tryb pełnoekranowy</string> + <string name="reading">Czytanie</string> + <string name="theme_dark_gray">Ciemny szary</string> + <string name="offline_articles_to_download">Ilość artykułów do pobrania</string> + <string name="offline_articles_to_download_long">Ilość artykułów do pobrania dla trybu offline (najnowsze najpierw).</string> + <string name="pref_headlines_show_content_long">Pokaż podgląd treści artykułu na liście nagłówków</string> + <string name="pref_headlines_show_content">Podgląd treści artykułu</string> + <string name="api_too_low">To działanie wymaga nowszej wersji Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL artykułu</string> + <string name="share_content_hint">Treść artykułu</string> + <string name="share_title_prompt">Tytuł:</string> + <string name="share_title_hint">Tytuł artykułu</string> + <string name="share_share_button">Udostępnij</string> + <string name="share_article_posted">Artykuł udostępniony.</string> + <string name="subscribe_name">Prenumeruj używając Tiny Tiny RSS</string> + <string name="feed_url">URL kanału</string> + <string name="subscribe_to_feed">Prenumeruj kanał</string> + <string name="error_while_subscribing">Podczas prenumerowania wystąpił błąd.</string> + <string name="category_list_updated">Lista kategorii została uaktualniona</string> + <string name="subscribed_to_feed">Zaprenumerowano kanał</string> + <string name="error_feed_already_exists_">Błąd: taki kanał już istnieje.</string> + <string name="error_invalid_url">Błąd: Nieprawidłowy adres URL.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Błąd: URL prowadzi do strony HTML, nie znaleziono żanych kanałów.</string> + <string name="error_url_contains_multiple_feeds">Błąd: URL prowadzi do wielu kanałów</string> + <string name="error_could_not_download_url">Błąd: Pobieranie adresu URL nie powiodło się</string> + <string name="headlines_view_mode">Ustaw tryb widoku</string> + <string name="headlines_set_view_mode">Ustaw tryb widoku</string> + <string name="headlines_adaptive">Adaptacyjny</string> + <string name="headlines_all_articles">Wszystkie artykuły</string> + <string name="headlines_starred">Oznaczone gwiazdką</string> + <string name="headlines_published">Opublikowane</string> + <string name="headlines_unread">Nieprzeczytane</string> + <string name="article_img_open">Otwórz obrazek</string> + <string name="article_img_share">Udostępnij obrazek</string> + <string name="requires_api5">Wymaga wersji 1.7.6</string> + <string name="labels">Etykiety</string> + <string name="article_img_view_caption">Zobacz opis</string> + <string name="light_theme_is_not_supported_on_honeycomb">Jasny styl nie jest wspierany na Androidzie Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Oznaczaj jako przeczytane podczas przewijania</string> + <string name="pref_headlines_mark_read_scroll_long">Nagłówki będą oznaczane jako przeczynane podczas przewijania ich listy</string> + <string name="mark_num_headlines_as_read">Oznaczyć %1$d artykuł(y) jako przeczytane?</string> + <string name="prefs_confirm_headlines_catchup">Potwierdzaj oznaczanie artykułów jako przeczytane</string> + <string name="author_formatted">przez %1$s</string> + <string name="n_unread_articles">%1$d nieprzeczytanych artykułów</string> + <string name="pref_headline_font_size">Rozmiar czcionki nagłówka</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-pt-rBR/strings.xml b/orgfoxttrss/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..ee55cdda --- /dev/null +++ b/orgfoxttrss/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">Conectando…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Por favor, configure a aplicação.</string> + <string name="login_ready">Pronto para conectar.</string> + <string name="login_login">Conectar</string> + <string name="logout">Desconectar</string> + <string name="login">Conectar</string> + <string name="debugging">Debugando</string> + <string name="password">Senha</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Aparência</string> + <string name="pref_theme">Tema</string> + <string name="pref_theme_long">Muda as cores da aplicação</string> + <string name="ttrss_url">URL do Tiny Tiny RSS</string> + <string name="theme_dark">Escuro</string> + <string name="preferences">Configurações</string> + <string name="theme_light">Claro</string> + <string name="connection">Conecção</string> + <string name="headline_context_multiple">Artigos selecionados</string> + <string name="http_authentication">Autenticação HTTP</string> + <string name="loading_message">Carregando, aguarde...</string> + <string name="menu_unread_feeds">Mostrar feeds não lidos</string> + <string name="menu_all_feeds">Mostrar todos os feeds</string> + <string name="update_feeds">Atualizar</string> + <string name="share_article">Compartilhar artigo</string> + <string name="catchup">Marcar como lido</string> + <string name="sort_feeds_by_unread">Ordenar por não lidos</string> + <string name="ssl_trust_any">Aceitar qualquer certificado</string> + <string name="category_browse_feeds">Navegar pelos feeds</string> + <string name="category_browse_articles">Navegar pelos artigos</string> + <string name="blank"></string> + <string name="transport_debugging">Registrar no log os dados enviados e recebidos</string> + <string name="article_toggle_marked">Colocar/Retirar estrela</string> + <string name="article_toggle_published">Publicar/Não publicar</string> + <string name="headlines_select">Selecionar artigos</string> + <string name="headlines_select_dialog">Selecionar artigos</string> + <string name="headlines_select_all">Tudo</string> + <string name="headlines_select_unread">Não lidos</string> + <string name="headlines_select_none">Deselecionar tudo</string> + <string name="selection_toggle_marked">Colocar/Retirar estrela</string> + <string name="selection_toggle_published">Publicar/Não publicar</string> + <string name="selection_toggle_unread">Lido/Não lido</string> + <string name="selection_select_none">Deselecionar tudo</string> + <string name="context_selection_toggle_marked">Colocar/Remover estrela</string> + <string name="context_selection_toggle_published">Publicar/Nâo publica</string> + <string name="context_selection_toggle_unread">Lido/Não lido</string> + <string name="article_set_unread">Artigo marcado como não lido</string> + <string name="article_mark_read_above">Marcar acima como lido</string> + <string name="http_login_summary">Opcional. Preencha este campo se sua instalação do tt-rss usa autenticação básica por http</string> + <string name="login_summary">Seu usuário tt-rss. Não é necessário no modo mono usuário</string> + <string name="ttrss_url_summary">URL da sua instalação tt-rss, por exemplo http://site.com/tt-rss/</string> + <string name="download_feed_icons">Habilitar ícones das assinaturas</string> + <string name="enable_cats">Habilitar categorias</string> + <string name="no_feeds_to_display">Nenhuma assinatura a mostrar</string> + <string name="no_headlines_to_display">Nenhum cabeçalho para mostrar</string> + <string name="no_caption_to_display">Nenhuma legenda a mostrar</string> + <string name="browse_cats_like_feeds">Navegar pelas categorias como se fossem feeds</string> + <string name="browse_cats_like_feeds_summary">Use o menu de categorias para substituir essa configuração</string> + <string name="headlines_mark_as_read">Marcar como lido</string> + <string name="error_unknown">Erro: Desconhecido (veja log)</string> + <string name="error_http_unauthorized">Erro: 401 Não autorizado</string> + <string name="error_http_forbidden">Erro: 403 Acesso negado</string> + <string name="error_http_not_found">Erro: 404 Não encontrado</string> + <string name="error_http_server_error">Erro: 500 Erro no servidor</string> + <string name="error_http_other_error">Erro: Outro erro HTTP (veja log)</string> + <string name="error_ssl_rejected">Erro: Certificado SSL foi rejeitado</string> + <string name="error_parse_error">Erro: Falha ao converter string JSON</string> + <string name="error_io_error">Erro: Falha de I/O (Servidor fora?)</string> + <string name="error_other_error">Erro: Erro desconhecido (veja log)</string> + <string name="error_api_disabled">Erro: Habilite o acesso por API externa na configuração do avançada do tt-rss</string> + <string name="error_api_unknown">Erro: Erro desconhecido na API (veja log)</string> + <string name="error_api_incorrect_usage">Erro: Uso incorreto da API</string> + <string name="error_login_failed">Erro: Usuário ou senha inválidos</string> + <string name="error_invalid_api_url">Erro: A URL da API é inválida</string> + <string name="combined_mode_summary">Mostrar todo o texto do arquivo ao invés de usar um painel separado</string> + <string name="combined_mode">Modo combinado</string> + <string name="go_offline">Modo offline</string> + <string name="go_online">Modo online</string> + <string name="offline_switch_error">Falha ao preparar o modo offline (ver log)</string> + <string name="no_feeds">Nenhuma assinatura para mostrar</string> + <string name="no_headlines">Nenhum artigo para mostrar</string> + <string name="dialog_offline_prompt">O Login falhou mas você tem dados armazenados, ir para modo offline?</string> + <string name="dialog_offline_success">Modo offline está disponível</string> + <string name="dialog_offline_go">Ir para offline</string> + <string name="dialog_cancel">Cancelar</string> + <string name="dialog_offline_switch_prompt">Baixar artigos não lidos e mudar para offline?</string> + <string name="notify_downloading_articles">Baixando artigos (%1$d)…</string> + <string name="notify_downloading_init">Iniciando download…</string> + <string name="notify_downloading_feeds">Baixando assinaturas…</string> + <string name="notify_uploading_sending_data">Enviando dados para o servidor…</string> + <string name="notify_downloading_title">Preparando modo offline</string> + <string name="notify_uploading_title">Sincronizando dados offline</string> + <string name="offline_sync_success">Sincronização de dados offline completa</string> + <string name="offline_mode">Modo offline</string> + <string name="offline_image_cache_enabled">Armazenar imagens</string> + <string name="offline_image_cache_enabled_summary">Baixar imagens para o cartão SD. Isso pode aumentar consideravelmente o tempo para o modo offline.</string> + <string name="notify_downloading_images">Baixando imagens (%1$d)…</string> + <string name="article_set_labels">Definir tags</string> + <string name="search">Pesquisar</string> + <string name="cancel">Cancelar</string> + <string name="font_size_small">Pequeno</string> + <string name="font_size_medium">Médio</string> + <string name="font_size_large">Grande</string> + <string name="pref_font_size">Tamanho do texto dos artigos</string> + <string name="donate">Doe</string> + <string name="dialog_close">Fechar</string> + <string name="donate_select">Por favor, selecione a doação</string> + <string name="donate_do">Doe!</string> + <string name="tablet_article_swipe">Swipe between articles</string> + <string name="article_link_copy">Copiar link para a área de transferência</string> + <string name="text_copied_to_clipboard">Texto copiado para a área de transferência</string> + <string name="attachments_prompt">Selecione anexo</string> + <string name="attachment_view">Exibir</string> + <string name="attachment_copy">Copiar URL</string> + <string name="justify_article_text">Justificar texto dos artigos</string> + <string name="dialog_offline_sync_in_progress">Sincronização offline em progresso</string> + <string name="dialog_offline_sync_stop">Para sincronização</string> + <string name="dialog_offline_sync_continue">Continuar</string> + <string name="article_set_note">Publicar com anotação</string> + <string name="close_feed">Fechar assinatura</string> + <string name="close_article">Fechar artigo</string> + <string name="dialog_open_preferences">Configuração</string> + <string name="dialog_need_configure_prompt">Preencha com a informação do servidor tt-rss como URL, login e senha.</string> + <string name="notify_article_marked">Artigo com estrela</string> + <string name="notify_article_unmarked">Artigo sem estrela</string> + <string name="notify_article_published">Artigo publicado</string> + <string name="notify_article_unpublished">Artigo não publicado</string> + <string name="notify_article_note_set">Nota do artigo salva</string> + <string name="update_headlines">Atualizar</string> + <string name="attachment_share">Compartilhar</string> + <string name="error_network_unavailable">Erro: Rede indisponível</string> + <string name="category_browse_headlines">Navegar pelos títulos</string> + <string name="pref_default_view_mode">Visualização padrão de assinaturas</string> + <string name="pref_default_view_mode_long">Qual assinatura abrir por padrão em smartphones</string> + <string name="donate_thanks">Doação encontrada, obrigado pelo suporte!</string> + <string name="use_volume_keys">Usar botões de volume</string> + <string name="use_volume_keys_long">Mudar entre artigos usando os botões de volume</string> + <string name="ssl_trust_any_host">Não verificar hostname</string> + <string name="error_api_unknown_method">Erro: metodo API desconhecido</string> + <string name="ssl_trust_any_long">Aceitar qualquer certificado SSL sem verificação</string> + <string name="ssl_trust_any_host_long">Não verificar o nome do servidor</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Erro: Nome SSL não verificado</string> + <string name="offline_oldest_first">Mostrar artigos mais antigos primeiro</string> + <string name="prefs_dim_status_bar">Ocultar barra de status</string> + <string name="prefs_dim_status_bar_long">Ocultar barra de status durante a leitura</string> + <string name="article_comments">%1$d comentários</string> + <string name="trial_mode_prompt">Modo de teste, resta(m) %1$d dia(s).</string> + <string name="trial_purchase">Desbloquear a versão completa</string> + <string name="trial_expired">Período de teste encerrado</string> + <string name="trial_expired_message">Para continuar usando o Tiny Tiny RSS por favor desbloqueie a versão completa comprando a chave.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Versão completa, obrigado pelo suporte!</string> + <string name="prefs_fullscreen_mode">Modo tela cheia</string> + <string name="reading">Lendo</string> + <string name="theme_dark_gray">Cinza escuro</string> + <string name="offline_articles_to_download">Número de artigos a baixar</string> + <string name="offline_articles_to_download_long">Número de artigos a baixar para o modo Offline (mais novos primeiro).</string> + <string name="pref_headlines_show_content_long">Mostrar prévia do conteúdo na lista de títulos</string> + <string name="pref_headlines_show_content">Prévia do conteúdos do artigo</string> + <string name="api_too_low">Esta ação precisa de uma nova versão do Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">URL do artigo</string> + <string name="share_content_hint">Conteúdo do artigo</string> + <string name="share_title_prompt">Título:</string> + <string name="share_title_hint">Título do artigo</string> + <string name="share_share_button">Compartilhar</string> + <string name="share_article_posted">Artigo postado.</string> + <string name="subscribe_name">Assinar com Tiny Tiny RSS</string> + <string name="feed_url">URL da assinatura</string> + <string name="subscribe_to_feed">Assinar feed</string> + <string name="error_while_subscribing">Erro ao assinar.</string> + <string name="category_list_updated">Lista de categorias atualizada</string> + <string name="subscribed_to_feed">Feed assinado</string> + <string name="error_feed_already_exists_">Erro: Assinatura já existe.</string> + <string name="error_invalid_url">Erro: URL inválida.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Erro: URL é de uma página HTML, nenhum feed encontrado.</string> + <string name="error_url_contains_multiple_feeds">Erro: URL contém múltiplos feeds</string> + <string name="error_could_not_download_url">Erro: Não foi possível baixar a URL</string> + <string name="headlines_view_mode">Define modo de visualização</string> + <string name="headlines_set_view_mode">Define modo de visualização</string> + <string name="headlines_adaptive">Adaptativa</string> + <string name="headlines_all_articles">Todos os artigos</string> + <string name="headlines_starred">Com estrela</string> + <string name="headlines_published">Publicados</string> + <string name="headlines_unread">Não lidos</string> + <string name="article_img_open">Abrir imagem</string> + <string name="article_img_share">Compartilhar imagem</string> + <string name="requires_api5">Requer versão 1.7.6</string> + <string name="labels">Tags</string> + <string name="article_img_view_caption">Ver legenda</string> + <string name="light_theme_is_not_supported_on_honeycomb">Tema claro não é suportado no Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Marcar como lida ao visualizar</string> + <string name="pref_headlines_mark_read_scroll_long">Títulos serão marcados como lidos ao rodar depois deles</string> + <string name="mark_num_headlines_as_read">Marcar %1$d artigo(s) como lido(s)?</string> + <string name="prefs_confirm_headlines_catchup">Confirme marcação de artigos como lidos</string> + <string name="author_formatted">por %1$s</string> + <string name="n_unread_articles">%1$d artigos não lidos</string> + <string name="pref_headline_font_size">Tamanho do texto para os títulos</string> +</resources> diff --git a/orgfoxttrss/src/main/res/values-v11/style.xml b/orgfoxttrss/src/main/res/values-v11/style.xml new file mode 100644 index 00000000..d09304cd --- /dev/null +++ b/orgfoxttrss/src/main/res/values-v11/style.xml @@ -0,0 +1,6 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="DarkDialogTheme" parent="android:Theme.Holo.Dialog"> + </style> + +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values-v19/style.xml b/orgfoxttrss/src/main/res/values-v19/style.xml new file mode 100644 index 00000000..67781339 --- /dev/null +++ b/orgfoxttrss/src/main/res/values-v19/style.xml @@ -0,0 +1,23 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="LightTheme" parent="LightThemeBase"> + <item name="android:windowTranslucentStatus">true</item> + <item name="android:windowTranslucentNavigation">false</item> + </style> + + <style name="SepiaTheme" parent="SepiaThemeBase"> + <item name="android:windowTranslucentStatus">true</item> + <item name="android:windowTranslucentNavigation">false</item> + </style> + + <style name="HoloTheme" parent="HoloThemeBase"> + <item name="android:windowTranslucentStatus">true</item> + <item name="android:windowTranslucentNavigation">false</item> + </style> + + <style name="DarkTheme" parent="DarkThemeBase"> + <item name="android:windowTranslucentStatus">true</item> + <item name="android:windowTranslucentNavigation">false</item> + </style> + +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values/arrays.xml b/orgfoxttrss/src/main/res/values/arrays.xml new file mode 100644 index 00000000..79d9a4d3 --- /dev/null +++ b/orgfoxttrss/src/main/res/values/arrays.xml @@ -0,0 +1,30 @@ +<resources> + <string-array name="pref_theme_names"> + <item>@string/theme_light</item> + <item>@string/theme_dark</item> + <item>@string/theme_sepia</item> + <item>@string/theme_holo</item> + </string-array> + <string-array name="pref_theme_values" translatable="false"> + <item>THEME_LIGHT</item> + <item>THEME_DARK</item> + <item>THEME_SEPIA</item> + <item>THEME_HOLO</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" translatable="false"> + <item>HEADLINES</item> + <item>ARTICLES</item> + </string-array> + <string-array name="pref_offline_amounts" translatable="false"> + <item>150</item> + <item>250</item> + <item>500</item> + <item>1000</item> + <item>1500</item> + <item>2000</item> + </string-array> +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values/attrs.xml b/orgfoxttrss/src/main/res/values/attrs.xml new file mode 100644 index 00000000..3558beb6 --- /dev/null +++ b/orgfoxttrss/src/main/res/values/attrs.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <attr name="ttrssHorizontalDivider" format="reference|color" /> + <attr name="feedlistBackground" format="reference|color" /> + <attr name="smallScreenBackground" format="reference|color" /> + <attr name="unreadCounterColor" format="reference|color" /> + <attr name="headlinesBackground" format="reference|color" /> + <attr name="headlinesBackgroundSolid" format="reference|color" /> + <attr name="articleBackground" format="reference|color" /> + <attr name="headlineSelectedBackground" format="reference|color" /> + <attr name="headlineUnreadBackground" format="reference|color" /> + <attr name="headlineNormalBackground" format="reference|color" /> + <attr name="feedsSelectedBackground" format="reference|color" /> + <attr name="feedlistTextColor" format="reference|color" /> + <attr name="feedlistSelectedTextColor" format="reference|color" /> + <attr name="headlineTextColor" format="reference|color" /> + <attr name="headlineUnreadTextColor" format="reference|color" /> + <attr name="headlineSelectedTextColor" format="reference|color" /> + <attr name="headlineExcerptTextColor" format="reference|color" /> + <attr name="headlineSecondaryTextColor" format="reference|color" /> + <attr name="headlineSelectedSecondaryTextColor" format="reference|color" /> + <attr name="headlineSelectedExcerptTextColor" format="reference|color" /> + <attr name="headlineTitleHighScoreUnreadTextColor" format="reference|color" /> + <attr name="linkColor" format="reference|color" /> + <attr name="loadingBackground" format="reference|color" /> + <attr name="unreadCounterBackground" format="reference|color" /> + <attr name="unreadSelectedCounterBackground" format="reference|color" /> + <attr name="articleNoteBackground" format="reference|color" /> + <attr name="articleNoteTextColor" format="reference|color" /> + <attr name="statusBarHintColor" format="reference|color" /> +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values/resources.xml b/orgfoxttrss/src/main/res/values/resources.xml new file mode 100644 index 00000000..a3c4151c --- /dev/null +++ b/orgfoxttrss/src/main/res/values/resources.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <color name="feeds_light">#e0e0e0</color> + <color name="headlines_light">#ffffff</color> + <color name="headlines_sepia">#EAE2DC</color> + <color name="feeds_sepia">#D3C6BA</color> + <color name="ics_cyan">#33b5e5</color> + <color name="unread_counter_background">#88b0f0</color> + <color name="unread_counter_background_dark">#63758E</color> + <color name="unread_counter_background_sepia">#C46262</color> + <color name="unread_counter_background_selected_light">#4684ff</color> + <color name="feeds_dark_gray">#1c1d1e</color> +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/values/strings.xml b/orgfoxttrss/src/main/res/values/strings.xml new file mode 100644 index 00000000..d5653d49 --- /dev/null +++ b/orgfoxttrss/src/main/res/values/strings.xml @@ -0,0 +1,226 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="login_in_progress">Logging in…</string> + <string name="app_name">Tiny Tiny RSS</string> + <string name="login_need_configure">Please configure the application first.</string> + <string name="login_ready">Ready to login.</string> + <string name="login_login">Log in</string> + <string name="logout">Log out</string> + <string name="login">Login</string> + <string name="debugging">Debugging</string> + <string name="password">Password</string> + <string name="default_url">http://example.domain/tt-rss/</string> + <string name="look_and_feel">Interface</string> + <string name="pref_theme">Theme</string> + <string name="pref_theme_long">Changes color theme of the application</string> + <string name="ttrss_url">Tiny Tiny RSS URL</string> + <string name="theme_light">Light</string> + <string name="theme_dark">Dark</string> + <string name="theme_holo">Holo</string> + <string name="preferences">Settings</string> + <string name="connection">Connection</string> + <string name="headline_context_multiple">Selected articles</string> + <string name="http_authentication">HTTP Authentication</string> + <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</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> + <string name="ssl_trust_any">Accept any certificate</string> + <string name="category_browse_feeds">Browse feeds</string> + <string name="category_browse_articles">Browse articles</string> + <string name="blank"></string> + <string name="transport_debugging">Log sent and received data</string> + <string name="article_toggle_marked">(Un)Star</string> + <string name="article_toggle_published">(Un)Publish</string> + <string name="headlines_select">Select articles</string> + <string name="headlines_select_dialog">Select articles</string> + <string name="headlines_select_all">Everything</string> + <string name="headlines_select_unread">Unread</string> + <string name="headlines_select_none">Deselect all</string> + <string name="selection_toggle_marked">(Un)Star</string> + <string name="selection_toggle_published">(Un)Publish</string> + <string name="selection_toggle_unread">(Un)Read</string> + <string name="selection_select_none">Deselect all</string> + <string name="context_selection_toggle_marked">(Un)Star</string> + <string name="context_selection_toggle_published">(Un)Publish</string> + <string name="context_selection_toggle_unread">(Un)Read</string> + <string name="article_set_unread">Article set unread</string> + <string name="article_mark_read_above">Mark above read</string> + <string name="http_login_summary">Optional. Fill this if your tt-rss installation is protected by HTTP Basic authentication</string> + <string name="login_summary">Your tt-rss login. Not needed for single user mode</string> + <string name="ttrss_url_summary">URL of your tt-rss installation directory, e.g. http://site.com/tt-rss/</string> + <string name="download_feed_icons">Enable feed icons</string> + <string name="enable_cats">Enable feed categories</string> + <string name="no_feeds_to_display">No feeds to display</string> + <string name="no_headlines_to_display">No headlines to display</string> + <string name="no_caption_to_display">No caption to display</string> + <string name="browse_cats_like_feeds">Browse categories like feeds</string> + <string name="browse_cats_like_feeds_summary">Use category context menu to override this setting</string> + <string name="headlines_mark_as_read">Mark read</string> + <string name="error_unknown">Error: Unknown error (see log)</string> + <string name="error_http_unauthorized">Error: 401 unauthorized</string> + <string name="error_http_forbidden">Error: 403 forbidden</string> + <string name="error_http_not_found">Error: 404 not found</string> + <string name="error_http_server_error">Error: 500 server error</string> + <string name="error_http_other_error">Error: other HTTP error (see log)</string> + <string name="error_ssl_rejected">Error: SSL certificate rejected</string> + <string name="error_parse_error">Error: JSON parse failed</string> + <string name="error_io_error">Error: I/O failure (server down?)</string> + <string name="error_other_error">Error: unknown error (see log)</string> + <string name="error_api_disabled">Error: Please enable external API access in tt-rss Settings - Advanced</string> + <string name="error_api_unknown">Error: unknown API error (see log)</string> + <string name="error_api_incorrect_usage">Error: incorrect API usage</string> + <string name="error_login_failed">Error: username or password incorrect</string> + <string name="error_invalid_api_url">Error: invalid API URL</string> + <string name="combined_mode_summary">Displays full article text inline, instead of a separate panel</string> + <string name="combined_mode">Combined mode</string> + <string name="go_offline">Go offline</string> + <string name="go_online">Go online</string> + <string name="offline_switch_error">Failed to prepare offline mode (see log)</string> + <string name="no_feeds">No feeds to display</string> + <string name="no_headlines">No articles to display</string> + <string name="dialog_offline_prompt">Login failed, but you have stored offline data. Would you like to go offline?</string> + <string name="dialog_offline_success">Offline mode is ready</string> + <string name="dialog_offline_go">Go offline</string> + <string name="dialog_cancel">Cancel</string> + <string name="dialog_offline_switch_prompt">Download unread articles and go offline?</string> + <string name="notify_downloading_articles">Downloading articles (%1$d)…</string> + <string name="notify_downloading_init">Starting download…</string> + <string name="notify_downloading_feeds">Downloading feeds…</string> + <string name="notify_uploading_sending_data">Sending data to server…</string> + <string name="notify_downloading_title">Preparing offline mode</string> + <string name="notify_uploading_title">Synchronizing offline data</string> + <string name="offline_sync_success">Finished synchronizing your offline data</string> + <string name="offline_mode">Offline mode</string> + <string name="offline_image_cache_enabled">Cache images</string> + <string name="offline_image_cache_enabled_summary">Download images to sdcard. This might significantly increase time it takes to go offline.</string> + <string name="notify_downloading_images">Downloading images (%1$d)…</string> + <string name="article_set_labels">Set labels</string> + <string name="search">Search</string> + <string name="cancel">Cancel</string> + <string name="font_size_small">Small</string> + <string name="font_size_medium">Medium</string> + <string name="font_size_large">Large</string> + <string name="pref_font_size">Article text size</string> + <string name="donate">Donate</string> + <string name="dialog_close">Close</string> + <string name="donate_select">Please select the donation</string> + <string name="donate_do">Donate!</string> + <string name="tablet_article_swipe">Swipe between articles</string> + <string name="article_link_copy">Copy link to clipboard</string> + <string name="text_copied_to_clipboard">Text copied to clipboard</string> + <string name="attachments_prompt">Select attachment</string> + <string name="attachment_view">View</string> + <string name="attachment_copy">Copy URL</string> + <string name="justify_article_text">Justify article text</string> + <string name="dialog_offline_sync_in_progress">Offline sync in progress</string> + <string name="dialog_offline_sync_stop">Stop syncing</string> + <string name="dialog_offline_sync_continue">Continue</string> + <string name="article_set_note">Publish with note</string> + <string name="close_feed">Close feed</string> + <string name="close_article">Close article</string> + <string name="dialog_open_preferences">Settings</string> + <string name="dialog_need_configure_prompt">Please fill in your tt-rss server information such as URL, login, and password.</string> + <string name="notify_article_marked">Article starred</string> + <string name="notify_article_unmarked">Article unstarred</string> + <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="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> + <string name="use_volume_keys">Use volume buttons</string> + <string name="use_volume_keys_long">Switch between articles with hardware volume buttons</string> + <string name="ssl_trust_any_host">No hostname verification</string> + <string name="error_api_unknown_method">Error: unknown API method</string> + <string name="ssl_trust_any_long">Accepts any SSL certificate without validation</string> + <string name="ssl_trust_any_host_long">Do not verify server hostname</string> + <string name="ssl">SSL</string> + <string name="error_ssl_hostname_rejected">Error: SSL hostname not verified</string> + <string name="offline_oldest_first">Show oldest articles first</string> + <string name="prefs_dim_status_bar">Dim status bar</string> + <string name="prefs_dim_status_bar_long">Dim status bar when reading</string> + <string name="article_comments">%1$d comments</string> + <string name="trial_mode_prompt">Trial mode, %1$d day(s) left.</string> + <string name="trial_purchase">Unlock full version</string> + <string name="trial_expired">Trial expired</string> + <string name="trial_expired_message">To continue using Tiny Tiny RSS please unlock the full version by purchasing the key.</string> + <string name="theme_sepia">Sepia</string> + <string name="trial_thanks">Full version, thank you for support!</string> + <string name="prefs_fullscreen_mode">Fullscreen mode</string> + <string name="reading">Reading</string> + <string name="offline_articles_to_download">Amount of articles to download</string> + <string name="offline_articles_to_download_long">How many articles to download for offline mode (newest first).</string> + <string name="pref_headlines_show_content_long">Show content previews in headlines list</string> + <string name="pref_headlines_show_content">Preview article content</string> + <string name="api_too_low">This action requires newer version of Tiny Tiny RSS</string> + <string name="share_url_prompt">URL:</string> + <string name="share_url_hint">Article URL</string> + <string name="share_content_hint">Article Content</string> + <string name="share_title_prompt">Title:</string> + <string name="share_title_hint">Article Title</string> + <string name="share_share_button">Share</string> + <string name="share_article_posted">Article posted.</string> + <string name="subscribe_name">Subscribe with Tiny Tiny RSS</string> + <string name="feed_url">Feed URL</string> + <string name="subscribe_to_feed">Subscribe to feed</string> + <string name="error_while_subscribing">Error while subscribing.</string> + <string name="category_list_updated">Category list updated</string> + <string name="subscribed_to_feed">Subscribed to feed</string> + <string name="error_feed_already_exists_">Error: feed already exists.</string> + <string name="error_invalid_url">Error: Invalid URL.</string> + <string name="error_url_is_an_html_page_no_feeds_found">Error: URL is an HTML page, no feeds found.</string> + <string name="error_url_contains_multiple_feeds">Error: URL contains multiple feeds</string> + <string name="error_could_not_download_url">Error: Could not download URL</string> + <string name="headlines_view_mode">Set view mode</string> + <string name="headlines_set_view_mode">Set view mode</string> + <string name="headlines_adaptive">Adaptive</string> + <string name="headlines_all_articles">All articles</string> + <string name="headlines_starred">Starred</string> + <string name="headlines_published">Published</string> + <string name="headlines_unread">Unread</string> + <string name="article_img_open">Open image</string> + <string name="article_img_share">Share image</string> + <string name="requires_api5">Requires tt-rss 1.7.6</string> + <string name="labels">Labels</string> + <string name="article_img_view_caption">View Caption</string> + <string name="light_theme_is_not_supported_on_honeycomb">Light theme is not supported on Honeycomb</string> + <string name="pref_headlines_mark_read_scroll">Mark read on scroll</string> + <string name="pref_headlines_mark_read_scroll_long">Headlines will be marked read when scrolling past them</string> + <string name="mark_num_headlines_as_read">Mark %1$d article(s) as read?</string> + <string name="prefs_confirm_headlines_catchup">Confirm marking articles as read</string> + <string name="author_formatted">by %1$s</string> + <string name="n_unread_articles">%1$d unread articles</string> + <string name="pref_headline_font_size">Headline text size</string> + <string name="context_confirm_catchup">Mark all articles in %1$s as read?</string> + <string name="theme_system">Device Default</string> + <string name="accel_webview_summary">Disable if you see flicker or visual glitches.</string> + <string name="accel_webview_title">Accelerate web views (3.0+)</string> + <string name="place_shortcut">Place shortcut</string> + <string name="shortcut_has_been_placed_on_the_home_screen">Shortcut has been placed on the home screen</string> + <string name="download_articles_and_go_offline">Download articles and go offline</string> + <string name="tasker_save_and_close">Save and close</string> + <string name="synchronize_read_articles_and_go_online">Synchronize read articles and go online</string> + <string name="prefs_compatible_article_layout">Compatible article layout</string> + <string name="prefs_compatible_layout_summary">Enable if you see glitches in article content</string> + <string name="font_size_dialog_suffix">sp</string> + <string name="server_function_not_available">Sorry, this function is not available on your tt-rss version.</string> + <string name="unsubscribe">Unsubscribe</string> + <string name="unsubscribe_from_prompt">Unsubscribe from %1$s?</string> + <string name="toggle_sidebar">Toggle sidebar</string> + <string name="open_article_in_web_browser">Open in web browser</string> + <string name="pref_headlines_use_condensed_fonts">Enable condensed fonts</string> + <string name="pref_headlines_use_condensed_fonts_long">Use condensed fonts for headline titles and a few other UI elements.</string> + <string name="pref_headlines_full_content_long">Show full article content in headlines. Resource intensive, can cause UI lag on some devices.</string> + <string name="pref_headlines_full_content">Show full content</string> + <string name="prefs_headlines_show_flavor_image">Show article image</string> + +</resources> diff --git a/orgfoxttrss/src/main/res/values/style.xml b/orgfoxttrss/src/main/res/values/style.xml new file mode 100644 index 00000000..8c1fb98f --- /dev/null +++ b/orgfoxttrss/src/main/res/values/style.xml @@ -0,0 +1,137 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="LightThemeBase" parent="Theme.AppCompat.Light.DarkActionBar"> + <item name="statusBarHintColor">#6482AF</item> + <item name="smallScreenBackground">#eeeeee</item> + <item name="ttrssHorizontalDivider">@android:drawable/divider_horizontal_bright</item> + <item name="feedlistBackground"><!-- #e0e0e0 -->@drawable/shadow_feeds</item> + <item name="unreadCounterColor">#ffffff</item> + <item name="headlinesBackground"><!-- #f0f0f0 -->@drawable/shadow_headlines</item> + <item name="headlinesBackgroundSolid">#f0f0f0</item> + <item name="articleBackground">@android:color/transparent</item> + <item name="headlineSelectedBackground">@drawable/headline_row_selected</item> + <item name="headlineUnreadBackground">@drawable/headline_row_unread</item> + <item name="headlineNormalBackground">@drawable/headline_row</item> + <item name="feedsSelectedBackground">#88b0f0</item> + <item name="feedlistTextColor">@android:color/primary_text_light</item> + <item name="feedlistSelectedTextColor">#ffffff</item> + <item name="headlineTextColor">@android:color/secondary_text_light</item> + <item name="headlineUnreadTextColor">@android:color/primary_text_light</item> + <item name="headlineSelectedTextColor">#ffffff</item> + <item name="headlineExcerptTextColor">@android:color/secondary_text_light</item> + <item name="headlineSecondaryTextColor">#909090</item> + <item name="headlineSelectedSecondaryTextColor">#606060</item> + <item name="headlineSelectedExcerptTextColor">@android:color/secondary_text_light</item> + <item name="headlineTitleHighScoreUnreadTextColor">#008000</item> + <item name="linkColor">#4684ff</item> + <item name="loadingBackground">@android:color/white</item> + <item name="unreadCounterBackground">@drawable/counter_background</item> + <item name="unreadSelectedCounterBackground">@drawable/counter_background_selected_light</item> + <item name="articleNoteTextColor">#9a8c59</item> + <item name="articleNoteBackground">#fff7d5</item> + <item name="android:actionBarStyle">@style/ActionBar.Light</item> + </style> + + <style name="LightTheme" parent="LightThemeBase"> + </style> + + <style name="SepiaThemeBase" parent="LightTheme"> + <item name="statusBarHintColor">#7F3F3F</item> + <item name="smallScreenBackground">@drawable/paper_sepia</item> + <item name="feedlistBackground">@drawable/shadow_feeds_sepia</item> + <item name="headlinesBackground">@drawable/shadow_headlines_sepia</item> + <item name="headlinesBackgroundSolid">@drawable/paper_sepia</item> + <item name="headlineUnreadBackground">@drawable/headline_row_unread_sepia</item> <!-- #F2EAE8 --> + <item name="headlineNormalBackground">@drawable/headline_row_sepia</item> + <item name="headlineSelectedBackground">@drawable/headline_row_selected_sepia</item> <!-- #E5B0A0 --> + <item name="feedsSelectedBackground">#E5B0A0</item> + <item name="articleBackground">@drawable/paper_sepia</item> + <item name="unreadCounterBackground">@drawable/counter_background_sepia</item> + <item name="unreadSelectedCounterBackground">@drawable/counter_background_sepia</item> + <item name="feedlistTextColor">#35281C</item> + <item name="linkColor">#C46262</item> + <item name="android:actionBarStyle">@style/ActionBar.Sepia</item> + </style> + + <style name="SepiaTheme" parent="SepiaThemeBase"> + </style> + + <style name="HoloThemeBase" parent="Theme.AppCompat"> + <item name="statusBarHintColor">@android:color/black</item> + <item name="smallScreenBackground">@android:color/transparent</item> + <item name="ttrssHorizontalDivider">@android:drawable/divider_horizontal_dark</item> + <item name="feedlistBackground">@android:color/transparent</item> + <item name="unreadCounterColor">#ffffff</item> + <item name="headlinesBackground">@android:color/black</item> + <item name="headlinesBackgroundSolid">@android:color/black</item> + <item name="articleBackground">@android:color/black</item> + <item name="headlineSelectedBackground">@color/ics_cyan</item> + <item name="headlineUnreadBackground">#202020</item> + <item name="headlineNormalBackground">#151515</item> + <item name="feedsSelectedBackground">@color/ics_cyan</item> + <item name="feedlistTextColor">@android:color/primary_text_dark</item> + <item name="feedlistSelectedTextColor">@android:color/black</item> + <item name="headlineTextColor">@android:color/secondary_text_dark</item> + <item name="headlineUnreadTextColor">@android:color/primary_text_dark</item> + <item name="headlineSelectedTextColor">@android:color/white</item> + <item name="headlineExcerptTextColor">@android:color/secondary_text_dark</item> + <item name="headlineSelectedExcerptTextColor">@android:color/black</item> + <item name="headlineSecondaryTextColor">#909090</item> + <item name="headlineSelectedSecondaryTextColor">#404040</item> + <item name="headlineTitleHighScoreUnreadTextColor">#00FF00</item> + <item name="linkColor">@color/ics_cyan</item> + <item name="loadingBackground">@android:color/black</item> + <item name="unreadCounterBackground">@drawable/counter_background_dark</item> + <item name="unreadSelectedCounterBackground">@drawable/counter_background_dark</item> + <item name="articleNoteTextColor">@android:color/secondary_text_dark</item> + <item name="articleNoteBackground">#303030</item> + </style> + + <style name="HoloTheme" parent="HoloThemeBase"> + </style> + + <style name="DarkThemeBase" parent="HoloTheme"> + <item name="statusBarHintColor">#394A63</item> + <item name="smallScreenBackground">@color/feeds_dark_gray</item> + <item name="ttrssHorizontalDivider">@android:drawable/divider_horizontal_dark</item> + <item name="feedlistBackground">@drawable/shadow_feeds_gray</item> + <item name="headlinesBackground">@drawable/shadow_headlines_gray</item> + <item name="headlinesBackgroundSolid">@color/feeds_dark_gray</item> + <item name="articleBackground">@color/feeds_dark_gray</item> + <item name="headlineSelectedBackground">#22667f</item> + <item name="headlineUnreadBackground">#383c42</item> + <item name="feedsSelectedBackground">#22667f</item> + <item name="feedlistSelectedTextColor">@android:color/primary_text_dark</item> + <item name="headlineSelectedExcerptTextColor">@android:color/secondary_text_dark</item> + <item name="headlineTextColor">@android:color/secondary_text_dark</item> + <!-- <item name="actionBarStyle">@style/ActionBarDarkGray</item> --> + <item name="android:actionBarStyle">@style/ActionBar.DarkGray</item> + <item name="headlineSelectedSecondaryTextColor">#a0a0a0</item> + </style> + + <style name="DarkTheme" parent="DarkThemeBase"> + </style> + + <style name="ActionBar.Light" parent="Widget.AppCompat.ActionBar.Solid"> + <item name="android:background">#6482AF</item> + <item name="android:titleTextStyle">@style/ActionBarText.Light</item> + </style> + + <style name="ActionBar.Sepia" parent="Widget.AppCompat.ActionBar.Solid"> + <item name="android:background">#7F3F3F</item> + <item name="android:titleTextStyle">@style/ActionBarText.Light</item> + </style> + + <style name="ActionBarText.Light" + parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"> + <item name="android:textColor">@android:color/white</item> + </style> + + <style name="ActionBar.DarkGray" parent="Widget.AppCompat.ActionBar.Solid"> + <item name="android:background">#394A63</item> + </style> + + <style name="DarkDialogTheme" parent="android:Theme.Dialog"> + </style> + +</resources>
\ No newline at end of file diff --git a/orgfoxttrss/src/main/res/xml/preferences.xml b/orgfoxttrss/src/main/res/xml/preferences.xml new file mode 100644 index 00000000..7ee21f84 --- /dev/null +++ b/orgfoxttrss/src/main/res/xml/preferences.xml @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="utf-8"?> +<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" + android:key="ttrss_url" + android:singleLine="true" + android:summary="@string/ttrss_url_summary" + android:title="@string/ttrss_url" > + </EditTextPreference> + </PreferenceCategory> + <PreferenceCategory android:title="@string/ssl" > + <CheckBoxPreference + android:defaultValue="false" + android:key="ssl_trust_any" + android:summary="@string/ssl_trust_any_long" + android:title="@string/ssl_trust_any" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="ssl_trust_any_host" + android:summary="@string/ssl_trust_any_host_long" + android:title="@string/ssl_trust_any_host" /> + </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" + android:singleLine="true" + android:title="@string/password" > + </EditTextPreference> + </PreferenceCategory> + <PreferenceCategory + android:key="category_look_and_feel" + android:title="@string/look_and_feel" > + <ListPreference + android:defaultValue="THEME_LIGHT" + android:entries="@array/pref_theme_names" + android:entryValues="@array/pref_theme_values" + android:key="theme" + android:summary="@string/pref_theme_long" + android:title="@string/pref_theme" /> + + <CheckBoxPreference + 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:summary="@string/browse_cats_like_feeds_summary" + android:title="@string/browse_cats_like_feeds" /> + + <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" /> + + <CheckBoxPreference + android:defaultValue="true" + android:key="headlines_show_content" + android:summary="@string/pref_headlines_show_content_long" + android:title="@string/pref_headlines_show_content" /> + + <CheckBoxPreference + android:defaultValue="true" + android:key="headlines_show_flavor_image" + android:title="@string/prefs_headlines_show_flavor_image" /> + + <!-- <CheckBoxPreference + android:defaultValue="false" + android:key="headlines_full_content" + android:summary="@string/pref_headlines_full_content_long" + android:title="@string/pref_headlines_full_content" /> --> + + <CheckBoxPreference + android:defaultValue="false" + android:key="oldest_first" + android:summary="@string/requires_api5" + android:title="@string/offline_oldest_first" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="headlines_mark_read_scroll" + android:summary="@string/pref_headlines_mark_read_scroll_long" + android:title="@string/pref_headlines_mark_read_scroll" /> + + <CheckBoxPreference + android:defaultValue="false" + android:key="enable_condensed_fonts" + android:summary="@string/pref_headlines_use_condensed_fonts_long" + android:title="@string/pref_headlines_use_condensed_fonts" /> + + <org.fox.ttrss.util.FontSizeDialogPreference + android:defaultValue="13" + android:key="headlines_font_size_sp" + android:dialogMessage="@string/pref_headline_font_size" + android:title="@string/pref_headline_font_size" /> + + </PreferenceCategory> + <PreferenceCategory android:title="@string/reading" > + <org.fox.ttrss.util.FontSizeDialogPreference + android:defaultValue="16" + android:key="article_font_size_sp" + android:inputType="number" + android:title="@string/pref_font_size" /> + <CheckBoxPreference + android:defaultValue="true" + android:key="justify_article_text" + android:title="@string/justify_article_text" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="use_volume_keys" + android:summary="@string/use_volume_keys_long" + android:title="@string/use_volume_keys" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="dim_status_bar" + android:summary="@string/prefs_dim_status_bar_long" + android:title="@string/prefs_dim_status_bar" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="full_screen_mode" + android:title="@string/prefs_fullscreen_mode" /> + <CheckBoxPreference + android:defaultValue="true" + android:key="confirm_headlines_catchup" + android:title="@string/prefs_confirm_headlines_catchup" /> + </PreferenceCategory> + <PreferenceCategory android:title="@string/offline_mode" > + <ListPreference + android:defaultValue="250" + android:entries="@array/pref_offline_amounts" + android:entryValues="@array/pref_offline_amounts" + android:key="offline_sync_max" + android:summary="@string/offline_articles_to_download_long" + android:title="@string/offline_articles_to_download" /> + + <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" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="offline_oldest_first" + android:title="@string/offline_oldest_first" /> + </PreferenceCategory> + <PreferenceCategory android:title="@string/debugging" > + <CheckBoxPreference + android:defaultValue="false" + android:key="article_compat_view" + android:title="@string/prefs_compatible_article_layout" + android:summary="@string/prefs_compatible_layout_summary" /> + <CheckBoxPreference + android:defaultValue="true" + android:key="webview_hardware_accel" + android:summary="@string/accel_webview_summary" + android:title="@string/accel_webview_title" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="transport_debugging" + android:title="@string/transport_debugging" /> + </PreferenceCategory> + +</PreferenceScreen> diff --git a/orgfoxttrss/src/main/res/xml/widget_small.xml b/orgfoxttrss/src/main/res/xml/widget_small.xml new file mode 100644 index 00000000..c41eb3d2 --- /dev/null +++ b/orgfoxttrss/src/main/res/xml/widget_small.xml @@ -0,0 +1,8 @@ +<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:minWidth="40dp" + android:minHeight="40dp" + android:updatePeriodMillis="86400000" + android:initialLayout="@layout/widget_small" + android:resizeMode="horizontal|vertical" + android:widgetCategory="home_screen"> +</appwidget-provider>
\ No newline at end of file |