package com.annimon.everlastingsummer; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.annimon.everlastingsummer.TouchGesture.TouchGestureType; import com.annimon.everlastingsummer.ast.Variables; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.text.*; import android.text.style.ForegroundColorSpan; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /** * Экран воспроизведения rpy-сценария. * @author aNNiMON */ public final class ViewActivity extends Activity implements TouchGesture.OnTouchGestureListener { public static final String EXTRA_NAME = "name"; public static final String EXTRA_SAVE = "save"; private static final String STATE_SAVEINFO = "saveinfo"; private static final FadeInfo NO_FADE = new FadeInfo(false, false, 0); private static ViewActivity instance; public static ViewActivity getInstance() { return instance; } private String scriptPath; private String backgroundName, backgroundType; private ImageView background; private FrameLayout container; private TextView textview; private MediaPlayer musicPlayer, soundPlayer; private LinkedList musicQueue, soundQueue; private MapPlaces places; private Characters characters; private Map spriteInContainer; private DisplayMetrics displayMetrics; private boolean blockTap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Постоянная подсветка. getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().setBackgroundDrawable(null); // Размер экрана. displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); setContentView(R.layout.main); instance = this; background = (ImageView) findViewById(R.id.background); container = (FrameLayout) findViewById(R.id.container); textview = (TextView) findViewById(R.id.text); musicQueue = new LinkedList(); soundQueue = new LinkedList(); background.setOnTouchListener(new TouchGesture(this, this)); spriteInContainer = new HashMap(); characters = new CharactersES(); characters.makeNamesKnown(); places = new MapPlacesES(); final Intent intent = getIntent(); final SaveInfo save; if (savedInstanceState != null) { save = savedInstanceState.getParcelable(STATE_SAVEINFO); } else { save = intent.getParcelableExtra(EXTRA_SAVE); } try { InputStream stream = null; if (save != null) { stream = fromSave(save); } else if (intent.hasExtra(EXTRA_NAME)) { stream = fromAssets( PathResolver.script(intent.getStringExtra(EXTRA_NAME)) ); } else if (intent.getData() != null) { stream = fromFile(intent.getData()); } else { Toast.makeText(this, R.string.no_data, Toast.LENGTH_LONG).show(); finish(); } Parser.parse(Lexer.tokenize( IOUtil.readContents(stream) ), save); stream = null; } catch (Exception ex) { final String message = getString(R.string.error_open_file, scriptPath); if (Logger.DEBUG) Logger.log(message, ex); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); finish(); } if (save != null) { backgroundType = save.getBackgroundType(); backgroundName = save.getBackgroundName(); Toast.makeText(this, R.string.loaded, Toast.LENGTH_SHORT).show(); } if (backgroundName != null) { background(backgroundType, backgroundName, ""); } Parser.getInstance().next(); } private InputStream fromAssets(String path) throws IOException { scriptPath = path; return getAssets().open(scriptPath); } private InputStream fromFile(Uri fileUri) throws IOException { scriptPath = fileUri.toString(); return getContentResolver().openInputStream(fileUri); } private InputStream fromSave(SaveInfo save) throws IOException { if (save.getPath().startsWith(ContentResolver.SCHEME_FILE)) { return fromFile(Uri.parse(save.getPath())); } return fromAssets(save.getPath()); } @Override protected void onPause() { stopMusic(NO_FADE); stopSound(NO_FADE); super.onPause(); } @Override protected void onDestroy() { safeClearImageView(background); spritesClear(); Parser.release(); super.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { final SaveInfo info = createSave(); outState.putParcelable(STATE_SAVEINFO, info); super.onSaveInstanceState(outState); } @Override public void onTouchGesture(TouchGestureType type) { switch (type) { case SINGLE_TAP: if (!blockTap) Parser.getInstance().next(); break; case SWIPE: Dialogs.with(this).showMenu(R.array.menu_items, menu); break; } } @Override public void onBackPressed() { Dialogs.with(this).showMenu(R.array.exit_items, exitMenu); } public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { Dialogs.with(this).showMenu(R.array.menu_items, menu); return true; } return super.onKeyUp(keyCode, event); } private void onMenuItemSelected(int item) { switch (item) { case 0: Parser.getInstance().prevScene(); break; case 1: Parser.getInstance().nextScene(); break; case 2: Dialogs.with(getInstance()).showNavigate(); break; case 3: saveState(); break; case 4: showLoadStateDialog(); break; } } public void windowShow() { if (textview.getVisibility() != View.VISIBLE) textview.setVisibility(View.VISIBLE); } public void windowHide() { if (textview.getVisibility() != View.INVISIBLE) textview.setVisibility(View.INVISIBLE); } public void background(String type, String name, String effect) { if (TextUtils.isEmpty(name)) return; backgroundType = type; backgroundName = name; text(""); spritesClear(); if (name.equalsIgnoreCase("black")) background.setImageResource(android.R.color.black); else if (name.equalsIgnoreCase("white")) background.setImageResource(android.R.color.white); else { try { background.setImageBitmap(IOUtil.readBitmap(PathResolver.background(type, name))); } catch (Exception ioe) { if (Logger.DEBUG) Logger.log("background: " + type + ", " + name, ioe); background.setImageResource(android.R.color.black); } } } public void spritesClear() { container.removeAllViews(); for (ImageView iv : spriteInContainer.values()) { safeClearImageView(iv); } spriteInContainer.clear(); } public void sprite(String whoid, String params, String position, String alias) { ImageView img; final String key = TextUtils.isEmpty(alias) ? whoid : alias; if (spriteInContainer.containsKey(key)) { img = spriteInContainer.get(key); } else { img = new ImageView(this); spriteInContainer.put(key, img); } final FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.MATCH_PARENT); setSpritePosition(img, flp, position); final String path = PathResolver.sprite(whoid, params); try { img.setImageBitmap(IOUtil.readBitmap(path)); if (container != img.getParent()) container.addView(img, flp); } catch (Exception ioe) { if (Logger.DEBUG) Logger.log("sprite: " + path, ioe); } } public void hideSprite(String whoid) { if (!spriteInContainer.containsKey(whoid)) return; final ImageView img = spriteInContainer.get(whoid); container.removeView(img); spriteInContainer.remove(img); safeClearImageView(img); } @SuppressLint("RtlHardcoded") private void setSpritePosition(ImageView img, FrameLayout.LayoutParams params, String position) { // Позиционирование спрайта. fleft, left, cleft, center, cright, right, fright final int width = displayMetrics.widthPixels; if (TextUtils.isEmpty(position) || position.equals("center")) { params.setMargins(0, 0, 0, 0); params.gravity = Gravity.CENTER_HORIZONTAL; img.setScaleType(ImageView.ScaleType.FIT_CENTER); } else if (position.equals("left")) { params.setMargins(0, 0, width/2, 0); params.gravity = Gravity.LEFT; img.setScaleType(ImageView.ScaleType.FIT_CENTER); } else if (position.equals("cleft")) { params.setMargins(0, 0, width/3, 0); params.gravity = Gravity.LEFT; img.setScaleType(ImageView.ScaleType.FIT_CENTER); } else if (position.equals("right")) { params.setMargins(width/2, 0, 0, 0); params.gravity = Gravity.RIGHT; img.setScaleType(ImageView.ScaleType.FIT_CENTER); } else if (position.equals("cright")) { params.setMargins(width/3, 0, 0, 0); params.gravity = Gravity.RIGHT; img.setScaleType(ImageView.ScaleType.FIT_CENTER); } else if (position.equals("fleft")) { params.setMargins(0, 0, 0, 0); params.gravity = Gravity.LEFT; img.setScaleType(ImageView.ScaleType.FIT_START); } else if (position.equals("fright")) { params.setMargins(0, 0, 0, 0); params.gravity = Gravity.RIGHT; img.setScaleType(ImageView.ScaleType.FIT_END); } } public void pause(final long duration, final boolean hard) { blockTap = hard; new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(duration); } catch (InterruptedException e) { } runOnUiThread(nextCommandRunnable); } }).start(); } public void text(String text) { if (TextUtils.isEmpty(text)) windowHide(); else { windowShow(); textview.setText(formatString(text)); } } public void text(String whoid, String text) { if (whoid.equalsIgnoreCase("th")) text("~ " + text + " ~"); else if (!characters.contains(whoid)) text(text); else { windowShow(); final Characters.NameInfo person = characters.get(whoid); final String who = person.name; Spannable spannable = formatString(who + "\n" + text); spannable.setSpan(new ForegroundColorSpan(person.color), 0, who.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); textview.setText(spannable); } } private Spannable formatString(String text) { String edited = text.replaceAll("\\{w.*?\\}", ""); final char[] codes = {'b','i','s','u'}; boolean html = false; for (int i = 0; i < codes.length; i++) { final char ch = codes[i]; if (edited.contains("{"+ch+"}")) { edited = edited.replace("{"+ch+"}", "<"+ch+">"); edited = edited.replace("{/"+ch+"}", ""); html = true; } } if (html) edited = edited.replace("\n", "
"); return new SpannableString(html ? Html.fromHtml(edited) : edited); } public void makeNamesKnown() { characters.makeNamesKnown(); } public void makeNamesUnknown() { characters.makeNamesUnknown(); } public void meet(String whoid, String name) { characters.setName(whoid, name); } public void disableAllZones() { places.disableAllZones(); } public void disableCurrentZone() { places.disableCurrentZone(); } public void resetZone(String zone) { places.resetZone(zone); } public void setZone(String name, String label) { places.setZone(name, label); } public void showMap() { places.showMap(this); } public void menu(final Menu menu) { Dialogs.with(this).showGameMenu(menu, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { dialog.dismiss(); Parser.getInstance().setPosition(menu.getPosition(item)); } }); } public void addMusicToQueue(String name) { if ( (musicPlayer != null) && (musicPlayer.isPlaying()) ) { // Если музыка играет, просто добавляем в очередь musicQueue.addLast(name); } else { // Воспроизводим music(name, NO_FADE); } } public void addSoundToQueue(String name) { if ( (soundPlayer != null) && (soundPlayer.isPlaying()) ) { soundQueue.addLast(name); } else { sound(name, false, NO_FADE); } } public void music(String name, FadeInfo fade) { try { stopMusic(fade); musicPlayer = new MediaPlayer(); musicPlayer.setOnCompletionListener(musicCompleteListener); musicPlayer.setDataSource( IOUtil.getFD(PathResolver.music(name)) ); musicPlayer.prepare(); musicPlayer.setVolume(1f, 1f); musicPlayer.setLooping(true); musicPlayer.start(); } catch (Exception e) { if (Logger.DEBUG) Logger.log("music: " + name, e); } } public void stopMusic(FadeInfo fade) { if (musicPlayer == null) return; if (musicPlayer.isPlaying()) { musicPlayer.stop(); musicPlayer.release(); } musicPlayer = null; } public void sound(String name, boolean loop, FadeInfo fade) { try { stopSound(fade); soundPlayer = new MediaPlayer(); soundPlayer.setOnCompletionListener(soundCompleteListener); soundPlayer.setDataSource( IOUtil.getFD(PathResolver.sound(name)) ); soundPlayer.prepare(); soundPlayer.setVolume(1f, 1f); soundPlayer.setLooping(loop); soundPlayer.start(); } catch (Exception e) { if (Logger.DEBUG) Logger.log("sound: " + name, e); } } public void stopSound(FadeInfo fade) { if (soundPlayer == null) return; if (soundPlayer.isPlaying()) { soundPlayer.stop(); soundPlayer.release(); } soundPlayer = null; } private static void safeClearImageView(ImageView iv) { if (iv == null) return; iv.setImageDrawable(null); iv = null; } private void showLoadStateDialog() { final List saves = IOUtil.listSaves(getApplicationContext()); if (saves == null || saves.isEmpty()) { Toast.makeText(this, R.string.no_saves, Toast.LENGTH_SHORT).show(); return; } Dialogs.with(this).showSaves(saves, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final SaveInfo save = saves.get(which); if (save.getPath().equals(scriptPath)) { // Восстанавливаем сохранение текущего сценария. Variables.setVariables(save.getVariables()); if (!TextUtils.isEmpty(save.getBackgroundName())) { backgroundType = save.getBackgroundType(); backgroundName = save.getBackgroundName(); ViewActivity.getInstance().background(backgroundType, backgroundName, ""); } Parser.getInstance().setPosition(save.getPosition()); Toast.makeText(ViewActivity.this, R.string.loaded, Toast.LENGTH_SHORT).show(); } else { // Пересоздаём активити final Intent intent = new Intent(ViewActivity.this, ViewActivity.class); intent.putExtra(ViewActivity.EXTRA_SAVE, save); startActivity(intent); finish(); } } }); } private void saveState() { final SaveInfo info = createSave(); try { final String filename = Long.toString(info.getTime()); IOUtil.writeSaveInfo(getApplicationContext(), filename, info); Toast.makeText(this, R.string.saved, Toast.LENGTH_SHORT).show(); } catch (IOException ioe) { if (Logger.DEBUG) Logger.log("Ошибка сохранения", ioe); } } private SaveInfo createSave() { final SaveInfo info = new SaveInfo(); info.setPath(scriptPath); info.setPosition(Parser.getInstance().getLastPosition()); info.setTime(System.currentTimeMillis()); info.setVariables(Variables.getVariables()); info.setBackgroundType(backgroundType); info.setBackgroundName(backgroundName); return info; } private final Runnable nextCommandRunnable = new Runnable() { @Override public void run() { blockTap = false; Parser.getInstance().next(); } }; private final DialogInterface.OnClickListener menu = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { onMenuItemSelected(item); } }; private final DialogInterface.OnClickListener exitMenu = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { switch (item) { case 0: saveState(); break; case 1: finish(); break; } } }; private final MediaPlayer.OnCompletionListener musicCompleteListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { if (musicQueue.size() > 0) { music(musicQueue.removeFirst(), NO_FADE); } } }; private final MediaPlayer.OnCompletionListener soundCompleteListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { if (soundQueue.size() > 0) { sound(soundQueue.removeFirst(), false, NO_FADE); } } }; }