diff options
-rw-r--r-- | AndroidManifest.xml | 6 | ||||
-rw-r--r-- | res/layout-land/fragment_comics_list.xml | 2 | ||||
-rw-r--r-- | res/layout-sw600dp/activity_main.xml | 2 | ||||
-rw-r--r-- | res/layout-sw600dp/activity_view_comic.xml | 2 | ||||
-rw-r--r-- | res/layout-sw600dp/fragment_comics_list.xml | 2 | ||||
-rw-r--r-- | res/layout/chooser_list.xml | 2 | ||||
-rw-r--r-- | res/layout/fragment_comics_list.xml | 2 | ||||
-rw-r--r-- | res/menu/activity_view_comic.xml | 4 | ||||
-rw-r--r-- | res/values/strings.xml | 8 | ||||
-rw-r--r-- | res/xml/preferences.xml | 8 | ||||
-rw-r--r-- | src/org/fox/ttcomics/CommonActivity.java | 47 | ||||
-rw-r--r-- | src/org/fox/ttcomics/MainActivity.java | 21 | ||||
-rw-r--r-- | src/org/fox/ttcomics/SyncClient.java | 123 | ||||
-rw-r--r-- | src/org/fox/ttcomics/ViewComicActivity.java | 40 |
14 files changed, 255 insertions, 14 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d0c4cff..127ab33 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -7,8 +7,10 @@ android:minSdkVersion="8"
android:targetSdkVersion="15" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
diff --git a/res/layout-land/fragment_comics_list.xml b/res/layout-land/fragment_comics_list.xml index fcacb20..e4999c8 100644 --- a/res/layout-land/fragment_comics_list.xml +++ b/res/layout-land/fragment_comics_list.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:text="No comic arhives found." />
+ android:text="@string/error_no_comic_arhives_found_" />
</LinearLayout>
diff --git a/res/layout-sw600dp/activity_main.xml b/res/layout-sw600dp/activity_main.xml index 3799552..3e0dc88 100644 --- a/res/layout-sw600dp/activity_main.xml +++ b/res/layout-sw600dp/activity_main.xml @@ -16,6 +16,6 @@ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:text="TextView" />
+ android:text="" />
</FrameLayout>
\ No newline at end of file diff --git a/res/layout-sw600dp/activity_view_comic.xml b/res/layout-sw600dp/activity_view_comic.xml index 28dc950..b32c9d7 100644 --- a/res/layout-sw600dp/activity_view_comic.xml +++ b/res/layout-sw600dp/activity_view_comic.xml @@ -16,6 +16,6 @@ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:text="TextView" />
+ android:text="" />
</FrameLayout>
\ No newline at end of file diff --git a/res/layout-sw600dp/fragment_comics_list.xml b/res/layout-sw600dp/fragment_comics_list.xml index fcacb20..e4999c8 100644 --- a/res/layout-sw600dp/fragment_comics_list.xml +++ b/res/layout-sw600dp/fragment_comics_list.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:text="No comic arhives found." />
+ android:text="@string/error_no_comic_arhives_found_" />
</LinearLayout>
diff --git a/res/layout/chooser_list.xml b/res/layout/chooser_list.xml index 53d28cd..b66982a 100644 --- a/res/layout/chooser_list.xml +++ b/res/layout/chooser_list.xml @@ -8,7 +8,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"/> - <Button android:text="Choose" + <Button android:text="@string/choose" android:id="@+id/btnChoose" android:layout_width="fill_parent" android:layout_height="wrap_content"/> diff --git a/res/layout/fragment_comics_list.xml b/res/layout/fragment_comics_list.xml index dd358ab..ee744a8 100644 --- a/res/layout/fragment_comics_list.xml +++ b/res/layout/fragment_comics_list.xml @@ -26,7 +26,7 @@ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:text="No comic arhives found." />
+ android:text="@string/error_no_comic_arhives_found_" />
</LinearLayout>
diff --git a/res/menu/activity_view_comic.xml b/res/menu/activity_view_comic.xml index 07bc560..898dc01 100644 --- a/res/menu/activity_view_comic.xml +++ b/res/menu/activity_view_comic.xml @@ -8,6 +8,10 @@ android:title="@string/menu_go_location" android:showAsAction="never" /> + <item android:id="@+id/menu_sync_location" + android:title="@string/menu_sync_location" + android:showAsAction="never" /> + <item android:id="@+id/menu_share" android:title="@string/menu_share" android:icon="@android:drawable/ic_menu_share" diff --git a/res/values/strings.xml b/res/values/strings.xml index adadd3a..cbb3db9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -35,4 +35,12 @@ <string name="error_could_not_read_folder_contents_">Could not read folder contents.</string>
<string name="picker_choose">Choose %1$s</string>
<string name="dialog_location_page_number">%1$d of %2$d</string>
+ <string name="sync_server_has_further_page">You are currently on page %1$d. Furthest read page stored on the server is %2$d. Open it instead?</string>
+ <string name="dialog_open_page">Open page</string>
+ <string name="menu_sync_location">Sync to last page read</string>
+ <string name="share_comic">Share comic</string>
+ <string name="choose">Choose</string>
+ <string name="error_no_comic_arhives_found_">No comic arhives found.</string>
+ <string name="prefs_use_position_sync">Sync last read page</string>
+ <string name="prefs_use_position_sync_summary">Requires at least one Google account on the device. No personally identifiable information is sent.</string>
</resources>
\ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 5ccf40b..b91609c 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -7,6 +7,14 @@ android:hint="@string/comics_directory_default"
android:title="@string/prefs_comics_directory" >
</Preference>
+
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="use_position_sync"
+ android:title="@string/prefs_use_position_sync"
+ android:summary="@string/prefs_use_position_sync_summary"
+ />
+
</PreferenceCategory>
<PreferenceCategory android:title="@string/prefs_reading" android:key="prefs_reading">
diff --git a/src/org/fox/ttcomics/CommonActivity.java b/src/org/fox/ttcomics/CommonActivity.java index 0a368b5..f4aa04d 100644 --- a/src/org/fox/ttcomics/CommonActivity.java +++ b/src/org/fox/ttcomics/CommonActivity.java @@ -5,6 +5,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException;
import java.util.Date;
+import android.accounts.Account;
+import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -25,7 +27,11 @@ public class CommonActivity extends FragmentActivity { protected static final String FRAG_COMICS_PAGER = "comic_pager";
protected static final String FRAG_COMICS_LIST = "comics_list";
+ protected final static int REQUEST_SHARE = 1;
+ protected static final int REQUEST_VIEWCOMIC = 2;
+
protected SharedPreferences m_prefs;
+ protected SyncClient m_syncClient = new SyncClient();
private boolean m_smallScreenMode = true;
@@ -34,7 +40,15 @@ public class CommonActivity extends FragmentActivity { super.onCreate(savedInstanceState);
m_prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-
+
+ String googleAccount = getGoogleAccount();
+
+ if (googleAccount != null) {
+ m_syncClient.setOwner(googleAccount);
+ } else {
+ //toast("No Google account found, sync disabled.");
+ m_syncClient.setOwner("TEST-ACCOUNT");
+ }
}
public static boolean isCompatMode() {
@@ -159,6 +173,25 @@ public class CommonActivity extends FragmentActivity { return null;
}
+ protected static String sha1(String s) {
+ try {
+ MessageDigest digest = java.security.MessageDigest.getInstance("SHA1");
+ 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;
+ }
+
public String getCacheFileName(String fileName) {
String hash = md5(fileName);
@@ -167,6 +200,18 @@ public class CommonActivity extends FragmentActivity { return file.getAbsolutePath();
}
+ public String getGoogleAccount() {
+ AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
+ Account[] list = manager.getAccounts();
+
+ for (Account account: list) {
+ if (account.type.equalsIgnoreCase("com.google")) {
+ return account.name;
+ }
+ }
+ return null;
+ }
+
public void toast(int msgId) {
Toast toast = Toast.makeText(CommonActivity.this, msgId, Toast.LENGTH_SHORT);
toast.show();
diff --git a/src/org/fox/ttcomics/MainActivity.java b/src/org/fox/ttcomics/MainActivity.java index f34ccb5..4252955 100644 --- a/src/org/fox/ttcomics/MainActivity.java +++ b/src/org/fox/ttcomics/MainActivity.java @@ -24,6 +24,7 @@ public class MainActivity extends CommonActivity { private TabListener m_tabListener; private int m_selectedTab; private String m_baseDirectory = ""; + private String m_fileName = ""; @SuppressLint("NewApi") private class TabListener implements ActionBar.TabListener { @@ -88,6 +89,7 @@ public class MainActivity extends CommonActivity { } else { m_selectedTab = -1; m_baseDirectory = savedInstanceState.getString("baseDir"); + m_fileName = savedInstanceState.getString("fileName"); } if (!isCompatMode()) { @@ -163,6 +165,7 @@ public class MainActivity extends CommonActivity { out.putInt("selectedTab", m_selectedTab); out.putString("baseDir", m_baseDirectory); + out.putString("fileName", m_fileName); } public boolean onOptionsItemSelected(MenuItem item) { @@ -201,10 +204,24 @@ public class MainActivity extends CommonActivity { ViewComicActivity.class); intent.putExtra("fileName", fileName); - - startActivityForResult(intent, 0); + m_fileName = fileName; + + startActivityForResult(intent, REQUEST_VIEWCOMIC); } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_VIEWCOMIC) { + //Log.d(TAG, "finished viewing comic: " + m_fileName); + + if (m_prefs.getBoolean("use_position_sync", false)) { + toast("Uploading sync data..."); + m_syncClient.setPosition(sha1(new File(m_fileName).getName()), getLastPosition(m_fileName)); + } + } + super.onActivityResult(requestCode, resultCode, intent); + } + } diff --git a/src/org/fox/ttcomics/SyncClient.java b/src/org/fox/ttcomics/SyncClient.java new file mode 100644 index 0000000..377a3fc --- /dev/null +++ b/src/org/fox/ttcomics/SyncClient.java @@ -0,0 +1,123 @@ +package org.fox.ttcomics;
+
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import android.os.AsyncTask;
+import android.util.Log;
+
+public class SyncClient {
+ public interface PositionReceivedListener {
+ void onPositionReceived(int position);
+ }
+
+ private class HttpTask extends AsyncTask<String, Integer, Boolean> {
+ protected String m_response = null;
+ protected int m_responseCode = -1;
+
+ @Override
+ protected Boolean doInBackground(String... params) {
+
+ String requestStr = "set".equals(params[0]) ? String.format("op=set&owner=%1$s&hash=%2$s&position=%3$s", m_owner, params[1], params[2]) :
+ String.format("op=get&owner=%1$s&hash=%2$s", m_owner, params[1]);
+
+ try {
+ byte[] postData = requestStr.getBytes("UTF-8");
+
+ URL url = new URL(SYNC_ENDPOINT);
+
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ conn.setRequestMethod("POST");
+
+ OutputStream out = conn.getOutputStream();
+ out.write(postData);
+ out.close();
+
+ m_responseCode = conn.getResponseCode();
+
+ if (m_responseCode == HttpURLConnection.HTTP_OK) {
+ StringBuffer response = new StringBuffer();
+ InputStreamReader in = new InputStreamReader(conn.getInputStream(), "UTF-8");
+
+ char[] buf = new char[1024];
+ int read = 0;
+
+ while ((read = in.read(buf)) >= 0) {
+ response.append(buf, 0, read);
+ }
+
+ //Log.d(TAG, "<<< " + response);
+
+ m_response = response.toString();
+
+ if (response.indexOf("ERROR") == -1) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ Log.d(TAG, "HTTP error, code: " + m_responseCode);
+ }
+
+ conn.disconnect();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ }
+
+ private final String TAG = this.getClass().getSimpleName();
+ private static final String SYNC_ENDPOINT = "http://tt-rss.org/tcrsync/";
+ private String m_owner = null;
+
+ public void setOwner(String owner) {
+ m_owner = CommonActivity.sha1(owner);
+ }
+
+ public int getPosition(String hash, final PositionReceivedListener listener) {
+ if (m_owner != null) {
+ Log.d(TAG, "Requesting sync data...");
+
+ HttpTask task = new HttpTask() {
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ try {
+ listener.onPositionReceived(Integer.valueOf(m_response));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ task.execute("get", hash);
+
+ }
+ return -1;
+ }
+
+ public void setPosition(String hash, int position) {
+ if (m_owner != null) {
+ Log.d(TAG, "Uploading sync data...");
+
+ HttpTask task = new HttpTask();
+
+ task.execute("set", hash, String.valueOf(position));
+ }
+ }
+
+ public boolean hasOwner() {
+ return m_owner != null;
+ }
+
+}
diff --git a/src/org/fox/ttcomics/ViewComicActivity.java b/src/org/fox/ttcomics/ViewComicActivity.java index 1bd47b5..8693048 100644 --- a/src/org/fox/ttcomics/ViewComicActivity.java +++ b/src/org/fox/ttcomics/ViewComicActivity.java @@ -28,8 +28,6 @@ import android.widget.TextView; public class ViewComicActivity extends CommonActivity {
private final String TAG = this.getClass().getSimpleName();
-
- private final static int REQUEST_SHARE = 1;
private String m_fileName;
private String m_tmpFileName;
@@ -66,11 +64,15 @@ public class ViewComicActivity extends CommonActivity { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
+
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_view_comic, menu);
+
+ menu.findItem(R.id.menu_sync_location).setVisible(m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner());
+
return true;
}
@@ -118,7 +120,7 @@ public class ViewComicActivity extends CommonActivity { m_tmpFileName = tmpFile.getAbsolutePath();
- startActivityForResult(Intent.createChooser(shareIntent, "Share comic"), REQUEST_SHARE);
+ startActivityForResult(Intent.createChooser(shareIntent, getString(R.string.share_comic)), REQUEST_SHARE);
} catch (IOException e) {
toast(getString(R.string.error_could_not_prepare_file_for_sharing));
@@ -146,6 +148,38 @@ public class ViewComicActivity extends CommonActivity { case R.id.menu_share:
shareComic();
return true;
+ case R.id.menu_sync_location:
+ m_syncClient.getPosition(sha1(new File(m_fileName).getName()), new SyncClient.PositionReceivedListener() {
+ @Override
+ public void onPositionReceived(final int position) {
+ final ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER);
+
+ if (pager != null && pager.isAdded()) {
+ int localPosition = pager.getPosition();
+
+ if (position > localPosition) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ViewComicActivity.this);
+ builder.setMessage(getString(R.string.sync_server_has_further_page, localPosition+1, position+1))
+ .setCancelable(false)
+ .setPositiveButton(R.string.dialog_open_page, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ pager.setCurrentItem(position);
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+
+ }
+
+ }
+ }
+ });
+ return true;
case R.id.menu_go_location:
Dialog dialog = new Dialog(ViewComicActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(ViewComicActivity.this)
|