From d406d43ab36e2ab3e38ce99a96026c00d0b379e0 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Tue, 10 Feb 2015 11:35:20 +0400 Subject: change package to ttcomics2 (2) --- .../main/java/org/fox/ttcomics2/Application.java | 17 + .../fox/ttcomics2/ByteArrayImageDownloader.java | 29 + .../java/org/fox/ttcomics2/CbzComicArchive.java | 92 ++++ .../main/java/org/fox/ttcomics2/ComicArchive.java | 29 + .../main/java/org/fox/ttcomics2/ComicFragment.java | 256 +++++++++ .../java/org/fox/ttcomics2/ComicListFragment.java | 461 ++++++++++++++++ .../main/java/org/fox/ttcomics2/ComicPager.java | 198 +++++++ .../java/org/fox/ttcomics2/CommonActivity.java | 605 +++++++++++++++++++++ .../java/org/fox/ttcomics2/DatabaseHelper.java | 68 +++ .../java/org/fox/ttcomics2/DirectoryPicker.java | 169 ++++++ .../main/java/org/fox/ttcomics2/MainActivity.java | 228 ++++++++ .../org/fox/ttcomics2/NaturalOrderComparator.java | 188 +++++++ .../org/fox/ttcomics2/PreferencesActivity.java | 156 ++++++ .../main/java/org/fox/ttcomics2/SyncClient.java | 161 ++++++ .../java/org/fox/ttcomics2/ViewComicActivity.java | 321 +++++++++++ .../src/main/java/org/fox/ttcomics2/ViewPager.java | 38 ++ 16 files changed, 3016 insertions(+) create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/Application.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CbzComicArchive.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicArchive.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicFragment.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicListFragment.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicPager.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CommonActivity.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DatabaseHelper.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DirectoryPicker.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/MainActivity.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/PreferencesActivity.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/SyncClient.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewComicActivity.java create mode 100644 org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewPager.java (limited to 'org.fox.ttcomics/src/main') diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/Application.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/Application.java new file mode 100644 index 0000000..fa2b15d --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/Application.java @@ -0,0 +1,17 @@ +package org.fox.ttcomics2; + +import org.acra.ACRA; +import org.acra.ReportingInteractionMode; +import org.acra.annotation.ReportsCrashes; + +@ReportsCrashes(formKey = "", mode = ReportingInteractionMode.DIALOG, + resDialogText = R.string.crash_dialog_text, + formUri = "http://tt-rss.org/acra/submit/") +public class Application extends android.app.Application { + @Override + public final void onCreate() { + super.onCreate(); + ACRA.init(this); + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java new file mode 100644 index 0000000..9b7f920 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java @@ -0,0 +1,29 @@ +package org.fox.ttcomics2; + +import android.content.Context; + +import com.nostra13.universalimageloader.core.download.BaseImageDownloader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ByteArrayImageDownloader extends BaseImageDownloader { + private static final String SCHEME_STREAM = "stream"; + private static final String STREAM_URI_PREFIX = SCHEME_STREAM + "://"; + + public ByteArrayImageDownloader(Context context) { + super(context); + } + + @Override + protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException { + if (imageUri.startsWith(STREAM_URI_PREFIX)) { + InputStream is = new ByteArrayInputStream((byte[])extra); + + return is; + } else { + return super.getStreamFromOtherSource(imageUri, extra); + } + } +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CbzComicArchive.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CbzComicArchive.java new file mode 100644 index 0000000..e533ff3 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CbzComicArchive.java @@ -0,0 +1,92 @@ +package org.fox.ttcomics2; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class CbzComicArchive extends ComicArchive { + private final String TAG = this.getClass().getSimpleName(); + + private String m_fileName; + private ZipFile m_zipFile; + private int m_count; + private ArrayList m_entries = new ArrayList(); + + @Override + public int getCount() { + return m_count; + } + + @Override + public InputStream getItem(int index) throws IOException { + return m_zipFile.getInputStream(m_entries.get(index)); + } + + protected void initialize() throws IOException { + m_zipFile = new ZipFile(m_fileName); + + Enumeration e = m_zipFile.entries(); + + while (e.hasMoreElements()) { + ZipEntry ze = e.nextElement(); + if (!ze.isDirectory() && isValidComic(ze.getName())) { + m_entries.add(ze); + m_count++; + } + } + + Collections.sort(m_entries, new NaturalOrderComparator()); + } + + public CbzComicArchive(String fileName) throws IOException { + m_fileName = fileName; + + initialize(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(m_fileName); + } + + public void readFromParcel(Parcel in) { + m_fileName = in.readString(); + } + + public CbzComicArchive(Parcel in) throws IOException { + readFromParcel(in); + + initialize(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public CbzComicArchive createFromParcel(Parcel in) { + try { + return new CbzComicArchive(in); + } catch (IOException e) { + e.printStackTrace(); + + return null; + } + } + + public CbzComicArchive[] newArray(int size) { + return new CbzComicArchive[size]; + } + }; + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicArchive.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicArchive.java new file mode 100644 index 0000000..f85263c --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicArchive.java @@ -0,0 +1,29 @@ +package org.fox.ttcomics2; + +import android.os.Parcelable; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public abstract class ComicArchive implements Parcelable { + public abstract int getCount(); + public abstract InputStream getItem(int index) throws IOException; + public boolean isValidComic(String fileName) { + return fileName.toLowerCase().matches(".*\\.(jpe?g|bmp|gif|png)$"); + } + public byte[] getByteArray(int m_page) throws IOException { + InputStream is = getItem(m_page); + + int size = 16384; + byte[] buf = new byte[size]; + int len = 0; + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + buf = new byte[size]; + while ((len = is.read(buf, 0, size)) != -1) + bos.write(buf, 0, len); + + return bos.toByteArray(); + } +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicFragment.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicFragment.java new file mode 100644 index 0000000..64a88a5 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicFragment.java @@ -0,0 +1,256 @@ +package org.fox.ttcomics2; + + +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v7.app.ActionBar; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.IOException; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; + +public class ComicFragment extends Fragment implements GestureDetector.OnDoubleTapListener { + private final String TAG = this.getClass().getSimpleName(); + + private SharedPreferences m_prefs; + private int m_page; + private ViewComicActivity m_activity; + private GestureDetector m_detector; + private boolean m_thumbnail = false; + private ComicArchive m_archive; + + public ComicFragment() { + super(); + } + + public void initialize(ComicArchive archive, int page) { + m_archive = archive; + m_page = page; + } + + public void setPage(int page) { + m_page = page; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.fragment_comic, container, false); + + final ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.comic_image); + + if (savedInstanceState != null) { + m_page = savedInstanceState.getInt("page"); + m_thumbnail = savedInstanceState.getBoolean("thumbnail"); + m_archive = savedInstanceState.getParcelable("archive"); + } + + image.setFitToScreen(true); + + ImageLoader imageLoader = ImageLoader.getInstance(); + + if (m_prefs.getBoolean("fit_to_width", false)) { + image.setFitToWidth(true); + } + + try { + byte[] buf = m_archive.getByteArray(m_page); + + DisplayImageOptions options = new DisplayImageOptions.Builder() + .showImageOnFail(R.drawable.badimage) + .extraForDownloader(buf) + .build(); + + imageLoader.displayImage("stream://" + m_page, image, options); + + } catch (IOException e) { + image.setImageResource(R.drawable.badimage); + e.printStackTrace(); + } + + image.setOnScaleChangedListener(new ImageViewTouch.OnScaleChangedListener() { + @Override + public void onScaleChanged(float scale) { + // TODO: shared scale change? + } + }); + + image.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + return m_detector.onTouchEvent(event); + } + }); + + //} + + /* TextView page = (TextView) view.findViewById(R.id.comic_page); + + if (page != null) { + page.setText(String.valueOf(m_page+1)); + } */ + + return view; + + } + + private void onLeftSideTapped() { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + boolean atLeftEdge = !image.canScroll(1); + + if (atLeftEdge) { + m_activity.selectPreviousComic(); + } + } + } + + public boolean canScroll(int direction) { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + return image.canScroll(direction); + } else { + return false; + } + } + + private void onRightSideTapped() { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + boolean atRightEdge = !image.canScroll(-1); + + if (atRightEdge) { + m_activity.selectNextComic(); + } + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + + //setThumbnail(!isVisibleToUser); disabled + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + m_activity = (ViewComicActivity) activity; + + m_detector = new GestureDetector(m_activity, new GestureDetector.OnGestureListener() { + + @Override + public boolean onSingleTapUp(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onDown(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + }); + + m_detector.setOnDoubleTapListener(this); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + out.putInt("page", m_page); + out.putBoolean("thumbnail", m_thumbnail); + out.putParcelable("archive", m_archive); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + int width = getView().getWidth(); + + int x = Math.round(e.getX()); + + if (x <= width/10) { + onLeftSideTapped(); + } else if (x >= width-(width/10)) { + onRightSideTapped(); + } else { + ActionBar bar = m_activity.getSupportActionBar(); + + if (bar.isShowing()) { + bar.hide(); + m_activity.hideSeekBar(true); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + } + + } else { + m_activity.hideSeekBar(false); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(false); + } + + bar.show(); + } + } + + return false; + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicListFragment.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicListFragment.java new file mode 100644 index 0000000..9b766f7 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicListFragment.java @@ -0,0 +1,461 @@ +package org.fox.ttcomics2; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.SQLException; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ComicListFragment extends Fragment implements OnItemClickListener { + private final String TAG = this.getClass().getSimpleName(); + + private final static int SIZE_DIR = -100; + + // corresponds to tab indexes + private final static int MODE_ALL = 0; + private final static int MODE_UNREAD = 1; + private final static int MODE_UNFINISHED = 2; + private final static int MODE_READ = 3; + + private CommonActivity m_activity; + private SharedPreferences m_prefs; + private ComicsListAdapter m_adapter; + private int m_mode = 0; + private String m_baseDirectory = ""; + private SwipeRefreshLayout m_swipeLayout; + + public ComicListFragment() { + super(); + } + + public void setBaseDirectory(String baseDirectory) { + m_baseDirectory = baseDirectory; + } + + public void setMode(int mode) { + m_mode = mode; + } + + private class ComicsListAdapter extends SimpleCursorAdapter { + public ComicsListAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + // TODO Auto-generated constructor stub + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + Cursor c = (Cursor) getItem(position); + + String filePath = c.getString(c.getColumnIndex("path")); + String fileBaseName = c.getString(c.getColumnIndex("filename")); + String firstChild = c.getString(c.getColumnIndex("firstchild")); + + int lastPos = m_activity.getLastPosition(filePath + "/" + fileBaseName); + int size = m_activity.getSize(filePath + "/" + fileBaseName); + + if (v == null) { + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + v = vi.inflate(R.layout.comics_grid_row, null); + } + + TextView name = (TextView) v.findViewById(R.id.file_name); + + if (name != null) { + name.setText(fileBaseName); + } + + TextView info = (TextView) v.findViewById(R.id.file_progress_info); + + if (info != null) { + if (size != -1 && size != SIZE_DIR) { + if (lastPos == size - 1) { + info.setText(getString(R.string.file_finished)); + } else if (lastPos > 0) { + info.setText(getString(R.string.file_progress_info, lastPos + 1, size, (int) (lastPos / (float) size * 100f))); + } else { + info.setText(getString(R.string.file_unread, size)); + } + info.setVisibility(View.VISIBLE); + } else if (size == SIZE_DIR) { + info.setText(getString(R.string.list_type_directory)); + info.setVisibility(View.VISIBLE); + } else { + info.setText(getString(R.string.list_type_unknown)); + info.setVisibility(View.VISIBLE); + } + } + + ProgressBar progressBar = (ProgressBar) v.findViewById(R.id.file_progress_bar); + + if (progressBar != null) { + if (size != -1 && size != SIZE_DIR) { + progressBar.setMax(size - 1); + progressBar.setProgress(lastPos); + progressBar.setEnabled(true); + progressBar.setVisibility(View.VISIBLE); + } else { + progressBar.setProgress(0); + progressBar.setMax(0); + progressBar.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + } + } + + /* ImageView overflow = (ImageView) v.findViewById(R.id.comic_overflow); + + if (overflow != null) { + if (size == SIZE_DIR) { + overflow.setVisibility(View.GONE); + } else { + overflow.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + } */ + + File thumbnailFile = new File(m_activity.getCacheFileName(firstChild != null ? firstChild : filePath + "/" + fileBaseName)); + + ImageView thumbnail = (ImageView) v.findViewById(R.id.thumbnail); + + if (thumbnail != null && thumbnailFile != null) { + + ImageLoader imageLoader = ImageLoader.getInstance(); + imageLoader.displayImage("file://" + thumbnailFile.getAbsolutePath(), thumbnail); + } + + return v; + } + } + + public int dpToPx(int dp) { + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)); + return px; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.fragment_comics_list, container, false); + + if (savedInstanceState != null) { + m_mode = savedInstanceState.getInt("mode"); + m_baseDirectory = savedInstanceState.getString("baseDir"); + } + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.comics_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + rescan(true); + } + }); + + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + + m_adapter = new ComicsListAdapter(getActivity(), R.layout.comics_grid_row, createCursor(), + new String[] { "filename" }, new int[] { R.id.file_name }, 0); + + if (view.findViewById(R.id.comics_list) != null) { + ListView list = (ListView) view.findViewById(R.id.comics_list); + list.setAdapter(m_adapter); + list.setEmptyView(view.findViewById(R.id.no_comics)); + list.setOnItemClickListener(this); + + registerForContextMenu(list); + + } else { + GridView grid = (GridView) view.findViewById(R.id.comics_grid); + grid.setAdapter(m_adapter); + grid.setEmptyView(view.findViewById(R.id.no_comics)); + grid.setOnItemClickListener(this); + + registerForContextMenu(grid); + + } + + return view; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + + getActivity().getMenuInflater().inflate(R.menu.comic_archive_context, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; + + Cursor c = (Cursor) m_adapter.getItem(info.position); + + if (c != null) { + menu.setHeaderTitle(c.getString(c.getColumnIndex("filename"))); + } + + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + .getMenuInfo(); + + Cursor c = (Cursor) m_adapter.getItem(info.position); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + switch (item.getItemId()) { + case R.id.menu_reset_progress: + if (fileName != null) { + m_activity.resetProgress(fileName); + m_adapter.notifyDataSetChanged(); + } + return true; + case R.id.menu_mark_as_read: + + if (fileName != null) { + m_activity.setLastPosition(fileName, m_activity.getSize(fileName)-1); + m_adapter.notifyDataSetChanged(); + } + + return true; + default: + Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); + return super.onContextItemSelected(item); + } + } + + private Cursor createCursor() { + String baseDir = m_baseDirectory.length() == 0 ? m_prefs.getString("comics_directory", "") : m_baseDirectory; + + String selection; + String selectionArgs[]; + + switch (m_mode) { + case MODE_READ: + selection = "path = ? AND position = size - 1"; + selectionArgs = new String[] { baseDir }; + break; + case MODE_UNFINISHED: + selection = "path = ? AND position < size AND position > 0 AND position != size - 1"; + selectionArgs = new String[] { baseDir }; + break; + case MODE_UNREAD: + selection = "path = ? AND position = 0 AND size != ?"; + selectionArgs = new String[] { baseDir, String.valueOf(SIZE_DIR) }; + break; + default: + selection = "path = ?"; + selectionArgs = new String[] { baseDir }; + } + + if (!m_prefs.getBoolean("enable_rar", false)) { + selection += " AND (UPPER(filename) NOT LIKE '%.CBR' AND UPPER(filename) NOT LIKE '%.RAR')"; + } + + return m_activity.getReadableDb().query("comics_cache", new String[] { BaseColumns._ID, "filename", "path", + "(SELECT path || '/' || filename FROM comics_cache AS t2 WHERE t2.path = comics_cache.path || '/' || comics_cache.filename AND filename != '' ORDER BY filename LIMIT 1) AS firstchild" }, + selection, selectionArgs, null, null, "size != " + SIZE_DIR + ", filename, size = " + SIZE_DIR + ", filename"); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (CommonActivity)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + } + + protected void rescan(final boolean fullRescan) { + AsyncTask rescanTask = new AsyncTask() { + + @Override + protected void onProgressUpdate(Integer... progress) { + if (isAdded()) { + m_activity.setProgress(Math.round(((float)progress[0] / (float)progress[1]) * 10000)); + } + } + + @Override + protected Integer doInBackground(String... params) { + String comicsDir = params[0]; + + File dir = new File(comicsDir); + + int fileIndex = 0; + + if (dir.isDirectory()) { + File archives[] = dir.listFiles(); + + java.util.Arrays.sort(archives); + + for (File archive : archives) { + String filePath = archive.getAbsolutePath(); + + if (archive.isDirectory()) { + m_activity.setSize(filePath, SIZE_DIR); + + } else if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip|cbr|rar)") && isAdded() && m_activity != null && + m_activity.getWritableDb() != null && m_activity.getWritableDb().isOpen()) { + try { + int size = m_activity.getSize(filePath); + + if (size == -1 || fullRescan) { + + ComicArchive cba = null; + + if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip)")) { + cba = new CbzComicArchive(filePath); + } + + if (cba != null && cba.getCount() > 0) { + // Get cover + + try { + File thumbnailFile = new File(m_activity.getCacheFileName(filePath)); + + if (m_activity.isStorageWritable() && (!thumbnailFile.exists() || fullRescan)) { + InputStream is = cba.getItem(0); + + if (is != null) { + FileOutputStream fos = new FileOutputStream(thumbnailFile); + + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + + size = cba.getCount(); + + m_activity.setSize(filePath, size); + } + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + ++fileIndex; + + publishProgress(Integer.valueOf(fileIndex), Integer.valueOf(archives.length)); + } + } + + if (isAdded() && m_activity != null) { + m_activity.cleanupCache(false); + m_activity.cleanupSqliteCache(comicsDir); + } + + return fileIndex; //m_files.size(); + } + + @Override + protected void onPostExecute(Integer result) { + if (isAdded() && m_adapter != null) { + m_adapter.changeCursor(createCursor()); + m_adapter.notifyDataSetChanged(); + + if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false); + } + } + }; + + String comicsDir = m_prefs.getString("comics_directory", null); + + if (comicsDir != null && m_activity.isStorageAvailable()) { + if (m_swipeLayout != null) m_swipeLayout.setRefreshing(true); + + rescanTask.execute(m_baseDirectory.length() > 0 ? m_baseDirectory : comicsDir); + } + } + + @Override + public void onResume() { + super.onResume(); + + m_adapter.notifyDataSetChanged(); + + String comicsDir = m_prefs.getString("comics_directory", ""); + + if (m_activity.getCachedItemCount(m_baseDirectory.length() > 0 ? m_baseDirectory : comicsDir) == 0) { + rescan(false); + } else { + m_adapter.notifyDataSetChanged(); + } + } + + public void onItemClick(AdapterView av, View view, int position, long id) { + //Log.d(TAG, "onItemClick position=" + position); + + Cursor c = (Cursor) m_adapter.getItem(position); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + if (fileName != null) { + m_activity.onComicArchiveSelected(fileName); + } + } + + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("mode", m_mode); + out.putString("baseDir", m_baseDirectory); + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicPager.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicPager.java new file mode 100644 index 0000000..8df3cf0 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ComicPager.java @@ -0,0 +1,198 @@ +package org.fox.ttcomics2; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.io.IOException; + +public class ComicPager extends Fragment { + private String m_fileName; + private SharedPreferences m_prefs; + private final String TAG = this.getClass().getSimpleName(); + private ComicArchive m_archive; + private CommonActivity m_activity; + private SeekBar m_seekBar; + private TextView m_pageView; + + public void hideSeekBar(boolean hide) { + m_seekBar.setVisibility(hide ? View.GONE : View.VISIBLE); + m_pageView.setVisibility(hide ? View.GONE : View.VISIBLE); + } + + private class PagerAdapter extends FragmentStatePagerAdapter { + public PagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + ComicFragment cf = new ComicFragment(); + cf.initialize(m_archive, position); + + return cf; + } + + @Override + public int getCount() { + return m_archive.getCount(); + } + } + + private PagerAdapter m_adapter; + + public ComicPager() { + super(); + } + + public ComicArchive getArchive() { + return m_archive; + } + + public int getCount() { + return m_adapter.getCount(); + } + + public int getPosition() { + ViewPager pager = (ViewPager) getView().findViewById(R.id.comics_pager); + + if (pager != null) { + return pager.getCurrentItem(); + } + + return 0; + } + + public String getFileName() { + return m_fileName; + } + + public void setCurrentItem(int item) { + ViewPager pager = (ViewPager) getView().findViewById(R.id.comics_pager); + + if (pager != null) { + try { + pager.setCurrentItem(item); + m_pageView.setText(String.valueOf(item+1)); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } + + } + + public void setFileName(String fileName) { + m_fileName = fileName; + } + + @SuppressLint("NewApi") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + final View view = inflater.inflate(R.layout.fragment_comics_pager, container, false); + + m_adapter = new PagerAdapter(getActivity().getSupportFragmentManager()); + + final ViewPager pager = (ViewPager) view.findViewById(R.id.comics_pager); + + if (savedInstanceState != null) { + m_fileName = savedInstanceState.getString("fileName"); + } + + try { + if (m_fileName.toLowerCase().matches(".*\\.(cbz|zip)")) { + m_archive = new CbzComicArchive(m_fileName); + } + + final int position = m_activity.getLastPosition(m_fileName); + + m_pageView = (TextView) view.findViewById(R.id.comics_page); + m_pageView.setText(String.valueOf(position+1)); + + m_seekBar = (SeekBar) view.findViewById(R.id.comics_seek_bar); + m_seekBar.setMax(m_archive.getCount()-1); + m_seekBar.setProgress(position); + m_seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + setCurrentItem(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + pager.setAdapter(m_adapter); + pager.setCurrentItem(position); + + m_activity.onComicSelected(m_fileName, position); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + hideSeekBar(true); + } + + } catch (IOException e) { + m_activity.toast(R.string.error_could_not_open_comic_archive); + e.printStackTrace(); + } + + pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + public void onPageSelected(int position) { + + m_activity.onComicSelected(m_fileName, position); + m_seekBar.setProgress(position); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + } + } + + public void onPageScrolled(int arg0, float arg1, int arg2) { + // TODO Auto-generated method stub + } + + public void onPageScrollStateChanged(int arg0) { + // TODO Auto-generated method stub + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (CommonActivity) activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("fileName", m_fileName); + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CommonActivity.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CommonActivity.java new file mode 100644 index 0000000..2095d87 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/CommonActivity.java @@ -0,0 +1,605 @@ +package org.fox.ttcomics2; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.Display; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; + +import java.io.File; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +public class CommonActivity extends ActionBarActivity { + private final String TAG = this.getClass().getSimpleName(); + + 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; + + private boolean m_storageAvailable; + private boolean m_storageWritable; + + private SQLiteDatabase m_readableDb; + private SQLiteDatabase m_writableDb; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (!ImageLoader.getInstance().isInited()) { + + DisplayImageOptions options = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .resetViewBeforeLoading(true) + .cacheOnDisk(false) + .displayer(new FadeInBitmapDisplayer(250)) + .build(); + + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this) + .defaultDisplayImageOptions(options) + .imageDownloader(new ByteArrayImageDownloader(this)) + .build(); + + ImageLoader.getInstance().init(config); + } + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + initDatabase(); + initSync(); + } + + @Override + public void onResume() { + super.onResume(); + + String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + m_storageAvailable = true; + m_storageWritable = true; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + m_storageAvailable = true; + m_storageWritable = false; + } else { + m_storageAvailable = false; + m_storageWritable = false; + } + + initSync(); + } + + private void initSync() { + if (m_prefs.getBoolean("use_position_sync", false)) { + String googleAccount = getGoogleAccount(); + + if (googleAccount != null) { + m_syncClient.setOwner(googleAccount); + } else { + if (Build.HARDWARE.contains("goldfish")) { + m_syncClient.setOwner("TEST-ACCOUNT"); + + //toast(R.string.sync_running_in_test_mode); + } else { + m_syncClient.setOwner(null); + toast(R.string.error_sync_no_account); + + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("use_position_sync", false); + editor.commit(); + } + } + } else { + m_syncClient.setOwner(null); + } + } + + protected void setSmallScreen(boolean smallScreen) { + Log.d(TAG, "m_smallScreenMode=" + smallScreen); + m_smallScreenMode = smallScreen; + } + + public boolean isSmallScreen() { + return m_smallScreenMode; + } + + + public void onComicArchiveSelected(String fileName) { + // + } + + public Cursor findComicByFileName(String fileName) { + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", null, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + return c; + } else { + c.close(); + + SQLiteStatement stmt = getWritableDb().compileStatement("INSERT INTO comics_cache " + + "(filename, path, position, max_position, size, checksum) VALUES (?, ?, 0, 0, -1, '')"); + stmt.bindString(1, file.getName()); + stmt.bindString(2, file.getParentFile().getAbsolutePath()); + stmt.execute(); + + c = getReadableDb().query("comics_cache", null, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + return c; + } else { + c.close(); + } + } + + return null; + } + + public void setSize(String fileName, int size) { + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET size = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, size); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public void setChecksum(String fileName, String checksum) { + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET checksum = ? WHERE filename = ? AND path = ?"); + stmt.bindString(1, checksum); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public void resetProgress(final String fileName) { + + if (m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()) { + setLastPosition(fileName, 0); + setLastMaxPosition(fileName, 0); + + AlertDialog.Builder builder = new AlertDialog.Builder(CommonActivity.this); + builder.setMessage(R.string.reset_remove_synced_progress) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + m_syncClient.clearData(sha1(new File(fileName).getName())); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } + + } + + public void setLastPosition(String fileName, int position) { + int lastPosition = getLastPosition(fileName); + + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET position = ?, max_position = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, position); + stmt.bindLong(2, Math.max(position, lastPosition)); + stmt.bindString(3, file.getName()); + stmt.bindString(4, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + + } + + public void setLastMaxPosition(String fileName, int position) { + + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET max_position = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, position); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public String getChecksum(String fileName) { + String checksum = null; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "checksum" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + checksum = c.getString(c.getColumnIndex("checksum")); + } + + c.close(); + + return checksum; + + } + + + public int getLastPosition(String fileName) { + int position = 0; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "position" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + position = c.getInt(c.getColumnIndex("position")); + } + + c.close(); + + return position; + + } + + public int getCachedItemCount(String baseDir) { + Cursor c = getReadableDb().query("comics_cache", new String[] { "COUNT(*)" }, + "path = ?", + new String[] { baseDir }, null, null, null); + + c.moveToFirst(); + int count = c.getInt(0); + c.close(); + + //Log.d(TAG, "getCachedItemCount:" + baseDir + "=" + count); + + return count; + } + + public int getMaxPosition(String fileName) { + int position = 0; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "max_position" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + position = c.getInt(c.getColumnIndex("max_position")); + } + + c.close(); + + return position; + } + + public int getSize(String fileName) { + int size = -1; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "size" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + size = c.getInt(c.getColumnIndex("size")); + } + + c.close(); + + //Log.d(TAG, "getSize:" + fileName + "=" + size); + + return size; + } + + public void onComicSelected(String fileName, int position) { + setLastPosition(fileName, position); + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_rescan: + ComicListFragment frag = (ComicListFragment) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_LIST); + + if (frag != null && frag.isAdded()) { + frag.rescan(true); + } + + return true; + case R.id.menu_settings: + Intent intent = new Intent(CommonActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @SuppressWarnings("deprecation") + public boolean isPortrait() { + Display display = getWindowManager().getDefaultDisplay(); + + int width = display.getWidth(); + int height = display.getHeight(); + + return width < height; + } + + protected static String md5(String s) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i=0; i reqHeight || width > reqWidth) { + if (width > height) { + inSampleSize = Math.round((float)height / (float)reqHeight); + } else { + inSampleSize = Math.round((float)width / (float)reqWidth); + } + } + return inSampleSize; + } + + + public void cleanupCache(boolean deleteAll) { + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + File cachePath = getExternalCacheDir(); + + long now = new Date().getTime(); + + if (cachePath.isDirectory()) { + for (File file : cachePath.listFiles()) { + if (deleteAll || now - file.lastModified() > 1000*60*60*24*7) { + file.delete(); + } + } + } + } + } + + public boolean isStorageAvailable() { + return m_storageAvailable; + } + + public boolean isStorageWritable() { + return m_storageWritable; + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + public synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } + + public synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + m_readableDb.close(); + m_writableDb.close(); + } + + public void selectPreviousComic() { + ComicPager frag = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (frag != null && frag.isAdded() && frag.getPosition() > 0) { + frag.setCurrentItem(frag.getPosition() - 1); + } + } + + public void selectNextComic() { + ComicPager frag = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (frag != null && frag.isAdded() && frag.getPosition() < frag.getCount()-1) { + frag.setCurrentItem(frag.getPosition() + 1); + } + } + + public void cleanupSqliteCache(String baseDir) { + Cursor c = getReadableDb().query("comics_cache", null, + null, null, null, null, null); + + if (c.moveToFirst()) { + while (!c.isAfterLast()) { + int id = c.getInt(c.getColumnIndex(BaseColumns._ID)); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + File file = new File(fileName); + + if (!file.exists()) { + SQLiteStatement stmt = getWritableDb().compileStatement("DELETE FROM comics_cache WHERE " + BaseColumns._ID + " = ?"); + stmt.bindLong(1, id); + stmt.execute(); + } + + c.moveToNext(); + } + } + + c.close(); + } + + public void hideSystemUI(boolean hide) { + View decorView = getWindow().getDecorView(); + + if (hide) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE); + + } else { + decorView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + + getSupportActionBar().hide(); + + } else { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DatabaseHelper.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DatabaseHelper.java new file mode 100644 index 0000000..bf66fec --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DatabaseHelper.java @@ -0,0 +1,68 @@ +package org.fox.ttcomics2; + +import java.io.File; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; + +public class DatabaseHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "ComicsCache.db"; + public static final int DATABASE_VERSION = 2; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS comics_cache;"); + + db.execSQL("CREATE TABLE IF NOT EXISTS comics_cache (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + "filename TEXT, " + + "path TEXT, " + //v2 + "checksum TEXT, " + //v2 + "size INTEGER, " + + "position INTEGER, " + + "max_position INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion == 1 && newVersion == 2) { + + db.execSQL("ALTER TABLE comics_cache ADD COLUMN path TEXT;"); + db.execSQL("ALTER TABLE comics_cache ADD COLUMN checksum TEXT;"); + + Cursor c = db.query("comics_cache", null, + null, null, null, null, null); + + if (c.moveToFirst()) { + while (!c.isAfterLast()) { + int id = c.getInt(c.getColumnIndex(BaseColumns._ID)); + String fileName = c.getString(c.getColumnIndex("filename")); + + File file = new File(fileName); + + SQLiteStatement stmt = db.compileStatement("UPDATE comics_cache SET filename = ?, path = ? WHERE " + BaseColumns._ID + " = ?"); + stmt.bindString(1, file.getName()); + stmt.bindString(2, file.getParentFile().getAbsolutePath()); + stmt.bindLong(3, id); + stmt.execute(); + + c.moveToNext(); + } + } + + c.close(); + } + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DirectoryPicker.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DirectoryPicker.java new file mode 100644 index 0000000..0c75f09 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/DirectoryPicker.java @@ -0,0 +1,169 @@ +package org.fox.ttcomics2; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +/** +Copyright (C) 2011 by Brad Greco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ + +public class DirectoryPicker extends ListActivity { + + public static final String START_DIR = "startDir"; + public static final String ONLY_DIRS = "onlyDirs"; + public static final String SHOW_HIDDEN = "showHidden"; + public static final String CHOSEN_DIRECTORY = "chosenDir"; + public static final int PICK_DIRECTORY = 43522432; + private File dir; + private boolean showHidden = false; + private boolean onlyDirs = true ; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle extras = getIntent().getExtras(); + dir = Environment.getExternalStorageDirectory(); + if (extras != null) { + String preferredStartDir = extras.getString(START_DIR); + showHidden = extras.getBoolean(SHOW_HIDDEN, false); + onlyDirs = extras.getBoolean(ONLY_DIRS, true); + if(preferredStartDir != null) { + File startDir = new File(preferredStartDir); + if(startDir.isDirectory()) { + dir = startDir; + } + } + } + + setContentView(R.layout.chooser_list); + setTitle(dir.getAbsolutePath()); + Button btnChoose = (Button) findViewById(R.id.btnChoose); + String name = dir.getName(); + if(name.length() == 0) + name = "/"; + btnChoose.setText(getString(R.string.picker_choose, name)); + btnChoose.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + returnDir(dir.getAbsolutePath()); + } + }); + + Button btnParent = (Button) findViewById(R.id.btnParent); + btnParent.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(DirectoryPicker.this, DirectoryPicker.class); + intent.putExtra(DirectoryPicker.START_DIR, dir.getParent()); + intent.putExtra(DirectoryPicker.SHOW_HIDDEN, showHidden); + intent.putExtra(DirectoryPicker.ONLY_DIRS, onlyDirs); + startActivityForResult(intent, PICK_DIRECTORY); + } + }); + + if (dir.getParent() == null) { + btnParent.setVisibility(View.GONE); + } + + ListView lv = getListView(); + lv.setTextFilterEnabled(true); + + if(!dir.canRead()) { + Context context = getApplicationContext(); + String msg = getString(R.string.error_could_not_read_folder_contents_); + Toast toast = Toast.makeText(context, msg, Toast.LENGTH_LONG); + toast.show(); + return; + } + + final ArrayList files = filter(dir.listFiles(), onlyDirs, showHidden); + String[] names = names(files); + setListAdapter(new ArrayAdapter(this, R.layout.list_item, names)); + + + lv.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + if(!files.get(position).isDirectory()) + return; + String path = files.get(position).getAbsolutePath(); + Intent intent = new Intent(DirectoryPicker.this, DirectoryPicker.class); + intent.putExtra(DirectoryPicker.START_DIR, path); + intent.putExtra(DirectoryPicker.SHOW_HIDDEN, showHidden); + intent.putExtra(DirectoryPicker.ONLY_DIRS, onlyDirs); + startActivityForResult(intent, PICK_DIRECTORY); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode == PICK_DIRECTORY && resultCode == RESULT_OK) { + Bundle extras = data.getExtras(); + String path = (String) extras.get(DirectoryPicker.CHOSEN_DIRECTORY); + returnDir(path); + } + } + + private void returnDir(String path) { + Intent result = new Intent(); + result.putExtra(CHOSEN_DIRECTORY, path); + setResult(RESULT_OK, result); + finish(); + } + + public ArrayList filter(File[] file_list, boolean onlyDirs, boolean showHidden) { + ArrayList files = new ArrayList(); + + for(File file: file_list) { + if(onlyDirs && !file.isDirectory()) + continue; + if(!showHidden && file.isHidden()) + continue; + files.add(file); + } + Collections.sort(files); + + return files; + } + + public String[] names(ArrayList files) { + String[] names = new String[files.size()]; + int i = 0; + for(File file: files) { + names[i] = file.getName(); + i++; + } + return names; + } +} + diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/MainActivity.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/MainActivity.java new file mode 100644 index 0000000..9dda29b --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/MainActivity.java @@ -0,0 +1,228 @@ +package org.fox.ttcomics2; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.ActionBar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import java.io.File; + +import it.neokree.materialtabs.MaterialTab; +import it.neokree.materialtabs.MaterialTabHost; +import it.neokree.materialtabs.MaterialTabListener; + +public class MainActivity extends CommonActivity implements MaterialTabListener { + private final String TAG = this.getClass().getSimpleName(); + private final static int TRIAL_DAYS = 7; + + private int m_selectedTab; + private String m_baseDirectory = ""; + private String m_fileName = ""; + private MaterialTabHost tabHost; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + setTitle(R.string.app_name); + + tabHost = (MaterialTabHost) this.findViewById(R.id.materialTabHost); + + if (savedInstanceState == null) { + m_selectedTab = getIntent().getIntExtra("selectedTab", 0); + + Log.d(TAG, "selTab=" + m_selectedTab); + + ComicListFragment frag = new ComicListFragment(); + frag.setMode(m_selectedTab); + + if (getIntent().getStringExtra("baseDir") != null) { + m_baseDirectory = getIntent().getStringExtra("baseDir"); + frag.setBaseDirectory(m_baseDirectory); + } + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.comics_list, frag, FRAG_COMICS_LIST); + ft.commit(); + + m_selectedTab = -1; + } else { + m_selectedTab = -1; + m_baseDirectory = savedInstanceState.getString("baseDir"); + m_fileName = savedInstanceState.getString("fileName"); + } + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_all_comics)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_unread)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_unfinished)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_read)) + .setTabListener(this)); + + if (savedInstanceState != null) { + m_selectedTab = savedInstanceState.getInt("selectedTab"); + } else { + m_selectedTab = getIntent().getIntExtra("selectedTab", 0); + } + + if (m_selectedTab != -1) + tabHost.setSelectedNavigationItem(m_selectedTab); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(m_baseDirectory.length() > 0); + + if (m_prefs.getString("comics_directory", null) == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.dialog_need_prefs_message) + .setCancelable(false) + .setPositiveButton(R.string.dialog_need_prefs_preferences, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // launch preferences + + Intent intent = new Intent(MainActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + //((ViewGroup)findViewById(R.id.comics_list)).setLayoutTransition(new LayoutTransition()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_main, menu); + + return true; + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("selectedTab", m_selectedTab); + out.putString("baseDir", m_baseDirectory); + out.putString("fileName", m_fileName); + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (m_baseDirectory.length() > 0) { + finish(); + } + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onComicArchiveSelected(String fileName) { + super.onComicArchiveSelected(fileName); + + File file = new File(fileName); + + if (file.isDirectory()) { + Intent intent = new Intent(MainActivity.this, + MainActivity.class); + + intent.putExtra("baseDir", fileName); + intent.putExtra("selectedTab", m_selectedTab); + + startActivityForResult(intent, 0); + + } else if (file.canRead()) { + Intent intent = new Intent(MainActivity.this, + ViewComicActivity.class); + + intent.putExtra("fileName", fileName); + m_fileName = fileName; + + startActivityForResult(intent, REQUEST_VIEWCOMIC); + } else { + toast(getString(R.string.error_cant_open_file, fileName)); + + ComicListFragment frag = (ComicListFragment) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_LIST); + + if (frag != null && frag.isAdded()) { + frag.rescan(true); + } + } + } + + @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) && m_syncClient.hasOwner()) { + toast(R.string.sync_uploading); + m_syncClient.setPosition(sha1(new File(m_fileName).getName()), getLastPosition(m_fileName)); + } + } + + System.gc(); + + super.onActivityResult(requestCode, resultCode, intent); + } + + @Override + public void onTabSelected(MaterialTab tab) { + + tabHost.setSelectedNavigationItem(tab.getPosition()); + + FragmentTransaction sft = getSupportFragmentManager().beginTransaction(); + + if (m_selectedTab != tab.getPosition() && m_selectedTab != -1) { + + ComicListFragment frag = new ComicListFragment(); + frag.setMode(tab.getPosition()); + + frag.setBaseDirectory(m_baseDirectory); + + sft.replace(R.id.comics_list, frag, FRAG_COMICS_LIST); + } + + m_selectedTab = tab.getPosition(); + + sft.commit(); + } + + @Override + public void onTabReselected(MaterialTab materialTab) { + + } + + @Override + public void onTabUnselected(MaterialTab materialTab) { + + } +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java new file mode 100644 index 0000000..d7522f9 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java @@ -0,0 +1,188 @@ +package org.fox.ttcomics2; + +/* + NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java. + Copyright (C) 2003 by Pierre-Luc Paour + + Based on the C version by Martin Pool, of which this is more or less a straight conversion. + Copyright (C) 2000 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + */ + +import java.util.*; + +public class NaturalOrderComparator implements Comparator +{ + int compareRight(String a, String b) + { + int bias = 0; + int ia = 0; + int ib = 0; + + // The longest run of digits wins. That aside, the greatest + // value wins, but we can't know that it will until we've scanned + // both numbers to know that they have the same magnitude, so we + // remember it in BIAS. + for (;; ia++, ib++) + { + char ca = charAt(a, ia); + char cb = charAt(b, ib); + + if (!Character.isDigit(ca) && !Character.isDigit(cb)) + { + return bias; + } + else if (!Character.isDigit(ca)) + { + return -1; + } + else if (!Character.isDigit(cb)) + { + return +1; + } + else if (ca < cb) + { + if (bias == 0) + { + bias = -1; + } + } + else if (ca > cb) + { + if (bias == 0) + bias = +1; + } + else if (ca == 0 && cb == 0) + { + return bias; + } + } + } + + public int compare(Object o1, Object o2) + { + String a = o1.toString(); + String b = o2.toString(); + + int ia = 0, ib = 0; + int nza = 0, nzb = 0; + char ca, cb; + int result; + + while (true) + { + // only count the number of zeroes leading the last number compared + nza = nzb = 0; + + ca = charAt(a, ia); + cb = charAt(b, ib); + + // skip over leading spaces or zeros + while (Character.isSpaceChar(ca) || ca == '0') + { + if (ca == '0') + { + nza++; + } + else + { + // only count consecutive zeroes + nza = 0; + } + + ca = charAt(a, ++ia); + } + + while (Character.isSpaceChar(cb) || cb == '0') + { + if (cb == '0') + { + nzb++; + } + else + { + // only count consecutive zeroes + nzb = 0; + } + + cb = charAt(b, ++ib); + } + + // process run of digits + if (Character.isDigit(ca) && Character.isDigit(cb)) + { + if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) + { + return result; + } + } + + if (ca == 0 && cb == 0) + { + // The strings compare the same. Perhaps the caller + // will want to call strcmp to break the tie. + return nza - nzb; + } + + if (ca < cb) + { + return -1; + } + else if (ca > cb) + { + return +1; + } + + ++ia; + ++ib; + } + } + + static char charAt(String s, int i) + { + if (i >= s.length()) + { + return 0; + } + else + { + return s.charAt(i); + } + } + + public static void main(String[] args) + { + String[] strings = new String[] { "1-2", "1-02", "1-20", "10-20", "fred", "jane", "pic01", + "pic2", "pic02", "pic02a", "pic3", "pic4", "pic 4 else", "pic 5", "pic05", "pic 5", + "pic 5 something", "pic 6", "pic 7", "pic100", "pic100a", "pic120", "pic121", + "pic02000", "tom", "x2-g8", "x2-y7", "x2-y08", "x8-y8" }; + + List orig = Arrays.asList(strings); + + System.out.println("Original: " + orig); + + List scrambled = Arrays.asList(strings); + Collections.shuffle(scrambled); + + System.out.println("Scrambled: " + scrambled); + + Collections.sort(scrambled, new NaturalOrderComparator()); + + System.out.println("Sorted: " + scrambled); + } +} \ No newline at end of file diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/PreferencesActivity.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/PreferencesActivity.java new file mode 100644 index 0000000..634b787 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/PreferencesActivity.java @@ -0,0 +1,156 @@ +package org.fox.ttcomics2; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.widget.Toast; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class PreferencesActivity extends PreferenceActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + addPreferencesFromResource(R.xml.preferences); + + Preference dirPref = findPreference("comics_directory"); + dirPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(PreferencesActivity.this, DirectoryPicker.class); + + intent.putExtra(DirectoryPicker.START_DIR, prefs.getString("comics_directory", + Environment.getExternalStorageDirectory().getAbsolutePath())); + + startActivityForResult(intent, DirectoryPicker.PICK_DIRECTORY); + return true; + } + }); + + Preference clearPref = findPreference("clear_sync_data"); + clearPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AlertDialog.Builder builder = new AlertDialog.Builder(PreferencesActivity.this); + builder.setMessage(R.string.dialog_clear_data_title) + .setCancelable(false) + .setPositiveButton(R.string.dialog_clear_data, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + + String googleAccount = getGoogleAccount(); + SyncClient m_syncClient = new SyncClient(); + + if (googleAccount != null) { + m_syncClient.setOwner(googleAccount); + } else { + if (Build.HARDWARE.equals("goldfish")) { + m_syncClient.setOwner("TEST-ACCOUNT"); + } else { + m_syncClient.setOwner(null); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean("use_position_sync", false); + editor.commit(); + + Toast toast = Toast.makeText(PreferencesActivity.this, R.string.error_sync_no_account, Toast.LENGTH_SHORT); + toast.show(); + } + } + + if (m_syncClient.hasOwner()) { + m_syncClient.clearData(); + } + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + return false; + } + }); + + String version = "?"; + int versionCode = -1; + String buildTimestamp = "N/A"; + + try { + PackageInfo packageInfo = getPackageManager(). + getPackageInfo(getPackageName(), 0); + + version = packageInfo.versionName; + versionCode = packageInfo.versionCode; + + ApplicationInfo appInfo = getPackageManager(). + getApplicationInfo(getPackageName(), 0); + + ZipFile zf = new ZipFile(appInfo.sourceDir); + ZipEntry ze = zf.getEntry("classes.dex"); + long time = ze.getTime(); + + buildTimestamp = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss", + Locale.getDefault()).format(time); + + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + findPreference("version").setTitle(getString(R.string.prefs_version, version, versionCode)); + findPreference("build_timestamp").setTitle(getString(R.string.prefs_build_timestamp, buildTimestamp)); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode == DirectoryPicker.PICK_DIRECTORY && resultCode == RESULT_OK) { + Bundle extras = data.getExtras(); + String path = (String) extras.get(DirectoryPicker.CHOSEN_DIRECTORY); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("comics_directory", path); + editor.commit(); + + } + } + + 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; + } +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/SyncClient.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/SyncClient.java new file mode 100644 index 0000000..7d4c64a --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/SyncClient.java @@ -0,0 +1,161 @@ +package org.fox.ttcomics2; + +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 { + protected String m_response = null; + protected int m_responseCode = -1; + + @Override + protected Boolean doInBackground(String... params) { + + String requestStr = null; + String op = params[0]; + + if (op.equals("set")) { + requestStr = String.format("op=set&owner=%1$s&hash=%2$s&position=%3$s", m_owner, params[1], params[2]); + } else if (op.equals("get")) { + requestStr = String.format("op=get&owner=%1$s&hash=%2$s", m_owner, params[1]); + } else if (op.equals("clear")) { + if (params.length > 1) { + requestStr = String.format("op=clear&owner=%1$s&hash=%2$s", m_owner, params[1]); + } else { + requestStr = String.format("op=clear&owner=%1$s", m_owner); + } + } + + requestStr += "&version=2"; + + Log.d(TAG, requestStr); + + if (requestStr == null) return false; + + 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 void clearData() { + if (m_owner != null) { + Log.d(TAG, "Clearing sync data..."); + + HttpTask task = new HttpTask(); + + task.execute("clear"); + } + } + + public void clearData(String hash) { + if (m_owner != null) { + Log.d(TAG, "Clearing sync data: " + hash); + + HttpTask task = new HttpTask(); + + task.execute("clear", hash); + } + } + + public boolean hasOwner() { + return m_owner != null; + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewComicActivity.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewComicActivity.java new file mode 100644 index 0000000..abbf835 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewComicActivity.java @@ -0,0 +1,321 @@ +package org.fox.ttcomics2; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.NumberPicker; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ViewComicActivity extends CommonActivity { + private final String TAG = this.getClass().getSimpleName(); + + private String m_fileName; + private String m_tmpFileName; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setTheme(m_prefs.getBoolean("use_dark_theme", false) ? R.style.ViewDarkTheme : R.style.ViewLightTheme); + + super.onCreate(savedInstanceState); + + if (m_prefs.getBoolean("prevent_screen_sleep", false)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + if (m_prefs.getBoolean("use_full_screen", false)) { + hideSystemUI(true); + } + + setContentView(R.layout.activity_view_comic); + + if (savedInstanceState == null) { + m_fileName = getIntent().getStringExtra("fileName"); + + ComicPager cp = new ComicPager(); + cp.setFileName(m_fileName); + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.comics_pager_container, cp, FRAG_COMICS_PAGER); + ft.commit(); + } else { + m_fileName = savedInstanceState.getString("fileName"); + m_tmpFileName = savedInstanceState.getString("tmpFileName"); + } + + setOrientationLock(isOrientationLocked(), true); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setTitle(new File(m_fileName).getName()); + } + + @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; + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("fileName", m_fileName); + out.putString("tmpFileName", m_tmpFileName); + } + + @Override + public void onComicSelected(String fileName, int position) { + super.onComicSelected(fileName, position); + } + + public void onPause() { + super.onPause(); + + // upload progress + if (m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()) { + //toast(R.string.sync_uploading); + m_syncClient.setPosition(sha1(new File(m_fileName).getName()), getLastPosition(m_fileName)); + } + } + + private void shareComic() { + + ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (pager != null) { + + try { + File tmpFile = File.createTempFile("trcshare" + sha1(m_fileName + " " + pager.getPosition()), ".jpg", getExternalCacheDir()); + + Log.d(TAG, "FILE=" + tmpFile); + + InputStream is = pager.getArchive().getItem(pager.getPosition()); + + FileOutputStream fos = new FileOutputStream(tmpFile); + + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + + Intent shareIntent = new Intent(Intent.ACTION_SEND); + + shareIntent.setType("image/jpeg"); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tmpFile)); + + m_tmpFileName = tmpFile.getAbsolutePath(); + + 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)); + e.printStackTrace(); + } + + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_SHARE) { + File tmpFile = new File(m_tmpFileName); + + if (tmpFile.exists()) { + tmpFile.delete(); + } + + } + super.onActivityResult(requestCode, resultCode, intent); + } + + protected boolean isOrientationLocked() { + return m_prefs.getBoolean("prefs_lock_orientation", false); + } + + private void setOrientationLock(boolean locked, boolean restoreLast) { + if (locked) { + + int currentOrientation = restoreLast ? m_prefs.getInt("last_orientation", getResources().getConfiguration().orientation) : + getResources().getConfiguration().orientation; + + if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + + if (locked != isOrientationLocked()) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("prefs_lock_orientation", locked); + editor.putInt("last_orientation", getResources().getConfiguration().orientation); + editor.commit(); + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + shareComic(); + return true; + case R.id.menu_toggle_orientation_lock: + setOrientationLock(!isOrientationLocked(), false); + 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(true) + .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(); + + } else { + toast(R.string.error_sync_no_data); + } + + } + } + }); + return true; + case R.id.menu_go_location: + Dialog dialog = new Dialog(ViewComicActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder(ViewComicActivity.this) + .setTitle("Go to...") + .setItems( + new String[] { + getString(R.string.dialog_location_beginning), + getString(R.string.dialog_location_furthest), + getString(R.string.dialog_location_location), + getString(R.string.dialog_location_end) + }, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + final ComicPager cp = (ComicPager) getSupportFragmentManager().findFragmentByTag(CommonActivity.FRAG_COMICS_PAGER); + + switch (which) { + case 0: + cp.setCurrentItem(0); + break; + case 1: + cp.setCurrentItem(getMaxPosition(m_fileName)); + break; + case 2: + if (true) { + LayoutInflater inflater = getLayoutInflater(); + View contentView = inflater.inflate(R.layout.dialog_location, null); + + final NumberPicker picker = (NumberPicker) contentView.findViewById(R.id.number_picker); + + picker.setMinValue(1); + picker.setMaxValue(getSize(m_fileName)); + picker.setValue(cp.getPosition() + 1); + + Dialog seekDialog = new Dialog(ViewComicActivity.this); + AlertDialog.Builder seekBuilder = new AlertDialog.Builder(ViewComicActivity.this) + .setTitle(R.string.dialog_open_location) + .setView(contentView) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }).setPositiveButton(R.string.dialog_open_location, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + + cp.setCurrentItem(picker.getValue()-1); + + } + }); + + seekDialog = seekBuilder.create(); + seekDialog.show(); + } + + break; + case 3: + cp.setCurrentItem(cp.getCount() - 1); + break; + } + + dialog.cancel(); + } + }); + + dialog = builder.create(); + dialog.show(); + + return true; + case android.R.id.home: + finish(); + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + public void hideSeekBar(boolean hide) { + ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (pager != null) { + pager.hideSeekBar(hide); + } + } + +} diff --git a/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewPager.java b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewPager.java new file mode 100644 index 0000000..91feea1 --- /dev/null +++ b/org.fox.ttcomics/src/main/java/org/fox/ttcomics2/ViewPager.java @@ -0,0 +1,38 @@ +package org.fox.ttcomics2; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class ViewPager extends android.support.v4.view.ViewPager { + public ViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ImageViewTouch) { + ImageViewTouch ivt = (ImageViewTouch) v; + try { + return ivt.canScroll(dx); + } catch (NullPointerException e) { + // bad image, etc + return false; + } + } else { + return super.canScroll(v, checkV, dx, x, y); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return super.onTouchEvent(event); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return super.onInterceptTouchEvent(event); + } +} \ No newline at end of file -- cgit v1.2.3