Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Automatic Display Answer #9631

Merged
merged 15 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 45 additions & 116 deletions AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
import com.ichi2.anki.multimediacard.AudioView;
import com.ichi2.anki.cardviewer.CardAppearance;
import com.ichi2.anki.receiver.SdCardReceiver;
import com.ichi2.anki.reviewer.AutomaticAnswer;
import com.ichi2.anki.reviewer.CardMarker;
import com.ichi2.anki.cardviewer.CardTemplate;
import com.ichi2.anki.reviewer.FullScreenMode;
Expand Down Expand Up @@ -169,7 +170,7 @@
import static com.ichi2.anim.ActivityTransitionAnimation.Direction.*;

@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.FieldDeclarationsShouldBeAtStartOfClass"})
public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity implements ReviewerUi, CommandProcessor, TagsDialogListener, WhiteboardMultiTouchMethods {
public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity implements ReviewerUi, CommandProcessor, TagsDialogListener, WhiteboardMultiTouchMethods, AutomaticAnswer.AutomaticallyAnswered {

/**
* Result codes that are returned when this activity finishes.
Expand Down Expand Up @@ -242,17 +243,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i
protected boolean mSpeakText;
protected boolean mDisableClipboard = false;

protected boolean mOptUseGeneralTimerSettings;

protected boolean mUseTimer;
protected int mWaitAnswerSecond;
protected int mWaitQuestionSecond;

protected boolean mPrefUseTimer;

protected boolean mOptUseTimer;
protected int mOptWaitAnswerSecond;
protected int mOptWaitQuestionSecond;
@NonNull protected AutomaticAnswer mAutomaticAnswer = AutomaticAnswer.defaultInstance(this);

protected TypeAnswer mTypeAnswer;

Expand Down Expand Up @@ -346,7 +337,10 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i

private final Sound mSoundPlayer = new Sound();

/** Time taken o play all medias in mSoundPlayer */
/**
* Time taken to play all medias in mSoundPlayer
* This is 0 if we have "Read card" enabled, as we can't calculate the duration.
*/
private long mUseTimerDynamicMS;

/** File of the temporary mic record **/
Expand Down Expand Up @@ -410,7 +404,7 @@ public void onClick(View view) {
return;
}
mLastClickTime = getElapsedRealTime();
mTimeoutHandler.removeCallbacks(mShowAnswerTask);
mAutomaticAnswer.onShowAnswer();
displayCardAnswer();
}
};
Expand Down Expand Up @@ -463,7 +457,7 @@ public void onClick(View view) {
view.setPressed(true);
}
mLastClickTime = getElapsedRealTime();
mTimeoutHandler.removeCallbacks(mShowQuestionTask);
mAutomaticAnswer.onSelectEase();
int id = view.getId();
if (id == R.id.flashcard_layout_ease1) {
Timber.i("AbstractFlashcardViewer:: EASE_1 pressed");
Expand Down Expand Up @@ -735,9 +729,6 @@ public void run() {
}
};

protected int mPrefWaitAnswerSecond;
protected int mPrefWaitQuestionSecond;


protected int getAnswerButtonCount() {
return getCol().getSched().answerButtons(mCurrentCard);
Expand Down Expand Up @@ -827,7 +818,7 @@ protected void onCollectionLoaded(Collection col) {

registerExternalStorageListener();

restoreCollectionPreferences();
restoreCollectionPreferences(col);

initLayout();

Expand Down Expand Up @@ -863,8 +854,7 @@ protected void onPause() {
super.onPause();
Timber.d("onPause()");

mTimeoutHandler.removeCallbacks(mShowAnswerTask);
mTimeoutHandler.removeCallbacks(mShowQuestionTask);
mAutomaticAnswer.stopAll();
mLongClickHandler.removeCallbacks(mLongClickTestRunnable);
mLongClickHandler.removeCallbacks(mStartLongClickAction);

Expand Down Expand Up @@ -1717,9 +1707,6 @@ protected SharedPreferences restorePreferences() {
mPrefFullscreenReview = FullScreenMode.fromPreference(preferences);
mRelativeButtonSize = preferences.getInt("answerButtonSize", 100);
mSpeakText = preferences.getBoolean("tts", false);
mPrefUseTimer = preferences.getBoolean("timeoutAnswer", false);
mPrefWaitAnswerSecond = preferences.getInt("timeoutAnswerSeconds", 20);
mPrefWaitQuestionSecond = preferences.getInt("timeoutQuestionSeconds", 60);
mScrollingButtons = preferences.getBoolean("scrolling_buttons", false);
mDoubleScrolling = preferences.getBoolean("double_scrolling", false);
mPrefShowTopbar = preferences.getBoolean("showTopbar", true);
Expand All @@ -1741,34 +1728,16 @@ protected SharedPreferences restorePreferences() {
}


protected void restoreCollectionPreferences() {
protected void restoreCollectionPreferences(Collection col) {

// These are preferences we pull out of the collection instead of SharedPreferences
try {
mShowNextReviewTime = getCol().get_config_boolean("estTimes");

// Dynamic don't have review options; attempt to get deck-specific auto-advance options
// but be prepared to go with all default if it's a dynamic deck
JSONObject revOptions = new JSONObject();
long selectedDid = getCol().getDecks().selected();
if (!getCol().getDecks().isDyn(selectedDid)) {
revOptions = getCol().getDecks().confForDid(selectedDid).getJSONObject("rev");
}

mOptUseGeneralTimerSettings = revOptions.optBoolean("useGeneralTimeoutSettings", true);
mOptUseTimer = revOptions.optBoolean("timeoutAnswer", false);
mOptWaitAnswerSecond = revOptions.optInt("timeoutAnswerSeconds", 20);
mOptWaitQuestionSecond = revOptions.optInt("timeoutQuestionSeconds", 60);
} catch (JSONException e) {
Timber.e(e, "Unable to restoreCollectionPreferences");
throw new RuntimeException(e);
} catch (NullPointerException npe) {
// NPE on collection only happens if the Collection is broken, follow AnkiActivity example
Timber.w(npe);
Intent deckPicker = new Intent(this, DeckPicker.class);
deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this
deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityWithAnimation(deckPicker, START);
mShowNextReviewTime = col.get_config_boolean("estTimes");
SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext());
mAutomaticAnswer = AutomaticAnswer.createInstance(this, preferences, col);
} catch (Exception ex) {
Timber.w(ex);
onCollectionLoadError();
}
}

Expand Down Expand Up @@ -1825,47 +1794,27 @@ protected void updateDeckName() {
}
}

/*
* Handler for the delay in auto showing question and/or answer One toggle for both question and answer, could set
* longer delay for auto next question
*/
@SuppressWarnings("deprecation") // #7111: new Handler()
protected final Handler mTimeoutHandler = new Handler();

protected final Runnable mShowQuestionTask = new Runnable() {
@Override
public void run() {
// Assume hitting the "Again" button when auto next question
if (mEase1Layout.isEnabled() && mEase1Layout.getVisibility() == View.VISIBLE) {
mEase1Layout.performClick();
}
@Override
public void automaticShowQuestion() {
// Assume hitting the "Again" button when auto next question
if (mEase1Layout.isEnabled() && mEase1Layout.getVisibility() == View.VISIBLE) {
mEase1Layout.performClick();
}
};
}

protected final Runnable mShowAnswerTask = new Runnable() {
@Override
public void run() {
if (mFlipCardLayout.isEnabled() && mFlipCardLayout.getVisibility() == View.VISIBLE) {
mFlipCardLayout.performClick();
}
@Override
public void automaticShowAnswer() {
if (mFlipCardLayout.isEnabled() && mFlipCardLayout.getVisibility() == View.VISIBLE) {
mFlipCardLayout.performClick();
}
};
}

class ReadTextListener implements ReadText.ReadTextListener {
public void onDone() {
if(!mUseTimer) {
return;
}
if (ReadText.getmQuestionAnswer() == SoundSide.QUESTION) {
long delay = mWaitAnswerSecond * 1000;
if (delay > 0) {
mTimeoutHandler.postDelayed(mShowAnswerTask, delay);
}
mAutomaticAnswer.scheduleAutomaticDisplayAnswer();
} else if (ReadText.getmQuestionAnswer() == SoundSide.ANSWER) {
long delay = mWaitQuestionSecond * 1000;
if (delay > 0) {
mTimeoutHandler.postDelayed(mShowQuestionTask, delay);
}
mAutomaticAnswer.scheduleAutomaticDisplayQuestion();
}
}
}
Expand Down Expand Up @@ -1944,28 +1893,13 @@ protected void displayCardQuestion(boolean reload) {
updateCard(displayString);
hideEaseButtons();

// Check if it should use the general 'Timeout settings' or the ones specific to this deck
if (mOptUseGeneralTimerSettings) {
mUseTimer = mPrefUseTimer;
mWaitAnswerSecond = mPrefWaitAnswerSecond;
mWaitQuestionSecond = mPrefWaitQuestionSecond;
} else {
mUseTimer = mOptUseTimer;
mWaitAnswerSecond = mOptWaitAnswerSecond;
mWaitQuestionSecond = mOptWaitQuestionSecond;
}

// If the user wants to show the answer automatically
if (mUseTimer) {
long delay = mWaitAnswerSecond * 1000 + mUseTimerDynamicMS;
if (mWaitAnswerSecond > 0) { // a wait of zero means auto-advance is disabled
mTimeoutHandler.removeCallbacks(mShowAnswerTask);
if (!mSpeakText) {
mTimeoutHandler.postDelayed(mShowAnswerTask, delay);
}
}
mAutomaticAnswer.onDisplayQuestion();
// If Card-based TTS is enabled, we "automatic display" after the TTS has finished as we don't know the duration
if (!mSpeakText) {
mAutomaticAnswer.scheduleAutomaticDisplayAnswer(mUseTimerDynamicMS);
}


Timber.i("AbstractFlashcardViewer:: Question successfully shown for card id %d", mCurrentCard.getId());
}

Expand Down Expand Up @@ -2005,15 +1939,11 @@ protected void displayCardAnswer() {
mIsSelecting = false;
updateCard(CardAppearance.enrichWithQADiv(answer, true));
displayAnswerBottomBar();
// If the user wants to show the next question automatically
if (mUseTimer) {
long delay = mWaitQuestionSecond * 1000 + mUseTimerDynamicMS;
if (mWaitQuestionSecond > 0) {
mTimeoutHandler.removeCallbacks(mShowQuestionTask);
if (!mSpeakText) {
mTimeoutHandler.postDelayed(mShowQuestionTask, delay);
}
}

mAutomaticAnswer.onDisplayAnswer();
// If Card-based TTS is enabled, we "automatic display" after the TTS has finished as we don't know the duration
if (!mSpeakText) {
mAutomaticAnswer.scheduleAutomaticDisplayQuestion(mUseTimerDynamicMS);
}
}

Expand Down Expand Up @@ -2092,7 +2022,7 @@ private void updateCard(final String newContent) {
mSoundPlayer.resetSounds();
mAnswerSoundsAdded = false;
mSoundPlayer.addSounds(mBaseUrl, newContent, SoundSide.QUESTION);
if (mUseTimer && !mAnswerSoundsAdded && getConfigForCurrentCard().optBoolean("autoplay", false)) {
if (mAutomaticAnswer.isEnabled() && !mAnswerSoundsAdded && getConfigForCurrentCard().optBoolean("autoplay", false)) {
addAnswerSounds(mCurrentCard.a());
}
}
Expand Down Expand Up @@ -2160,13 +2090,13 @@ protected void playSounds(boolean doAudioReplay) {
playSounds(SoundSide.QUESTION_AND_ANSWER);
} else if (sDisplayAnswer) {
playSounds(SoundSide.ANSWER);
if (mUseTimer) {
if (mAutomaticAnswer.isEnabled()) {
mUseTimerDynamicMS = mSoundPlayer.getSoundsLength(SoundSide.ANSWER);
}
} else { // question is displayed
playSounds(SoundSide.QUESTION);
// If the user wants to show the answer automatically
if (mUseTimer) {
if (mAutomaticAnswer.isEnabled()) {
mUseTimerDynamicMS = mSoundPlayer.getSoundsLength(SoundSide.QUESTION_AND_ANSWER);
}
}
Expand Down Expand Up @@ -2722,8 +2652,7 @@ protected void closeReviewer(int result, boolean saveDeck) {
}
}

mTimeoutHandler.removeCallbacks(mShowAnswerTask);
mTimeoutHandler.removeCallbacks(mShowQuestionTask);
mAutomaticAnswer.stopAll();
mTimerHandler.removeCallbacks(mRemoveChosenAnswerText);
mLongClickHandler.removeCallbacks(mLongClickTestRunnable);
mLongClickHandler.removeCallbacks(mStartLongClickAction);
Expand Down
14 changes: 10 additions & 4 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -390,14 +390,20 @@ public void startLoadingCollection() {
Timber.d("Asynchronously calling onCollectionLoaded");
onCollectionLoaded(col);
} else {
Intent deckPicker = new Intent(this, DeckPicker.class);
deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this
deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityWithAnimation(deckPicker, START);
onCollectionLoadError();
}
});
}

/** The action to take when there was an error loading the collection */
protected void onCollectionLoadError() {
Intent deckPicker = new Intent(this, DeckPicker.class);
deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this
deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityWithAnimation(deckPicker, START);
}


public void showProgressBar() {
ProgressBar progressBar = findViewById(R.id.progress_bar);
if (progressBar != null) {
Expand Down
29 changes: 6 additions & 23 deletions AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import com.ichi2.anki.dialogs.ConfirmationDialog;
import com.ichi2.anki.multimediacard.AudioView;
import com.ichi2.anki.dialogs.RescheduleDialog;
import com.ichi2.anki.reviewer.AnswerButtons;
import com.ichi2.anki.reviewer.FullScreenMode;
import com.ichi2.anki.reviewer.PeripheralKeymap;
import com.ichi2.anki.reviewer.ReviewerUi;
Expand Down Expand Up @@ -904,26 +905,8 @@ protected void displayAnswerBottomBar() {
// Set correct label and background resource for each button
// Note that it's necessary to set the resource dynamically as the ease2 / ease3 buttons
// (which libanki expects ease to be 2 and 3) can either be hard, good, or easy - depending on num buttons shown
int[] backgroundIds;
if (animationEnabled()) {
backgroundIds = new int [] {
R.attr.againButtonRippleRef,
R.attr.hardButtonRippleRef,
R.attr.goodButtonRippleRef,
R.attr.easyButtonRippleRef};
} else {
backgroundIds = new int [] {
R.attr.againButtonRef,
R.attr.hardButtonRef,
R.attr.goodButtonRef,
R.attr.easyButtonRef};
}
final int[] background = Themes.getResFromAttr(this, backgroundIds);
final int[] textColor = Themes.getColorFromAttr(this, new int [] {
R.attr.againButtonTextColor,
R.attr.hardButtonTextColor,
R.attr.goodButtonTextColor,
R.attr.easyButtonTextColor});
final int[] background = AnswerButtons.getBackgroundColors(this);
final int[] textColor = AnswerButtons.getTextColors(this);
mEase1Layout.setVisibility(View.VISIBLE);
mEase1Layout.setBackgroundResource(background[0]);
mEase4Layout.setBackgroundResource(background[3]);
Expand Down Expand Up @@ -1115,9 +1098,9 @@ protected void initControls() {
}
}

protected void restoreCollectionPreferences() {
super.restoreCollectionPreferences();
mShowRemainingCardCount = getCol().get_config_boolean("dueCounts");
protected void restoreCollectionPreferences(Collection col) {
super.restoreCollectionPreferences(col);
mShowRemainingCardCount = col.get_config_boolean("dueCounts");
}

@Override
Expand Down
Loading