summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml6
-rw-r--r--res/layout-land/fragment_comics_list.xml2
-rw-r--r--res/layout-sw600dp/activity_main.xml2
-rw-r--r--res/layout-sw600dp/activity_view_comic.xml2
-rw-r--r--res/layout-sw600dp/fragment_comics_list.xml2
-rw-r--r--res/layout/chooser_list.xml2
-rw-r--r--res/layout/fragment_comics_list.xml2
-rw-r--r--res/menu/activity_view_comic.xml4
-rw-r--r--res/values/strings.xml8
-rw-r--r--res/xml/preferences.xml8
-rw-r--r--src/org/fox/ttcomics/CommonActivity.java47
-rw-r--r--src/org/fox/ttcomics/MainActivity.java21
-rw-r--r--src/org/fox/ttcomics/SyncClient.java123
-rw-r--r--src/org/fox/ttcomics/ViewComicActivity.java40
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)