diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1d48a3be..5b17ba4f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,6 +47,21 @@
android:name=".ArticleActivity"
android:label="@string/app_name" >
+
+
+
+
+
+
+
+
+
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 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", m_sessionId);
+
+ 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 (m_sessionId != 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);
+ refresh.putExtra("sessionId", m_sessionId);
+ refresh.putExtra("apiLevel", m_apiLevel);
+ 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() {
if (m_prefs.getString("ttrss_url", "").trim().length() == 0) {
@@ -186,6 +426,9 @@ public class OnlineActivity extends CommonActivity {
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, 0);
+
+ if (hasPendingOfflineData())
+ syncOfflineData();
finish();
}
@@ -203,7 +446,7 @@ public class OnlineActivity extends CommonActivity {
login();
return true;
case R.id.go_offline:
- // FIXME go offline
+ switchOffline();
return true;
case R.id.article_set_note:
if (ap != null && ap.getSelectedArticle() != null) {
@@ -538,6 +781,30 @@ public class OnlineActivity extends CommonActivity {
protected void loginFailure() {
m_sessionId = 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();
+ }
}
public boolean getUnreadArticlesOnly() {
@@ -556,12 +823,20 @@ public class OnlineActivity extends CommonActivity {
out.putInt("apiLevel", m_apiLevel);
out.putBoolean("unreadOnly", m_unreadOnly);
out.putBoolean("unreadArticlesOnly", m_unreadArticlesOnly);
+ out.putInt("offlineModeStatus", m_offlineModeStatus);
}
@Override
public void onResume() {
super.onResume();
+ 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 (m_sessionId == null) {
login();
} else {
diff --git a/src/org/fox/ttrss/offline/OfflineActivity.java b/src/org/fox/ttrss/offline/OfflineActivity.java
new file mode 100644
index 00000000..7fb8c1a9
--- /dev/null
+++ b/src/org/fox/ttrss/offline/OfflineActivity.java
@@ -0,0 +1,192 @@
+package org.fox.ttrss.offline;
+
+import org.fox.ttrss.CommonActivity;
+import org.fox.ttrss.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.BaseColumns;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+public class OfflineActivity extends CommonActivity {
+ private final String TAG = this.getClass().getSimpleName();
+
+ protected SharedPreferences m_prefs;
+ protected Menu m_menu;
+ protected boolean m_unreadOnly;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ m_prefs = PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+
+ if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
+ setTheme(R.style.DarkTheme);
+ } else {
+ setTheme(R.style.LightTheme);
+ }
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.online);
+
+ 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_unreadOnly = savedInstanceState.getBoolean("unreadOnly");
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle out) {
+ super.onSaveInstanceState(out);
+
+ out.putBoolean("unreadOnly", m_unreadOnly);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.go_online:
+ switchOnline();
+ 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;
+ }
+
+ public boolean getUnreadOnly() {
+ return m_unreadOnly;
+ }
+
+ protected void initMenu() {
+ if (m_menu != null) {
+ m_menu.setGroupVisible(R.id.menu_group_headlines, false);
+ m_menu.setGroupVisible(R.id.menu_group_headlines_selection, false);
+ m_menu.setGroupVisible(R.id.menu_group_article, false);
+ m_menu.setGroupVisible(R.id.menu_group_feeds, 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;
+ }
+
+ 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) {
+ 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;
+ }
+
+ 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.id.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;
+ }
+
+}
diff --git a/src/org/fox/ttrss/offline/OfflineArticleEventListener.java b/src/org/fox/ttrss/offline/OfflineArticleEventListener.java
new file mode 100644
index 00000000..35b9de28
--- /dev/null
+++ b/src/org/fox/ttrss/offline/OfflineArticleEventListener.java
@@ -0,0 +1,5 @@
+package org.fox.ttrss.offline;
+
+public interface OfflineArticleEventListener {
+
+}
diff --git a/src/org/fox/ttrss/offline/OfflineArticleFragment.java b/src/org/fox/ttrss/offline/OfflineArticleFragment.java
new file mode 100644
index 00000000..266c89b2
--- /dev/null
+++ b/src/org/fox/ttrss/offline/OfflineArticleFragment.java
@@ -0,0 +1,269 @@
+package org.fox.ttrss.offline;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.fox.ttrss.OnlineActivity;
+import org.fox.ttrss.R;
+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.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.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebSettings.LayoutAlgorithm;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class OfflineArticleFragment extends Fragment {
+ @SuppressWarnings("unused")
+ private final String TAG = this.getClass().getSimpleName();
+
+ private SharedPreferences m_prefs;
+ private int m_articleId;
+ private boolean m_isCat = false; // FIXME use
+ private Cursor m_cursor;
+ private OfflineActivity m_activity;
+
+ public OfflineArticleFragment() {
+ super();
+ }
+
+ public OfflineArticleFragment(int articleId) {
+ super();
+ 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")));
+
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ if (savedInstanceState != null) {
+ m_articleId = savedInstanceState.getInt("articleId");
+ }
+
+ View view = inflater.inflate(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()) {
+
+ TextView title = (TextView)view.findViewById(R.id.title);
+
+ if (title != null) {
+
+ 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.setMovementMethod(LinkMovementMethod.getInstance());
+ title.setText(Html.fromHtml("" + titleStr + ""));
+ registerForContextMenu(title);
+ }
+
+ WebView web = (WebView)view.findViewById(R.id.content);
+
+ if (web != null) {
+
+ String content;
+ String cssOverride = "";
+
+ WebSettings ws = web.getSettings();
+ ws.setSupportZoom(true);
+ ws.setBuiltInZoomControls(true);
+
+ web.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);
+
+ TypedValue tv = new TypedValue();
+ getActivity().getTheme().resolveAttribute(R.attr.linkColor, tv, true);
+
+ // prevent flicker in ics
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ web.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) {
+ cssOverride = "body { background : transparent; color : #e0e0e0}";
+ //view.setBackgroundColor(android.R.color.black);
+ web.setBackgroundColor(getResources().getColor(android.R.color.transparent));
+ } else {
+ cssOverride = "";
+ }
+
+ String hexColor = String.format("#%06X", (0xFFFFFF & tv.data));
+ cssOverride += " a:link {color: "+hexColor+";} a:visited { color: "+hexColor+";}";
+
+ 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(url)) {
+ img.attr("src", "file://" + ImageCacheService.getCacheFileName(url));
+ }
+ }
+ }
+
+ // thanks webview for crashing on