package com.door43.translationstudio.newui.translate;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.door43.tools.reporting.Logger;
import com.door43.translationstudio.R;
import com.door43.translationstudio.core.Library;
import com.door43.translationstudio.core.SourceTranslation;
import com.door43.translationstudio.core.TargetTranslation;
import com.door43.translationstudio.core.TranslationViewMode;
import com.door43.translationstudio.core.Translator;
import com.door43.translationstudio.newui.BaseFragment;
import com.door43.translationstudio.AppContext;
import org.json.JSONException;
import java.security.InvalidParameterException;
/**
* Created by joel on 9/18/2015.
*/
public abstract class ViewModeFragment extends BaseFragment implements ViewModeAdapter.OnEventListener, ChooseSourceTranslationDialog.OnClickListener {
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private ViewModeAdapter mAdapter;
private boolean mFingerScroll = false;
private OnEventListener mListener;
private TargetTranslation mTargetTranslation;
private Translator mTranslator;
private Library mLibrary;
private String mSourceTranslationId;
private GestureDetector mGesture;
private boolean mRememberLastPosition = true;
/**
* Returns an instance of the adapter
* @param activity
* @param targetTranslationId
* @param sourceTranslationId
* @return
*/
abstract ViewModeAdapter generateAdapter(Activity activity, String targetTranslationId, String sourceTranslationId, String chapterId, String frameId, Bundle extras);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_stacked_card_list, container, false);
mLibrary = AppContext.getLibrary();
mTranslator = AppContext.getTranslator();
Bundle args = getArguments();
String targetTranslationId = args.getString(AppContext.EXTRA_TARGET_TRANSLATION_ID, null);
mTargetTranslation = mTranslator.getTargetTranslation(targetTranslationId);
if(mTargetTranslation == null) {
Logger.e(getClass().getName() ,"A valid target translation id is required. Received '" + targetTranslationId + "' but the translation could not be found");
getActivity().finish();
}
String chapterId = args.getString(AppContext.EXTRA_CHAPTER_ID, AppContext.getLastFocusChapterId(targetTranslationId));
String frameId = args.getString(AppContext.EXTRA_FRAME_ID, AppContext.getLastFocusFrameId(targetTranslationId));
// check if we have draft source
String draftTranslationId = args.getString(AppContext.EXTRA_SOURCE_DRAFT_TRANSLATION_ID, null);
if(null != draftTranslationId) {
SourceTranslation sourceTranslation = mLibrary.getDraftTranslation(draftTranslationId);
mSourceTranslationId = sourceTranslation.getId();
} else {
// open selected tab
mSourceTranslationId = AppContext.getSelectedSourceTranslationId(targetTranslationId);
}
if(null == mSourceTranslationId) {
mListener.onNoSourceTranslations(targetTranslationId);
} else {
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = generateAdapter(this.getActivity(), targetTranslationId, mSourceTranslationId, chapterId, frameId, args);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
mFingerScroll = true;
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mFingerScroll) {
int position = mLayoutManager.findFirstVisibleItemPosition();
mListener.onScrollProgress(position);
}
}
});
mListener.onItemCountChanged(mAdapter.getItemCount(), 0);
mAdapter.setOnClickListener(this);
if(savedInstanceState == null) {
mLayoutManager.scrollToPosition(mAdapter.getListStartPosition());
mListener.onScrollProgress(mAdapter.getListStartPosition());
}
}
mGesture = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
public MotionEvent mLastOnDownEvent;
private final float SWIPE_THRESHOLD_VELOCITY = 20f;
private final float SWIPE_MIN_DISTANCE = 50f;
private final float SWIPE_MAX_ANGLE_DEG = 30;
@Override
public boolean onDown(MotionEvent e) {
mLastOnDownEvent = e;
return super.onDown(e);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if(e1 == null) {
e1 = mLastOnDownEvent;
}
try {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
// don't handle vertical swipes (division error)
if (distanceX == 0) return false;
double flingAngle = Math.toDegrees(Math.asin(Math.abs(distanceY / distanceX)));
if (flingAngle <= SWIPE_MAX_ANGLE_DEG && Math.abs(distanceX) >= SWIPE_MIN_DISTANCE && Math.abs(velocityX) >= SWIPE_THRESHOLD_VELOCITY) {
if (distanceX > 0) {
onRightSwipe(e1, e2);
} else {
onLeftSwipe(e1, e2);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
});
// let child classes modify the view
onPrepareView(rootView);
return rootView;
}
/**
* Called when the user performs a swipe towards the right
* @param e1
* @param e2
*/
protected void onRightSwipe(MotionEvent e1, MotionEvent e2) {
}
/**
* Called when the user performs a swipe towards the left
* @param e1
* @param e2
*/
protected void onLeftSwipe(MotionEvent e1, MotionEvent e2) {
}
/**
* Returns the currently selected source translation
* @return
*/
protected SourceTranslation getSourceTranslation() {
return mLibrary.getSourceTranslation(mSourceTranslationId);
}
/**
* Scrolls to the given frame
* @param chapterId
* @param frameId
*/
public void scrollToFrame(String chapterId, String frameId) {
closeKeyboard();
int position = mAdapter.getItemPosition(chapterId, frameId);
if(position != -1) {
mLayoutManager.scrollToPosition(position);
mListener.onScrollProgress(position);
}
}
/**
* Returns the adapter position of a view holder under the coordinates
* @param x
* @param y
* @return
*/
protected int findViewHolderAdapterPosition(float x, float y) {
if(mRecyclerView != null) {
View view = mRecyclerView.findChildViewUnder(x, y);
return mRecyclerView.getChildAdapterPosition(view);
} else {
return 0;
}
}
/**
* Returns a viewholder item for the adapter position
* @param position
* @return
*/
protected RecyclerView.ViewHolder getViewHolderForAdapterPosition(int position) {
if(mRecyclerView != null) {
return mRecyclerView.findViewHolderForAdapterPosition(position);
} else {
return null;
}
}
/**
* Returns a sample viewholder so we can check on the state of the ui
* @return
*/
protected RecyclerView.ViewHolder getViewHolderSample() {
if(mLayoutManager != null && mRecyclerView != null) {
int position = mLayoutManager.findFirstVisibleItemPosition();
return mRecyclerView.findViewHolderForLayoutPosition(position);
} else {
return null;
}
}
protected void onPrepareView(View rootView) {
// place holder so child classes can modify the view
}
protected ViewModeAdapter getAdapter() {
return mAdapter;
}
@Override
public void onResume() {
super.onResume();
if(mAdapter != null) {
mAdapter.rebuild();
}
}
/**
* Forces the software keyboard to close
*/
public void closeKeyboard() {
AppContext.closeKeyboard(getActivity());
}
@Override
public void onTranslationWordClick(String translationWordId, int width) {
}
@Override
public void onTranslationArticleClick(String volume, String manual, String slug, int width) {
}
@Override
public void onTranslationNoteClick(String chapterId, String frameId, String translationNoteId, int width) {
}
@Override
public void onCheckingQuestionClick(String chapterId, String frameId, String checkingQuestionId, int width) {
}
/**
* Require correct interface
* @param activity
*/
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
this.mListener = (OnEventListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement ViewModeFragment.OnEventListener");
}
}
/**
* Called when the scroll progress manually changes
* @param scrollProgress
*/
public void onScrollProgressUpdate(int scrollProgress) {
mFingerScroll = false;
mRecyclerView.scrollToPosition(scrollProgress);
}
@Override
public void onSourceTranslationTabClick(String sourceTranslationId) {
AppContext.setSelectedSourceTranslation(mTargetTranslation.getId(), sourceTranslationId);
mSourceTranslationId = sourceTranslationId;
mAdapter.setSourceTranslation(sourceTranslationId);
}
@Override
public void onNewSourceTranslationTabClick() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("tabsDialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
ChooseSourceTranslationDialog dialog = new ChooseSourceTranslationDialog();
Bundle args = new Bundle();
args.putString(ChooseSourceTranslationDialog.ARG_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
dialog.setOnClickListener(this);
dialog.setArguments(args);
dialog.show(ft, "tabsDialog");
}
@Override
public void onCancelTabsDialog(String targetTranslationId) {
}
@Override
public void onConfirmTabsDialog(String targetTranslationId, String[] sourceTranslationIds) {
String[] oldSourceTranslationIds = AppContext.getOpenSourceTranslationIds(targetTranslationId);
for(String id:oldSourceTranslationIds) {
AppContext.removeOpenSourceTranslation(targetTranslationId, id);
}
if(sourceTranslationIds.length > 0) {
// save open source language tabs
for(String id:sourceTranslationIds) {
SourceTranslation sourceTranslation = mLibrary.getSourceTranslation(id);
if(sourceTranslation != null) {
AppContext.addOpenSourceTranslation(targetTranslationId, sourceTranslation.getId());
TargetTranslation targetTranslation = mTranslator.getTargetTranslation(targetTranslationId);
if (targetTranslation != null) {
try {
targetTranslation.addSourceTranslation(sourceTranslation);
} catch (JSONException e) {
Logger.e(this.getClass().getName(), "Failed to record source translation (" + sourceTranslation.getId() + ") usage in the target translation " + targetTranslation.getId(), e);
}
}
}
}
String selectedSourceTranslationId = AppContext.getSelectedSourceTranslationId(targetTranslationId);
mAdapter.setSourceTranslation(selectedSourceTranslationId);
} else {
mListener.onNoSourceTranslations(targetTranslationId);
}
}
@Override
public void onDestroy() {
// save position state
if(mRememberLastPosition && (mLayoutManager != null)) {
int lastItemPosition = mLayoutManager.findFirstVisibleItemPosition();
String chapterId = mAdapter.getFocusedChapterId(lastItemPosition);
String frameId = mAdapter.getFocusedFrameId(lastItemPosition);
AppContext.setLastFocus(mTargetTranslation.getId(), chapterId, frameId);
}
super.onDestroy();
}
/**
* Receives touch events directly from the activity
* Some times click events can get consumed by a list view or some other object.
* Receiving events directly from the activity avoids these issues
*
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event) {
if(mGesture != null) {
return mGesture.onTouchEvent(event);
} else {
Logger.w(this.getClass().getName(), "The gesture detector was not initialized so the touch was not handled");
return false;
}
}
/**
* Opens a translation mode
* @param mode
*/
public void openTranslationMode(TranslationViewMode mode, Bundle extras) {
mListener.openTranslationMode(mode, extras);
}
/**
* Restarts the auto commit timer
*/
public void restartAutoCommitTimer() {
mListener.restartAutoCommitTimer();
}
public void setRememberLastPosition(boolean rememberLastPosition) {
this.mRememberLastPosition = rememberLastPosition;
}
public interface OnEventListener {
/**
* Called when the user scrolls with their finger
* @param progress
*/
void onScrollProgress(int progress);
/**
* Called when the number of items in the fragment adapter changes
* @param itemCount
* @param progress
*/
void onItemCountChanged(int itemCount, int progress);
/**
* No source translation has been chosen for this target translation
* @param targetTranslationId
*/
void onNoSourceTranslations(String targetTranslationId);
/**
* Opens a particular translation mode
* @param mode
*/
void openTranslationMode(TranslationViewMode mode, Bundle extras);
/**
* Restarts the timer to auto commit changes
*/
void restartAutoCommitTimer();
}
}