package com.door43.translationstudio.newui.translate;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.text.Layout;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.SeekBar;
import android.widget.TextView;
import com.door43.tools.reporting.Logger;
import com.door43.translationstudio.R;
import com.door43.translationstudio.SettingsActivity;
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.dialogs.CustomAlertDialog;
import com.door43.translationstudio.newui.BackupDialog;
import com.door43.translationstudio.newui.FeedbackDialog;
import com.door43.translationstudio.newui.PrintDialog;
import com.door43.translationstudio.newui.draft.DraftActivity;
import com.door43.translationstudio.newui.publish.PublishActivity;
import com.door43.translationstudio.AppContext;
import com.door43.translationstudio.util.SdUtils;
import com.door43.widget.VerticalSeekBar;
import com.door43.widget.ViewUtil;
import com.door43.translationstudio.newui.BaseActivity;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class TargetTranslationActivity extends BaseActivity implements ViewModeFragment.OnEventListener, FirstTabFragment.OnEventListener {
private static final String TAG = TargetTranslationActivity.class.getSimpleName();
private static final long COMMIT_INTERVAL = 2 * 60 * 1000; // commit changes every 2 minutes
private Fragment mFragment;
private SeekBar mSeekBar;
private ViewGroup mGraduations;
private Translator mTranslator;
private TargetTranslation mTargetTranslation;
private Timer mCommitTimer = new Timer();
private ImageButton mReadButton;
private ImageButton mChunkButton;
private ImageButton mReviewButton;
private List<SourceTranslation> draftTranslations;
private ImageButton mMoreButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target_translation_detail);
mTranslator = AppContext.getTranslator();
// validate parameters
Bundle args = getIntent().getExtras();
final String targetTranslationId = args.getString(AppContext.EXTRA_TARGET_TRANSLATION_ID, null);
mTargetTranslation = mTranslator.getTargetTranslation(targetTranslationId);
if (mTargetTranslation == null) {
Logger.e(TAG ,"A valid target translation id is required. Received '" + targetTranslationId + "' but the translation could not be found");
finish();
return;
}
// open used source translations by default
if(AppContext.getOpenSourceTranslationIds(mTargetTranslation.getId()).length == 0) {
String[] slugs = mTargetTranslation.getSourceTranslations();
for (String slug : slugs) {
SourceTranslation sourceTranslation = AppContext.getLibrary().getSourceTranslation(slug);
if(sourceTranslation != null) {
AppContext.addOpenSourceTranslation(mTargetTranslation.getId(), sourceTranslation.getId());
}
}
}
// notify user that a draft translation exists the first time actvity starts
if(savedInstanceState == null && draftIsAvailable() && !targetTranslationHasDraft()) {
Snackbar snack = Snackbar.make(findViewById(android.R.id.content), R.string.draft_translation_exists, Snackbar.LENGTH_LONG)
.setAction(R.string.preview, new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(TargetTranslationActivity.this, DraftActivity.class);
intent.putExtra(DraftActivity.EXTRA_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
startActivity(intent);
}
});
ViewUtil.setSnackBarTextColor(snack, getResources().getColor(R.color.light_primary_text));
snack.show();
}
// manual location settings
String viewModeId = args.getString(AppContext.EXTRA_VIEW_MODE, null);
if (viewModeId != null && TranslationViewMode.get(viewModeId) != null) {
AppContext.setLastViewMode(targetTranslationId, TranslationViewMode.get(viewModeId));
}
mReadButton = (ImageButton) findViewById(R.id.action_read);
mChunkButton = (ImageButton) findViewById(R.id.action_chunk);
mReviewButton = (ImageButton) findViewById(R.id.action_review);
setupSidebarModeIcons();
// inject fragments
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
mFragment = getFragmentManager().findFragmentById(R.id.fragment_container);
} else {
TranslationViewMode viewMode = AppContext.getLastViewMode(mTargetTranslation.getId());
switch (viewMode) {
case READ:
mFragment = new ReadModeFragment();
break;
case CHUNK:
mFragment = new ChunkModeFragment();
break;
case REVIEW:
mFragment = new ReviewModeFragment();
break;
}
mFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(R.id.fragment_container, mFragment).commit();
// TODO: animate
// TODO: udpate menu
}
}
// set up menu items
mGraduations = (ViewGroup) findViewById(R.id.action_seek_graduations);
mSeekBar = (SeekBar) findViewById(R.id.action_seek);
mSeekBar.setMax(100);
mSeekBar.setProgress(computePositionFromProgress(0));
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int position;
if (progress < 0) {
position = computePositionFromProgress(0);
} else if (progress <= seekBar.getMax()) {
position = computePositionFromProgress(progress);
} else {
position = 0;
}
// If this change was initiated by a click on a UI element (rather than as a result
// of updates within the program), then update the view accordingly.
if (mFragment instanceof ViewModeFragment && fromUser) {
((ViewModeFragment) mFragment).onScrollProgressUpdate(position);
}
TargetTranslationActivity activity = (TargetTranslationActivity) seekBar.getContext();
if (activity != null) {
activity.closeKeyboard();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mGraduations.animate().alpha(1.f);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mGraduations.animate().alpha(0.f);
}
});
mMoreButton = (ImageButton) findViewById(R.id.action_more);
buildMenu();
mReadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openTranslationMode(TranslationViewMode.READ, null);
TargetTranslationActivity activity = (TargetTranslationActivity) v.getContext();
}
});
mChunkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openTranslationMode(TranslationViewMode.CHUNK, null);
TargetTranslationActivity activity = (TargetTranslationActivity) v.getContext();
}
});
mReviewButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openTranslationMode(TranslationViewMode.REVIEW, null);
TargetTranslationActivity activity = (TargetTranslationActivity) v.getContext();
}
});
restartAutoCommitTimer();
}
private void buildMenu() {
mMoreButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu moreMenu = new PopupMenu(TargetTranslationActivity.this, v);
ViewUtil.forcePopupMenuIcons(moreMenu);
moreMenu.getMenuInflater().inflate(R.menu.menu_target_translation_detail, moreMenu.getMenu());
// display menu item for draft translations
MenuItem draftsMenuItem = moreMenu.getMenu().findItem(R.id.action_drafts_available);
draftsMenuItem.setVisible(draftIsAvailable() && !targetTranslationHasDraft());
moreMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_translations:
finish();
return true;
case R.id.action_publish:
Intent publishIntent = new Intent(TargetTranslationActivity.this, PublishActivity.class);
publishIntent.putExtra(PublishActivity.EXTRA_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
publishIntent.putExtra(PublishActivity.EXTRA_CALLING_ACTIVITY, PublishActivity.ACTIVITY_TRANSLATION);
startActivity(publishIntent);
// TRICKY: we may move back and forth between the publisher and translation activites
// so we finish to avoid filling the stack.
finish();
return true;
case R.id.action_drafts_available:
Intent intent = new Intent(TargetTranslationActivity.this, DraftActivity.class);
intent.putExtra(DraftActivity.EXTRA_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
startActivity(intent);
return true;
case R.id.action_backup:
FragmentTransaction backupFt = getFragmentManager().beginTransaction();
Fragment backupPrev = getFragmentManager().findFragmentByTag(BackupDialog.TAG);
if (backupPrev != null) {
backupFt.remove(backupPrev);
}
backupFt.addToBackStack(null);
BackupDialog backupDialog = new BackupDialog();
Bundle args = new Bundle();
args.putString(BackupDialog.ARG_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
backupDialog.setArguments(args);
backupDialog.show(backupFt, BackupDialog.TAG);
return true;
case R.id.action_print:
FragmentTransaction printFt = getFragmentManager().beginTransaction();
Fragment printPrev = getFragmentManager().findFragmentByTag("printDialog");
if (printPrev != null) {
printFt.remove(printPrev);
}
printFt.addToBackStack(null);
PrintDialog printDialog = new PrintDialog();
Bundle printArgs = new Bundle();
printArgs.putString(PrintDialog.ARG_TARGET_TRANSLATION_ID, mTargetTranslation.getId());
printDialog.setArguments(printArgs);
printDialog.show(printFt, "printDialog");
return true;
case R.id.action_feedback:
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("bugDialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
FeedbackDialog dialog = new FeedbackDialog();
dialog.show(ft, "bugDialog");
return true;
case R.id.action_settings:
Intent settingsIntent = new Intent(TargetTranslationActivity.this, SettingsActivity.class);
startActivity(settingsIntent);
return true;
}
return false;
}
});
moreMenu.show();
}
});
}
/**
* Checks if the target translation aleady has the best draft
* @return
*/
private boolean targetTranslationHasDraft() {
return mTargetTranslation.getParentDraft() != null;
// TODO: 1/20/2016 once users are forced to specify a resource they are translating into we'll use this to check
// if(mTargetTranslation.getParentDraft() != null) {
// // check for matching resource
// if(mTargetTranslation.resourceSlug.equals(mTargetTranslation.getParentDraft().resourceSlug)) {
// return true;
// } else {
// // check for second best
// if (draftTranslations == null) {
// draftTranslations = AppContext.getLibrary().getDraftTranslations(mTargetTranslation.getProjectId(), mTargetTranslation.getTargetLanguageId());
// }
// for (SourceTranslation st : draftTranslations) {
// if (st.resourceSlug.equals(mTargetTranslation.getParentDraft().resourceSlug)) {
// return true;
// }
// }
// }
// }
// return false;
}
/**
* Checks if a draft is available
*
* @return
*/
private boolean draftIsAvailable() {
if(draftTranslations == null) {
draftTranslations = AppContext.getLibrary().getDraftTranslations(mTargetTranslation.getProjectId(), mTargetTranslation.getTargetLanguageId());
}
for(SourceTranslation st:draftTranslations) {
if(AppContext.getLibrary().sourceTranslationHasSource(st)) {
return true;
}
}
return false;
// TODO: 1/20/2016 once users are forced to specify a resource they are translating into we'll use this to check
// for(SourceTranslation st:draftTranslations) {
// if(st.resourceSlug.equals(mTargetTranslation.resourceSlug) && AppContext.getLibrary().sourceTranslationHasSource(st)) {
// return true;
// }
// }
// return false;
}
@Override
public void onResume() {
super.onResume();
notifyDatasetChanged();
buildMenu();
}
public void closeKeyboard() {
if (mFragment instanceof ViewModeFragment) {
((ViewModeFragment) mFragment).closeKeyboard();
}
}
public void checkIfCursorStillOnScreen() {
Rect cursorPos = getCursorPositionOnScreen();
if (cursorPos != null) {
View scrollView = findViewById(R.id.fragment_container);
if (scrollView != null) {
Boolean visible = true;
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (cursorPos.top < scrollBounds.top) {
visible = false;
}
else if (cursorPos.bottom > scrollBounds.bottom) {
visible = false;
}
if (!visible) {
closeKeyboard();
}
}
}
}
public Rect getCursorPositionOnScreen() {
View focusedView = (View) getCurrentFocus();
if (focusedView != null) {
// get view position on screen
int[] l = new int[2];
focusedView.getLocationOnScreen(l);
int focusedViewX = l[0];
int focusedViewY = l[1];
if (focusedView instanceof EditText) {
// getting relative cursor position
EditText editText = (EditText) focusedView;
int pos = editText.getSelectionStart();
Layout layout = editText.getLayout();
if (layout != null) {
int line = layout.getLineForOffset(pos);
int baseline = layout.getLineBaseline(line);
int ascent = layout.getLineAscent(line);
// convert relative positions to absolute position
int x = focusedViewX + (int) layout.getPrimaryHorizontal(pos);
int bottomY = focusedViewY + baseline;
int y = bottomY + ascent;
return new Rect(x, y, x, bottomY); // ignore width of cursor for now
}
}
}
return null;
}
@Override
public void onScrollProgress(int position) {
mSeekBar.setProgress(computeProgressFromPosition(position));
checkIfCursorStillOnScreen();
}
@Override
public void onItemCountChanged(int itemCount, int progress) {
mSeekBar.setMax(itemCount);
mSeekBar.setProgress(itemCount - progress);
closeKeyboard();
setupGraduations();
}
private void setupGraduations() {
final int numChapters = mSeekBar.getMax();
TranslationViewMode viewMode = AppContext.getLastViewMode(mTargetTranslation.getId());
// Set up visibility of the graduation bar.
// Display graduations evenly spaced by number of chapters (but not more than the number
// of chapters that exist). As a special case, display nothing if there's only one chapter.
// Also, show nothing unless we're in read mode, since the other modes are indexed by
// frame, not by chapter, so displaying either frame numbers or chapter numbers would be
// nonsensical.
int numVisibleGraduations = Math.min(numChapters, mGraduations.getChildCount());
if (numChapters < 2) {
numVisibleGraduations = 0;
}
if (viewMode != TranslationViewMode.READ) {
numVisibleGraduations = 0;
}
// Set up the visible chapters.
for (int i = 0; i < numVisibleGraduations; ++i) {
ViewGroup container = (ViewGroup) mGraduations.getChildAt(i);
container.setVisibility(View.VISIBLE);
TextView text = (TextView) container.getChildAt(1);
// This calculation, full of fudge factors, has the following properties:
// - It starts at 1.
// - It ends with the last chapter.
// - It's evenly spaced in between.
int label = 1 + i * (numChapters - 1) / (numVisibleGraduations - 1);
text.setText(Integer.toString(label));
}
// Undisplay the invisible chapters.
for (int i = numVisibleGraduations; i < mGraduations.getChildCount(); ++i) {
mGraduations.getChildAt(i).setVisibility(View.GONE);
}
}
private boolean displaySeekBarAsInverted() {
return mSeekBar instanceof VerticalSeekBar;
}
private int computeProgressFromPosition(int position) {
return displaySeekBarAsInverted() ? mSeekBar.getMax() - position : position;
}
private int computePositionFromProgress(int progress) {
return displaySeekBarAsInverted() ? Math.abs(mSeekBar.getMax() - progress) : progress;
}
@Override
public void onNoSourceTranslations(String targetTranslationId) {
if (mFragment instanceof FirstTabFragment == false) {
mFragment = new FirstTabFragment();
mFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().replace(R.id.fragment_container, mFragment).commit();
// TODO: animate
// TODO: udpate menu
}
}
@Override
public void openTranslationMode(TranslationViewMode mode, Bundle extras) {
Bundle fragmentExtras = new Bundle();
fragmentExtras.putAll(getIntent().getExtras());
if (extras != null) {
fragmentExtras.putAll(extras);
}
// close the keyboard when switching between modes
if (getCurrentFocus() != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
} else {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
AppContext.setLastViewMode(mTargetTranslation.getId(), mode);
setupSidebarModeIcons();
switch (mode) {
case READ:
if (mFragment instanceof ReadModeFragment == false) {
mFragment = new ReadModeFragment();
mFragment.setArguments(fragmentExtras);
getFragmentManager().beginTransaction().replace(R.id.fragment_container, mFragment).commit();
// TODO: animate
// TODO: update menu
}
break;
case CHUNK:
if (mFragment instanceof ChunkModeFragment == false) {
mFragment = new ChunkModeFragment();
mFragment.setArguments(fragmentExtras);
getFragmentManager().beginTransaction().replace(R.id.fragment_container, mFragment).commit();
// TODO: animate
// TODO: update menu
}
break;
case REVIEW:
if (mFragment instanceof ReviewModeFragment == false) {
mFragment = new ReviewModeFragment();
mFragment.setArguments(fragmentExtras);
getFragmentManager().beginTransaction().replace(R.id.fragment_container, mFragment).commit();
// TODO: animate
// TODO: update menu
}
break;
}
}
/**
* Restart scheduled translation commits
*/
public void restartAutoCommitTimer() {
mCommitTimer.cancel();
mCommitTimer = new Timer();
mCommitTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if(mTargetTranslation != null) {
try {
mTargetTranslation.commit();
} catch (Exception e) {
Logger.e(TargetTranslationActivity.class.getName(), "Failed to commit the latest translation of " + mTargetTranslation.getId(), e);
}
} else {
Logger.w(TAG, "cannot auto commit target translation. The target translation is null.");
mCommitTimer.cancel();
}
}
}, COMMIT_INTERVAL, COMMIT_INTERVAL);
}
@Override
public void onHasSourceTranslations() {
TranslationViewMode viewMode = AppContext.getLastViewMode(mTargetTranslation.getId());
if (viewMode == TranslationViewMode.READ) {
mFragment = new ReadModeFragment();
} else if (viewMode == TranslationViewMode.CHUNK) {
mFragment = new ChunkModeFragment();
} else if (viewMode == TranslationViewMode.REVIEW) {
mFragment = new ReviewModeFragment();
}
mFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().replace(R.id.fragment_container, (Fragment) mFragment).commit();
// TODO: animate
// TODO: update menu
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mFragment instanceof ViewModeFragment) {
if (!((ViewModeFragment) mFragment).onTouchEvent(event)) {
return super.dispatchTouchEvent(event);
} else {
return true;
}
} else {
return super.dispatchTouchEvent(event);
}
}
@Override
public void onDestroy() {
mCommitTimer.cancel();
try {
mTargetTranslation.commit();
} catch (Exception e) {
Logger.e(this.getClass().getName(), "Failed to commit changes before closing translation", e);
}
super.onDestroy();
}
/**
* Causes the activity to tell the fragment it needs to reload
*/
public void notifyDatasetChanged() {
if (mFragment instanceof ViewModeFragment) {
((ViewModeFragment) mFragment).getAdapter().reload();
}
}
/**
* Updates the visual state of all the sidebar icons to match the application's current mode.
*
* Call this when creating the activity or when changing modes.
*/
private void setupSidebarModeIcons() {
TranslationViewMode viewMode = AppContext.getLastViewMode(mTargetTranslation.getId());
// Set the non-highlighted icons by default.
mReviewButton.setImageResource(R.drawable.icon_check_inactive);
mChunkButton.setImageResource(R.drawable.icon_frame_inactive);
mReadButton.setImageResource(R.drawable.icon_study_inactive);
// Clear the highlight background.
//
// This is more properly done with setBackground(), but that requires a higher API
// level than this application's minimum. Equivalently use setBackgroundDrawable(),
// which is deprecated, instead.
mReviewButton.setBackgroundDrawable(null);
mChunkButton.setBackgroundDrawable(null);
mReadButton.setBackgroundDrawable(null);
// For the active view, set the correct icon, and highlight the background.
final int backgroundColor = getResources().getColor(R.color.primary_dark);
switch (viewMode) {
case READ:
mReadButton.setImageResource(R.drawable.icon_study_active);
mReadButton.setBackgroundColor(backgroundColor);
break;
case CHUNK:
mChunkButton.setImageResource(R.drawable.icon_frame_active);
mChunkButton.setBackgroundColor(backgroundColor);
break;
case REVIEW:
mReviewButton.setImageResource(R.drawable.icon_check_active);
mReviewButton.setBackgroundColor(backgroundColor);
break;
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SdUtils.REQUEST_CODE_STORAGE_ACCESS) {
Uri treeUri = null;
String msg = "";
if (resultCode == Activity.RESULT_OK) {
// Get Uri from Storage Access Framework.
treeUri = data.getData();
final int takeFlags = data.getFlags();
boolean success = SdUtils.validateSdCardWriteAccess(treeUri, takeFlags);
if (!success) {
String template = getResources().getString(R.string.access_failed);
msg = String.format(template, treeUri.toString());
} else {
msg = getResources().getString(R.string.access_granted_backup);
}
} else {
msg = getResources().getString(R.string.access_skipped);
}
CustomAlertDialog.Create(this)
.setTitle(R.string.access_title)
.setMessage(msg)
.setPositiveButton(R.string.label_ok, null)
.show("AccessResults");
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}