coroutines changes

perftest
Laurent 3 years ago
parent 1718bea582
commit c99ef285af
  1. 2
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  2. 6
      app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt
  3. 12
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt
  4. 13
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  5. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  6. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  7. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  8. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  9. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  10. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt
  11. 3
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  12. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  13. 21
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt
  14. 571
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  15. 30
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  16. 11
      app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt

@ -18,7 +18,7 @@ class CurrencyConverterApi {
companion object { companion object {
val json = Json { ignoreUnknownKeys = true } private val json = Json { ignoreUnknownKeys = true }
fun currencyRate(fromCurrency: String, toCurrency: String, context: Context, callback: (Double?, VolleyError?) -> (Unit)) { fun currencyRate(fromCurrency: String, toCurrency: String, context: Context, callback: (Double?, VolleyError?) -> (Unit)) {

@ -131,9 +131,6 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
private var cancelled = false private var cancelled = false
private val coroutineContext: CoroutineContext
get() = Dispatchers.Default
fun start() { fun start() {
launchReports() launchReports()
} }
@ -143,7 +140,8 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
} }
private fun launchReports() { private fun launchReports() {
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()

@ -2,10 +2,7 @@ package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.Transaction
@ -15,9 +12,6 @@ import kotlin.coroutines.CoroutineContext
object BankrollReportManager { object BankrollReportManager {
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private var reports: MutableMap<String?, BankrollReport> = mutableMapOf() private var reports: MutableMap<String?, BankrollReport> = mutableMapOf()
private var computableResults: RealmResults<ComputableResult> private var computableResults: RealmResults<ComputableResult>
@ -68,10 +62,10 @@ object BankrollReportManager {
} }
// otherwise compute it // otherwise compute it
GlobalScope.launch(coroutineContext) { CoroutineScope(Dispatchers.Default).launch {
var report: BankrollReport? = null var report: BankrollReport? = null
val coroutine = GlobalScope.async { val coroutine = async {
val s = Date() val s = Date()
Timber.d(">>>>> start computing bankroll...") Timber.d(">>>>> start computing bankroll...")

@ -9,6 +9,7 @@ import android.provider.MediaStore
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -52,11 +53,11 @@ open class MediaActivity : BaseActivity() {
val filesList = ArrayList<File>() val filesList = ArrayList<File>()
GlobalScope.launch { CoroutineScope(Dispatchers.Default).launch {
if (tempFile != null) { if (tempFile != null) {
tempFile?.let { tempFile?.let {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(Dispatchers.Main).launch {
filesList.add(it) filesList.add(it)
getPictures(filesList) getPictures(filesList)
} }
@ -65,7 +66,7 @@ open class MediaActivity : BaseActivity() {
data.clipData?.let { clipData -> data.clipData?.let { clipData ->
try { try {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(Dispatchers.Main).launch {
isLoadingNewPictures() isLoadingNewPictures()
} }
@ -78,7 +79,7 @@ open class MediaActivity : BaseActivity() {
filesList.add(photoFile) filesList.add(photoFile)
} }
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(Dispatchers.Main).launch {
getPictures(filesList) getPictures(filesList)
} }
@ -90,7 +91,7 @@ open class MediaActivity : BaseActivity() {
data.data?.let { uri -> data.data?.let { uri ->
try { try {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(Dispatchers.Main).launch {
isLoadingNewPictures() isLoadingNewPictures()
} }
@ -98,7 +99,7 @@ open class MediaActivity : BaseActivity() {
val photoFile = ImageUtils.createTempImageFile(this@MediaActivity) val photoFile = ImageUtils.createTempImageFile(this@MediaActivity)
ImageUtils.copyInputStreamToFile(inputStream!!, photoFile) ImageUtils.copyInputStreamToFile(inputStream!!, photoFile)
filesList.add(photoFile) filesList.add(photoFile)
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(Dispatchers.Main).launch {
getPictures(filesList) getPictures(filesList)
} }

@ -6,10 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.databinding.FragmentImportBinding import net.pokeranalytics.android.databinding.FragmentImportBinding
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
@ -89,7 +86,7 @@ class ImportFragment : RealmFragment(), ImportDelegate {
this.importer = CSVImporter(inputStream) this.importer = CSVImporter(inputStream)
this.importer.delegate = this this.importer.delegate = this
CoroutineScope(coroutineContext).launch { CoroutineScope(Dispatchers.Default).launch {
val coroutine = GlobalScope.async { val coroutine = GlobalScope.async {
val s = Date() val s = Date()

@ -131,7 +131,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id -> itemToDeleteId?.let { id ->
CoroutineScope(coroutineContext).launch { CoroutineScope(Dispatchers.Default).launch {
delay(300) delay(300)
deleteItem(dataListAdapter, reportSetups, id) deleteItem(dataListAdapter, reportSetups, id)
} }
@ -313,7 +313,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
showLoader() showLoader()
CoroutineScope(coroutineContext).launch { CoroutineScope(Dispatchers.Default).launch {
val startDate = Date() val startDate = Date()
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()

@ -8,10 +8,7 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputableGroup
@ -158,9 +155,9 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
*/ */
private fun launchStatComputation() { private fun launchStatComputation() {
CoroutineScope(coroutineContext).launch { CoroutineScope(Dispatchers.Default).launch {
val async = GlobalScope.async { val async = async {
val s = Date() val s = Date()
Timber.d(">>> start...") Timber.d(">>> start...")
@ -179,8 +176,10 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
} }
async.await() async.await()
if (isAdded && !isDetached) { launch(Dispatchers.Main) {
tableReportFragment.showResults() if (isAdded && !isDetached) {
tableReportFragment.showResults()
}
} }
} }
} }

@ -18,9 +18,6 @@ interface RealmAsyncListener {
open class RealmFragment : BaseFragment() { open class RealmFragment : BaseFragment() {
val coroutineContext: CoroutineContext
get() = Dispatchers.Main
/** /**
* A realm instance * A realm instance
*/ */

@ -7,10 +7,7 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.calcul.ReportDisplay import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
@ -33,12 +30,9 @@ import net.pokeranalytics.android.util.TextFormat
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, CoroutineScope, open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate { RowRepresentableDelegate {
// override val coroutineContext: CoroutineContext
// get() = Dispatchers.Main
companion object { companion object {
/** /**
@ -211,7 +205,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
showLoader() showLoader()
GlobalScope.launch(coroutineContext) { CoroutineScope(Dispatchers.Default).launch {
var report: Report? = null var report: Report? = null
val test = GlobalScope.async { val test = GlobalScope.async {

@ -11,6 +11,7 @@ import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -169,7 +170,7 @@ class ProgressReportFragment : AbstractReportFragment() {
graphContainer.hideWithAnimation() graphContainer.hideWithAnimation()
progressBar.showWithAnimation() progressBar.showWithAnimation()
GlobalScope.launch { CoroutineScope(Dispatchers.Default).launch {
val s = Date() val s = Date()
Timber.d(">>> start...") Timber.d(">>> start...")

@ -13,6 +13,7 @@ import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -167,7 +168,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
this.model.computedResults?.let { computedResults -> this.model.computedResults?.let { computedResults ->
GlobalScope.launch { CoroutineScope(Dispatchers.Default).launch {
val startDate = Date() val startDate = Date()

@ -44,7 +44,7 @@ import java.util.*
import kotlin.collections.set import kotlin.collections.set
class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource, class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate, RealmAsyncListener { RowRepresentableDelegate, RealmAsyncListener {
enum class TimeFilter { enum class TimeFilter {
@ -348,7 +348,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
binding.progressBar.showWithAnimation() binding.progressBar.showWithAnimation()
binding.recyclerView.hideWithAnimation() binding.recyclerView.hideWithAnimation()
GlobalScope.launch { CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() realm.refresh()
@ -357,7 +357,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
realm.close() realm.close()
GlobalScope.launch(Dispatchers.Main) { launch(Dispatchers.Main) {
displayData() displayData()
} }
} }

@ -5,10 +5,7 @@ import android.text.InputType
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.HandSetup import net.pokeranalytics.android.model.handhistory.HandSetup
@ -49,9 +46,6 @@ interface PlayerSetupCreationListener {
class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener { class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener {
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
/*** /***
* The hand history * The hand history
*/ */
@ -656,15 +650,12 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer
private fun defineWinnerPositions() { private fun defineWinnerPositions() {
val hhId = this.handHistory.id val hhId = this.handHistory.id
GlobalScope.launch(coroutineContext) { CoroutineScope(Dispatchers.Default).launch {
val c = GlobalScope.async { val realm = Realm.getDefaultInstance()
val realm = Realm.getDefaultInstance() realm.executeTransaction {
realm.executeTransaction { realm.findById<HandHistory>(hhId)?.defineWinnerPositions()
realm.findById<HandHistory>(hhId)?.defineWinnerPositions()
}
realm.close()
} }
c.await() realm.close()
} }
} }

@ -13,7 +13,9 @@ import android.provider.MediaStore
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.realm.handhistory.HandHistory
@ -26,373 +28,321 @@ import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
enum class FileType(var value: String) { enum class FileType(var value: String) {
IMAGE_GIF("image/gif"), IMAGE_GIF("image/gif"),
VIDEO_MP4("video/mp4") VIDEO_MP4("video/mp4")
} }
class ReplayExportService : Service() { class ReplayExportService : Service() {
private lateinit var handHistoryId: String private lateinit var handHistoryId: String
private val binder = LocalBinder() private val binder = LocalBinder()
private val coroutineContext: CoroutineContext override fun onBind(intent: Intent?): IBinder {
get() = Dispatchers.Main return binder
}
override fun onBind(intent: Intent?): IBinder { inner class LocalBinder : Binder() {
return binder fun getService(): ReplayExportService = this@ReplayExportService
} }
inner class LocalBinder : Binder() { fun videoExport(handHistoryId: String) {
fun getService(): ReplayExportService = this@ReplayExportService this@ReplayExportService.handHistoryId = handHistoryId
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startFFMPEGVideoExport()
} else {
startFFMPEGVideoExportPreQ()
}
}
fun videoExport(handHistoryId: String) { fun gifExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startFFMPEGVideoExport() startGIFExport()
} else { } else {
startFFMPEGVideoExportPreQ() startGIFExportPreQ()
} }
} }
fun gifExport(handHistoryId: String) { private fun startGIFExport() {
this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startGIFExport()
} else {
startGIFExportPreQ()
}
}
private fun startGIFExport() { CoroutineScope(Dispatchers.Default).launch {
GlobalScope.launch(coroutineContext) { val realm = Realm.getDefaultInstance()
val c = GlobalScope.async { realm.refresh()
val realm = Realm.getDefaultInstance() val handHistory = realm.findById<HandHistory>(handHistoryId)
realm.refresh() ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") val context = this@ReplayExportService
val context = this@ReplayExportService val animator = ReplayerAnimator(handHistory, true)
val animator = ReplayerAnimator(handHistory, true) val square = 1024
val square = 1024 val width = square
val height = square
val width = square animator.configure(width.toFloat(), height.toFloat(), context)
val height = square
animator.configure(width.toFloat(), height.toFloat(), context) val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}"
val formattedDate = Date().dateTimeFileFormatted // Add a specific media item.
val fileName = "hand_${formattedDate}" val resolver = applicationContext.contentResolver
// Add a specific media item. // Q version tested before calling the function
val resolver = applicationContext.contentResolver val imageCollection =
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// Q version tested before calling the function val gifDetails = ContentValues().apply {
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value)
}
val gifDetails = ContentValues().apply { val uri = resolver.insert(imageCollection, gifDetails)
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value)
}
val uri = resolver.insert(imageCollection, gifDetails) if (uri != null) {
if (uri != null) { val os = resolver.openOutputStream(uri)
val os = resolver.openOutputStream(uri) val writer = AnimatedGIFWriter(false)
writer.prepareForWrite(os, width, height)
val writer = AnimatedGIFWriter(false) val drawer = TableDrawer()
writer.prepareForWrite(os, width, height) drawer.configurePaints(context, animator)
val drawer = TableDrawer() var animationCount = 0
drawer.configurePaints(context, animator) animator.frames(context) { bitmap, count ->
var animationCount = 0 when {
animator.frames(context) { bitmap, count -> count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
}
writer.finishWrite(os)
when { realm.close()
count > 10 -> { notifyUser(uri, FileType.IMAGE_GIF)
writer.writeFrame(os, bitmap, count * 8) } else {
animationCount = 0 Timber.w("Resolver insert ended without uri...")
} }
else -> { }
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
}
writer.finishWrite(os)
realm.close() }
notifyUser(uri, FileType.IMAGE_GIF)
} else {
Timber.w("Resolver insert ended without uri...")
}
}
c.await()
}
} private fun startFFMPEGVideoExport() {
private fun startFFMPEGVideoExport() { CoroutineScope(Dispatchers.Default).launch {
GlobalScope.launch(coroutineContext) { val realm = Realm.getDefaultInstance()
val async = GlobalScope.async { val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val realm = Realm.getDefaultInstance() val context = this@ReplayExportService
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService val animator = ReplayerAnimator(handHistory, true)
val animator = ReplayerAnimator(handHistory, true) val square = 1024
val square = 1024 val width = square
val height = square
val width = square animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
val height = square val drawer = TableDrawer()
drawer.configurePaints(context, animator)
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) // generates all images and file descriptor
val drawer = TableDrawer() Timber.d("Generating images for video...")
drawer.configurePaints(context, animator) val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
// generates all images and file descriptor val formattedDate = Date().dateTimeFileFormatted
Timber.d("Generating images for video...") val fileName = "hand_${formattedDate}.mp4"
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
val formattedDate = Date().dateTimeFileFormatted val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
val fileName = "hand_${formattedDate}.mp4" ?: throw PAIllegalStateException("File is invalid")
val output = "${outputDirectory.path}/$fileName"
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid") Timber.d("Assembling images for video...")
val output = "${outputDirectory.path}/$fileName"
Timber.d("Assembling images for video...") val command =
"-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" when {
FFmpegKit.executeAsync(command) { it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.")
}
}
when { File(dpath).delete()
it.returnCode.isSuccess -> { tmpDir.delete()
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value))
}
}
File(dpath).delete() val file = File(output)
tmpDir.delete()
val file = File(output) val resolver = applicationContext.contentResolver
val resolver = applicationContext.contentResolver // Q version tested before calling the function
val videoCollection =
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// Q version tested before calling the function val fileDetails = ContentValues().apply {
val videoCollection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) Timber.d("set file details = $fileName")
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value)
}
val fileDetails = ContentValues().apply { // copy video to nice path
Timber.d("set file details = $fileName") resolver.insert(videoCollection, fileDetails)?.let { uri ->
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value)
}
// copy video to nice path Timber.d("copy file at uri = $uri")
resolver.insert(videoCollection, fileDetails)?.let { uri ->
Timber.d("copy file at uri = $uri") val os = resolver.openOutputStream(uri)
os?.write(file.readBytes())
os?.close()
val os = resolver.openOutputStream(uri) file.delete() // delete temp file
os?.write(file.readBytes())
os?.close()
file.delete() // delete temp file notifyUser(uri, FileType.VIDEO_MP4)
notifyUser(uri, FileType.VIDEO_MP4) } ?: run {
Timber.w("Resolver insert ended without uri...")
}
} ?: run { }
Timber.w("Resolver insert ended without uri...")
}
} }
} }
async.await()
}
} private fun startGIFExportPreQ() {
// private fun startVideoExport() { CoroutineScope(Dispatchers.Default).launch {
//
// GlobalScope.launch(coroutineContext) {
// val c = GlobalScope.async {
//
// val realm = Realm.getDefaultInstance()
// val handHistory = realm.findById<HandHistory>(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 startGIFExportPreQ() {
GlobalScope.launch(coroutineContext) {
val c = GlobalScope.async {
val realm = Realm.getDefaultInstance()
realm.refresh()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") val realm = Realm.getDefaultInstance()
realm.refresh()
val context = this@ReplayExportService val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val animator = ReplayerAnimator(handHistory, true) val context = this@ReplayExportService
val square = 1024 val animator = ReplayerAnimator(handHistory, true)
val width = square val square = 1024
val height = square
animator.configure(width.toFloat(), height.toFloat(), context) val width = square
val height = square
val formattedDate = Date().dateTimeFileFormatted animator.configure(width.toFloat(), height.toFloat(), context)
val path = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.gif"
).toString()
val writer = AnimatedGIFWriter(false) val formattedDate = Date().dateTimeFileFormatted
val os = FileOutputStream(path) val path = File(
writer.prepareForWrite(os, width, height) Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.gif"
).toString()
val drawer = TableDrawer() val writer = AnimatedGIFWriter(false)
drawer.configurePaints(context, animator) val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
var animationCount = 0 val drawer = TableDrawer()
animator.frames(context) { bitmap, count -> drawer.configurePaints(context, animator)
when { var animationCount = 0
count > 10 -> { animator.frames(context) { bitmap, count ->
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
} when {
writer.finishWrite(os) count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
realm.close() }
notifyUser(path) writer.finishWrite(os)
} realm.close()
c.await() notifyUser(path)
}
} }
private fun startFFMPEGVideoExportPreQ() { }
GlobalScope.launch(coroutineContext) { private fun startFFMPEGVideoExportPreQ() {
val async = GlobalScope.async {
val realm = Realm.getDefaultInstance() CoroutineScope(Dispatchers.Default).launch {
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val animator = ReplayerAnimator(handHistory, true) val context = this@ReplayExportService
val square = 1024 val animator = ReplayerAnimator(handHistory, true)
val width = square val square = 1024
val height = square
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) val width = square
val drawer = TableDrawer() val height = square
drawer.configurePaints(context, animator)
// generates all images and file descriptor animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
Timber.d("Generating images for video...") val drawer = TableDrawer()
val tmpDir = animator.generateVideoContent(this@ReplayExportService) drawer.configurePaints(context, animator)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
val formattedDate = Date().dateTimeFileFormatted // generates all images and file descriptor
val output = File( Timber.d("Generating images for video...")
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), val tmpDir = animator.generateVideoContent(this@ReplayExportService)
"hand_${formattedDate}.mp4" val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
).path
Environment.getExternalStorageState(tmpDir) val formattedDate = Date().dateTimeFileFormatted
val output = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.mp4"
).path
Timber.d("Assembling images for video...") Environment.getExternalStorageState(tmpDir)
Timber.d("Assembling images for video...")
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" val command =
FFmpegKit.executeAsync(command) { "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
when { when {
it.returnCode.isSuccess -> { it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully") Timber.d("FFMPEG command execution completed successfully")
} }
it.returnCode.isCancel -> { it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.") Timber.d("Command execution cancelled by user.")
} }
else -> { else -> {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value)) Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.")
} }
} }
// FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output") { id, rc -> // FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output") { id, rc ->
// //
@ -403,61 +353,60 @@ class ReplayExportService : Service() {
// } else { // } else {
// Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc)) // Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc))
// } // }
// Delete descriptor and image files // Delete descriptor and image files
// tmpDir.delete() // tmpDir.delete()
// File(dpath).delete() // File(dpath).delete()
notifyUser(output) notifyUser(output)
} }
} }
async.await()
}
} }
private fun notifyUser(uri: Uri, type: FileType) { private fun notifyUser(uri: Uri, type: FileType) {
val title = getString(R.string.video_available) val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + uri.path val body = getString(R.string.video_retrieval_message) + ": " + uri.path
this.showNotification(title, body, uri, type.value) this.showNotification(title, body, uri, type.value)
} }
private fun notifyUser(path: String) { private fun notifyUser(path: String) {
val title = getString(R.string.video_available) val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + path val body = getString(R.string.video_retrieval_message) + ": " + path
Timber.d("Show local notification, path of file: ${path}") Timber.d("Show local notification, path of file: ${path}")
val uri = FileProvider.getUriForFile( val uri = FileProvider.getUriForFile(
this, this,
this.applicationContext.packageName.toString() + ".fileprovider", this.applicationContext.packageName.toString() + ".fileprovider",
File(path) File(path)
) )
val type = when { val type = when {
path.contains("gif") -> "image/gif" path.contains("gif") -> "image/gif"
else -> "video/*" else -> "video/*"
} }
this.showNotification(title, body, uri, type) this.showNotification(title, body, uri, type)
} }
private fun showNotification(title: String, body: String, uri: Uri, type: String) { private fun showNotification(title: String, body: String, uri: Uri, type: String) {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, type) intent.setDataAndType(uri, type)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with)) val chooser = Intent.createChooser(intent, getString(R.string.open_file_with))
val pendingIntent = PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT) val pendingIntent =
PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT)
TriggerNotification(this, title, body, pendingIntent) TriggerNotification(this, title, body, pendingIntent)
} }
} }

@ -11,10 +11,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator
@ -42,6 +39,7 @@ import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.* import net.pokeranalytics.android.util.extensions.*
import timber.log.Timber import timber.log.Timber
import java.lang.Runnable
import java.util.* import java.util.*
class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource, ResultCaptureTypeDelegate { class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource, ResultCaptureTypeDelegate {
@ -406,14 +404,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
Timber.d("Start optimal duration finding attempt...") Timber.d("Start optimal duration finding attempt...")
val isLive = this.currentSession.isLive val isLive = this.currentSession.isLive
CoroutineScope(coroutineContext).launch { CoroutineScope(Dispatchers.Default).launch {
var optimalDuration: Double? = null val optimalDuration: Double? = CashGameOptimalDurationCalculator.start(isLive)
// optimalDuration =
val cr = GlobalScope.async { // val cr = GlobalScope.async {
optimalDuration = CashGameOptimalDurationCalculator.start(isLive) // }
} // cr.await()
cr.await()
if (!isDetached) { if (!isDetached) {
optimalDuration?.let { optimalDuration?.let {
@ -421,10 +418,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
val delay = it.toLong() //5000L val delay = it.toLong() //5000L
currentSession.scheduleStopNotification(requireContext(), delay) currentSession.scheduleStopNotification(requireContext(), delay)
val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration() launch(Dispatchers.Main) {
Timber.d("Setting stop notification in: $formattedDuration") val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration()
val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration) Timber.d("Setting stop notification in: $formattedDuration")
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration)
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
}
} }
} }
} }

@ -2,15 +2,12 @@ package net.pokeranalytics.android.util
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.* import android.graphics.*
import android.graphics.Paint.FILTER_BITMAP_FLAG
import android.media.ExifInterface import android.media.ExifInterface
import android.net.Uri
import android.os.Environment import android.os.Environment
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import timber.log.Timber import timber.log.Timber
@ -270,8 +267,8 @@ object ImageUtils {
* Save the bitmap in a file * Save the bitmap in a file
*/ */
fun saveBitmapInFile(context: Context, bitmap: Bitmap, filename: String, action: (filePath: String) -> Unit) { fun saveBitmapInFile(context: Context, bitmap: Bitmap, filename: String, action: (filePath: String) -> Unit) {
GlobalScope.launch { CoroutineScope(Dispatchers.Default).launch {
val outputFile = File(context.filesDir, filename) val outputFile = File(context.filesDir, filename)
@ -291,7 +288,7 @@ object ImageUtils {
} }
} }
GlobalScope.launch(Dispatchers.Main) { launch(Dispatchers.Main) {
Timber.d("Save file here: ${outputFile.absolutePath}") Timber.d("Save file here: ${outputFile.absolutePath}")
action(outputFile.absolutePath) action(outputFile.absolutePath)
} }

Loading…
Cancel
Save