From 92c99e605f13e1d332523cd201a2d3e79c996a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Mar=C3=ADa?= Date: Sat, 14 Aug 2021 19:19:11 +0200 Subject: [PATCH] Include Platforms and MediaPlayer --- jchucomponentscompose/build.gradle | 2 +- .../extensions/strings/StringExtensions.kt | 76 ++++---- .../core/platform/BaseViewModel.kt | 20 ++ .../core/platform/ContextHandler.kt | 8 + .../core/platform/NetworkHandler.kt | 9 + .../utils/mediaplayer/MediaPlayerHolder.kt | 180 ++++++++++++++++++ .../utils/mediaplayer/PlaybackInfoListener.kt | 34 ++++ .../utils/mediaplayer/PlayerAdapter.kt | 21 ++ 8 files changed, 310 insertions(+), 40 deletions(-) create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/BaseViewModel.kt create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/ContextHandler.kt create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/NetworkHandler.kt create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/MediaPlayerHolder.kt create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlaybackInfoListener.kt create mode 100644 jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlayerAdapter.kt diff --git a/jchucomponentscompose/build.gradle b/jchucomponentscompose/build.gradle index ebc4e794..01f557e2 100644 --- a/jchucomponentscompose/build.gradle +++ b/jchucomponentscompose/build.gradle @@ -91,7 +91,7 @@ afterEvaluate { from components.release groupId = "com.jeluchu" artifactId = "jchucomponentscompose" - version = "0.0.3" + version = "0.0.4" } } } diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/extensions/strings/StringExtensions.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/extensions/strings/StringExtensions.kt index 7e5580f5..e0db7c22 100644 --- a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/extensions/strings/StringExtensions.kt +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/extensions/strings/StringExtensions.kt @@ -6,6 +6,7 @@ import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.util.Base64 +import androidx.compose.ui.graphics.Color import org.intellij.lang.annotations.RegExp import java.io.File import java.io.IOException @@ -17,23 +18,9 @@ import java.text.SimpleDateFormat import java.util.* import java.util.regex.Pattern -fun String.deleteDouble() = this.split(".")[0] - -/** ---- DATE ---------------------------------------------------------------------------------- **/ - -fun String.parserDate(): String { - val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale("es", "ES")) - val formatter = SimpleDateFormat("dd/MM/yyyy", Locale("es", "ES")) - return formatter.format(parser.parse(this)) -} - -fun String?.compareDate(): Boolean { - val sdf = SimpleDateFormat("dd/M/yyyy", Locale("es", "ES")) - val currentDate = sdf.format(Date()) - return if (this.isNullOrEmpty()) false else sdf.parse(this).before(sdf.parse(currentDate)) -} +/** ---- COMPOSE FUNCTIONS --------------------------------------------------------------------- **/ -fun Long.bytesToMeg(): String = (this / (1024L * 1024L)).toString() +fun String.getColor() = Color(android.graphics.Color.parseColor(this)) /** ---- YOUTUBE ------------------------------------------------------------------------------- **/ @@ -50,7 +37,6 @@ fun String.extractYTId(): String? { return vId } - /** ---- BITMAPS ------------------------------------------------------------------------------- **/ fun String.getBitmapFromURL(): Bitmap? = @@ -66,29 +52,22 @@ fun String.getBitmapFromURL(): Bitmap? = fun String.toBitmapDrawable(context: Context): BitmapDrawable = BitmapDrawable(context.resources, getBitmapFromURL()) -/** ---- ENCODE & DECODE ----------------------------------------------------------------------- **/ - -fun String.decodeBase64toImage(): Bitmap { - val imageBytes = Base64.decode(this, Base64.DEFAULT) - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) -} - -/** ---- MODIFY STRING ------------------------------------------------------------------------- **/ -fun String.capitalizeWords(): String = - split(" ").joinToString(" ") { it.capitalize(Locale.getDefault()) } - -fun String.capitalizeFirstLetter(): String = - substring(0, 1).uppercase(Locale("es", "ES")) + this.substring(1) - -/** ---- REMOVE STRING ------------------------------------------------------------------------- **/ +fun String.deleteDouble() = this.split(".")[0] -fun String.remove(value: String, ignoreCase: Boolean = false): String = - replace(value, "", ignoreCase) +/** ---- DATE ---------------------------------------------------------------------------------- **/ -fun String.remove(@RegExp pattern: String) = remove(Regex(pattern, RegexOption.IGNORE_CASE)) -fun String.remove(regex: Regex) = replace(regex, "") +fun String.parserDate(): String { + val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale("es", "ES")) + val formatter = SimpleDateFormat("dd/MM/yyyy", Locale("es", "ES")) + return formatter.format(parser.parse(this)) +} +fun String?.compareDate(): Boolean { + val sdf = SimpleDateFormat("dd/M/yyyy", Locale("es", "ES")) + val currentDate = sdf.format(Date()) + return if (this.isNullOrEmpty()) false else sdf.parse(this).before(sdf.parse(currentDate)) +} /** ---- CHECKER ------------------------------------------------------------------------------- **/ @@ -130,13 +109,32 @@ inline val String.isIp: Boolean return m.find() } -/** ---- FUNCTION STRING ----------------------------------------------------------------------- **/ +/** ---- ENCODE & DECODE ----------------------------------------------------------------------- **/ + +fun String.decodeBase64toImage(): Bitmap { + val imageBytes = Base64.decode(this, Base64.DEFAULT) + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) +} + +/** ---- CONVERTERS AND MODIFIERS -------------------------------------------------------------- **/ + +fun Long.bytesToMeg(): String = (this / (1024L * 1024L)).toString() + +fun String.capitalizeWords(): String = + split(" ").joinToString(" ") { it.uppercase(Locale.getDefault()) } + +fun String.capitalizeFirstLetter(): String = + substring(0, 1).uppercase(Locale.getDefault()) + this.substring(1) + +fun String.remove(value: String, ignoreCase: Boolean = false): String = + replace(value, "", ignoreCase) + +fun String.remove(@RegExp pattern: String) = remove(Regex(pattern, RegexOption.IGNORE_CASE)) +fun String.remove(regex: Regex) = replace(regex, "") fun String.Companion.empty() = "" -fun String?.orEmpty(): String = this ?: String.empty() fun String.isEmpty(): Boolean = length == 0 - fun String.replace(): String = replace("-", " ") fun String.parseDate(): String? = diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/BaseViewModel.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/BaseViewModel.kt new file mode 100644 index 00000000..b5472487 --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/BaseViewModel.kt @@ -0,0 +1,20 @@ +package com.jeluchu.jchucomponentscompose.core.platform + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.jeluchu.jchucomponentscompose.core.exception.Failure + +abstract class BaseViewModel : ViewModel() { + + var failure: MutableLiveData = MutableLiveData() + var showSpinner: MutableLiveData = MutableLiveData() + + protected fun handleShowSpinner(show: Boolean) { + this.showSpinner.value = show + } + + protected fun handleFailure(failure: Failure) { + this.failure.value = failure + } + +} \ No newline at end of file diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/ContextHandler.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/ContextHandler.kt new file mode 100644 index 00000000..1f20516c --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/ContextHandler.kt @@ -0,0 +1,8 @@ +package com.jeluchu.jchucomponentscompose.core.platform + +import android.content.Context + +class ContextHandler + (private val context: Context) { + val appContext: Context get() = context.applicationContext +} \ No newline at end of file diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/NetworkHandler.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/NetworkHandler.kt new file mode 100644 index 00000000..8728a81c --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/core/platform/NetworkHandler.kt @@ -0,0 +1,9 @@ +package com.jeluchu.jchucomponentscompose.core.platform + +import android.content.Context +import com.jeluchu.jchucomponentscompose.core.extensions.context.checkNetworkState + +class NetworkHandler + (private val context: Context) { + val isConnected get() = context.checkNetworkState() +} \ No newline at end of file diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/MediaPlayerHolder.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/MediaPlayerHolder.kt new file mode 100644 index 00000000..d227317b --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/MediaPlayerHolder.kt @@ -0,0 +1,180 @@ +package com.jeluchu.jchucomponentscompose.utils.mediaplayer + +import android.content.Context +import android.media.MediaPlayer +import android.net.Uri +import com.jeluchu.inook.core.utils.mediaplayer.PlaybackInfoListener +import com.jeluchu.jchucomponentscompose.core.extensions.ints.milliSecondsToTimer +import com.jeluchu.jchucomponentscompose.core.extensions.strings.empty +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit + +class MediaPlayerHolder(context: Context) : PlayerAdapter { + private val mContext: Context = context.applicationContext + private var mMediaPlayer: MediaPlayer? = null + private var mResourceId = String.empty() + private var mPlaybackInfoListener: PlaybackInfoListener? = null + private var mExecutor: ScheduledExecutorService? = null + private var mSeekbarPositionUpdateTask: Runnable? = null + + /** + * Once the [MediaPlayer] is released, it can't be used again, and another one has to be + * created. In the onStop() method of the [MainActivity] the [MediaPlayer] is + * released. Then in the onStart() of the [MainActivity] a new [MediaPlayer] + * object has to be created. That's why this method is private, and called by load(int) and + * not the constructor. + */ + private fun initializeMediaPlayer() { + if (mMediaPlayer == null) { + mMediaPlayer = MediaPlayer() + mMediaPlayer!!.setOnCompletionListener { + stopUpdatingCallbackWithPosition(true) + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onStateChanged(PlaybackInfoListener.State.COMPLETED) + mPlaybackInfoListener!!.onPlaybackCompleted() + } + } + } + } + + override val isPlaying: Boolean + get() = if (mMediaPlayer != null) mMediaPlayer!!.isPlaying else false + + override val currentProgress: Float + get() = if (mMediaPlayer != null) { + val currentSeconds: Long = (mMediaPlayer!!.currentPosition / 1000).toLong() + val totalSeconds: Long = (mMediaPlayer!!.duration / 1000).toLong() + (currentSeconds.toDouble() / totalSeconds * 100).toFloat() + } else 0F + + override val currentTime: String + get() = if (mMediaPlayer != null) mMediaPlayer!!.currentPosition.milliSecondsToTimer() + else String.empty() + + override val totalTime: String + get() = if (mMediaPlayer != null) mMediaPlayer!!.duration.milliSecondsToTimer() + else String.empty() + + override fun togglePlaying(isPlaying: Boolean) { + if (mMediaPlayer != null) { + when (isPlaying) { + true -> mMediaPlayer!!.pause() + false -> mMediaPlayer!!.start() + } + } + } + + fun setPlaybackInfoListener(listener: PlaybackInfoListener?) { + mPlaybackInfoListener = listener + } + + override fun loadMedia(mp3Link: String) { + mResourceId = mp3Link + initializeMediaPlayer() + try { + mMediaPlayer!!.setDataSource(mContext, Uri.parse(mResourceId)) + } catch (e: Exception) { + e.message + } + try { + mMediaPlayer!!.prepare() + } catch (e: Exception) { + e.message + } + initializeProgressCallback() + } + + override fun release() { + if (mMediaPlayer != null) { + mMediaPlayer!!.release() + mMediaPlayer = null + } + } + + + override fun play() { + if (mMediaPlayer != null && !mMediaPlayer!!.isPlaying) { + mMediaPlayer!!.start() + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onStateChanged(PlaybackInfoListener.State.PLAYING) + } + startUpdatingCallbackWithPosition() + } + } + + override fun reset() { + if (mMediaPlayer != null) { + mMediaPlayer!!.reset() + loadMedia(mResourceId) + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onStateChanged(PlaybackInfoListener.State.RESET) + } + stopUpdatingCallbackWithPosition(true) + } + } + + override fun pause() { + if (mMediaPlayer != null && mMediaPlayer!!.isPlaying) { + mMediaPlayer!!.pause() + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onStateChanged(PlaybackInfoListener.State.PAUSED) + } + } + } + + override fun seekTo(position: Int) { + if (mMediaPlayer != null) { + mMediaPlayer!!.seekTo(position) + } + } + + private fun startUpdatingCallbackWithPosition() { + if (mExecutor == null) { + mExecutor = Executors.newSingleThreadScheduledExecutor() + } + if (mSeekbarPositionUpdateTask == null) { + mSeekbarPositionUpdateTask = Runnable { updateProgressCallbackTask() } + } + mExecutor!!.scheduleAtFixedRate( + mSeekbarPositionUpdateTask, + 0, + PLAYBACK_POSITION_REFRESH_INTERVAL_MS.toLong(), + TimeUnit.MILLISECONDS + ) + } + + // Reports media playback position to mPlaybackProgressCallback. + private fun stopUpdatingCallbackWithPosition(resetUIPlaybackPosition: Boolean) { + if (mExecutor != null) { + mExecutor!!.shutdownNow() + mExecutor = null + mSeekbarPositionUpdateTask = null + if (resetUIPlaybackPosition && mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onPositionChanged(0) + } + } + } + + private fun updateProgressCallbackTask() { + if (mMediaPlayer != null && mMediaPlayer!!.isPlaying) { + val currentPosition = mMediaPlayer!!.currentPosition + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onPositionChanged(currentPosition) + } + } + } + + override fun initializeProgressCallback() { + val duration = mMediaPlayer!!.duration + if (mPlaybackInfoListener != null) { + mPlaybackInfoListener!!.onDurationChanged(duration) + mPlaybackInfoListener!!.onPositionChanged(0) + } + } + + companion object { + const val PLAYBACK_POSITION_REFRESH_INTERVAL_MS = 1000 + } + +} \ No newline at end of file diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlaybackInfoListener.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlaybackInfoListener.kt new file mode 100644 index 00000000..20b3354e --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlaybackInfoListener.kt @@ -0,0 +1,34 @@ +package com.jeluchu.inook.core.utils.mediaplayer + +abstract class PlaybackInfoListener { + + internal annotation class State { + companion object { + var INVALID = -1 + var PLAYING = 0 + var PAUSED = 1 + var RESET = 2 + var COMPLETED = 3 + } + } + + open fun onLogUpdated(formattedMessage: String?) {} + open fun onDurationChanged(duration: Int) {} + open fun onPositionChanged(position: Int) {} + open fun onStateChanged(@State state: Int) {} + open fun onPlaybackCompleted() {} + + companion object { + @JvmStatic + fun convertStateToString(@State state: Int): String { + return when (state) { + State.COMPLETED -> "COMPLETED" + State.INVALID -> "INVALID" + State.PAUSED -> "PAUSED" + State.PLAYING -> "PLAYING" + State.RESET -> "RESET" + else -> "N/A" + } + } + } +} \ No newline at end of file diff --git a/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlayerAdapter.kt b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlayerAdapter.kt new file mode 100644 index 00000000..a7606067 --- /dev/null +++ b/jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/utils/mediaplayer/PlayerAdapter.kt @@ -0,0 +1,21 @@ +package com.jeluchu.jchucomponentscompose.utils.mediaplayer + +interface PlayerAdapter { + fun loadMedia(mp3Link: String) + fun release() + + val isPlaying: Boolean + val currentProgress: Float + val currentTime: String + val totalTime: String + + fun play() + fun reset() + fun pause() + + fun togglePlaying(isPlaying: Boolean) + + fun initializeProgressCallback() + fun seekTo(position: Int) + +} \ No newline at end of file