diff --git a/res/values/strings.xml b/res/values/strings.xml index c2637c9d..3a508cc5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -86,6 +86,7 @@ Error: unknown error (see log) Error: API disabled for this user Error: unknown API error (see log) + Error: incorrect API usage Error: username or password incorrect Error: invalid API URL Displays full article text inline, instead of a separate panel @@ -96,4 +97,8 @@ Failed to prepare offline mode (see log) No feeds to display No articles to display + Login failed, but you have stored offline data. Would you like to go offline? + Go offline + Cancel + Synchronizing offline data... \ No newline at end of file diff --git a/src/org/fox/ttrss/ApiRequest.java b/src/org/fox/ttrss/ApiRequest.java index 56a7a498..d1b832c8 100644 --- a/src/org/fox/ttrss/ApiRequest.java +++ b/src/org/fox/ttrss/ApiRequest.java @@ -37,7 +37,7 @@ public class ApiRequest extends AsyncTask, Integer, JsonE private final String TAG = this.getClass().getSimpleName(); public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, - HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, API_UNKNOWN, LOGIN_FAILED, INVALID_URL }; + HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, API_UNKNOWN, LOGIN_FAILED, INVALID_URL, INCORRECT_USAGE }; public static final int API_STATUS_OK = 0; public static final int API_STATUS_ERR = 1; @@ -94,6 +94,8 @@ public class ApiRequest extends AsyncTask, Integer, JsonE return R.string.error_login_failed; case INVALID_URL: return R.string.error_invalid_api_url; + case INCORRECT_USAGE: + return R.string.error_api_incorrect_usage; default: Log.d(TAG, "getErrorMessage: unknown error code=" + m_lastError); return R.string.error_unknown; @@ -202,6 +204,8 @@ public class ApiRequest extends AsyncTask, Integer, JsonE m_lastError = ApiError.LOGIN_FAILED; } else if (error.equals("NOT_LOGGED_IN")) { m_lastError = ApiError.LOGIN_FAILED; + } else if (error.equals("INCORRECT_USAGE")) { + m_lastError = ApiError.INCORRECT_USAGE; } else { Log.d(TAG, "Unknown API error: " + error); m_lastError = ApiError.API_UNKNOWN; diff --git a/src/org/fox/ttrss/FeedsFragment.java b/src/org/fox/ttrss/FeedsFragment.java index 47d66198..fa160d93 100644 --- a/src/org/fox/ttrss/FeedsFragment.java +++ b/src/org/fox/ttrss/FeedsFragment.java @@ -127,7 +127,7 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh ListView list = (ListView)view.findViewById(R.id.feeds); m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, (ArrayList)m_feeds); list.setAdapter(m_adapter); - list.setEmptyView(view.findViewById(R.id.no_feeds)); + //list.setEmptyView(view.findViewById(R.id.no_feeds)); list.setOnItemClickListener(this); registerForContextMenu(list); @@ -308,9 +308,9 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh sortFeeds(); - //if (m_feeds.size() == 0) - // setLoadingStatus(R.string.no_feeds_to_display, false); - //else + if (m_feeds.size() == 0) + setLoadingStatus(R.string.no_feeds_to_display, false); + else setLoadingStatus(R.string.blank, false); if (m_enableFeedIcons && !m_feedIconsChecked) getFeedIcons(); diff --git a/src/org/fox/ttrss/HeadlinesFragment.java b/src/org/fox/ttrss/HeadlinesFragment.java index cf437258..ac4797b2 100644 --- a/src/org/fox/ttrss/HeadlinesFragment.java +++ b/src/org/fox/ttrss/HeadlinesFragment.java @@ -127,7 +127,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, list.setAdapter(m_adapter); list.setOnItemClickListener(this); list.setOnScrollListener(this); - list.setEmptyView(view.findViewById(R.id.no_headlines)); + //list.setEmptyView(view.findViewById(R.id.no_headlines)); registerForContextMenu(list); Log.d(TAG, "onCreateView, feed=" + m_feed); @@ -279,9 +279,9 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener, m_adapter.notifyDataSetChanged(); - //if (m_articles.size() == 0) - // setLoadingStatus(R.string.no_headlines_to_display, false); - //else + if (m_articles.size() == 0) + setLoadingStatus(R.string.no_headlines_to_display, false); + else setLoadingStatus(R.string.blank, false); m_refreshInProgress = false; diff --git a/src/org/fox/ttrss/MainActivity.java b/src/org/fox/ttrss/MainActivity.java index 455e34bc..4111b03f 100644 --- a/src/org/fox/ttrss/MainActivity.java +++ b/src/org/fox/ttrss/MainActivity.java @@ -13,6 +13,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import android.net.ConnectivityManager; @@ -79,6 +80,36 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe return m_apiLevel; } + public boolean hasPendingOfflineData() { + 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; + } + + return false; + } + + public void clearPendingOfflineData() { + getWritableDb().execSQL("UPDATE articles SET modified = 0"); + } + + public boolean hasOfflineData() { + 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; + } + + return false; + } + @SuppressWarnings({ "unchecked", "serial" }) public void saveArticleUnread(final Article article) { ApiRequest req = new ApiRequest(getApplicationContext()); @@ -1031,6 +1062,135 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe m_writableDb.close(); } + + private void syncOfflineRead() { + Log.d(TAG, "syncing modified offline data... (read)"); + + final String ids = getOfflineModifiedIds(ModifiedCriteria.READ); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + syncOfflineMarked(); + } else { + setLoadingStatus(getErrorMessage(), false); + } + } + }; + + @SuppressWarnings("serial") + HashMap map = new HashMap() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "0"); + put("field", "2"); + } + }; + + req.execute(map); + } else { + syncOfflineMarked(); + } + } + + private void syncOfflineMarked() { + Log.d(TAG, "syncing modified offline data... (marked)"); + + final String ids = getOfflineModifiedIds(ModifiedCriteria.MARKED); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + syncOfflinePublished(); + } else { + setLoadingStatus(getErrorMessage(), false); + } + } + }; + + @SuppressWarnings("serial") + HashMap map = new HashMap() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "0"); + put("field", "0"); + } + }; + + req.execute(map); + } else { + syncOfflinePublished(); + } + } + + private void syncOfflinePublished() { + Log.d(TAG, "syncing modified offline data... (published)"); + + final String ids = getOfflineModifiedIds(ModifiedCriteria.MARKED); + + if (ids.length() > 0) { + ApiRequest req = new ApiRequest(getApplicationContext()) { + @Override + protected void onPostExecute(JsonElement result) { + if (result != null) { + loginSuccessInitUI(); + loginSuccess(); + clearPendingOfflineData(); + } else { + setLoadingStatus(getErrorMessage(), false); + } + } + }; + + @SuppressWarnings("serial") + HashMap map = new HashMap() { + { + put("sid", m_sessionId); + put("op", "updateArticle"); + put("article_ids", ids); + put("mode", "0"); + put("field", "1"); + } + }; + + req.execute(map); + } else { + loginSuccessInitUI(); + loginSuccess(); + clearPendingOfflineData(); + } + } + + private void syncOfflineData() { + setLoadingStatus(R.string.syncing_offline_data, true); + syncOfflineRead(); + } + + private void loginSuccessInitUI() { + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + + if (m_enableCats) { + FeedCategoriesFragment frag = new FeedCategoriesFragment(); + ft.replace(R.id.cats_fragment, frag); + } else { + FeedsFragment frag = new FeedsFragment(); + ft.replace(R.id.feeds_fragment, frag); + } + + try { + ft.commit(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } private void loginSuccess() { findViewById(R.id.loading_container).setVisibility(View.INVISIBLE); @@ -1056,6 +1216,42 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe m_refreshTimer.schedule(m_refreshTask, 60*1000L, 120*1000L); } + private enum ModifiedCriteria { READ, MARKED, PUBLISHED }; + + private String getOfflineModifiedIds(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(",$", ""); + + //Log.d(TAG, "getOfflineModifiedIds " + criteria + " = " + tmp); + + c.close(); + + return tmp; + } + private class LoginRequest extends ApiRequest { public LoginRequest(Context context) { super(context); @@ -1081,20 +1277,14 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe Log.d(TAG, "Received API level: " + m_apiLevel); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - - if (m_enableCats) { - FeedCategoriesFragment frag = new FeedCategoriesFragment(); - ft.replace(R.id.cats_fragment, frag); - } else { - FeedsFragment frag = new FeedsFragment(); - ft.replace(R.id.feeds_fragment, frag); - } + if (hasPendingOfflineData()) { - try { - ft.commit(); - } catch (IllegalStateException e) { - e.printStackTrace(); + syncOfflineData(); + + //loginSuccess(); + } else { + loginSuccessInitUI(); + loginSuccess(); } } @@ -1112,7 +1302,6 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe setLoadingStatus(R.string.loading_message, true); - loginSuccess(); return; } @@ -1124,6 +1313,26 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe m_sessionId = null; setLoadingStatus(getErrorMessage(), false); + + if (hasOfflineData()) { + + AlertDialog.Builder builder = new AlertDialog.Builder(m_context). + 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(); + } + //m_menu.findItem(R.id.login).setVisible(true); } @@ -1278,7 +1487,7 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe } else { - LoginRequest ar = new LoginRequest(getApplicationContext()); + LoginRequest ar = new LoginRequest(this); // do not use getApplicationContext() here because alertdialog chokes on it HashMap map = new HashMap() { { diff --git a/src/org/fox/ttrss/OfflineFeedsFragment.java b/src/org/fox/ttrss/OfflineFeedsFragment.java index 369ca5db..b76dd4d4 100644 --- a/src/org/fox/ttrss/OfflineFeedsFragment.java +++ b/src/org/fox/ttrss/OfflineFeedsFragment.java @@ -249,9 +249,11 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene } public void sortFeeds() { - // TODO implement - - m_adapter.notifyDataSetInvalidated(); + try { + refresh(); + } catch (IllegalStateException e) { + // we're probably closing and DB is gone already + } } @Override