package monakhv.android.samlib; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.*; import android.widget.AdapterView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import monakhv.android.samlib.adapter.*; import monakhv.android.samlib.data.DataExportImport; import monakhv.android.samlib.dialogs.ContextMenuDialog; import monakhv.android.samlib.dialogs.MyMenuData; import monakhv.android.samlib.dialogs.SingleChoiceSelectDialog; import monakhv.android.samlib.recyclerview.DividerItemDecoration; import monakhv.samlib.service.AuthorGuiState; import monakhv.samlib.service.BookGuiState; import monakhv.samlib.service.GuiUpdateObject; import monakhv.android.samlib.sortorder.BookSortOrder; import monakhv.samlib.db.AuthorController; import monakhv.samlib.db.entity.Book; import monakhv.samlib.db.entity.GroupBook; import monakhv.samlib.db.entity.SamLibConfig; import monakhv.samlib.log.Log; import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import java.util.List; /* * Copyright 2014 Dmitry Monakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * 12/11/14. */ public class BookFragment extends MyBaseAbstractFragment implements ListSwipeListener.SwipeCallBack, LoaderManager.LoaderCallbacks<List<GroupListItem>>, BookExpandableAdapter.CallBack { interface Callbacks { AuthorGuiState getAuthorGuiState(); void showTags(long author_id); } @Override public void makeBookNewFlip(Book book) { getSamlibOperation().makeBookReadFlip(book, new BookGuiState((int) author_id, order.getOrder()), mCallbacks.getAuthorGuiState()); } @Override public void makeGroupNewFlip(GroupBook groupBook) { getSamlibOperation().makeGroupReadFlip(groupBook, new BookGuiState((int) author_id, order.getOrder()), mCallbacks.getAuthorGuiState()); } private static final String DEBUG_TAG = "BookFragment"; static final String AUTHOR_ID = "AUTHOR_ID"; static final String ADAPTER_STATE_EXTRA = "BookFragment.ADAPTER_STATE_EXTRA"; private static final int BOOK_LOADER_ID = 190; private RecyclerView bookRV; private long author_id; private BookExpandableAdapter adapter; private Bundle adapterState; private int mScrollPosition; private Book book = null;//for context menu private BookSortOrder order; private GestureDetector detector; private ProgressDialog progress; private ProgressBar mProgressBar; private ContextMenuDialog contextMenuDialog; private TextView emptyText; private DataExportImport dataExportImport; private SingleChoiceSelectDialog dialog = null; private Callbacks mCallbacks; private AuthorController sql; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(DEBUG_TAG, "onCreate call"); adapterState = null; if (getActivity().getIntent().getExtras() == null) { author_id = 0; } else { author_id = getActivity().getIntent().getExtras().getLong(AUTHOR_ID, 0); } Log.i(DEBUG_TAG, "onCreate: author_id = " + author_id); detector = new GestureDetector(getActivity(), new ListSwipeListener(this)); order = BookSortOrder.valueOf(getSettingsHelper().getBookSortOrderString()); dataExportImport = new DataExportImport(getSettingsHelper()); } @Override public void onAttach(Context activity) { super.onAttach(activity); Log.i(DEBUG_TAG, "onAttach call"); if (!(activity instanceof BookFragment.Callbacks)) { throw new IllegalStateException( "Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) activity; sql = getAuthorController(); } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.i(DEBUG_TAG, "onCreateView call"); View view = inflater.inflate(R.layout.book_fragment, container, false); Log.i(DEBUG_TAG, "Done making view"); bookRV = (RecyclerView) view.findViewById(R.id.bookRV); emptyText = (TextView) view.findViewById(R.id.id_empty_book_text); mProgressBar = (ProgressBar) view.findViewById(R.id.bookProgress); adapter = new BookExpandableAdapter(GroupListItem.EMPTY, -1, getActivity(), this, getSettingsHelper()); adapter.setAuthor_id(author_id); bookRV.setHasFixedSize(true); bookRV.setLayoutManager(new LinearLayoutManager(getActivity())); bookRV.setAdapter(adapter); bookRV.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); bookRV.setOnTouchListener((v, event) -> { detector.onTouchEvent(event); return false; }); emptyText.setVisibility(View.GONE); bookRV.setVisibility(View.GONE); mProgressBar.setVisibility(View.VISIBLE); getLoaderManager().initLoader(BOOK_LOADER_ID, null, this); bookRV.setItemAnimator(new BookAnimator()); return view; } @Override public Loader<List<GroupListItem>> onCreateLoader(int id, Bundle args) { Log.d(DEBUG_TAG, "onCreateLoader call"); return new BookLoader(getActivity(), getAuthorController(), author_id, order.getOrder()); } @Override public void onLoadFinished(Loader<List<GroupListItem>> loader, List<GroupListItem> data) { //adapter.setData(data); BookLoader bookLoader = (BookLoader) loader; adapter = new BookExpandableAdapter(data, bookLoader.getMaxGroupId(), getActivity(), this, getSettingsHelper()); adapter.setAuthor_id(author_id); bookRV.setAdapter(adapter); Log.d(DEBUG_TAG, "onLoadFinished: adapter size = " + adapter.getItemCount()+" Order: "+bookLoader.getOrder()); mProgressBar.setVisibility(View.GONE); makeEmpty(); if (adapterState != null && !adapterState.isEmpty()) { Log.d(DEBUG_TAG, "onLoadFinished: load adapter data"); adapter.onRestoreInstanceState(adapterState); adapterState.clear(); LinearLayoutManager lm = (LinearLayoutManager) bookRV.getLayoutManager(); lm.scrollToPosition(mScrollPosition); } } @Override public void onLoaderReset(Loader<List<GroupListItem>> loader) { //adapter.setData(null); adapter = new BookExpandableAdapter(GroupListItem.EMPTY, -1, getActivity(), this, getSettingsHelper()); bookRV.setAdapter(adapter); } /** * Make empty text view */ private void makeEmpty() { if (adapter.getItemCount() == 0) { bookRV.setVisibility(View.GONE); emptyText.setVisibility(View.VISIBLE); if (author_id == SamLibConfig.SELECTED_BOOK_ID) { emptyText.setText(R.string.no_selected_books); } else { emptyText.setText(R.string.no_new_books); } } else { bookRV.setVisibility(View.VISIBLE); emptyText.setVisibility(View.GONE); } } private void updateAdapter() { Log.d(DEBUG_TAG, "updateAdapter call"); //very ugly hack if (order == null) { Context ctx = getActivity().getApplicationContext(); if (ctx == null) { Log.e(DEBUG_TAG, "Context is NULL"); } order = BookSortOrder.valueOf(getSettingsHelper().getBookSortOrderString()); } getLoaderManager().restartLoader(BOOK_LOADER_ID, null, this); } private void updateAdapter(Book book) { getSamlibOperation().makeBookReload(book, getBookGuiState()); } Subscriber<GuiUpdateObject> getSubscriber() { return new Subscriber<GuiUpdateObject>() { @Override public void onCompleted() { Log.i(DEBUG_TAG, "onCompleted"); } @Override public void onError(Throwable e) { Log.e(DEBUG_TAG, "onError", e); } @Override public void onNext(GuiUpdateObject guiUpdateObject) { if (guiUpdateObject.isBook()) { Book b = (Book) guiUpdateObject.getObject(); GroupBook groupBook = b.getGroupBook(); Log.d(DEBUG_TAG, "onNext: get Book: " + b.getUri() + " groupId: " + groupBook.getId() + " sort: " + guiUpdateObject.getSortOrder()); adapter.updateData(b, groupBook, guiUpdateObject.getSortOrder()); } if (guiUpdateObject.isGroup()) {//begin group Log.d(DEBUG_TAG, "onNext: get Group"); GroupListItem groupListItem; GroupBook groupBook = (GroupBook) guiUpdateObject.getObject(); if (groupBook.getBooks() == null) { Log.d(DEBUG_TAG, "onNext: make group reload: " + groupBook.getName()); getSamlibOperation().makeGroupReload(groupBook, getBookGuiState()); return; } groupListItem = new GroupListItem(groupBook); Log.d(DEBUG_TAG, "onNext: get Group: " + groupBook.getName() + " id: " + groupBook.getId() + " NewNumber: " + groupBook.getNewNumber()); adapter.updateData(groupListItem, guiUpdateObject.getSortOrder()); }//end group } }; } private BookGuiState getBookGuiState() { return new BookGuiState((int) author_id, order.getOrder()); } /** * Set new author_id and update selection,adapter and empty view * * @param id Author id or special parameters */ void setAuthorId(long id) { emptyText.setVisibility(View.GONE); bookRV.setVisibility(View.GONE); mProgressBar.setVisibility(View.VISIBLE); author_id = id; adapter.setAuthor_id(id); updateAdapter(); adapter.cleanSelection(); } @Override public boolean singleClick(MotionEvent e) { int position = bookRV.getChildAdapterPosition(bookRV.findChildViewUnder(e.getX(), e.getY())); if (position < 0) { return true; } try { if (adapter.getItemViewType(position) == BookExpandableAdapter.TYPE_PARENT) { adapter.flipCollapse(position); return true; } } catch (IllegalStateException ex) { Log.w(DEBUG_TAG, "singleClick: wrong state: " + position, ex); } book = adapter.getBook(position); if (book == null) { Log.e(DEBUG_TAG, "singleClick: null book error position = " + position); return false; } selected_position = position; loadBook(book); bookRV.playSoundEffect(SoundEffectConstants.CLICK); return true; } @Override public boolean swipeRight(MotionEvent e) { Log.v(DEBUG_TAG, "making swipeRight"); int position = bookRV.getChildAdapterPosition(bookRV.findChildViewUnder(e.getX(), e.getY())); adapter.makeAllRead(position); return true; } @Override public boolean swipeLeft(MotionEvent e) { int position = bookRV.getChildAdapterPosition(bookRV.findChildViewUnder(e.getX(), e.getY())); adapter.toggleSelection(position); book = adapter.getSelected(); if (book == null) { return false; } launchBrowser(book); return true; } private final int menu_mark_read = 1; private final int menu_browser = 2; private final int menu_selected = 3; private final int menu_deselected = 4; private final int menu_reload = 10; private final int menu_fixed = 6; private final int menu_choose_version = 7; private int selected_position; @Override public void longPress(MotionEvent e) { int position = bookRV.getChildAdapterPosition(bookRV.findChildViewUnder(e.getX(), e.getY())); adapter.toggleSelection(position); book = adapter.getSelected(); if (book == null) { return; } selected_position = position; final MyMenuData menu = new MyMenuData(); if (book.isIsNew()) { menu.add(menu_mark_read, getString(R.string.menu_read)); } menu.add(menu_browser, getString(R.string.menu_open_web)); if (book.isSelected()) { menu.add(menu_deselected, getString(R.string.menu_deselected)); } else { menu.add(menu_selected, getString(R.string.menu_selected)); } menu.add(menu_reload, getString(R.string.menu_reload)); if (book.isPreserve()) { menu.add(menu_fixed, getString(R.string.menu_set_unfixed)); menu.add(menu_choose_version, getString(R.string.menu_version_choose)); } else { menu.add(menu_fixed, getString(R.string.menu_set_fixed)); } contextMenuDialog = ContextMenuDialog.getInstance(menu, (parent, view, position1, id) -> { int item = menu.getIdByPosition(position1); contextSelector(item); contextMenuDialog.dismiss(); }, null); contextMenuDialog.show(getActivity().getSupportFragmentManager(), "bookContext"); } private void contextSelector(int item) { if (item == menu_browser) { launchBrowser(book); } if (item == menu_mark_read) { adapter.makeRead(selected_position); } if (item == menu_selected) { sql.getBookController().setSelected(book); updateAdapter(book); } if (item == menu_deselected) { sql.getBookController().setDeselected(book); updateAdapter(book); } if (item == menu_reload) { getSettingsHelper().cleanBookFile(book); loadBook(book); } if (item == menu_fixed) { if (book.isPreserve()) { Log.i(DEBUG_TAG, "remove preserved mark for book " + book.getUri()); book.setPreserve(false); } else { Log.i(DEBUG_TAG, "making book preserved " + book.getUri()); getSettingsHelper().makePreserved(book); book.setPreserve(true); } sql.getBookController().update(book); updateAdapter(book); } if (item == menu_choose_version) { book.setFileType(getSettingsHelper().getFileType()); final String[] files = getSettingsHelper().getBookFileVersions(book); if (files.length == 0L) { Log.i(DEBUG_TAG, "file is NULL"); return; } dialog = SingleChoiceSelectDialog.getInstance(files, (parent, view, position, id) -> { dialog.dismiss(); String file = files[position]; launchReader(book, file); }, getString(R.string.menu_version_choose)); dialog.show(getActivity().getSupportFragmentManager(), "readVersion"); } } /** * Launch Browser to load book from web server * * @param book book to read */ private void launchBrowser(Book book) { String sUrl = book.getUrlForBrowser(getSettingsHelper()); Log.d(DEBUG_TAG, "book url: " + sUrl); Uri uri = Uri.parse(sUrl); Intent launchBrowser = new Intent(Intent.ACTION_VIEW, uri); if (getSettingsHelper().getAutoMarkFlag() && book.isIsNew()) { adapter.makeRead(selected_position); } startActivity(launchBrowser); } private SingleChoiceSelectDialog sortDialog; /** * Show Dialog to select sort order for Book list */ private void selectSortOrder() { AdapterView.OnItemClickListener listener = (parent, view, position, id) -> { BookSortOrder so = BookSortOrder.values()[position]; setSortOrder(so); sortDialog.dismiss(); }; sortDialog = SingleChoiceSelectDialog.getInstance(BookSortOrder.getTitles(getActivity()), listener, this.getString(R.string.dialog_title_sort_book), getSortOrder().ordinal()); sortDialog.show(getActivity().getSupportFragmentManager(), "DoBookSortDialog"); } private void setSortOrder(BookSortOrder so) { order = so; updateAdapter(); } private BookSortOrder getSortOrder() { return order; } private void loadBook(Book book) { book.setFileType(getSettingsHelper().getFileType()); if (dataExportImport.needUpdateFile(book)) { progress = new ProgressDialog(getActivity()); progress.setMessage(getActivity().getText(R.string.download_Loading)); progress.setCancelable(true); //progress.setIndeterminate(true); progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.setMax((int) book.getSize()); progress.show(); //DownloadBookService.start(getActivity(), book.getId(), false); final Subscription subscription = getBookDownloadService().downloadBook(book) .distinctUntilChanged() .onBackpressureBuffer() .observeOn(AndroidSchedulers.mainThread()) .subscribe(getBookDownloadProgress()); addSubscription(subscription); } else { launchReader(book); } } private Subscriber<Integer> getBookDownloadProgress() { return new Subscriber<Integer>() { @Override public void onCompleted() { progress.setProgress(progress.getMax()); progress.dismiss(); launchReader(book); } @Override public void onError(Throwable e) { progress.dismiss(); Log.e(DEBUG_TAG, "onError", e); Toast toast = Toast.makeText(getActivity(), getActivity().getText(R.string.download_book_error), Toast.LENGTH_SHORT); toast.show(); } @Override public void onNext(Integer integer) { progress.setProgress(integer); //Log.d(DEBUG_TAG,"onNext: "+integer); } }; } /** * Launch Reader to read the book considering book is downloaded * * @param book the book to read */ private void launchReader(Book book) { launchReader(book, null); } /** * Launch Reader to read the book considering book is downloaded * To read given version * * @param book the book to read * @param file version file name */ private void launchReader(Book book, String file) { String url; if (file == null) { url = getSettingsHelper().getBookFileURL(book); } else { url = getSettingsHelper().getBookFileURL(book, file); } Intent launchBrowser = new Intent(); launchBrowser.setAction(android.content.Intent.ACTION_VIEW); launchBrowser.setDataAndType(Uri.parse(url), book.getFileMime()); if (launchBrowser.resolveActivity(getActivity().getPackageManager()) == null) { Toast toast = Toast.makeText(getContext(), getString(R.string.no_such_application_to_view), Toast.LENGTH_SHORT); toast.show(); return; } if (getSettingsHelper().getAutoMarkFlag() && book.isIsNew()) { adapter.makeRead(selected_position); } startActivity(launchBrowser); } @Override public void onDestroy() { super.onDestroy(); Log.d(DEBUG_TAG, "onDestroy call"); getLoaderManager().destroyLoader(BOOK_LOADER_ID); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.books_menu, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { int sel = item.getItemId(); if (sel == R.id.menu_books_tags && author_id > 0) { mCallbacks.showTags(author_id); } if (sel == R.id.menu_books_sort) { selectSortOrder(); } return super.onOptionsItemSelected(item); } @Override public void onResume() { super.onResume(); Log.d(DEBUG_TAG, "onResume call"); } @Override public void onPause() { super.onPause(); Log.d(DEBUG_TAG, "onPause call"); if (adapterState == null) { adapterState = new Bundle(); } adapter.onSaveInstanceState(adapterState); LinearLayoutManager lm = (LinearLayoutManager) bookRV.getLayoutManager(); mScrollPosition=lm.findFirstVisibleItemPosition(); } Bundle getAdapterState() { return adapterState; } void setAdapterState(Bundle adapterState) { this.adapterState = adapterState; } }