app/src/main/java/com/bedrock/padder/model/tutorial/Tutorial.java
package com.bedrock.padder.model.tutorial;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.util.Log;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import static com.bedrock.padder.helper.WindowHelper.getId;
import static com.bedrock.padder.model.tutorial.TimingListener.timings;
public class Tutorial {
public static TimingListener TIMING_LISTENER;
private String tag;
private int bpm;
private Activity activity;
private ArrayList<Sync> syncs = new ArrayList<>();
private int currentSyncIndex = 1;
private TutorialListener listener;
private Handler handler;
private View decks[] = new View[4];
private View pads[] = new View[45];
private Animation animations[] = new Animation[5];
public static Animation ANIMATION_FADE = new AlphaAnimation(1, 0);
public static int TUTORIAL_ANIMATION_DURATION = 400; // 400 ~ 500 seems legit
public Tutorial(String tag, Activity activity) {
this.tag = tag;
this.activity = activity;
init();
}
public Tutorial(String tag, int bpm, Activity activity) {
this.tag = tag;
this.bpm = bpm;
this.activity = activity;
init();
}
public void parse() {
listener.onLoadStart(this);
new Parse().execute();
}
private class Parse extends AsyncTask<Void, Void, Tutorial> {
TutorialXmlParser parser;
@Override
protected void onPreExecute() {
super.onPreExecute();
parser = new TutorialXmlParser(tag, activity);
}
@Override
protected Tutorial doInBackground(Void... voids) {
return parser.parse();
}
@Override
protected void onPostExecute(Tutorial tutorial) {
super.onPostExecute(tutorial);
init(tutorial);
Log.d("TUTORIAL", "onPostExecute");
listener.onLoadFinish(getTutorial());
}
}
public void setTag(String tag) {
this.tag = tag;
}
public void setBpm(int bpm) {
this.bpm = bpm;
}
public int getBpm() {
return bpm;
}
public Tutorial getTutorial() {
return this;
}
public void addSync(Sync sync) {
syncs.add(sync);
}
public void addSyncs(ArrayList<Sync> sync) {
syncs.addAll(sync);
}
public boolean isSorted() {
boolean sorted = true;
int start = -1;
for (Sync sync : syncs) {
if (sync.start > start) {
// normal
start = sync.start;
} else {
sorted = false;
break;
}
}
return sorted;
}
public void setTutorialListener(TutorialListener listener) {
this.listener = listener;
}
public void sort() {
Collections.sort(syncs, new SyncComparator());
}
private Runnable nextTutorial = new Runnable() {
@Override
public void run() {
// Current sync
Sync sync = syncs.get(currentSyncIndex++);
if (currentSyncIndex < syncs.size()) {
// Next sync is syncs.get(currentSyncIndex)
int delay = syncs.get(currentSyncIndex).getStart() - sync.getStart();
boolean hasSamePad = sync.hasSamePad(syncs.get(currentSyncIndex));
for (Sync.Item item : sync.items) {
if (hasSamePad && delay < 400) {
// Set duration as 50ms less than next interval
if (delay > 50) {
show(item.deck, item.pad, item.gesture, delay - 50);
} else {
Log.e("Sync", "TUTORIAL SKIPPED: bad tutorial composition (same pad under 50ms)");
}
} else {
show(item.deck, item.pad, item.gesture);
}
}
if (delay == 0) {
handler.post(this);
} else {
handler.postDelayed(this, delay);
}
} else {
for (Sync.Item item : sync.items) {
show(item.deck, item.pad, item.gesture);
}
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i("TUTORIAL", "ended");
listener.onFinish(getTutorial());
timings.clear();
currentSyncIndex = 1;
}
}, 1000); // delay the finish
}
}
};
public void stop() {
handler.removeCallbacks(nextTutorial);
listener.onFinish(this);
}
public int pause() {
listener.onPause(this, currentSyncIndex);
return currentSyncIndex;
}
public void resume(int syncIndex) {
currentSyncIndex = syncIndex;
handler.postDelayed(nextTutorial, 3000);
// TODO add countdown
}
public void start(int delay) {
int startDeckDelay = 200;
currentSyncIndex = 1;
handler.postDelayed(new Runnable() {
@Override
public void run() {
Sync.Item item = syncs.get(0).items.get(0);
show(item.deck, item.pad, item.gesture);
}
}, startDeckDelay);
if (delay > 0) {
handler.postDelayed(nextTutorial, delay + startDeckDelay + 400);
} else {
handler.postDelayed(nextTutorial, startDeckDelay + 400);
}
listener.onStart(this);
}
private void show(int deck, int pad, int gesture) {
if (deck > 0) {
TutorialView view;
if (pad != -1) {
if (gesture != -1) {
// gesture pad
view = new TutorialView(pads[pad], new Timing(deck, pad, gesture));
view.setAnimation(animations[gesture]);
} else {
view = new TutorialView(pads[pad], new Timing(deck, pad));
view.setAnimation(animations[0]);
}
} else {
view = new TutorialView(decks[deck - 1], new Timing(deck));
view.setAnimation(animations[0]);
}
view.start();
}
Log.d("TUTORIAL", "show [" + deck + ", " + pad + ", " + gesture + "]");
}
private void show(int deck, int pad, int gesture, int duration) {
if (deck > 0) {
TutorialView view;
if (pad != -1) {
if (gesture != -1) {
// gesture pad
view = new TutorialView(pads[pad], new Timing(deck, pad, gesture));
view.setAnimation(animations[gesture]);
} else {
view = new TutorialView(pads[pad], new Timing(deck, pad));
view.setAnimation(animations[0]);
}
} else {
view = new TutorialView(decks[deck - 1], new Timing(deck));
view.setAnimation(animations[0]);
}
view.setDuration(duration);
view.start();
}
Log.d("TUTORIAL", "show [" + deck + ", " + pad + ", " + gesture + "]");
}
private void init(Tutorial tutorial) {
if (tutorial != null) {
this.bpm = tutorial.bpm;
this.syncs = tutorial.syncs;
Log.i(this.getClass().getSimpleName(), "tutorial.syncs.size = " + tutorial.syncs.size());
Log.i(this.getClass().getSimpleName(), "syncs.size = " + syncs.size());
init();
} else {
Log.e(this.getClass().getSimpleName(), ".init: tutorial is null");
}
}
private void init() {
handler = new Handler(Looper.getMainLooper());
for (int i = 0; i < decks.length; i++) {
decks[i] = activity.findViewById(getId("tgl" + String.valueOf(i + 1) + "_tutorial"));
}
pads[0] = activity.findViewById(getId("btn00_tutorial"));
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
pads[i * 10 + j] = activity.findViewById(getId("btn" + String.valueOf(i) + String.valueOf(j) + "_tutorial"));
}
}
ANIMATION_FADE.setDuration(50);
animations[0] = new ScaleAnimation(
1, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animations[1] = new ScaleAnimation(
1, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 1.0f);
animations[2] = new ScaleAnimation(
0, 1, 1, 1,
Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 0.5f);
animations[3] = new ScaleAnimation(
1, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.0f);
animations[4] = new ScaleAnimation(
0, 1, 1, 1,
Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0.5f);
for (Animation animation : animations) {
animation.setInterpolator(new LinearOutSlowInInterpolator());
animation.setDuration(TUTORIAL_ANIMATION_DURATION);
}
}
public int size() {
return syncs.size();
}
@Override
public String toString() {
return "Tutorial{" +
"tag='" + tag + '\'' +
", bpm=" + bpm +
", activity=" + activity +
", syncs.size()=" + syncs.size() +
'}';
}
public String syncToString() {
return new GsonBuilder().setPrettyPrinting().create().toJson(syncs.toArray());
}
public interface TutorialListener {
void onLoadStart(Tutorial tutorial);
void onLoadFinish(Tutorial tutorial);
void onStart(Tutorial tutorial);
void onPause(Tutorial tutorial, int syncIndex);
void onFinish(Tutorial tutorial);
}
public static class SyncComparator implements Comparator<Sync> {
@Override
public int compare(Sync t0, Sync t1) {
// Sorting in ascending order of start offset
return t0.start - t1.start;
}
}
}