From 7dafd0b87b42e442d1762439cd08b0f18619affd Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 26 Aug 2020 16:37:39 +0200 Subject: [PATCH] Fixes #34 : visual glitches when replaying a hand and exporting at the same time due to the static nature of the drawer --- .../replayer/ReplayExportService.kt | 103 +-- .../handhistory/replayer/ReplayerAnimator.kt | 15 +- .../handhistory/replayer/ReplayerView.kt | 6 +- .../handhistory/replayer/TableDrawer.kt | 658 +++++++++--------- 4 files changed, 380 insertions(+), 402 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt index 1d58bd9f..1267f764 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt @@ -18,13 +18,11 @@ import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.handhistory.HandHistory -import net.pokeranalytics.android.ui.extensions.toByteArray import net.pokeranalytics.android.util.FFMPEG_DESCRIPTOR_FILE import net.pokeranalytics.android.util.TriggerNotification import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.video.AnimatedGIFWriter -import net.pokeranalytics.android.util.video.MMediaMuxer import timber.log.Timber import java.io.File import java.io.FileOutputStream @@ -79,7 +77,6 @@ class ReplayExportService : Service() { val height = square animator.setDimension(width.toFloat(), height.toFloat()) - TableDrawer.configurePaints(context, animator) val formattedDate = Date().dateTimeFileFormatted val path = File( @@ -91,8 +88,11 @@ class ReplayExportService : Service() { val os = FileOutputStream(path) writer.prepareForWrite(os, width, height) + val drawer = TableDrawer() + drawer.configurePaints(context, animator) + var animationCount = 0 - animator.frames(context) { bitmap, count -> + animator.frames(drawer, context) { bitmap, count -> when { count > 10 -> { @@ -137,11 +137,12 @@ class ReplayExportService : Service() { val height = square animator.setDimension(width.toFloat(), height.toFloat()) - TableDrawer.configurePaints(context, animator) + val drawer = TableDrawer() + drawer.configurePaints(context, animator) // generates all images and file descriptor Timber.d("Generating images for video...") - val tmpDir = animator.generateVideoContent(this@ReplayExportService) + val tmpDir = animator.generateVideoContent(drawer, this@ReplayExportService) val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" val directory = context.getExternalFilesDir(null) ?: throw PAIllegalStateException("File is invalid") @@ -170,51 +171,51 @@ class ReplayExportService : Service() { } - private fun startVideoExport() { - - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { - - val realm = Realm.getDefaultInstance() - val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") - - val context = this@ReplayExportService - - val animator = ReplayerAnimator(handHistory, true) - - val square = 1024 - - val width = square - val height = square - - animator.setDimension(width.toFloat(), height.toFloat()) - TableDrawer.configurePaints(context, animator) - - val muxer = MMediaMuxer() - muxer.init(null, width, height, "hhVideo", "YES!") - - animator.frames(context) { bitmap, count -> - - try { - val byteArray = bitmap.toByteArray() - muxer.addFrame(byteArray, count, false) - } catch (e: Exception) { - Timber.e("error = ${e.message}") - } - - } - - realm.close() - - muxer.createVideo { path -> - notifyUser(path) - } - - } - c.await() - } - - } +// private fun startVideoExport() { +// +// GlobalScope.launch(coroutineContext) { +// val c = GlobalScope.async { +// +// val realm = Realm.getDefaultInstance() +// val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") +// +// val context = this@ReplayExportService +// +// val animator = ReplayerAnimator(handHistory, true) +// +// val square = 1024 +// +// val width = square +// val height = square +// +// animator.setDimension(width.toFloat(), height.toFloat()) +// TableDrawer.configurePaints(context, animator) +// +// val muxer = MMediaMuxer() +// muxer.init(null, width, height, "hhVideo", "YES!") +// +// animator.frames(context) { bitmap, count -> +// +// try { +// val byteArray = bitmap.toByteArray() +// muxer.addFrame(byteArray, count, false) +// } catch (e: Exception) { +// Timber.e("error = ${e.message}") +// } +// +// } +// +// realm.close() +// +// muxer.createVideo { path -> +// notifyUser(path) +// } +// +// } +// c.await() +// } +// +// } private fun notifyUser(path: String) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerAnimator.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerAnimator.kt index 719b0edf..563daad0 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerAnimator.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerAnimator.kt @@ -4,7 +4,6 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.RectF -import kotlinx.coroutines.delay import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.handhistory.Street import net.pokeranalytics.android.model.realm.handhistory.Action @@ -147,7 +146,6 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) { init { loadHandHistory(this.handHistory) - TableDrawer.clearColors() } private fun loadHandHistory(handHistory: HandHistory) { @@ -479,7 +477,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) { * Generates images and image descriptor to build the video using ffmpeg * Command line: https://trac.ffmpeg.org/wiki/Slideshow */ - suspend fun generateVideoContent(context: Context): File { + fun generateVideoContent(tableDrawer: TableDrawer, context: Context): File { var ffmpegImageDescriptor = "" var count = 0 @@ -500,21 +498,20 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) { val canvas = Canvas(bitmap) val vo = this.visualOccurences / 90.0 // this is needed before the call to drawTable which pass to the next frame - TableDrawer.drawTable(this, canvas, context) + tableDrawer.drawTable(canvas, context) imagePath = File(directory, "img_$count.png").path FileUtils.writeBitmapToPNG(bitmap, imagePath) - bitmap.recycle() +// bitmap.recycle() ffmpegImageDescriptor = ffmpegImageDescriptor.plus("file '$imagePath'\n") ffmpegImageDescriptor = ffmpegImageDescriptor.plus("duration $vo\n") count++ - Thread.sleep(100L) +// awaitFrame() -// delay(100L) } nextStep() @@ -537,7 +534,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) { * As soon as a bitmap has been created, the [frameHandler] is called with the created * bitmap, and a appropriate occurrences of the bitmap to be added is passed along */ - fun frames(context: Context, frameHandler: (Bitmap, Int) -> Unit) { + fun frames(tableDrawer: TableDrawer, context: Context, frameHandler: (Bitmap, Int) -> Unit) { Timber.d("Step count = ${this.steps.size}") @@ -553,7 +550,7 @@ class ReplayerAnimator(var handHistory: HandHistory, var export: Boolean) { val canvas = Canvas(bitmap) val vo = this.visualOccurences // this is needed before the call to drawTable which pass to the next frame - TableDrawer.drawTable(this, canvas, context) + tableDrawer.drawTable(canvas, context) frameHandler(bitmap, vo) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerView.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerView.kt index 0b8a8640..edcbe607 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerView.kt @@ -9,6 +9,8 @@ import android.view.View class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs) { + private var drawer: TableDrawer = TableDrawer() + lateinit var animator: ReplayerAnimator private val animationHandler = Handler(Looper.getMainLooper()) @@ -20,7 +22,7 @@ class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs) init { this.viewTreeObserver.addOnGlobalLayoutListener { this.animator.setDimension(width.toFloat(), height.toFloat()) - TableDrawer.configurePaints(context, this.animator) + this.drawer.configurePaints(context, this.animator) } } @@ -37,7 +39,7 @@ class ReplayerView(context: Context, attrs: AttributeSet) : View(context, attrs) super.onDraw(canvas) canvas?.let { - TableDrawer.drawTable(animator, canvas, context) + this.drawer.drawTable(canvas, context) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/TableDrawer.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/TableDrawer.kt index 3389214e..c332f9e5 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/TableDrawer.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/TableDrawer.kt @@ -12,446 +12,424 @@ import net.pokeranalytics.android.ui.modules.handhistory.model.ComputedAction import net.pokeranalytics.android.util.RANDOM_PLAYER import net.pokeranalytics.android.util.extensions.formatted -class TableDrawer(bitmap: Bitmap) : Canvas(bitmap) { +class TableDrawer { - companion object { + lateinit var animator: ReplayerAnimator - private const val backgroundColor = R.color.green_darker - private val backgroundPaint = Paint() - private val strokePaint = Paint() - private val fillPaint = Paint() + private val backgroundColor = R.color.green_darker + private val backgroundPaint = Paint() + private val strokePaint = Paint() + private val fillPaint = Paint() - private val tablePaint = Paint() - private val feltPaint = Paint() - private val textPaint = Paint() - private val cardTextPaint = Paint() - private val cardStrokePaint = Paint() - private val chipBorderPaint = Paint() + private val tablePaint = Paint() + private val feltPaint = Paint() + private val textPaint = Paint() + private val cardTextPaint = Paint() + private val cardStrokePaint = Paint() + private val chipBorderPaint = Paint() - private val colorsByAmount = hashMapOf() + private val colorsByAmount = hashMapOf() - fun clearColors() { - this.colorsByAmount.clear() - } +// fun clearColors() { +// this.colorsByAmount.clear() +// } - fun configurePaints(context: Context, animator: ReplayerAnimator) { + fun configurePaints(context: Context, animator: ReplayerAnimator) { - backgroundPaint.color = context.getColor(backgroundColor) + this.animator = animator - tablePaint.isAntiAlias = true - tablePaint.strokeWidth = animator.tableStrokeWidth - tablePaint.color = context.getColor(R.color.green) - tablePaint.style = Paint.Style.STROKE + backgroundPaint.color = context.getColor(backgroundColor) - feltPaint.isAntiAlias = true - feltPaint.color = context.getColor(R.color.green_transparent_felt) - feltPaint.style = Paint.Style.STROKE - feltPaint.strokeWidth = animator.tableStrokeWidth + tablePaint.isAntiAlias = true + tablePaint.strokeWidth = animator.tableStrokeWidth + tablePaint.color = context.getColor(R.color.green) + tablePaint.style = Paint.Style.STROKE - strokePaint.isAntiAlias = true - strokePaint.style = Paint.Style.STROKE - strokePaint.strokeWidth = animator.playerStrokeWidth + feltPaint.isAntiAlias = true + feltPaint.color = context.getColor(R.color.green_transparent_felt) + feltPaint.style = Paint.Style.STROKE + feltPaint.strokeWidth = animator.tableStrokeWidth - cardStrokePaint.isAntiAlias = true - cardStrokePaint.style = Paint.Style.STROKE - cardStrokePaint.strokeWidth = animator.cardStrokeWidth + strokePaint.isAntiAlias = true + strokePaint.style = Paint.Style.STROKE + strokePaint.strokeWidth = animator.playerStrokeWidth - chipBorderPaint.isAntiAlias = true - chipBorderPaint.style = Paint.Style.STROKE + cardStrokePaint.isAntiAlias = true + cardStrokePaint.style = Paint.Style.STROKE + cardStrokePaint.strokeWidth = animator.cardStrokeWidth - fillPaint.isAntiAlias = true + chipBorderPaint.isAntiAlias = true + chipBorderPaint.style = Paint.Style.STROKE - textPaint.color = context.getColor(R.color.white) - textPaint.textAlign = Paint.Align.CENTER - textPaint.isAntiAlias = true - textPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold) + fillPaint.isAntiAlias = true - cardTextPaint.color = context.getColor(R.color.black) - cardTextPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold) - cardTextPaint.textAlign = Paint.Align.CENTER - cardTextPaint.isAntiAlias = true - } + textPaint.color = context.getColor(R.color.white) + textPaint.textAlign = Paint.Align.CENTER + textPaint.isAntiAlias = true + textPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold) - fun drawTable(animator: ReplayerAnimator, canvas: Canvas, context: Context) { - drawTableItems(animator, canvas, context) - animator.frameDrawn() - } + cardTextPaint.color = context.getColor(R.color.black) + cardTextPaint.typeface = ResourcesCompat.getFont(context, R.font.roboto_bold) + cardTextPaint.textAlign = Paint.Align.CENTER + cardTextPaint.isAntiAlias = true + } - private fun drawTableItems(animator: ReplayerAnimator, canvas: Canvas, context: Context) { + fun drawTable(canvas: Canvas, context: Context) { + drawTableItems(canvas, context) + animator.frameDrawn() + } - // base - initializeTable(animator, canvas, context) + private fun drawTableItems(canvas: Canvas, context: Context) { - val step = animator.currentStep - val street = step.street + // base + initializeTable(canvas, context) - val computedAction = step as? ComputedAction? + val step = animator.currentStep + val street = step.street - // draw board - drawBoardCards(street, animator, canvas, context) + val computedAction = step as? ComputedAction? - // draw player shapes and chips - val hh = animator.handHistory - for (i in 0 until hh.numberOfPlayers) { - drawPlayerShapes(i, computedAction, animator, canvas, context) - drawPlayerChips(i, animator, canvas, context) - } + // draw board + drawBoardCards(street, canvas, context) - // draw pot - drawPot(street, computedAction, animator, canvas, context) + // draw player shapes and chips + val hh = animator.handHistory + for (i in 0 until hh.numberOfPlayers) { + drawPlayerShapes(i, computedAction, canvas, context) + drawPlayerChips(i, canvas, context) + } - // draw player cards - animator.activePositions.forEach { - drawPlayerCards(it, animator, canvas, context) - } + // draw pot + drawPot(street, computedAction, canvas, context) + // draw player cards + animator.activePositions.forEach { + drawPlayerCards(it, canvas, context) } - private fun drawPlayerShapes(i: Int, computedAction: ComputedAction?, animator: ReplayerAnimator, canvas: Canvas, context: Context) { + } + + private fun drawPlayerShapes(i: Int, computedAction: ComputedAction?, canvas: Canvas, context: Context) { - // draw player rectangles with action or name + stack - if (computedAction?.positionIndex == i) { - drawPlayerRectangle(i, true, animator, canvas, context) - drawAction(i, computedAction, animator, canvas, context) + // draw player rectangles with action or name + stack + if (computedAction?.positionIndex == i) { + drawPlayerRectangle(i, true, canvas, context) + drawAction(i, computedAction, canvas, context) + } else { + val info = if (animator.isPlayerAllin(i)) { + context.getString(R.string.allin) } else { - val info = if (animator.isPlayerAllin(i)) { - context.getString(R.string.allin) - } else { - val remainingStack = animator.playerRemainingStack(i) - remainingStack?.formatted - } - drawPlayerRectangle(i, false, animator, canvas, context) - drawPositionAndInfo(i, info, animator, canvas) + val remainingStack = animator.playerRemainingStack(i) + remainingStack?.formatted } - + drawPlayerRectangle(i, false, canvas, context) + drawPositionAndInfo(i, info, canvas) } - /*** - * Draw chips - * A chip should appears when the player has put some money during the current street - */ - private fun drawPlayerChips(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - when (animator.currentStep) { - is Street -> { - when (animator.frameType) { - FrameType.STATE -> { - if (animator.currentStep == Street.SUMMARY) { - - val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i } - winnerPots?.let { pot -> - val color = colorForAmount(pot.amount) - val circle = animator.animatedChipCircleFromPot(i) - drawChipCircle(circle, color, canvas, context) - drawChipText(pot.amount, animator.chipText(i), canvas, context) - } + } - } - } - FrameType.GATHER_ANIMATION -> { - lastCommittedAmount(i, animator)?.let { amount -> - val color = colorForAmount(amount) - val circle = animator.animatedChipCircleToPot(i) - drawChipCircle(circle, color, canvas, context) - } - } - FrameType.DISTRIBUTION_ANIMATION -> { + /*** + * Draw chips + * A chip should appears when the player has put some money during the current street + */ + private fun drawPlayerChips(i: Int, canvas: Canvas, context: Context) { + + when (animator.currentStep) { + is Street -> { + when (animator.frameType) { + FrameType.STATE -> { + if (animator.currentStep == Street.SUMMARY) { val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i } winnerPots?.let { pot -> val color = colorForAmount(pot.amount) val circle = animator.animatedChipCircleFromPot(i) drawChipCircle(circle, color, canvas, context) + drawChipText(pot.amount, animator.chipText(i), canvas, context) } + } } - } - is ComputedAction -> { - drawChipForAction(i, animator, canvas, context) - } - } - - } + FrameType.GATHER_ANIMATION -> { + lastCommittedAmount(i)?.let { amount -> + val color = colorForAmount(amount) + val circle = animator.animatedChipCircleToPot(i) + drawChipCircle(circle, color, canvas, context) + } + } + FrameType.DISTRIBUTION_ANIMATION -> { - private fun drawChipForAction(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - lastCommittedAmount(i, animator)?.let { amount -> - drawChip(amount, i, animator, canvas, context) + val winnerPots = animator.handHistory.winnerPots.firstOrNull { it.position == i } + winnerPots?.let { pot -> + val color = colorForAmount(pot.amount) + val circle = animator.animatedChipCircleFromPot(i) + drawChipCircle(circle, color, canvas, context) + } + } + } } - } - - private fun lastCommittedAmount(i: Int, animator: ReplayerAnimator): Double? { - var committingAction = animator.lastChipCommittingActionOfPlayer(i) - if (committingAction?.action?.type?.isCall == true) { - committingAction = committingAction.getStreetLastSignificantAction() + is ComputedAction -> { + drawChipForAction(i, canvas, context) } - return committingAction?.action?.amount } - /*** - * WARNING: Avoid instancing objects here, as it's called from onDraw method - */ - private fun initializeTable(animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - canvas.drawColor(context.getColor(backgroundColor)) - - // Felt rosace -// val xr = animator.tableCornerXRadius -// val yr = animator.tableCornerYRadius -// val r = animator.tableRect -// val r1 = RectF(r.left, r.top, r.left + 2 * xr, r.top + 2 * yr) -// canvas.drawRoundRect(r1, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r2 = RectF(r.right - 2 * xr, r.top, r.right, r.top + 2 * yr) -// canvas.drawRoundRect(r2, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r3 = RectF(r.right - 2 * xr, r.bottom - 2 * yr, r.right, r.bottom) -// canvas.drawRoundRect(r3, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r4 = RectF(r.left, r.bottom - 2 * yr, r.left + 2 * xr, r.bottom) -// canvas.drawRoundRect(r4, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r5 = RectF(r.right - 2 * xr, r.centerY() - yr, r.right, r.centerY() + yr) -// canvas.drawRoundRect(r5, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r6 = RectF(r.right - 2 * xr, r.top, r.right, r.top + 2 * yr) -// canvas.drawRoundRect(r6, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r7 = RectF(r.left, r.centerY() - yr, r.left + 2 * xr, r.centerY() + yr) -// canvas.drawRoundRect(r7, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) -// val r8 = RectF(r.left, r.bottom - 2 * yr, r.left + 2 * xr, r.bottom) -// canvas.drawRoundRect(r8, animator.tableCornerXRadius, animator.tableCornerYRadius, this.feltPaint) - - canvas.drawRoundRect(animator.tableRect, animator.tableCornerXRadius, animator.tableCornerYRadius, this.tablePaint) - - this.cardTextPaint.textSize = animator.cardSpecs.height * .38f - - val hh = animator.handHistory - for (i in 0 until hh.numberOfPlayers) { - drawPlayerRectangle(i,false, animator, canvas, context) - drawPlayerCircle(i, animator, canvas, context) - } - - } + } - private fun drawChip(amount: Double, playerIndex: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - val circle = animator.chipCircle(playerIndex) - val text = animator.chipText(playerIndex) - drawChip(amount, text, circle, canvas, context) + private fun drawChipForAction(i: Int, canvas: Canvas, context: Context) { + lastCommittedAmount(i)?.let { amount -> + drawChip(amount, i, canvas, context) } + } - private fun drawChip(amount: Double, chipText: ReplayerAnimator.TextPoint, chipCircle: ReplayerAnimator.Circle, canvas: Canvas, context: Context) { - val color = colorForAmount(amount) - drawChipCircle(chipCircle, color, canvas, context) - drawChipText(amount, chipText, canvas, context) + private fun lastCommittedAmount(i: Int): Double? { + var committingAction = animator.lastChipCommittingActionOfPlayer(i) + if (committingAction?.action?.type?.isCall == true) { + committingAction = committingAction.getStreetLastSignificantAction() } + return committingAction?.action?.amount + } - private fun drawChipText(amount: Double, chipText: ReplayerAnimator.TextPoint, canvas: Canvas, context: Context) { - this.textPaint.textSize = chipText.fontSize - this.textPaint.color = context.getColor(R.color.white) - canvas.drawText(amount.formatted, chipText.x, chipText.y, this.textPaint) - } + /*** + * WARNING: Avoid instancing objects here, as it's called from onDraw method + */ + private fun initializeTable(canvas: Canvas, context: Context) { - private fun colorForAmount(amount: Double): ChipColor { + canvas.drawColor(context.getColor(backgroundColor)) + canvas.drawRoundRect(animator.tableRect, animator.tableCornerXRadius, animator.tableCornerYRadius, this.tablePaint) - this.colorsByAmount[amount]?.let { return it } + this.cardTextPaint.textSize = animator.cardSpecs.height * .38f - val index = this.colorsByAmount.size % ChipColor.all.size - val color = ChipColor.all[index] - this.colorsByAmount[amount] = color - return color + val hh = animator.handHistory + for (i in 0 until hh.numberOfPlayers) { + drawPlayerRectangle(i,false, canvas, context) + drawPlayerCircle(i, canvas, context) } - private fun drawChipCircle(chipCircle: ReplayerAnimator.Circle, chipColor: ChipColor, canvas: Canvas, context: Context) { - - this.fillPaint.color = context.getColor(chipColor.fillColor) - canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius, this.fillPaint) - this.chipBorderPaint.color = context.getColor(chipColor.borderColor) - val chipBorderStrokeWidth = chipCircle.radius / 3f - this.chipBorderPaint.strokeWidth = chipBorderStrokeWidth + } - chipBorderPaint.pathEffect = DashPathEffect(floatArrayOf(chipCircle.radius * 0.6f, chipCircle.radius * 0.44f), 0f) - canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius - chipBorderStrokeWidth / 2, this.chipBorderPaint) + private fun drawChip(amount: Double, playerIndex: Int, canvas: Canvas, context: Context) { + val circle = animator.chipCircle(playerIndex) + val text = animator.chipText(playerIndex) + drawChip(amount, text, circle, canvas, context) + } - } + private fun drawChip(amount: Double, chipText: ReplayerAnimator.TextPoint, chipCircle: ReplayerAnimator.Circle, canvas: Canvas, context: Context) { + val color = colorForAmount(amount) + drawChipCircle(chipCircle, color, canvas, context) + drawChipText(amount, chipText, canvas, context) + } - private fun drawPlayerCards(playerIndex: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - val playerSetup = animator.handHistory.playerSetupForPosition(playerIndex) - val cardRects = animator.cardRects(playerIndex) - val cards = playerSetup?.cards - val isHero = (animator.handHistory.heroIndex == playerIndex) - cardRects.forEachIndexed { j, cardRect -> - - if (j < cards?.size ?: 0 && (animator.showVillainHands || isHero)) { // show card - val card = cards?.get(j)!! // tested line before - drawCard(card, cardRect, animator, canvas, context) - } else { // show hidden cards - fillPaint.color = context.getColor(R.color.card_fill) - canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint) - cardStrokePaint.color = context.getColor(R.color.card_border) - canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, cardStrokePaint) - } + private fun drawChipText(amount: Double, chipText: ReplayerAnimator.TextPoint, canvas: Canvas, context: Context) { + this.textPaint.textSize = chipText.fontSize + this.textPaint.color = context.getColor(R.color.white) + canvas.drawText(amount.formatted, chipText.x, chipText.y, this.textPaint) + } - } + private fun colorForAmount(amount: Double): ChipColor { - } + this.colorsByAmount[amount]?.let { return it } - private fun drawCard(card: Card, cardRect: RectF, animator: ReplayerAnimator, canvas: Canvas, context: Context) { + val index = this.colorsByAmount.size % ChipColor.all.size + val color = ChipColor.all[index] + this.colorsByAmount[amount] = color + return color + } - fillPaint.color = context.getColor(R.color.white) - canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint) + private fun drawChipCircle(chipCircle: ReplayerAnimator.Circle, chipColor: ChipColor, canvas: Canvas, context: Context) { - cardTextPaint.color = context.getColor(R.color.black) - val valueY = cardRect.top + animator.cardSpecs.height * 0.44f - canvas.drawText(card.formattedValue, cardRect.centerX(), valueY, cardTextPaint) + this.fillPaint.color = context.getColor(chipColor.fillColor) + canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius, this.fillPaint) + this.chipBorderPaint.color = context.getColor(chipColor.borderColor) + val chipBorderStrokeWidth = chipCircle.radius / 3f + this.chipBorderPaint.strokeWidth = chipBorderStrokeWidth - val suit = card.suit ?: Card.Suit.UNDEFINED - cardTextPaint.color = context.getColor(suit.color) - val suitY = cardRect.top + animator.cardSpecs.height * 0.88f - canvas.drawText(suit.value, cardRect.centerX(), suitY, cardTextPaint) + chipBorderPaint.pathEffect = DashPathEffect(floatArrayOf(chipCircle.radius * 0.6f, chipCircle.radius * 0.44f), 0f) + canvas.drawCircle(chipCircle.x, chipCircle.y, chipCircle.radius - chipBorderStrokeWidth / 2, this.chipBorderPaint) - } + } - private fun drawPlayerCircle(i: Int, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - val playerSetup = animator.handHistory.playerSetupForPosition(i) - - // Player portrait zone - val circle = animator.circleForPlayer(i) - val radius = circle.radius - this.fillPaint.color = context.getColor(R.color.green_darker) - canvas.drawCircle(circle.x, circle.y, radius, this.fillPaint) - this.strokePaint.color = context.getColor(R.color.green) - canvas.drawCircle(circle.x, circle.y, radius, this.strokePaint) - val playerInitials = playerSetup?.player?.initials ?: RANDOM_PLAYER - this.textPaint.textSize = radius - canvas.drawText(playerInitials, circle.x, circle.y + radius * 0.4f, this.textPaint) - - // Player picture - playerSetup?.player?.picture?.let { picturePath -> - val pictureBitmap = RoundedBitmapDrawableFactory.create(context.resources, picturePath) - pictureBitmap.setAntiAlias(true) - pictureBitmap.cornerRadius = circle.radius - pictureBitmap.bitmap?.let { bitmap -> - val pictureRect = RectF(circle.x - radius, circle.y - radius, circle.x + radius, circle.y + radius) - canvas.drawBitmap(bitmap, null, pictureRect, null) - } + private fun drawPlayerCards(playerIndex: Int, canvas: Canvas, context: Context) { + + val playerSetup = animator.handHistory.playerSetupForPosition(playerIndex) + val cardRects = animator.cardRects(playerIndex) + val cards = playerSetup?.cards + val isHero = (animator.handHistory.heroIndex == playerIndex) + cardRects.forEachIndexed { j, cardRect -> + + if (j < cards?.size ?: 0 && (animator.showVillainHands || isHero)) { // show card + val card = cards?.get(j)!! // tested line before + drawCard(card, cardRect, canvas, context) + } else { // show hidden cards + fillPaint.color = context.getColor(R.color.card_fill) + canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint) + cardStrokePaint.color = context.getColor(R.color.card_border) + canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, cardStrokePaint) } } - /*** - * [i] is the player position in the hand - */ - private fun drawPlayerRectangle(i: Int, highlighted: Boolean, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - val rect = animator.stackRectForPlayer(i) - val rectRadius = (rect.bottom - rect.top) / 4 - - val color = if (highlighted) R.color.kaki else R.color.green_darker - fillPaint.color = context.getColor(color) - canvas.drawRoundRect( - animator.stackRectForPlayer(i), - rectRadius, - rectRadius, - this.fillPaint - ) - strokePaint.color = context.getColor(R.color.green) - canvas.drawRoundRect( - animator.stackRectForPlayer(i), - rectRadius, - rectRadius, - this.strokePaint - ) + } - if (i == animator.handHistory.numberOfPlayers - 1) { // refresh dealer button - drawDealerButton(animator, canvas, context) - } - } + private fun drawCard(card: Card, cardRect: RectF, canvas: Canvas, context: Context) { - private fun drawDealerButton(animator: ReplayerAnimator, canvas: Canvas, context: Context) { + fillPaint.color = context.getColor(R.color.white) + canvas.drawRoundRect(cardRect, animator.cardRadius, animator.cardRadius, fillPaint) - // Dealer button - val dealerCircle = animator.dealerCircle - this.fillPaint.color = context.getColor(R.color.red) - canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, fillPaint) - this.strokePaint.color = context.getColor(R.color.white) - canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, strokePaint) - this.textPaint.textSize = dealerCircle.radius - canvas.drawText("D", dealerCircle.x, dealerCircle.y + this.textPaint.textSize / 3, this.textPaint) + cardTextPaint.color = context.getColor(R.color.black) + val valueY = cardRect.top + animator.cardSpecs.height * 0.44f + canvas.drawText(card.formattedValue, cardRect.centerX(), valueY, cardTextPaint) - } + val suit = card.suit ?: Card.Suit.UNDEFINED + cardTextPaint.color = context.getColor(suit.color) + val suitY = cardRect.top + animator.cardSpecs.height * 0.88f + canvas.drawText(suit.value, cardRect.centerX(), suitY, cardTextPaint) - private fun drawPositionAndInfo(i: Int, secondLine: String?, animator: ReplayerAnimator, canvas: Canvas) { - - val hh = animator.handHistory - // Player position - val positions = Position.positionsPerPlayers(hh.numberOfPlayers) - val name = positions.elementAt(i).value - val pnPoint = animator.pointForPlayerName(i) - this.textPaint.textSize = pnPoint.fontSize - canvas.drawText(name, pnPoint.x, pnPoint.y, this.textPaint) - - // Player stack - secondLine?.let { - val psPoint = animator.pointForPlayerStack(i) - this.textPaint.textSize = psPoint.fontSize - canvas.drawText(it, psPoint.x, psPoint.y, this.textPaint) + } + + private fun drawPlayerCircle(i: Int, canvas: Canvas, context: Context) { + + val playerSetup = animator.handHistory.playerSetupForPosition(i) + + // Player portrait zone + val circle = animator.circleForPlayer(i) + val radius = circle.radius + this.fillPaint.color = context.getColor(R.color.green_darker) + canvas.drawCircle(circle.x, circle.y, radius, this.fillPaint) + this.strokePaint.color = context.getColor(R.color.green) + canvas.drawCircle(circle.x, circle.y, radius, this.strokePaint) + val playerInitials = playerSetup?.player?.initials ?: RANDOM_PLAYER + this.textPaint.textSize = radius + canvas.drawText(playerInitials, circle.x, circle.y + radius * 0.4f, this.textPaint) + + // Player picture + playerSetup?.player?.picture?.let { picturePath -> + val pictureBitmap = RoundedBitmapDrawableFactory.create(context.resources, picturePath) + pictureBitmap.setAntiAlias(true) + pictureBitmap.cornerRadius = circle.radius + pictureBitmap.bitmap?.let { bitmap -> + val pictureRect = RectF(circle.x - radius, circle.y - radius, circle.x + radius, circle.y + radius) + canvas.drawBitmap(bitmap, null, pictureRect, null) } + } + } + + /*** + * [i] is the player position in the hand + */ + private fun drawPlayerRectangle(i: Int, highlighted: Boolean, canvas: Canvas, context: Context) { + + val rect = animator.stackRectForPlayer(i) + val rectRadius = (rect.bottom - rect.top) / 4 + + val color = if (highlighted) R.color.kaki else R.color.green_darker + fillPaint.color = context.getColor(color) + canvas.drawRoundRect( + animator.stackRectForPlayer(i), + rectRadius, + rectRadius, + this.fillPaint + ) + strokePaint.color = context.getColor(R.color.green) + canvas.drawRoundRect( + animator.stackRectForPlayer(i), + rectRadius, + rectRadius, + this.strokePaint + ) + + if (i == animator.handHistory.numberOfPlayers - 1) { // refresh dealer button + drawDealerButton(canvas, context) } + } - private fun drawAction(i: Int, action: ComputedAction, animator: ReplayerAnimator, canvas: Canvas, context: Context) { - - action.action.type?.let { type -> - val actionName = context.getString(type.resId) - val actionPoint: ReplayerAnimator.TextPoint - if (!type.isPassive) { // show action + amount - actionPoint = animator.pointForPlayerName(i) - action.action.displayedAmount?.let { amount -> - val amountPoint = animator.pointForPlayerStack(i) - this.textPaint.textSize = amountPoint.fontSize - canvas.drawText(amount.formatted, amountPoint.x, amountPoint.y, this.textPaint) - } - } else { // show action name only - actionPoint = animator.pointForPlayerAction(i) - } - this.textPaint.textSize = actionPoint.fontSize - canvas.drawText(actionName, actionPoint.x, actionPoint.y, this.textPaint) + private fun drawDealerButton(canvas: Canvas, context: Context) { - } + // Dealer button + val dealerCircle = animator.dealerCircle + this.fillPaint.color = context.getColor(R.color.red) + canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, fillPaint) + this.strokePaint.color = context.getColor(R.color.white) + canvas.drawCircle(dealerCircle.x, dealerCircle.y, dealerCircle.radius, strokePaint) + this.textPaint.textSize = dealerCircle.radius + canvas.drawText("D", dealerCircle.x, dealerCircle.y + this.textPaint.textSize / 3, this.textPaint) + + } + private fun drawPositionAndInfo(i: Int, secondLine: String?, canvas: Canvas) { + + val hh = animator.handHistory + // Player position + val positions = Position.positionsPerPlayers(hh.numberOfPlayers) + val name = positions.elementAt(i).value + val pnPoint = animator.pointForPlayerName(i) + this.textPaint.textSize = pnPoint.fontSize + canvas.drawText(name, pnPoint.x, pnPoint.y, this.textPaint) + + // Player stack + secondLine?.let { + val psPoint = animator.pointForPlayerStack(i) + this.textPaint.textSize = psPoint.fontSize + canvas.drawText(it, psPoint.x, psPoint.y, this.textPaint) } - private fun drawBoardCards(street: Street, animator: ReplayerAnimator, canvas: Canvas, context: Context) { + } - val cards = animator.handHistory.cardsForStreet(street) - animator.boardCardRects.take(street.totalBoardCards).forEachIndexed { index, rectF -> - if (index < cards.size) { - drawCard(cards[index], rectF, animator, canvas, context) + private fun drawAction(i: Int, action: ComputedAction, canvas: Canvas, context: Context) { + + action.action.type?.let { type -> + val actionName = context.getString(type.resId) + val actionPoint: ReplayerAnimator.TextPoint + if (!type.isPassive) { // show action + amount + actionPoint = animator.pointForPlayerName(i) + action.action.displayedAmount?.let { amount -> + val amountPoint = animator.pointForPlayerStack(i) + this.textPaint.textSize = amountPoint.fontSize + canvas.drawText(amount.formatted, amountPoint.x, amountPoint.y, this.textPaint) } + } else { // show action name only + actionPoint = animator.pointForPlayerAction(i) } + this.textPaint.textSize = actionPoint.fontSize + canvas.drawText(actionName, actionPoint.x, actionPoint.y, this.textPaint) } - private fun drawPot(street: Street, computedAction: ComputedAction?, animator: ReplayerAnimator, canvas: Canvas, context: Context) { + } + + private fun drawBoardCards(street: Street, canvas: Canvas, context: Context) { - if (street == Street.SUMMARY && animator.frameType != FrameType.GATHER_ANIMATION) { - return + val cards = animator.handHistory.cardsForStreet(street) + animator.boardCardRects.take(street.totalBoardCards).forEachIndexed { index, rectF -> + if (index < cards.size) { + drawCard(cards[index], rectF, canvas, context) } + } - val pot = animator.actionList.potSizeForStreet(street) + } - val action = computedAction ?: animator.lastActionBeforeStreet(street) - val totalPot = action?.let { - animator.actionList.totalPotSize(it.action.index + 1) - } ?: run { - pot - } + private fun drawPot(street: Street, computedAction: ComputedAction?, canvas: Canvas, context: Context) { - drawChip(pot, animator.potTextPoint, animator.potChipCircle, canvas, context) - val tpTextPoint = animator.totalPotTextPoint - this.textPaint.textSize = tpTextPoint.fontSize - this.textPaint.color = context.getColor(R.color.white) + if (street == Street.SUMMARY && animator.frameType != FrameType.GATHER_ANIMATION) { + return + } - canvas.drawText(totalPot.formatted, animator.totalPotTextPoint.x, animator.totalPotTextPoint.y, this.textPaint) + val pot = animator.actionList.potSizeForStreet(street) + val action = computedAction ?: animator.lastActionBeforeStreet(street) + val totalPot = action?.let { + animator.actionList.totalPotSize(it.action.index + 1) + } ?: run { + pot } + drawChip(pot, animator.potTextPoint, animator.potChipCircle, canvas, context) + val tpTextPoint = animator.totalPotTextPoint + this.textPaint.textSize = tpTextPoint.fontSize + this.textPaint.color = context.getColor(R.color.white) + + canvas.drawText(totalPot.formatted, animator.totalPotTextPoint.x, animator.totalPotTextPoint.y, this.textPaint) + } }