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_other_error">Error: unknown error (see log)</string>
|
||||||
<string name="error_api_disabled">Error: API disabled for this user</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_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_login_failed">Error: username or password incorrect</string>
|
||||||
<string name="error_invalid_api_url">Error: invalid API URL</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_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="offline_switch_error">Failed to prepare offline mode (see log)</string>
|
||||||
<string name="no_feeds">No feeds to display</string>
|
<string name="no_feeds">No feeds to display</string>
|
||||||
<string name="no_headlines">No articles 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>
|
</resources>
|
@ -37,7 +37,7 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
|||||||
private final String TAG = this.getClass().getSimpleName();
|
private final String TAG = this.getClass().getSimpleName();
|
||||||
|
|
||||||
public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND,
|
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_OK = 0;
|
||||||
public static final int API_STATUS_ERR = 1;
|
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;
|
return R.string.error_login_failed;
|
||||||
case INVALID_URL:
|
case INVALID_URL:
|
||||||
return R.string.error_invalid_api_url;
|
return R.string.error_invalid_api_url;
|
||||||
|
case INCORRECT_USAGE:
|
||||||
|
return R.string.error_api_incorrect_usage;
|
||||||
default:
|
default:
|
||||||
Log.d(TAG, "getErrorMessage: unknown error code=" + m_lastError);
|
Log.d(TAG, "getErrorMessage: unknown error code=" + m_lastError);
|
||||||
return R.string.error_unknown;
|
return R.string.error_unknown;
|
||||||
@ -202,6 +204,8 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
|
|||||||
m_lastError = ApiError.LOGIN_FAILED;
|
m_lastError = ApiError.LOGIN_FAILED;
|
||||||
} else if (error.equals("NOT_LOGGED_IN")) {
|
} else if (error.equals("NOT_LOGGED_IN")) {
|
||||||
m_lastError = ApiError.LOGIN_FAILED;
|
m_lastError = ApiError.LOGIN_FAILED;
|
||||||
|
} else if (error.equals("INCORRECT_USAGE")) {
|
||||||
|
m_lastError = ApiError.INCORRECT_USAGE;
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Unknown API error: " + error);
|
Log.d(TAG, "Unknown API error: " + error);
|
||||||
m_lastError = ApiError.API_UNKNOWN;
|
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);
|
ListView list = (ListView)view.findViewById(R.id.feeds);
|
||||||
m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, (ArrayList<Feed>)m_feeds);
|
m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, (ArrayList<Feed>)m_feeds);
|
||||||
list.setAdapter(m_adapter);
|
list.setAdapter(m_adapter);
|
||||||
list.setEmptyView(view.findViewById(R.id.no_feeds));
|
//list.setEmptyView(view.findViewById(R.id.no_feeds));
|
||||||
list.setOnItemClickListener(this);
|
list.setOnItemClickListener(this);
|
||||||
|
|
||||||
registerForContextMenu(list);
|
registerForContextMenu(list);
|
||||||
@ -308,9 +308,9 @@ public class FeedsFragment extends Fragment implements OnItemClickListener, OnSh
|
|||||||
|
|
||||||
sortFeeds();
|
sortFeeds();
|
||||||
|
|
||||||
//if (m_feeds.size() == 0)
|
if (m_feeds.size() == 0)
|
||||||
// setLoadingStatus(R.string.no_feeds_to_display, false);
|
setLoadingStatus(R.string.no_feeds_to_display, false);
|
||||||
//else
|
else
|
||||||
setLoadingStatus(R.string.blank, false);
|
setLoadingStatus(R.string.blank, false);
|
||||||
|
|
||||||
if (m_enableFeedIcons && !m_feedIconsChecked) getFeedIcons();
|
if (m_enableFeedIcons && !m_feedIconsChecked) getFeedIcons();
|
||||||
|
@ -127,7 +127,7 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
|||||||
list.setAdapter(m_adapter);
|
list.setAdapter(m_adapter);
|
||||||
list.setOnItemClickListener(this);
|
list.setOnItemClickListener(this);
|
||||||
list.setOnScrollListener(this);
|
list.setOnScrollListener(this);
|
||||||
list.setEmptyView(view.findViewById(R.id.no_headlines));
|
//list.setEmptyView(view.findViewById(R.id.no_headlines));
|
||||||
registerForContextMenu(list);
|
registerForContextMenu(list);
|
||||||
|
|
||||||
Log.d(TAG, "onCreateView, feed=" + m_feed);
|
Log.d(TAG, "onCreateView, feed=" + m_feed);
|
||||||
@ -279,9 +279,9 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
|
|||||||
|
|
||||||
m_adapter.notifyDataSetChanged();
|
m_adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
//if (m_articles.size() == 0)
|
if (m_articles.size() == 0)
|
||||||
// setLoadingStatus(R.string.no_headlines_to_display, false);
|
setLoadingStatus(R.string.no_headlines_to_display, false);
|
||||||
//else
|
else
|
||||||
setLoadingStatus(R.string.blank, false);
|
setLoadingStatus(R.string.blank, false);
|
||||||
|
|
||||||
m_refreshInProgress = false;
|
m_refreshInProgress = false;
|
||||||
|
@ -13,6 +13,7 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteStatement;
|
import android.database.sqlite.SQLiteStatement;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
@ -79,6 +80,36 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
return m_apiLevel;
|
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" })
|
@SuppressWarnings({ "unchecked", "serial" })
|
||||||
public void saveArticleUnread(final Article article) {
|
public void saveArticleUnread(final Article article) {
|
||||||
ApiRequest req = new ApiRequest(getApplicationContext());
|
ApiRequest req = new ApiRequest(getApplicationContext());
|
||||||
@ -1032,6 +1063,135 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
private void loginSuccess() {
|
||||||
findViewById(R.id.loading_container).setVisibility(View.INVISIBLE);
|
findViewById(R.id.loading_container).setVisibility(View.INVISIBLE);
|
||||||
findViewById(R.id.main).setVisibility(View.VISIBLE);
|
findViewById(R.id.main).setVisibility(View.VISIBLE);
|
||||||
@ -1056,6 +1216,42 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
m_refreshTimer.schedule(m_refreshTask, 60*1000L, 120*1000L);
|
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 {
|
private class LoginRequest extends ApiRequest {
|
||||||
public LoginRequest(Context context) {
|
public LoginRequest(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -1081,20 +1277,14 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
|
|
||||||
Log.d(TAG, "Received API level: " + m_apiLevel);
|
Log.d(TAG, "Received API level: " + m_apiLevel);
|
||||||
|
|
||||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
if (hasPendingOfflineData()) {
|
||||||
|
|
||||||
if (m_enableCats) {
|
syncOfflineData();
|
||||||
FeedCategoriesFragment frag = new FeedCategoriesFragment();
|
|
||||||
ft.replace(R.id.cats_fragment, frag);
|
//loginSuccess();
|
||||||
} else {
|
} else {
|
||||||
FeedsFragment frag = new FeedsFragment();
|
loginSuccessInitUI();
|
||||||
ft.replace(R.id.feeds_fragment, frag);
|
loginSuccess();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ft.commit();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1112,7 +1302,6 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
|
|
||||||
setLoadingStatus(R.string.loading_message, true);
|
setLoadingStatus(R.string.loading_message, true);
|
||||||
|
|
||||||
loginSuccess();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1124,6 +1313,26 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
m_sessionId = null;
|
m_sessionId = null;
|
||||||
|
|
||||||
setLoadingStatus(getErrorMessage(), false);
|
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);
|
//m_menu.findItem(R.id.login).setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,7 +1487,7 @@ public class MainActivity extends FragmentActivity implements FeedsFragment.OnFe
|
|||||||
|
|
||||||
} else {
|
} 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>() {
|
HashMap<String,String> map = new HashMap<String,String>() {
|
||||||
{
|
{
|
||||||
|
@ -249,9 +249,11 @@ public class OfflineFeedsFragment extends Fragment implements OnItemClickListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sortFeeds() {
|
public void sortFeeds() {
|
||||||
// TODO implement
|
try {
|
||||||
|
refresh();
|
||||||
m_adapter.notifyDataSetInvalidated();
|
} catch (IllegalStateException e) {
|
||||||
|
// we're probably closing and DB is gone already
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user