1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.replica.replicaisland; 18 19 import java.util.ArrayList; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.graphics.drawable.AnimationDrawable; 27 import android.os.Bundle; 28 import android.os.SystemClock; 29 import android.text.SpannableStringBuilder; 30 import android.text.TextUtils; 31 import android.util.AttributeSet; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.widget.ImageView; 35 import android.widget.TextView; 36 37 import com.replica.replicaisland.ConversationUtils.Conversation; 38 import com.replica.replicaisland.ConversationUtils.ConversationPage; 39 40 public class ConversationDialogActivity extends Activity { 41 42 private final static float TEXT_CHARACTER_DELAY = 0.1f; 43 private final static int TEXT_CHARACTER_DELAY_MS = (int)(TEXT_CHARACTER_DELAY * 1000); 44 private ConversationUtils.Conversation mConversation; 45 private ArrayList<ConversationUtils.ConversationPage> mPages; 46 private int mCurrentPage; 47 48 private ImageView mOkArrow; 49 private AnimationDrawable mOkAnimation; 50 51 @Override 52 protected void onCreate(Bundle savedInstanceState) { 53 super.onCreate(savedInstanceState); 54 setContentView(R.layout.conversation_dialog); 55 56 mOkArrow = (ImageView)findViewById(R.id.ok); 57 mOkArrow.setBackgroundResource(R.anim.ui_button); 58 mOkAnimation = (AnimationDrawable) mOkArrow.getBackground(); 59 mOkArrow.setVisibility(View.INVISIBLE); 60 61 final Intent callingIntent = getIntent(); 62 final int levelRow = callingIntent.getIntExtra("levelRow", -1); 63 final int levelIndex = callingIntent.getIntExtra("levelIndex", -1); 64 final int index = callingIntent.getIntExtra("index", -1); 65 final int character = callingIntent.getIntExtra("character", 1); 66 67 mPages = null; 68 69 // LevelTree.get(mLevelRow, mLevelIndex).dialogResources.character2Entry.get(index) 70 if (levelRow != -1 && levelIndex != -1 && index != -1) { 71 if (character == 1) { 72 mConversation = LevelTree.get(levelRow, levelIndex).dialogResources.character1Conversations.get(index); 73 } else { 74 mConversation = LevelTree.get(levelRow, levelIndex).dialogResources.character2Conversations.get(index); 75 } 76 TypewriterTextView tv = (TypewriterTextView)findViewById(R.id.typewritertext); 77 tv.setParentActivity(this); 78 79 } else { 80 // bail 81 finish(); 82 } 83 } 84 85 private void formatPages(Conversation conversation, TextView textView) { 86 Paint paint = new Paint(); 87 final int maxWidth = textView.getWidth(); 88 final int maxHeight = textView.getHeight(); 89 paint.setTextSize(textView.getTextSize()); 90 paint.setTypeface(textView.getTypeface()); 91 92 for (int page = conversation.pages.size() - 1; page >= 0 ; page--) { 93 ConversationUtils.ConversationPage currentPage = conversation.pages.get(page); 94 CharSequence text = currentPage.text; 95 // Iterate line by line through the text. Add \n if it gets too wide, 96 // and split into a new page if it gets too long. 97 int currentOffset = 0; 98 int textLength = text.length(); 99 SpannableStringBuilder spannedText = new SpannableStringBuilder(text); 100 int lineCount = 0; 101 final float fontHeight = -paint.ascent() + paint.descent(); 102 final int maxLinesPerPage = (int)(maxHeight / fontHeight); 103 CharSequence newline = "\n"; 104 int addedPages = 0; 105 int lastPageStart = 0; 106 do { 107 int fittingChars = paint.breakText(text, currentOffset, textLength, true, maxWidth, null); 108 109 if (currentOffset + fittingChars < textLength) { 110 fittingChars -= 2; 111 // Text doesn't fit on the line. Insert a return after the last space. 112 int lastSpace = TextUtils.lastIndexOf(text, ' ', currentOffset + fittingChars - 1); 113 if (lastSpace == -1) { 114 // No spaces, just split at the last character. 115 lastSpace = currentOffset + fittingChars - 1; 116 } 117 spannedText.replace(lastSpace, lastSpace + 1, newline, 0, 1); 118 lineCount++; 119 currentOffset = lastSpace + 1; 120 } else { 121 lineCount++; 122 currentOffset = textLength; 123 } 124 125 if (lineCount >= maxLinesPerPage || currentOffset >= textLength) { 126 lineCount = 0; 127 if (addedPages == 0) { 128 // overwrite the original page 129 currentPage.text = spannedText.subSequence(lastPageStart, currentOffset); 130 } else { 131 // split into a new page 132 ConversationPage newPage = new ConversationPage(); 133 newPage.imageResource = currentPage.imageResource; 134 newPage.text = spannedText.subSequence(lastPageStart, currentOffset); 135 newPage.title = currentPage.title; 136 conversation.pages.add(page + addedPages, newPage); 137 } 138 lastPageStart = currentOffset; 139 addedPages++; 140 } 141 } while (currentOffset < textLength); 142 143 144 } 145 146 // Holy crap we did a lot of allocation there. 147 Runtime.getRuntime().gc(); 148 } 149 150 @Override 151 public boolean onTouchEvent(MotionEvent event) { 152 if (event.getAction() == MotionEvent.ACTION_UP) { 153 TypewriterTextView tv = (TypewriterTextView)findViewById(R.id.typewritertext); 154 155 if (tv.getRemainingTime() > 0) { 156 tv.snapToEnd(); 157 } else { 158 mCurrentPage++; 159 if (mCurrentPage < mPages.size()) { 160 showPage(mPages.get(mCurrentPage)); 161 } else { 162 finish(); 163 } 164 } 165 } 166 // Sleep so that the main thread doesn't get flooded with UI events. 167 try { 168 Thread.sleep(32); 169 } catch (InterruptedException e) { 170 // No big deal if this sleep is interrupted. 171 } 172 return true; 173 } 174 175 protected void showPage(ConversationUtils.ConversationPage page) { 176 TypewriterTextView tv = (TypewriterTextView)findViewById(R.id.typewritertext); 177 tv.setTypewriterText(page.text); 178 179 mOkArrow.setVisibility(View.INVISIBLE); 180 mOkAnimation.start(); 181 182 tv.setOkArrow(mOkArrow); 183 184 ImageView image = (ImageView)findViewById(R.id.speaker); 185 if (page.imageResource != 0) { 186 image.setImageResource(page.imageResource); 187 image.setVisibility(View.VISIBLE); 188 } else { 189 image.setVisibility(View.GONE); 190 } 191 192 TextView title = (TextView)findViewById(R.id.speakername); 193 if (page.title != null) { 194 title.setText(page.title); 195 title.setVisibility(View.VISIBLE); 196 } else { 197 title.setVisibility(View.GONE); 198 } 199 200 } 201 202 public void processText() { 203 if (!mConversation.splittingComplete) { 204 TextView textView = (TextView)findViewById(R.id.typewritertext); 205 formatPages(mConversation, textView); 206 mConversation.splittingComplete = true; 207 } 208 209 if (mPages == null) { 210 mPages = mConversation.pages; 211 showPage(mPages.get(0)); 212 213 mCurrentPage = 0; 214 } 215 } 216 217 218 public static class TypewriterTextView extends TextView { 219 private int mCurrentCharacter; 220 private long mLastTime; 221 private CharSequence mText; 222 private View mOkArrow; 223 private ConversationDialogActivity mParentActivity; // This really sucks. 224 225 public TypewriterTextView(Context context) { 226 super(context); 227 } 228 229 public TypewriterTextView(Context context, AttributeSet attrs) { 230 super(context, attrs); 231 } 232 233 public TypewriterTextView(Context context, AttributeSet attrs, int defStyle) { 234 super(context, attrs, defStyle); 235 } 236 237 public void setParentActivity(ConversationDialogActivity parent) { 238 mParentActivity = parent; 239 } 240 241 public void setTypewriterText(CharSequence text) { 242 mText = text; 243 mCurrentCharacter = 0; 244 mLastTime = 0; 245 postInvalidate(); 246 } 247 248 public long getRemainingTime() { 249 return (mText.length() - mCurrentCharacter) * TEXT_CHARACTER_DELAY_MS; 250 } 251 252 public void snapToEnd() { 253 mCurrentCharacter = mText.length() - 1; 254 } 255 256 public void setOkArrow(View arrow) { 257 mOkArrow = arrow; 258 } 259 260 261 @Override 262 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 263 // We need to wait until layout has occurred before we can setup the 264 // text page. Ugh. Bidirectional dependency! 265 if (mParentActivity != null) { 266 mParentActivity.processText(); 267 } 268 super.onSizeChanged(w, h, oldw, oldh); 269 } 270 271 @Override 272 public void onDraw(Canvas canvas) { 273 final long time = SystemClock.uptimeMillis(); 274 final long delta = time - mLastTime; 275 if (delta > TEXT_CHARACTER_DELAY_MS) { 276 if (mText != null) { 277 if (mCurrentCharacter <= mText.length()) { 278 CharSequence subtext = mText.subSequence(0, mCurrentCharacter); 279 setText(subtext, TextView.BufferType.SPANNABLE); 280 mCurrentCharacter++; 281 postInvalidateDelayed(TEXT_CHARACTER_DELAY_MS); 282 } else { 283 if (mOkArrow != null) { 284 mOkArrow.setVisibility(View.VISIBLE); 285 } 286 } 287 } 288 } 289 super.onDraw(canvas); 290 } 291 } 292 293 294 295 } 296