From c99ef285af30310f5eed51f34f695a42f5a11a4f Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 17 Nov 2022 12:29:35 +0100 Subject: [PATCH] coroutines changes --- .../android/api/CurrencyConverterApi.kt | 2 +- .../android/calculus/ReportWhistleBlower.kt | 6 +- .../bankroll/BankrollReportManager.kt | 12 +- .../ui/activity/components/MediaActivity.kt | 13 +- .../android/ui/fragment/ImportFragment.kt | 7 +- .../android/ui/fragment/ReportsFragment.kt | 4 +- .../android/ui/fragment/StatisticsFragment.kt | 15 +- .../ui/fragment/components/RealmFragment.kt | 3 - .../report/ComposableTableReportFragment.kt | 12 +- .../fragment/report/ProgressReportFragment.kt | 3 +- .../calendar/CalendarDetailsFragment.kt | 3 +- .../ui/modules/calendar/CalendarFragment.kt | 6 +- .../handhistory/model/EditorViewModel.kt | 21 +- .../replayer/ReplayExportService.kt | 571 ++++++++---------- .../ui/modules/session/SessionFragment.kt | 30 +- .../pokeranalytics/android/util/ImageUtils.kt | 11 +- 16 files changed, 319 insertions(+), 400 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt b/app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt index 217aa88d..70280ac4 100644 --- a/app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt +++ b/app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt @@ -18,7 +18,7 @@ class CurrencyConverterApi { 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)) { diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt index 112093d9..7e181fa3 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt @@ -131,9 +131,6 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co private var cancelled = false - private val coroutineContext: CoroutineContext - get() = Dispatchers.Default - fun start() { launchReports() } @@ -143,7 +140,8 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co } private fun launchReports() { - CoroutineScope(coroutineContext).launch { + + CoroutineScope(Dispatchers.Default).launch { val realm = Realm.getDefaultInstance() diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt index 211589e0..795311f3 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt @@ -2,10 +2,7 @@ package net.pokeranalytics.android.calculus.bankroll import io.realm.Realm import io.realm.RealmResults -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Transaction @@ -15,9 +12,6 @@ import kotlin.coroutines.CoroutineContext object BankrollReportManager { - private val coroutineContext: CoroutineContext - get() = Dispatchers.Main - private var reports: MutableMap = mutableMapOf() private var computableResults: RealmResults @@ -68,10 +62,10 @@ object BankrollReportManager { } // otherwise compute it - GlobalScope.launch(coroutineContext) { + CoroutineScope(Dispatchers.Default).launch { var report: BankrollReport? = null - val coroutine = GlobalScope.async { + val coroutine = async { val s = Date() Timber.d(">>>>> start computing bankroll...") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt index 141012b7..b3a291a9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt @@ -9,6 +9,7 @@ import android.provider.MediaStore import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -52,11 +53,11 @@ open class MediaActivity : BaseActivity() { val filesList = ArrayList() - GlobalScope.launch { + CoroutineScope(Dispatchers.Default).launch { if (tempFile != null) { tempFile?.let { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(Dispatchers.Main).launch { filesList.add(it) getPictures(filesList) } @@ -65,7 +66,7 @@ open class MediaActivity : BaseActivity() { data.clipData?.let { clipData -> try { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(Dispatchers.Main).launch { isLoadingNewPictures() } @@ -78,7 +79,7 @@ open class MediaActivity : BaseActivity() { filesList.add(photoFile) } - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(Dispatchers.Main).launch { getPictures(filesList) } @@ -90,7 +91,7 @@ open class MediaActivity : BaseActivity() { data.data?.let { uri -> try { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(Dispatchers.Main).launch { isLoadingNewPictures() } @@ -98,7 +99,7 @@ open class MediaActivity : BaseActivity() { val photoFile = ImageUtils.createTempImageFile(this@MediaActivity) ImageUtils.copyInputStreamToFile(inputStream!!, photoFile) filesList.add(photoFile) - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(Dispatchers.Main).launch { getPictures(filesList) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index 89bb5374..e6a428c2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -6,10 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.databinding.FragmentImportBinding import net.pokeranalytics.android.ui.fragment.components.RealmFragment @@ -89,7 +86,7 @@ class ImportFragment : RealmFragment(), ImportDelegate { this.importer = CSVImporter(inputStream) this.importer.delegate = this - CoroutineScope(coroutineContext).launch { + CoroutineScope(Dispatchers.Default).launch { val coroutine = GlobalScope.async { val s = Date() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt index 02ed5483..c86b2049 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt @@ -131,7 +131,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) itemToDeleteId?.let { id -> - CoroutineScope(coroutineContext).launch { + CoroutineScope(Dispatchers.Default).launch { delay(300) deleteItem(dataListAdapter, reportSetups, id) } @@ -313,7 +313,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc showLoader() - CoroutineScope(coroutineContext).launch { + CoroutineScope(Dispatchers.Default).launch { val startDate = Date() val realm = Realm.getDefaultInstance() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt index e46d98fa..2190009d 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt @@ -8,10 +8,7 @@ import android.os.Bundle import android.view.* import androidx.appcompat.widget.Toolbar import io.realm.Realm -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.ComputableGroup @@ -158,9 +155,9 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { */ private fun launchStatComputation() { - CoroutineScope(coroutineContext).launch { + CoroutineScope(Dispatchers.Default).launch { - val async = GlobalScope.async { + val async = async { val s = Date() Timber.d(">>> start...") @@ -179,8 +176,10 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { } async.await() - if (isAdded && !isDetached) { - tableReportFragment.showResults() + launch(Dispatchers.Main) { + if (isAdded && !isDetached) { + tableReportFragment.showResults() + } } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt index a61491e3..3c3a3e84 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt @@ -18,9 +18,6 @@ interface RealmAsyncListener { open class RealmFragment : BaseFragment() { - val coroutineContext: CoroutineContext - get() = Dispatchers.Main - /** * A realm instance */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt index 9f054949..c7a0b4aa 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt @@ -7,10 +7,7 @@ import android.view.ViewGroup import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import io.realm.Realm -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.calcul.ReportDisplay import net.pokeranalytics.android.calculus.Calculator @@ -33,12 +30,9 @@ import net.pokeranalytics.android.util.TextFormat import timber.log.Timber import java.util.* -open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, CoroutineScope, +open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { -// override val coroutineContext: CoroutineContext -// get() = Dispatchers.Main - companion object { /** @@ -211,7 +205,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab showLoader() - GlobalScope.launch(coroutineContext) { + CoroutineScope(Dispatchers.Default).launch { var report: Report? = null val test = GlobalScope.async { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt index c4a863ce..5db9e843 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt @@ -11,6 +11,7 @@ import com.github.mikephil.charting.data.LineDataSet import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import io.realm.Realm +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -169,7 +170,7 @@ class ProgressReportFragment : AbstractReportFragment() { graphContainer.hideWithAnimation() progressBar.showWithAnimation() - GlobalScope.launch { + CoroutineScope(Dispatchers.Default).launch { val s = Date() Timber.d(">>> start...") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt index 210b26ba..343c8a02 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt @@ -13,6 +13,7 @@ import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.LineDataSet import com.google.android.material.tabs.TabLayout import io.realm.Realm +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -167,7 +168,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource this.model.computedResults?.let { computedResults -> - GlobalScope.launch { + CoroutineScope(Dispatchers.Default).launch { val startDate = Date() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt index 02591d28..17ebb25b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt @@ -44,7 +44,7 @@ import java.util.* import kotlin.collections.set -class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource, +class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate, RealmAsyncListener { enum class TimeFilter { @@ -348,7 +348,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable binding.progressBar.showWithAnimation() binding.recyclerView.hideWithAnimation() - GlobalScope.launch { + CoroutineScope(Dispatchers.Default).launch { val realm = Realm.getDefaultInstance() realm.refresh() @@ -357,7 +357,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable realm.close() - GlobalScope.launch(Dispatchers.Main) { + launch(Dispatchers.Main) { displayData() } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt index 11f7b6ff..3024c702 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt @@ -5,10 +5,7 @@ import android.text.InputType import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.realm.Realm -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.handhistory.HandSetup @@ -49,9 +46,6 @@ interface PlayerSetupCreationListener { class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener { - private val coroutineContext: CoroutineContext - get() = Dispatchers.Main - /*** * The hand history */ @@ -656,15 +650,12 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer private fun defineWinnerPositions() { val hhId = this.handHistory.id - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { - val realm = Realm.getDefaultInstance() - realm.executeTransaction { - realm.findById(hhId)?.defineWinnerPositions() - } - realm.close() + CoroutineScope(Dispatchers.Default).launch { + val realm = Realm.getDefaultInstance() + realm.executeTransaction { + realm.findById(hhId)?.defineWinnerPositions() } - c.await() + realm.close() } } 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 b93957a6..5d9c2227 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 @@ -13,7 +13,9 @@ import android.provider.MediaStore import androidx.core.content.FileProvider import com.arthenica.ffmpegkit.FFmpegKit 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.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.handhistory.HandHistory @@ -26,373 +28,321 @@ import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.util.* -import kotlin.coroutines.CoroutineContext enum class FileType(var value: String) { - IMAGE_GIF("image/gif"), - VIDEO_MP4("video/mp4") + IMAGE_GIF("image/gif"), + VIDEO_MP4("video/mp4") } 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 - get() = Dispatchers.Main + override fun onBind(intent: Intent?): IBinder { + return binder + } - override fun onBind(intent: Intent?): IBinder { - return binder - } + inner class LocalBinder : Binder() { + fun getService(): ReplayExportService = this@ReplayExportService + } - inner class LocalBinder : Binder() { - fun getService(): ReplayExportService = this@ReplayExportService - } + fun videoExport(handHistoryId: String) { + this@ReplayExportService.handHistoryId = handHistoryId + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startFFMPEGVideoExport() + } else { + startFFMPEGVideoExportPreQ() + } + } - fun videoExport(handHistoryId: String) { - this@ReplayExportService.handHistoryId = handHistoryId - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startFFMPEGVideoExport() - } else { - startFFMPEGVideoExportPreQ() - } - } + fun gifExport(handHistoryId: String) { + this@ReplayExportService.handHistoryId = handHistoryId + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startGIFExport() + } else { + startGIFExportPreQ() + } + } - fun gifExport(handHistoryId: String) { - this@ReplayExportService.handHistoryId = handHistoryId - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startGIFExport() - } else { - startGIFExportPreQ() - } - } + private fun startGIFExport() { - private fun startGIFExport() { + CoroutineScope(Dispatchers.Default).launch { - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { + val realm = Realm.getDefaultInstance() + realm.refresh() - val realm = Realm.getDefaultInstance() - realm.refresh() + val handHistory = realm.findById(handHistoryId) + ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") - val handHistory = realm.findById(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 - val height = square + animator.configure(width.toFloat(), height.toFloat(), context) - animator.configure(width.toFloat(), height.toFloat(), context) + val formattedDate = Date().dateTimeFileFormatted + val fileName = "hand_${formattedDate}" - val formattedDate = Date().dateTimeFileFormatted - val fileName = "hand_${formattedDate}" + // Add a specific media item. + val resolver = applicationContext.contentResolver - // Add a specific media item. - val resolver = applicationContext.contentResolver + // Q version tested before calling the function + val imageCollection = + MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) - // Q version tested before calling the function - val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + val gifDetails = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, fileName) + put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value) + } - val gifDetails = ContentValues().apply { - put(MediaStore.Images.Media.DISPLAY_NAME, fileName) - put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value) - } + val uri = resolver.insert(imageCollection, gifDetails) - 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) - writer.prepareForWrite(os, width, height) + val drawer = TableDrawer() + drawer.configurePaints(context, animator) - val drawer = TableDrawer() - drawer.configurePaints(context, animator) + var animationCount = 0 + animator.frames(context) { bitmap, count -> - var animationCount = 0 - animator.frames(context) { bitmap, count -> + when { + count > 10 -> { + writer.writeFrame(os, bitmap, count * 8) + animationCount = 0 + } + else -> { + if (animationCount % 2 == 0) { + writer.writeFrame(os, bitmap) + } + animationCount++ + } + } + } + writer.finishWrite(os) - when { - count > 10 -> { - writer.writeFrame(os, bitmap, count * 8) - animationCount = 0 - } - 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...") + } + } - 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 async = GlobalScope.async { + val realm = Realm.getDefaultInstance() + val handHistory = realm.findById(handHistoryId) + ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") - val realm = Realm.getDefaultInstance() - val handHistory = realm.findById(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 - val height = square + animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) + val drawer = TableDrawer() + drawer.configurePaints(context, animator) - animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) - 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 dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" - // generates all images and file descriptor - Timber.d("Generating images for video...") - val tmpDir = animator.generateVideoContent(this@ReplayExportService) - val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" + val formattedDate = Date().dateTimeFileFormatted + val fileName = "hand_${formattedDate}.mp4" - val formattedDate = Date().dateTimeFileFormatted - val fileName = "hand_${formattedDate}.mp4" + val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) + ?: throw PAIllegalStateException("File is invalid") + val output = "${outputDirectory.path}/$fileName" - val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid") - val output = "${outputDirectory.path}/$fileName" + Timber.d("Assembling images for video...") - 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" - FFmpegKit.executeAsync(command) { + when { + 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 { - it.returnCode.isSuccess -> { - 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() + tmpDir.delete() - File(dpath).delete() - tmpDir.delete() + val file = File(output) - 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 videoCollection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + val fileDetails = ContentValues().apply { + 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 { - Timber.d("set file details = $fileName") - put(MediaStore.Video.Media.DISPLAY_NAME, fileName) - put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value) - } + // copy video to nice path + resolver.insert(videoCollection, fileDetails)?.let { uri -> - // copy video to nice path - resolver.insert(videoCollection, fileDetails)?.let { uri -> + Timber.d("copy file at uri = $uri") - Timber.d("copy file at uri = $uri") + val os = resolver.openOutputStream(uri) + os?.write(file.readBytes()) + os?.close() - val os = resolver.openOutputStream(uri) - os?.write(file.readBytes()) - os?.close() + file.delete() // delete temp file - 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() { -// -// 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 startGIFExportPreQ() { - - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { - - val realm = Realm.getDefaultInstance() - realm.refresh() + CoroutineScope(Dispatchers.Default).launch { - val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") + val realm = Realm.getDefaultInstance() + realm.refresh() - val context = this@ReplayExportService + val handHistory = realm.findById(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 height = square + val square = 1024 - animator.configure(width.toFloat(), height.toFloat(), context) + val width = square + val height = square - val formattedDate = Date().dateTimeFileFormatted - val path = File( - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), - "hand_${formattedDate}.gif" - ).toString() + animator.configure(width.toFloat(), height.toFloat(), context) - val writer = AnimatedGIFWriter(false) - val os = FileOutputStream(path) - writer.prepareForWrite(os, width, height) + val formattedDate = Date().dateTimeFileFormatted + val path = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), + "hand_${formattedDate}.gif" + ).toString() - val drawer = TableDrawer() - drawer.configurePaints(context, animator) + val writer = AnimatedGIFWriter(false) + val os = FileOutputStream(path) + writer.prepareForWrite(os, width, height) - var animationCount = 0 - animator.frames(context) { bitmap, count -> + val drawer = TableDrawer() + drawer.configurePaints(context, animator) - when { - count > 10 -> { - writer.writeFrame(os, bitmap, count * 8) - animationCount = 0 - } - else -> { - if (animationCount % 2 == 0) { - writer.writeFrame(os, bitmap) - } - animationCount++ - } - } + var animationCount = 0 + animator.frames(context) { bitmap, count -> - } - writer.finishWrite(os) + when { + 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) - } - c.await() - } + realm.close() + notifyUser(path) - } + } - private fun startFFMPEGVideoExportPreQ() { + } - GlobalScope.launch(coroutineContext) { - val async = GlobalScope.async { + private fun startFFMPEGVideoExportPreQ() { - val realm = Realm.getDefaultInstance() - val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") + CoroutineScope(Dispatchers.Default).launch { - val context = this@ReplayExportService + val realm = Realm.getDefaultInstance() + val handHistory = realm.findById(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 height = square + val square = 1024 - animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) - val drawer = TableDrawer() - drawer.configurePaints(context, animator) + val width = square + val height = square - // generates all images and file descriptor - Timber.d("Generating images for video...") - val tmpDir = animator.generateVideoContent(this@ReplayExportService) - val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" + animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) + val drawer = TableDrawer() + drawer.configurePaints(context, animator) - val formattedDate = Date().dateTimeFileFormatted - val output = File( - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), - "hand_${formattedDate}.mp4" - ).path + // generates all images and file descriptor + Timber.d("Generating images for video...") + val tmpDir = animator.generateVideoContent(this@ReplayExportService) + val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" - 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" - 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" + FFmpegKit.executeAsync(command) { - when { - it.returnCode.isSuccess -> { - 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)) - } - } + when { + 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.") + } + } // 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 { // 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() // 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 body = getString(R.string.video_retrieval_message) + ": " + uri.path + val title = getString(R.string.video_available) + 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 body = getString(R.string.video_retrieval_message) + ": " + path + val title = getString(R.string.video_available) + 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( - this, - this.applicationContext.packageName.toString() + ".fileprovider", - File(path) - ) + val uri = FileProvider.getUriForFile( + this, + this.applicationContext.packageName.toString() + ".fileprovider", + File(path) + ) - val type = when { - path.contains("gif") -> "image/gif" - else -> "video/*" - } + val type = when { + path.contains("gif") -> "image/gif" + 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) - intent.setDataAndType(uri, type) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - val chooser = Intent.createChooser(intent, getString(R.string.open_file_with)) + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, type) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + 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) - } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt index 368d6b92..edd16ba8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt @@ -11,10 +11,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DiffUtil -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager 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.extensions.* import timber.log.Timber +import java.lang.Runnable import java.util.* class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource, ResultCaptureTypeDelegate { @@ -406,14 +404,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr Timber.d("Start optimal duration finding attempt...") val isLive = this.currentSession.isLive - CoroutineScope(coroutineContext).launch { + CoroutineScope(Dispatchers.Default).launch { - var optimalDuration: Double? = null - - val cr = GlobalScope.async { - optimalDuration = CashGameOptimalDurationCalculator.start(isLive) - } - cr.await() + val optimalDuration: Double? = CashGameOptimalDurationCalculator.start(isLive) +// optimalDuration = +// val cr = GlobalScope.async { +// } +// cr.await() if (!isDetached) { optimalDuration?.let { @@ -421,10 +418,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr val delay = it.toLong() //5000L currentSession.scheduleStopNotification(requireContext(), delay) - val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration() - Timber.d("Setting stop notification in: $formattedDuration") - val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration) - Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() + launch(Dispatchers.Main) { + val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration() + Timber.d("Setting stop notification in: $formattedDuration") + val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration) + Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() + } + } } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt index 911eb734..efbbcdda 100755 --- a/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt @@ -2,15 +2,12 @@ package net.pokeranalytics.android.util import android.content.Context -import android.content.Intent import android.graphics.* -import android.graphics.Paint.FILTER_BITMAP_FLAG import android.media.ExifInterface -import android.net.Uri import android.os.Environment import androidx.core.content.ContextCompat +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R import timber.log.Timber @@ -270,8 +267,8 @@ object ImageUtils { * Save the bitmap in a file */ fun saveBitmapInFile(context: Context, bitmap: Bitmap, filename: String, action: (filePath: String) -> Unit) { - - GlobalScope.launch { + + CoroutineScope(Dispatchers.Default).launch { 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}") action(outputFile.absolutePath) }