package monakhv.android.samlib; import android.app.AlertDialog; import android.app.Dialog; import android.content.*; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; 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.EditText; import android.widget.ProgressBar; import android.widget.TextView; import in.srain.cube.views.ptr.PtrClassicFrameLayout; import in.srain.cube.views.ptr.PtrDefaultHandler; import in.srain.cube.views.ptr.PtrFrameLayout; import in.srain.cube.views.ptr.PtrHandler; import monakhv.android.samlib.adapter.AuthorAdapter; import monakhv.android.samlib.adapter.AuthorAnimator; import monakhv.android.samlib.adapter.AuthorLoader; import monakhv.android.samlib.adapter.RecyclerAdapter; import monakhv.android.samlib.dialogs.ContextMenuDialog; import monakhv.android.samlib.dialogs.EnterStringDialog; import monakhv.android.samlib.dialogs.MyMenuData; import monakhv.android.samlib.recyclerview.DividerItemDecoration; import monakhv.android.samlib.service.MessageConstructor; import monakhv.samlib.service.AuthorGuiState; import monakhv.samlib.service.GuiUpdateObject; import monakhv.android.samlib.service.UpdateLocalService; import monakhv.android.samlib.sortorder.AuthorSortOrder; import monakhv.samlib.db.entity.Author; import monakhv.samlib.db.entity.SamLibConfig; import monakhv.samlib.log.Log; import rx.Subscriber; import java.util.List; import static monakhv.android.samlib.ActivityUtils.getClipboardText; /* * 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/5/14. */ public class AuthorFragment extends MyBaseAbstractFragment implements PtrHandler, ListSwipeListener.SwipeCallBack, RecyclerAdapter.CallBack, LoaderManager.LoaderCallbacks<List<Author>> { AuthorGuiState getGuiState() { return new AuthorGuiState(selectedTag, order.getOrder()); } interface Callbacks { void onAuthorSelected(long id); void cleanBookSelection(); void drawerToggle(); } private static final String DEBUG_TAG = "AuthorFragment"; private static final int AUTHOR_LOADER_ID = 201; private RecyclerView authorRV; private ProgressBar mProgressBar; private AuthorAdapter adapter; private AuthorSortOrder order; private GestureDetector detector; private PtrClassicFrameLayout mPtrFrame; private boolean updateAuthor = false;//true update the only selected author private Author author = null;//for context menu selection private TextView emptyTagAuthor; private ContextMenuDialog contextMenu; private View empty; private boolean canUpdate; //private SettingsHelper settingsHelper; private int selectedTag = SamLibConfig.TAG_AUTHOR_ALL; private int aId = -1;//preserve selection private boolean mBound; private int mAppBarOffset; private UpdateLocalService mUpdateService; private Menu mMenu; private MessageConstructor mMessageConstructor; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { UpdateLocalService.LocalBinder binder = (UpdateLocalService.LocalBinder) service; mUpdateService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; void setAppBarOffset(int appBarOffset) { mAppBarOffset = appBarOffset; } @Override public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { return canUpdate && (mAppBarOffset == 0) && (adapter.getItemCount() != 0) && (mUpdateService != null) && !mUpdateService.isRunning() && PtrDefaultHandler.checkContentCanBePulledDown(frame, content, header); } private Callbacks mCallbacks; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(DEBUG_TAG, "onCreate"); if (savedInstanceState != null) { Log.d(DEBUG_TAG,"onCreate: Have not NULL Statel "); } order = AuthorSortOrder.valueOf(getSettingsHelper().getAuthorSortOrderString()); detector = new GestureDetector(getActivity(), new ListSwipeListener(this)); Intent service = new Intent(getActivity(), UpdateLocalService.class); getActivity().bindService(service, mConnection, Context.BIND_AUTO_CREATE); mMessageConstructor = new MessageConstructor(getActivity(), getSettingsHelper()); } @Override public void onAttach(Context activity) { super.onAttach(activity); if (!(activity instanceof Callbacks)) { throw new IllegalStateException( "Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) activity; } private View view; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d(DEBUG_TAG, "onCreateView"); canUpdate = true; view = inflater.inflate(R.layout.author_fragment, container, false); authorRV = (RecyclerView) view.findViewById(R.id.authorRV); empty = view.findViewById(R.id.add_author_panel); mProgressBar = (ProgressBar) view.findViewById(R.id.authorProgress); emptyTagAuthor = (TextView) view.findViewById(R.id.emptyTagAuthor); authorRV.setHasFixedSize(true); final LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getActivity()); authorRV.setLayoutManager(mLinearLayoutManager); authorRV.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); makePulToRefresh(); authorRV.setOnTouchListener((v, event) -> { detector.onTouchEvent(event); return false; }); adapter = new AuthorAdapter(this); authorRV.setAdapter(adapter); mProgressBar.setVisibility(View.VISIBLE); authorRV.setVisibility(View.GONE); empty.setVisibility(View.GONE); emptyTagAuthor.setVisibility(View.GONE); getLoaderManager().initLoader(AUTHOR_LOADER_ID, null, this); authorRV.setItemAnimator(new AuthorAnimator()); return view; } /** * Create initialization for pull to Refresh interface */ private void makePulToRefresh() { mPtrFrame = (PtrClassicFrameLayout) view.findViewById(R.id.ptr_frame); mPtrFrame.setPtrHandler(this); mPtrFrame.setLastUpdateTimeKey(UpdateLocalService.PREF_NAME, UpdateLocalService.PREF_KEY_LAST_UPDATE); } @Override public void makeNewFlip(Author a) { getSamlibOperation().makeAuthorRead(a, getGuiState()); } @Override public Loader<List<Author>> onCreateLoader(int id, Bundle args) { return new AuthorLoader(getActivity(), getAuthorController(), selectedTag, order.getOrder()); } @Override public void onLoadFinished(Loader<List<Author>> loader, List<Author> data) { adapter.setData(data); mProgressBar.setVisibility(View.GONE); checkEmptyViewState(); } private void checkEmptyViewState(){ if (adapter.getItemCount() == 0) { authorRV.setVisibility(View.GONE); if (selectedTag == SamLibConfig.TAG_AUTHOR_ALL) { empty.setVisibility(View.VISIBLE); emptyTagAuthor.setVisibility(View.GONE); } else { empty.setVisibility(View.GONE); emptyTagAuthor.setVisibility(View.VISIBLE); } } else { empty.setVisibility(View.GONE); emptyTagAuthor.setVisibility(View.GONE); authorRV.setVisibility(View.VISIBLE); if (aId > 0) { selectAuthor(aId); aId = -1; } } } @Override public void onLoaderReset(Loader<List<Author>> loader) { adapter.setData(null); } /** * Update Author List preserve currently selected Author */ private void updateAdapter() { if (adapter.getSelected() != null) { aId = adapter.getSelected().getId(); } getLoaderManager().restartLoader(AUTHOR_LOADER_ID, null, this); } @Override public void onRefreshBegin(PtrFrameLayout view) { Log.d(DEBUG_TAG, "onRefreshBegin: Start update service"); adapter.cleanSelection();//clean selection before check updates canUpdate = false; isProgressShow(true); if (getActivity() == null) { return;//try to prevent some ANR reports } if (updateAuthor) { updateAuthor(author); //UpdateServiceIntent.makeUpdateAuthor(getActivity(), author.getId()); } else { updateTag(); //UpdateServiceIntent.makeUpdate(getActivity(), selectedTag); } } /** * Stop GUI update state * Call - from on pause * call - from Main activity as a result for broadcast */ private void onRefreshComplete() { Log.d(DEBUG_TAG, "onRefreshComplete: Stop updating state"); mPtrFrame.refreshComplete(); canUpdate = true; updateAuthor = false; isProgressShow(false); checkEmptyViewState(); } /** * Change view for refresh menu item * Progress and static icon switcher * * @param isShow if true we show progress */ private void isProgressShow(boolean isShow) { if (mMenu == null) { return; } MenuItem item = mMenu.findItem(R.id.menu_refresh); if (item != null) { if (isShow) { item.setActionView(R.layout.actionbar_indeterminate_progress); } else { item.setActionView(null); } } } @Override public boolean singleClick(MotionEvent e) { authorRV.playSoundEffect(SoundEffectConstants.CLICK); int position = authorRV.getChildAdapterPosition(authorRV.findChildViewUnder(e.getX(), e.getY())); Log.d(DEBUG_TAG, "singleClick: Selected position: " + position); adapter.toggleSelection(position); authorRV.refreshDrawableState(); final Author author1 = adapter.getSelected(); if (author1 == null) { Log.e(DEBUG_TAG, "singleClick: position: " + position + " Author is NULL"); return false; } mCallbacks.onAuthorSelected(author1.getId()); return true; //return false; } @Override public boolean swipeRight(MotionEvent e) { int position = authorRV.getChildAdapterPosition(authorRV.findChildViewUnder(e.getX(), e.getY())); adapter.toggleSelection(position, false); author = adapter.getSelected(); if (author == null) { return false; } adapter.makeSelectedRead(); return true; } @Override public boolean swipeLeft(MotionEvent e) { int position = authorRV.getChildAdapterPosition(authorRV.findChildViewUnder(e.getX(), e.getY())); adapter.toggleSelection(position); author = adapter.getSelected(); adapter.cleanSelection(); if (author == null) { return false; } launchBrowser(author); return true; } private final int read_option_item = 21; private final int browser_option_item = 23; private final int edit_author_option_item = 24; private final int delete_option_item = 25; private final int update_option_item = 35; @Override public void longPress(MotionEvent e) { final int position = authorRV.getChildAdapterPosition(authorRV.findChildViewUnder(e.getX(), e.getY())); adapter.toggleSelection(position); author = adapter.getSelected(); if (author == null) { return; } final MyMenuData menu = new MyMenuData(); if (author.isIsNew()) { menu.add(read_option_item, getString(R.string.menu_read)); } menu.add(browser_option_item, getString(R.string.menu_open_web)); menu.add(edit_author_option_item, getString(R.string.menu_edit)); menu.add(delete_option_item, getString(R.string.menu_delete)); menu.add(update_option_item, getString(R.string.menu_refresh)); contextMenu = ContextMenuDialog.getInstance(menu, (parent, view1, position1, id) -> { int item = menu.getIdByPosition(position1); contextSelector(item, position); contextMenu.dismiss(); }, author.getName()); contextMenu.show(getActivity().getSupportFragmentManager(), "authorContext"); } private void contextSelector(int item, int position) { if (item == delete_option_item) { Dialog alert = createDeleteAuthorAlert(author.getName()); alert.show(); } if (item == read_option_item) { adapter.makeSelectedRead(); } if (item == browser_option_item) { launchBrowser(author); } if (item == update_option_item) { updateAuthor = true; startRefresh(); } if (item == edit_author_option_item) { EnterStringDialog dDialog = new EnterStringDialog(getActivity(), txt -> { author.setName(txt); getAuthorController().update(author); adapter.notifyChange(author, position); }, getText(R.string.dialog_title_edit_author).toString(), author.getName()); dDialog.show(); } } private void startRefresh() { if (canUpdate) { mPtrFrame.performRefresh(); } } /** * Launch Browser to load Author home page * * @param a Author object */ private void launchBrowser(Author a) { Uri uri = Uri.parse(a.getUrlForBrowser(getSettingsHelper())); Intent launchBrowser = new Intent(Intent.ACTION_VIEW, uri); getActivity().startActivity(launchBrowser); } /** * Create Alert Dialog to wrn about Author delete * * @param authorName Name of the author * @return Warning Author delete dialog */ private Dialog createDeleteAuthorAlert(String authorName) { AlertDialog.Builder adb = new AlertDialog.Builder(getActivity()); adb.setTitle(R.string.Attention); String msg = getString(R.string.alert_delete_author); msg = msg.replaceAll("__", authorName); adb.setMessage(msg); adb.setIcon(android.R.drawable.ic_dialog_alert); adb.setPositiveButton(R.string.Yes, deleteAuthorListener); adb.setNegativeButton(R.string.No, deleteAuthorListener); return adb.create(); } private final DialogInterface.OnClickListener deleteAuthorListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { switch (which) { case Dialog.BUTTON_POSITIVE: if (author != null) { getSamlibOperation().makeAuthorDel(author, getGuiState()); mCallbacks.cleanBookSelection(); } break; case Dialog.BUTTON_NEGATIVE: break; } } }; void searchOrAdd() { empty.setVisibility(View.VISIBLE); String txt = null; try { txt = getClipboardText(getActivity()); } catch (Exception ex) { Log.e(DEBUG_TAG, "Clipboard Error!", ex); } if (txt != null) { if (SamLibConfig.getParsedUrl(txt) != null) { EditText editText = (EditText) getActivity().findViewById(R.id.addUrlText); editText.setText(txt); } } } /** * Get selection string for author search * * @return Selected ag */ int getSelection() { return selectedTag; } /** * Restore Author selection * * @param id id of Author to make selected */ private void selectAuthor(long id) { Log.d(DEBUG_TAG, "selectAuthor: id = " + id); int pos = adapter.findAndSelect(id); if (pos < 0) { Log.e(DEBUG_TAG, "selectAuthor: id not found - " + id); return; } authorRV.smoothScrollToPosition(pos); } void cleanSelection() { adapter.cleanSelection(); } /** * update sort order and selection parameters and restart loader * * @param selectedTag selection tag id * @param so sort order string */ void selectTag(int selectedTag, AuthorSortOrder so) { Log.d(DEBUG_TAG, "selectTag: set Selection: " + selectedTag); cleanSelection(); this.selectedTag = selectedTag; if (so != null) { order = so; } updateAdapter(); } Subscriber<GuiUpdateObject> getSubscriber(){ return new Subscriber<GuiUpdateObject>() { @Override public void onCompleted() { Log.i(DEBUG_TAG, "onCompleted"); onRefreshComplete(); } @Override public void onError(Throwable e) { Log.e(DEBUG_TAG, "onError", e); } @Override public void onNext(GuiUpdateObject guiUpdateObject) { if (guiUpdateObject.isAuthor()) { adapter.cleanSelection(); Author author = (Author) guiUpdateObject.getObject(); Log.d(DEBUG_TAG,"onNext: get Author: "+author.getName()+" new: "+author.isIsNew()); int sort = guiUpdateObject.getSortOrder(); if (sort == -1) { getSamlibOperation().makeAuthorReload(author,getGuiState()); return; } GuiUpdateObject.UpdateType updateType = guiUpdateObject.getUpdateType(); switch (updateType) { case DELETE: adapter.remove(sort); break; case ADD: adapter.add(author, sort); authorRV.scrollToPosition(sort); break; default: LinearLayoutManager llm = (LinearLayoutManager) authorRV.getLayoutManager(); int firstPosition = llm.findFirstVisibleItemPosition(); adapter.notifyChange(author, sort); authorRV.scrollToPosition(firstPosition); } //adapter.toggleSelection(sort); //authorRV.scrollToPosition(sort); //Log.d(DEBUG_TAG, "updateAdapter: scroll to position: " + sort); } if (guiUpdateObject.isResult()) { mMessageConstructor.showMessage(guiUpdateObject); onRefreshComplete(); } } }; } @Override public void refresh() { Log.d(DEBUG_TAG, "refresh: call "); updateAdapter(); } /** * set sort order and restart loader to make is actual * * @param so new sort order */ void setSortOrder(AuthorSortOrder so) { cleanSelection(); order = so; updateAdapter(); } AuthorSortOrder getSortOrder() { return order; } @Override public void onDestroy() { Log.d(DEBUG_TAG, "onDestroy call"); if (mBound) { getActivity().unbindService(mConnection); mBound = false; } super.onDestroy(); getLoaderManager().destroyLoader(AUTHOR_LOADER_ID); } @Override public void onResume() { super.onResume(); Log.d(DEBUG_TAG, "onResume call"); canUpdate = true; if (mBound) { isProgressShow(mUpdateService.isRunning()); if (!mUpdateService.isRunning()) { mPtrFrame.refreshComplete(); } } } private void updateTag() { UpdateLocalService.makeUpdate(getActivity(), null, getGuiState()); } private void updateAuthor(Author author) { UpdateLocalService.makeUpdate(getContext(), author, getGuiState()); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.options_menu, menu); mMenu = menu; if (mBound) { isProgressShow(mUpdateService.isRunning()); } } @Override public boolean onOptionsItemSelected(MenuItem item) { int sel = item.getItemId(); if (sel == android.R.id.home) { mCallbacks.drawerToggle(); } if (sel == R.id.menu_refresh) { startRefresh(); } if (sel == R.id.selected_option_item) { Log.d(DEBUG_TAG, "go to Selected"); //cleanSelection(); mCallbacks.onAuthorSelected(SamLibConfig.SELECTED_BOOK_ID); } return super.onOptionsItemSelected(item); } }