implement synchronization of offline stuff back to the mothership
This commit is contained in:
parent
797860e517
commit
19cb77aa35
@ -86,6 +86,7 @@
|
||||
<string name="error_other_error">Error: unknown error (see log)</string>
|
||||
<string name="error_api_disabled">Error: API disabled for this user</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>
|
||||
@ -96,4 +97,8 @@
|
||||
<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_go">Go offline</string>
|
||||
<string name="dialog_cancel">Cancel</string>
|
||||
<string name="syncing_offline_data">Synchronizing offline data...</string>
|
||||
</resources>
|
@ -37,7 +37,7 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND,
|
||||
HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED, API_UNKNOWN, LOGIN_FAILED, INVALID_URL };
|
||||
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<HashMap<String,String>, 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<HashMap<String,String>, 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;
|
||||
|
@ -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<Feed>)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();
|
||||
|
@ -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;
|
||||
|
@ -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<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 {
|
||||
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<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
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<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
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<String,String> map = new HashMap<String,String>() {
|
||||
{
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user