Remove just_audio etc (#665)

* Play with exoplayer

* Update Generate Riverpod Files.run.xml

* Improve Android audio player

* Update .gitignore

* Update gradle and packages

* Disable audio providers

* Player listener

* Fix player for android

* Media notification

* Fix merge

* Fix endscreen

* Fix player

* Remove unused files

* Update pubspec.yaml

* Fix analysis issues

* Fix pipeline
This commit is contained in:
Michael Speed 2024-01-23 18:51:47 +01:00 committed by GitHub
parent 9efaaded97
commit eebead2dbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
91 changed files with 1853 additions and 1240 deletions

View File

@ -65,13 +65,13 @@ jobs:
flutter pub run dart_code_metrics:metrics check-unused-files --fatal-unused lib
# - name: Create or update PR comment
# uses: peter-evans/create-or-update-comment@v1
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
# repository: ${{ github.repository }}
# issue-number: ${{ github.event.pull_request.number }}
# body-file: metrics_report.txt
# edit-mode: replace
# uses: peter-evans/create-or-update-comment@v1
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
# repository: ${{ github.repository }}
# issue-number: ${{ github.event.pull_request.number }}
# body-file: metrics_report.txt
# edit-mode: replace
- name: Create a dummy .prod.env file
run: echo -e "var BASE_URL = ''; \n var SENTRY_URL = ''; \nvar INIT_TOKEN = '';" > .prod.env
@ -85,6 +85,9 @@ jobs:
- name: Run Riverpod generator
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Run Pigeon generator
run: flutter pub run pigeon --input pigeon_conf.dart
- name: Analyze
run: flutter analyze --no-fatal-warnings --no-fatal-infos .

1
.gitignore vendored
View File

@ -97,3 +97,4 @@ android/meditokey.jks
ios/Runner.xcodeproj/project.pbxproj
ios/Flutter/AppFrameworkInfo.plist
android/app/src/google-services.json
android/app/local.properties

View File

@ -4,8 +4,8 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "5b12b7467fcbbdc7351d76690ce8a8693e804179"
channel: "master"
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"
project_type: app
@ -13,11 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 5b12b7467fcbbdc7351d76690ce8a8693e804179
base_revision: 5b12b7467fcbbdc7351d76690ce8a8693e804179
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: ios
create_revision: 5b12b7467fcbbdc7351d76690ce8a8693e804179
base_revision: 5b12b7467fcbbdc7351d76690ce8a8693e804179
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: web
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
# User provided section

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Generate Riverpod Files" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="dart run build_runner watch --delete-conflicting-outputs" />
<option name="SCRIPT_TEXT" value="flutter pub run build_runner watch " />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />

View File

@ -0,0 +1,2 @@
#Wed Jan 17 22:37:33 CET 2024
java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home

View File

@ -23,7 +23,7 @@ if (flutterVersionName == null) {
def appMinSdkVersion = localProperties.getProperty('flutter.minSdkVersion')
if (appMinSdkVersion == null) {
appMinSdkVersion = 21
appMinSdkVersion = 26
}
apply plugin: 'com.android.application'
@ -32,7 +32,7 @@ apply plugin: 'com.google.gms.google-services'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
@ -110,6 +110,7 @@ android {
minifyEnabled false
}
}
namespace 'meditofoundation.medito'
configurations.all {
resolutionStrategy {
eachDependency {
@ -130,7 +131,12 @@ dependencies {
implementation platform('com.google.firebase:firebase-bom:32.1.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation "androidx.multidex:multidex:2.0.1"
implementation 'androidx.media3:media3-session:1.2.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.window:window:1.0.0'
implementation 'androidx.window:window-java:1.0.0'
implementation "androidx.media3:media3-exoplayer:1.2.0"
implementation "androidx.media3:media3-ui:1.2.0"
implementation "androidx.media3:media3-common:1.2.0"
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="meditofoundation.medito">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.

View File

@ -1,6 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="meditofoundation.medito"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
@ -34,23 +32,13 @@
</intent>
</queries>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="Medito"
android:usesCleartextTraffic="true">
<meta-data
android:name="ic_audiofileplayer"
android:value="drawable/logo" />
<activity
android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:name=".MainActivity"
android:allowBackup="false"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
@ -60,89 +48,28 @@
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
<!-- Deep linking -->
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="true" />
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="medito.app"
android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="google_analytics_automatic_screen_reporting_enabled"
android:value="false" />
<meta-data
android:name="assets.audio.player.notification.icon"
android:resource="@drawable/logo" />
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/notification_icon_push" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@android:color/transparent" />
<service
android:name="com.ryanheise.audioservice.AudioService"
android:exported="true"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
<service
android:name=".AudioPlayerService"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="android.media.browse.MediaBrowser" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,516 @@
// Autogenerated from Pigeon (v15.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
private fun wrapError(exception: Throwable): List<Any?> {
if (exception is FlutterError) {
return listOf(
exception.code,
exception.message,
exception.details
)
} else {
return listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
private fun createConnectionError(channelName: String): FlutterError {
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class AudioData (
val url: String,
val track: Track
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): AudioData {
val url = list[0] as String
val track = Track.fromList(list[1] as List<Any?>)
return AudioData(url, track)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
url,
track.toList(),
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class PlaybackState (
val isPlaying: Boolean,
val isBuffering: Boolean,
val isSeeking: Boolean,
val isCompleted: Boolean,
val position: Long,
val duration: Long,
val speed: Speed,
val volume: Long,
val track: Track,
val backgroundSound: BackgroundSound? = null
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): PlaybackState {
val isPlaying = list[0] as Boolean
val isBuffering = list[1] as Boolean
val isSeeking = list[2] as Boolean
val isCompleted = list[3] as Boolean
val position = list[4].let { if (it is Int) it.toLong() else it as Long }
val duration = list[5].let { if (it is Int) it.toLong() else it as Long }
val speed = Speed.fromList(list[6] as List<Any?>)
val volume = list[7].let { if (it is Int) it.toLong() else it as Long }
val track = Track.fromList(list[8] as List<Any?>)
val backgroundSound: BackgroundSound? = (list[9] as List<Any?>?)?.let {
BackgroundSound.fromList(it)
}
return PlaybackState(isPlaying, isBuffering, isSeeking, isCompleted, position, duration, speed, volume, track, backgroundSound)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
isPlaying,
isBuffering,
isSeeking,
isCompleted,
position,
duration,
speed.toList(),
volume,
track.toList(),
backgroundSound?.toList(),
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class BackgroundSound (
val uri: String? = null,
val title: String
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): BackgroundSound {
val uri = list[0] as String?
val title = list[1] as String
return BackgroundSound(uri, title)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
uri,
title,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class Speed (
val speed: Double
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): Speed {
val speed = list[0] as Double
return Speed(speed)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
speed,
)
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class Track (
val title: String,
val description: String,
val imageUrl: String,
val artist: String? = null,
val artistUrl: String? = null
) {
companion object {
@Suppress("UNCHECKED_CAST")
fun fromList(list: List<Any?>): Track {
val title = list[0] as String
val description = list[1] as String
val imageUrl = list[2] as String
val artist = list[3] as String?
val artistUrl = list[4] as String?
return Track(title, description, imageUrl, artist, artistUrl)
}
}
fun toList(): List<Any?> {
return listOf<Any?>(
title,
description,
imageUrl,
artist,
artistUrl,
)
}
}
@Suppress("UNCHECKED_CAST")
private object MeditoAudioServiceApiCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
128.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AudioData.fromList(it)
}
}
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Track.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is AudioData -> {
stream.write(128)
writeValue(stream, value.toList())
}
is Track -> {
stream.write(129)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface MeditoAudioServiceApi {
fun playAudio(audioData: AudioData): Boolean
fun playPauseAudio()
fun stopAudio()
fun setSpeed(speed: Double)
fun seekToPosition(position: Long)
fun skip10SecondsForward()
fun skip10SecondsBackward()
fun setBackgroundSound(uri: String?)
fun setBackgroundSoundVolume(volume: Double)
fun stopBackgroundSound()
fun playBackgroundSound()
companion object {
/** The codec used by MeditoAudioServiceApi. */
val codec: MessageCodec<Any?> by lazy {
MeditoAudioServiceApiCodec
}
/** Sets up an instance of `MeditoAudioServiceApi` to handle messages through the `binaryMessenger`. */
@Suppress("UNCHECKED_CAST")
fun setUp(binaryMessenger: BinaryMessenger, api: MeditoAudioServiceApi?) {
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.playAudio", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val audioDataArg = args[0] as AudioData
var wrapped: List<Any?>
try {
wrapped = listOf<Any?>(api.playAudio(audioDataArg))
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.playPauseAudio", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.playPauseAudio()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.stopAudio", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.stopAudio()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.setSpeed", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val speedArg = args[0] as Double
var wrapped: List<Any?>
try {
api.setSpeed(speedArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.seekToPosition", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val positionArg = args[0].let { if (it is Int) it.toLong() else it as Long }
var wrapped: List<Any?>
try {
api.seekToPosition(positionArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.skip10SecondsForward", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.skip10SecondsForward()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.skip10SecondsBackward", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.skip10SecondsBackward()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.setBackgroundSound", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val uriArg = args[0] as String?
var wrapped: List<Any?>
try {
api.setBackgroundSound(uriArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.setBackgroundSoundVolume", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val volumeArg = args[0] as Double
var wrapped: List<Any?>
try {
api.setBackgroundSoundVolume(volumeArg)
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.stopBackgroundSound", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.stopBackgroundSound()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.Medito.MeditoAudioServiceApi.playBackgroundSound", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.playBackgroundSound()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@Suppress("UNCHECKED_CAST")
private object MeditoAudioServiceCallbackApiCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
128.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
BackgroundSound.fromList(it)
}
}
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PlaybackState.fromList(it)
}
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Speed.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Track.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is BackgroundSound -> {
stream.write(128)
writeValue(stream, value.toList())
}
is PlaybackState -> {
stream.write(129)
writeValue(stream, value.toList())
}
is Speed -> {
stream.write(130)
writeValue(stream, value.toList())
}
is Track -> {
stream.write(131)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
@Suppress("UNCHECKED_CAST")
class MeditoAudioServiceCallbackApi(private val binaryMessenger: BinaryMessenger) {
companion object {
/** The codec used by MeditoAudioServiceCallbackApi. */
val codec: MessageCodec<Any?> by lazy {
MeditoAudioServiceCallbackApiCodec
}
}
fun updatePlaybackState(stateArg: PlaybackState, callback: (Result<Unit>) -> Unit) {
val channelName = "dev.flutter.pigeon.Medito.MeditoAudioServiceCallbackApi.updatePlaybackState"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(stateArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(createConnectionError(channelName)))
}
}
}
}

View File

@ -0,0 +1,308 @@
package meditofoundation.medito
import AudioData
import MeditoAudioServiceApi
import MeditoAudioServiceCallbackApi
import PlaybackState
import Speed
import Track
import android.app.Notification
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.common.util.NotificationUtil
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.media3.session.MediaStyleNotificationHelper
import io.flutter.embedding.engine.FlutterEngineCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AudioPlayerService : MediaSessionService(), Player.Listener, MeditoAudioServiceApi,
MediaSession.Callback {
private var backgroundMusicVolume: Float = 0.0F
private var backgroundSoundUri: String? = null
private lateinit var primaryPlayer: ExoPlayer
private lateinit var backgroundMusicPlayer: ExoPlayer
private var primaryMediaSession: MediaSession? = null
private var meditoAudioApi: MeditoAudioServiceCallbackApi? = null
override fun onCreate() {
super.onCreate()
primaryPlayer = ExoPlayer.Builder(this)
.setAudioAttributes(AudioAttributes.DEFAULT, false)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.build()
backgroundMusicPlayer = ExoPlayer.Builder(this)
.setAudioAttributes(AudioAttributes.DEFAULT, false)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.build()
primaryPlayer.addListener(this)
primaryMediaSession = MediaSession.Builder(this, primaryPlayer)
.setCallback(this)
.build()
FlutterEngineCache.getInstance().get(MainActivity.ENGINE_ID)?.let { engine ->
MeditoAudioServiceApi.setUp(engine.dartExecutor.binaryMessenger, this)
meditoAudioApi = MeditoAudioServiceCallbackApi(engine.dartExecutor.binaryMessenger)
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacks(positionUpdateRunnable)
primaryPlayer.removeListener(this)
backgroundMusicPlayer.removeListener(this)
primaryMediaSession?.run {
player.release()
release()
primaryMediaSession = null
}
}
override fun onTaskRemoved(rootIntent: Intent?) {
handler.removeCallbacks(positionUpdateRunnable)
val player = primaryMediaSession?.player!!
if (!player.playWhenReady || player.mediaItemCount == 0) {
this.primaryPlayer.clearMediaItems()
this.backgroundMusicPlayer.clearMediaItems()
this.primaryPlayer.stop()
this.backgroundMusicPlayer.stop()
stopSelf()
}
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
return primaryMediaSession
}
override fun playAudio(audioData: AudioData): Boolean {
val primaryMediaItem = MediaItem.Builder()
.setUri(audioData.url)
.setMediaId(audioData.url)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(audioData.track.title)
.setArtist(audioData.track.artist)
.setDescription(audioData.track.description)
.setArtworkUri(Uri.parse(audioData.track.imageUrl))
.build()
)
.build()
primaryPlayer.setMediaItem(primaryMediaItem)
primaryPlayer.prepare()
primaryPlayer.play()
handler.postDelayed(positionUpdateRunnable, 500)
playBackgroundSound()
showNotification()
return true
}
@OptIn(UnstableApi::class)
private fun createMediaNotification(
session: MediaSession?,
artworkBitmap: Bitmap?
): Notification {
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(primaryPlayer.currentMediaItem?.mediaMetadata?.title ?: "Medito")
.setContentText(primaryPlayer.currentMediaItem?.mediaMetadata?.artist ?: "Medito")
.setSmallIcon(R.drawable.notification_icon_push)
.setLargeIcon(artworkBitmap)
.setStyle(session?.let { MediaStyleNotificationHelper.MediaStyle(it) })
return builder.build()
}
@OptIn(UnstableApi::class)
private fun showNotification() {
CoroutineScope(Dispatchers.Main).launch {
// Move player access to the main thread
val artworkUri = primaryPlayer.currentMediaItem?.mediaMetadata?.artworkUri
val artworkBitmap = withContext(Dispatchers.IO) {
// Download the bitmap in the background thread
artworkUri?.let { downloadBitmap(it) }
}
val notification = createMediaNotification(primaryMediaSession, artworkBitmap)
try {
NotificationUtil.setNotification(
this@AudioPlayerService,
NOTIFICATION_ID,
notification
)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private suspend fun downloadBitmap(uri: Uri): Bitmap? = withContext(Dispatchers.IO) {
try {
val inputStream = java.net.URL(uri.toString()).openStream()
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
override fun playBackgroundSound() {
if (backgroundSoundUri == null) {
return
}
val backgroundMediaItem = MediaItem.Builder()
.setUri(backgroundSoundUri)
.build()
backgroundMusicPlayer.repeatMode = Player.REPEAT_MODE_ONE
backgroundMusicPlayer.setMediaItem(backgroundMediaItem)
backgroundMusicPlayer.prepare()
backgroundMusicPlayer.play()
}
override fun setBackgroundSound(uri: String?) {
this.backgroundSoundUri = uri
}
@OptIn(UnstableApi::class)
override fun stopBackgroundSound() {
backgroundMusicPlayer.stop()
NotificationUtil.setNotification(
this,
NOTIFICATION_ID,
null
)
}
override fun setBackgroundSoundVolume(volume: Double) {
this.backgroundMusicVolume = volume.toFloat() / 100
backgroundMusicPlayer.volume = this.backgroundMusicVolume
}
override fun seekToPosition(position: Long) {
primaryPlayer.seekTo(position)
}
override fun setSpeed(speed: Double) {
primaryPlayer.setPlaybackSpeed(speed.toFloat())
}
override fun skip10SecondsForward() {
if (primaryPlayer.currentPosition + 10000 > primaryPlayer.duration) {
primaryPlayer.seekTo(primaryPlayer.duration)
return
}
primaryPlayer.seekTo(primaryPlayer.currentPosition + 10000)
}
override fun skip10SecondsBackward() {
if (primaryPlayer.duration - primaryPlayer.currentPosition < 10000) {
primaryPlayer.seekTo(0)
return
}
primaryPlayer.seekTo(primaryPlayer.currentPosition - 10000)
}
override fun stopAudio() {
primaryPlayer.stop()
backgroundMusicPlayer.stop()
handler.removeCallbacks(positionUpdateRunnable)
}
override fun playPauseAudio() {
if (primaryPlayer.isPlaying) {
primaryPlayer.pause()
backgroundMusicPlayer.pause()
} else {
primaryPlayer.play()
backgroundMusicPlayer.play()
}
}
private val fadeOutDurationMillis = 10000
private val handler = Handler(Looper.getMainLooper())
private val positionUpdateRunnable = object : Runnable {
override fun run() {
val currentPosition = primaryPlayer.currentPosition
val trackDuration = primaryPlayer.duration
applyBackgroundSoundVolume(trackDuration, currentPosition)
val state = PlaybackState(
isPlaying = primaryPlayer.isPlaying,
position = primaryPlayer.currentPosition,
volume = (primaryPlayer.volume * 100).toLong(),
speed = Speed(primaryPlayer.playbackParameters.speed.toDouble()),
isBuffering = primaryPlayer.playbackState == Player.STATE_BUFFERING,
duration = primaryPlayer.duration,
isSeeking = primaryPlayer.playbackState == Player.STATE_BUFFERING,
isCompleted = primaryPlayer.playbackState == Player.STATE_ENDED,
track = Track(
title = primaryPlayer.currentMediaItem?.mediaMetadata?.title.toString(),
description = primaryPlayer.currentMediaItem?.mediaMetadata?.description.toString(),
imageUrl = primaryPlayer.currentMediaItem?.mediaMetadata?.artworkUri.toString(),
artist = primaryPlayer.currentMediaItem?.mediaMetadata?.artist.toString(),
),
)
meditoAudioApi?.updatePlaybackState(state) {
if (primaryPlayer.playbackState != Player.STATE_ENDED) {
handler.postDelayed(this, 250)
}
}
}
private fun applyBackgroundSoundVolume(trackDuration: Long, currentPosition: Long) {
if (trackDuration - currentPosition <= fadeOutDurationMillis && trackDuration > fadeOutDurationMillis) {
val volumeFraction =
(trackDuration - currentPosition).toFloat() / fadeOutDurationMillis
backgroundMusicPlayer.volume =
backgroundMusicVolume * volumeFraction
} else {
backgroundMusicPlayer.volume = backgroundMusicVolume
}
}
}
companion object {
const val CHANNEL_ID = "medito_media_channel"
const val NOTIFICATION_ID = 101011
}
}

View File

@ -1,11 +1,47 @@
package meditofoundation.medito
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
FlutterEngineCache
.getInstance()
.put(ENGINE_ID, flutterEngine);
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createNotificationChannel()
val intent = Intent(this, AudioPlayerService::class.java)
startService(intent)
}
companion object {
const val ENGINE_ID = "medito_flutter_engine"
}
private fun createNotificationChannel() {
val channelName = "Meditation audio"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(AudioPlayerService.CHANNEL_ID, channelName, importance).apply {
description = "Notification for media control of meditation audio"
}
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="meditofoundation.medito">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -6,8 +6,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath 'com.google.gms:google-services:4.3.15'
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.google.gms:google-services:4.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -0,0 +1,4 @@
class EnvironmentConstants {
static const String stagingEnv = '.staging.env';
static const String prodEnv = '.prod.env';
}

View File

@ -10,8 +10,6 @@ class StringConstants {
static const String begin = 'Begin';
static const String none = 'None';
static const String removed = 'Removed';
static const String stagingEnv = '.staging.env';
static const String prodEnv = '.prod.env';
static const String supportEmail = 'hello@meditofoundation.org';
static const String pickNarratorAndDuration = 'Pick a narrator & duration';
static const String pickDuration = 'Pick a duration';
@ -48,8 +46,6 @@ class StringConstants {
static const String x08 = 'X0.8';
static const String x09 = 'X0.9';
static const String x1 = 'X1';
static const String x125 = 'X1.25';
static const String x15 = 'X1.5';
//Join
static const String joinTheMeditoFamily = 'Join the Medito Family';

View File

@ -12,20 +12,20 @@ const fontSize16 = 16.0;
const fontSize14 = 14.0;
const smallScreenWidth = 480.0;
const miniPlayerHeight = 56.0;
const height32 = SizedBox(height: 32);
const height24 = SizedBox(height: 24);
const width2 = SizedBox(width: 2);
const width4 = SizedBox(width: 4);
const width8 = SizedBox(width: 8);
const width12 = SizedBox(width: 12);
const width16 = SizedBox(width: 16);
const height4 = SizedBox(height: 4);
const height5 = SizedBox(height: 5);
const height6 = SizedBox(height: 6);
const height8 = SizedBox(height: 8);
const height12 = SizedBox(height: 12);
const height16 = SizedBox(height: 16);
const height20 = SizedBox(height: 20);
const height12 = SizedBox(height: 12);
const width16 = SizedBox(width: 16);
const width12 = SizedBox(width: 12);
const height8 = SizedBox(height: 8);
const height6 = SizedBox(height: 6);
const height5 = SizedBox(height: 5);
const height4 = SizedBox(height: 4);
const width8 = SizedBox(width: 8);
const width4 = SizedBox(width: 4);
const width2 = SizedBox(width: 2);
const height24 = SizedBox(height: 24);
const height32 = SizedBox(height: 32);
const bottomSheetBoxDecoration = BoxDecoration(
color: ColorConstants.onyx,
borderRadius: BorderRadius.only(

View File

@ -18,10 +18,9 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/constants/theme/app_theme.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/routes/routes.dart';
import 'package:Medito/src/audio_pigeon.g.dart';
import 'package:Medito/utils/stats_utils.dart';
import 'package:Medito/utils/utils.dart';
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -31,13 +30,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'constants/environments/environment_constants.dart';
import 'services/notifications/notifications_service.dart';
late AudioPlayerNotifier audioHandler;
var audioStateNotifier = AudioStateNotifier();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: StringConstants.stagingEnv);
await dotenv.load(fileName: EnvironmentConstants.stagingEnv);
MeditoAudioServiceCallbackApi.setup(AudioStateProvider(audioStateNotifier));
var sharedPreferences = await initializeSharedPreferences();
var isPlayServices = await checkGooglePlayServices();
@ -56,8 +59,6 @@ Future<void> main() async {
},
);
audioHandler = await initAudioService();
usePathUrlStrategy();
runApp(
@ -70,36 +71,6 @@ Future<void> main() async {
);
}
Future<AudioPlayerNotifier> initAudioService() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playback,
avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.duckOthers,
avAudioSessionMode: AVAudioSessionMode.defaultMode,
avAudioSessionRouteSharingPolicy:
AVAudioSessionRouteSharingPolicy.defaultPolicy,
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
androidAudioAttributes: AndroidAudioAttributes(
contentType: AndroidAudioContentType.music,
flags: AndroidAudioFlags.none,
usage: AndroidAudioUsage.media,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gainTransientMayDuck,
androidWillPauseWhenDucked: true,
));
return await AudioService.init(
builder: () => AudioPlayerNotifier(),
config: AudioServiceConfig(
androidNotificationChannelId: 'com.medito.app.channel.audio',
androidNotificationChannelName: 'Medito Meditation',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
);
}
// This Widget is the main application widget.
// ignore: prefer-match-file-name
class ParentWidget extends ConsumerStatefulWidget {
static const String _title = 'Medito';
@ -116,12 +87,6 @@ class _ParentWidgetState extends ConsumerState<ParentWidget>
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
unawaited(updateStatsFromBg(ref));
} else if (state == AppLifecycleState.detached) {
final audioProvider = ref.read(audioPlayerNotifierProvider);
audioProvider.stop();
audioProvider.trackAudioPlayer.dispose();
audioProvider.backgroundSoundAudioPlayer.dispose();
audioProvider.dispose();
}
currentState = state;
}
@ -129,10 +94,6 @@ class _ParentWidgetState extends ConsumerState<ParentWidget>
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
final audioProvider = ref.read(audioPlayerNotifierProvider);
audioProvider.trackAudioPlayer.dispose();
audioProvider.backgroundSoundAudioPlayer.dispose();
audioProvider.dispose();
super.dispose();
}
@ -155,15 +116,9 @@ class _ParentWidgetState extends ConsumerState<ParentWidget>
);
onMessageAppOpened(context, ref);
initializeNotification(context, ref);
initializeAudioPlayer();
WidgetsBinding.instance.addObserver(this);
}
void initializeAudioPlayer() {
var audioPlayerProvider = ref.read(audioPlayerNotifierProvider);
audioPlayerProvider.initAudioHandler();
}
@override
Widget build(BuildContext context) {
final goRouter = ref.watch(goRouterProvider);

View File

@ -16,20 +16,20 @@ export 'events/pack_viewed/pack_viewed_model.dart';
export 'events/save_fcm_token/save_fcm_token_model.dart';
export 'events/track_viewed/track_viewed_model.dart';
export 'events/transfer_stats/transfer_stats_model.dart';
export 'explore/explore_model.dart';
export 'home/announcement/announcement_model.dart';
export 'home/chips/home_chips_items_model.dart';
export 'home/editorial/editorial_model.dart';
export 'home/header/home_header_model.dart';
export 'home/home_model.dart';
export 'home/menu/home_menu_model.dart';
export 'home/quote/quote_model.dart';
export 'home/editorial/editorial_model.dart';
export 'home/rows/home_rows_model.dart';
export 'home/shortcuts/shortcuts_model.dart';
export 'join/join_route_params_model.dart';
export 'me/me_model.dart';
export 'notification/notification_payload_model.dart';
export 'pack/pack_model.dart';
export 'explore/explore_model.dart';
export 'stats/all_stats/all_stats_model.dart';
export 'stats/stats_model.dart';
export 'stats/tiles/tiles_model.dart';

View File

@ -10,7 +10,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'background_sounds_provider.g.dart';
import '../../src/audio_pigeon.g.dart';
part 'background_sounds_notifier.g.dart';
final _api = MeditoAudioServiceApi();
@riverpod
Future<List<BackgroundSoundsModel>> backgroundSounds(BackgroundSoundsRef ref) {
@ -43,7 +47,7 @@ final backgroundSoundsNotifierProvider =
//ignore:prefer-match-file-name
class BackgroundSoundsNotifier extends ChangeNotifier {
final Ref ref;
double volume = 0;
double volume = 50;
BackgroundSoundsModel? selectedBgSound;
BackgroundSoundsNotifier(this.ref);
@ -51,18 +55,21 @@ class BackgroundSoundsNotifier extends ChangeNotifier {
void handleOnChangeVolume(double vol) {
volume = vol;
ref.read(backgroundSoundsRepositoryProvider).handleOnChangeVolume(vol);
_api.setBackgroundSoundVolume(volume);
notifyListeners();
}
void handleOnChangeSound(BackgroundSoundsModel? sound) {
selectedBgSound = sound;
var bgSoundRepoProvider = ref.read(backgroundSoundsRepositoryProvider);
if (sound != null) {
final downloadAudio = ref.read(downloaderRepositoryProvider);
bgSoundRepoProvider.handleOnChangeSound(sound);
updateItemsInSavedBgSoundList(sound);
_updateItemsInSavedBgSoundList(sound);
if (sound.title != StringConstants.none) {
var name = '${sound.title}.mp3';
final downloadAudio = ref.read(downloaderRepositoryProvider);
downloadAudio.getDownloadedFile(name).then((value) {
if (value == null) {
downloadAudio.downloadFile(
@ -70,15 +77,25 @@ class BackgroundSoundsNotifier extends ChangeNotifier {
name: name,
);
}
_api.setBackgroundSound(value);
_api.playBackgroundSound();
});
}
} else {
bgSoundRepoProvider.removeSelectedBgSound();
stopBackgroundSound();
}
notifyListeners();
}
void updateItemsInSavedBgSoundList(BackgroundSoundsModel sound) {
void stopBackgroundSound() {
var bgSoundRepoProvider = ref.read(backgroundSoundsRepositoryProvider);
bgSoundRepoProvider.removeSelectedBgSound();
_api.setBackgroundSound(null);
_api.stopBackgroundSound();
notifyListeners();
}
void _updateItemsInSavedBgSoundList(BackgroundSoundsModel sound) {
final provider = ref.read(backgroundSoundsRepositoryProvider);
provider.updateItemsInSavedBgSoundList(sound);
}
@ -94,12 +111,9 @@ class BackgroundSoundsNotifier extends ChangeNotifier {
}
void getVolumeFromPref() {
var value = ref.read(backgroundSoundsRepositoryProvider).getBgSoundVolume();
if (value != null) {
volume = value;
notifyListeners();
} else {
handleOnChangeVolume(50.0);
}
var value =
ref.read(backgroundSoundsRepositoryProvider).getBgSoundVolume() ?? 50.0;
handleOnChangeVolume(value);
volume = value;
}
}

View File

@ -1,9 +1,10 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/repositories/repositories.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../me/me_provider.dart';
part 'device_and_app_info_provider.g.dart';
@riverpod

View File

@ -1,23 +0,0 @@
import 'package:Medito/providers/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final audioPlayPauseStateProvider = StateProvider<PLAY_PAUSE_AUDIO>(
(ref) {
return PLAY_PAUSE_AUDIO.PAUSE;
},
);
final audioPlayPauseProvider = Provider<void>(
(ref) {
final audioPlayer = ref.watch(audioPlayerNotifierProvider);
final currentState = ref.watch(audioPlayPauseStateProvider);
if (currentState == PLAY_PAUSE_AUDIO.PLAY) {
audioPlayer.play();
} else {
audioPlayer.pause();
}
},
);
//ignore: prefer-match-file-name
enum PLAY_PAUSE_AUDIO { PLAY, PAUSE }

View File

@ -1,251 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:Medito/main.dart';
import 'package:Medito/models/models.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
final audioPlayerNotifierProvider =
ChangeNotifierProvider<AudioPlayerNotifier>((ref) {
return audioHandler;
});
//ignore:prefer-match-file-name
class AudioPlayerNotifier extends BaseAudioHandler
with QueueHandler, SeekHandler, ChangeNotifier {
var backgroundSoundAudioPlayer = AudioPlayer();
final trackAudioPlayer = AudioPlayer();
TrackFilesModel? currentlyPlayingTrack;
final hasBgSoundKey = 'hasBgSound';
final fadeDurationInSeconds = 5;
var bgVolume;
@override
Future<void> pause() async {
try {
pauseBackgroundSound();
unawaited(trackAudioPlayer.pause());
} catch (err) {
unawaited(Sentry.captureException(
err,
stackTrace: err,
));
}
}
@override
Future<void> play() async {
try {
unawaited(trackAudioPlayer.play());
var hasBgSound = mediaItemHasBGSound();
if (hasBgSound) {
playBackgroundSound();
} else {
pauseBackgroundSound();
}
} catch (err) {
unawaited(Sentry.captureException(
err,
stackTrace: err,
));
}
}
@override
Future<void> stop() async {
unawaited(trackAudioPlayer.stop());
if (mediaItemHasBGSound()) {
stopBackgroundSound();
}
}
@override
Future<void> seek(Duration position) async {
seekValueFromSlider(position.inMilliseconds);
}
void initAudioHandler() {
trackAudioPlayer.playbackEventStream
.map(_transformEvent)
.pipe(playbackState);
}
void setBackgroundAudio(BackgroundSoundsModel sound) {
getApplicationDocumentsDirectory().then((file) async {
var savePath = file.path + '/${sound.title}.mp3';
var filePath = File(savePath);
if (await filePath.exists()) {
unawaited(backgroundSoundAudioPlayer.setFilePath(filePath.path));
} else {
unawaited(
backgroundSoundAudioPlayer.setAudioSource(
AudioSource.uri(
Uri.parse(sound.path),
),
),
);
}
});
}
void setTrackAudio(
TrackModel trackModel,
TrackFilesModel file, {
String? filePath,
}) {
try {
if (filePath != null) {
unawaited(trackAudioPlayer.setFilePath(filePath));
setMediaItem(trackModel, file, filePath: filePath);
} else {
setMediaItem(trackModel, file);
trackAudioPlayer.setUrl(file.path);
}
} catch (e) {
unawaited(Sentry.captureException(
e,
stackTrace: e,
));
}
}
void playBackgroundSound() {
backgroundSoundAudioPlayer.play();
backgroundSoundAudioPlayer.setLoopMode(LoopMode.all);
}
void pauseBackgroundSound() {
backgroundSoundAudioPlayer.pause();
}
void stopBackgroundSound() {
backgroundSoundAudioPlayer.stop();
}
void setTrackAudioSpeed(double speed) {
trackAudioPlayer.setSpeed(speed);
}
void seekValueFromSlider(int duration) {
try {
trackAudioPlayer.seek(Duration(milliseconds: duration));
} catch (err) {
Sentry.captureException(
err,
stackTrace: err,
);
}
}
void skipForward10Secs() async {
var seekDuration = trackAudioPlayer.position.inMilliseconds +
Duration(seconds: 10).inMilliseconds;
await trackAudioPlayer.seek(Duration(milliseconds: seekDuration));
}
void skipBackward10Secs() async {
var seekDuration = max(
0,
trackAudioPlayer.position.inMilliseconds -
Duration(seconds: 10).inMilliseconds,
);
await trackAudioPlayer.seek(Duration(milliseconds: seekDuration));
}
void setBackgroundSoundVolume(double volume) async {
bgVolume = volume;
await backgroundSoundAudioPlayer.setVolume(volume / 100);
}
bool handleFadeAtEnd(
Duration position,
Duration maxDuration,
) {
var isEnding = maxDuration.inSeconds > 0 &&
position.inSeconds > maxDuration.inSeconds - fadeDurationInSeconds;
if (isEnding) {
_setBgVolumeFadeAtEnd();
} else {
if (bgVolume != null) {
Future.delayed(Duration(milliseconds: 500), () {
setBackgroundSoundVolume(bgVolume);
});
}
}
return isEnding;
}
void _setBgVolumeFadeAtEnd() {
if (backgroundSoundAudioPlayer.volume > 0) {
Future.delayed(Duration(milliseconds: 500), () {
var newVolume = backgroundSoundAudioPlayer.volume - 0.05;
if (newVolume > 0) {
unawaited(backgroundSoundAudioPlayer.setVolume(newVolume));
}
});
}
}
void setMediaItem(
TrackModel trackModel,
TrackFilesModel file, {
String? filePath,
}) {
var item = MediaItem(
id: filePath ?? file.path,
title: trackModel.title,
artist: trackModel.artist?.name,
duration: Duration(milliseconds: file.duration),
artUri: Uri.parse(
trackModel.coverUrl,
),
extras: {
hasBgSoundKey: trackModel.hasBackgroundSound,
'trackId': trackModel.id,
'fileId': file.id,
},
);
mediaItem.add(item);
}
bool mediaItemHasBGSound() {
return mediaItem.value?.extras?[hasBgSoundKey] ?? false;
}
PlaybackState _transformEvent(PlaybackEvent event) {
return PlaybackState(
controls: [
MediaControl.rewind,
if (trackAudioPlayer.playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.fastForward,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[trackAudioPlayer.processingState]!,
playing: trackAudioPlayer.playing,
updatePosition: trackAudioPlayer.position,
bufferedPosition: trackAudioPlayer.bufferedPosition,
speed: trackAudioPlayer.speed,
queueIndex: event.currentIndex,
);
}
}

View File

@ -1,64 +0,0 @@
import 'package:Medito/providers/providers.dart';
import 'package:just_audio/just_audio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
part 'audio_position_provider.g.dart';
@riverpod
void slideAudioPosition(
SlideAudioPositionRef ref, {
required int duration,
}) {
final audioPlayer = ref.watch(audioPlayerNotifierProvider);
audioPlayer.seekValueFromSlider(duration);
}
@riverpod
void skipAudio(
SkipAudioRef ref, {
required SKIP_AUDIO skip,
}) {
final audioPlayer = ref.watch(audioPlayerNotifierProvider);
switch (skip) {
case SKIP_AUDIO.SKIP_FORWARD_10:
audioPlayer.skipForward10Secs();
break;
case SKIP_AUDIO.SKIP_BACKWARD_10:
audioPlayer.skipBackward10Secs();
break;
}
}
final audioPositionAndPlayerStateProvider =
StreamProvider<PositionAndPlayerStateState>((ref) {
final audioPlayer = ref.watch(audioPlayerNotifierProvider);
return Rx.combineLatest3<Duration, Duration?, PlayerState,
PositionAndPlayerStateState>(
audioPlayer.trackAudioPlayer.positionStream,
audioPlayer.trackAudioPlayer.durationStream,
audioPlayer.trackAudioPlayer.playerStateStream,
(position, duration, playerState) =>
PositionAndPlayerStateState(playerState, duration, position),
);
});
final audioPlaybackStreamProvider = StreamProvider<ProcessingState>((ref) {
final audioPlayer = ref.watch(audioPlayerNotifierProvider);
return audioPlayer.trackAudioPlayer.playbackEventStream
.map((event) => event.processingState);
});
//ignore: prefer-match-file-name
enum SKIP_AUDIO { SKIP_FORWARD_10, SKIP_BACKWARD_10 }
class PositionAndPlayerStateState {
final PlayerState playerState;
final Duration position;
final Duration? duration;
PositionAndPlayerStateState(this.playerState, this.duration, this.position);
}

View File

@ -1,74 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final audioSpeedProvider = ChangeNotifierProvider<AudioSpeedProvider>((ref) {
return AudioSpeedProvider(ref);
});
class AudioSpeedProvider extends ChangeNotifier {
final Ref ref;
final List<String> _speedList = [
StringConstants.x06,
StringConstants.x07,
StringConstants.x08,
StringConstants.x09,
StringConstants.x1,
StringConstants.x125,
StringConstants.x15,
];
AudioSpeedModel audioSpeedModel = AudioSpeedModel();
AudioSpeedProvider(this.ref);
void setAudioTrackSpeed() {
double speed;
String label;
var nextIndex = _speedList.indexOf(audioSpeedModel.label) + 1;
label =
nextIndex >= _speedList.length ? _speedList[0] : _speedList[nextIndex];
if (label == StringConstants.x06) {
speed = 0.6;
} else if (label == StringConstants.x07) {
speed = 0.7;
} else if (label == StringConstants.x08) {
speed = 0.8;
} else if (label == StringConstants.x09) {
speed = 0.9;
} else if (label == StringConstants.x1) {
speed = 1;
} else if (label == StringConstants.x125) {
speed = 1.25;
} else if (label == StringConstants.x15) {
speed = 1.5;
} else {
speed = 1;
}
audioSpeedModel = AudioSpeedModel(label: label, speed: speed);
unawaited(
ref.read(sharedPreferencesProvider).setString(
SharedPreferenceConstants.sessionAudioSpeed,
json.encode(
audioSpeedModel.toJson(),
),
),
);
notifyListeners();
}
void getAudioTrackSpeedFromPref() {
var audioSpeedFromPref = ref.read(sharedPreferencesProvider).getString(
SharedPreferenceConstants.sessionAudioSpeed,
);
if (audioSpeedFromPref != null) {
audioSpeedModel =
AudioSpeedModel.fromJson(json.decode(audioSpeedFromPref));
notifyListeners();
}
}
}

View File

@ -0,0 +1,53 @@
import 'package:Medito/main.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../src/audio_pigeon.g.dart';
class AudioStateProvider implements MeditoAudioServiceCallbackApi {
final AudioStateNotifier notifier;
AudioStateProvider(this.notifier);
@override
void updatePlaybackState(PlaybackState state) {
notifier.updatePlaybackState(state);
}
}
class AudioStateNotifier extends StateNotifier<PlaybackState> {
AudioStateNotifier()
: super(PlaybackState(
position: 0,
isPlaying: false,
isBuffering: false,
isSeeking: false,
isCompleted: false,
duration: 0,
speed: Speed(speed: 1),
volume: 100,
track: Track(title: '', description: '', imageUrl: '', artist: ''),
));
void updatePlaybackState(PlaybackState newState) {
state = newState;
}
void resetState() {
state = PlaybackState(
position: 0,
isPlaying: false,
isBuffering: false,
isSeeking: false,
isCompleted: false,
duration: 0,
speed: Speed(speed: 1),
volume: 100,
track: Track(title: '', description: '', imageUrl: '', artist: ''),
);
}
}
final audioStateProvider =
StateNotifierProvider<AudioStateNotifier, PlaybackState>((ref) {
return audioStateNotifier;
});

View File

@ -26,7 +26,7 @@ class AudioDownloaderProvider extends ChangeNotifier {
'${trackModel.id}-${file.id}${getAudioFileExtension(file.path)}';
try {
final downloadAudio = ref.read(downloaderRepositoryProvider);
audioDownloadState[fileName] = AUDIO_DOWNLOAD_STATE.DOWNLOADIING;
audioDownloadState[fileName] = AUDIO_DOWNLOAD_STATE.DOWNLOADING;
await downloadAudio.downloadFile(
file.path,
name: fileName,
@ -67,7 +67,7 @@ class AudioDownloaderProvider extends ChangeNotifier {
notifyListeners();
}
Future<String?> getTrackAudio(String fileName) async {
Future<String?> getTrackPath(String fileName) async {
final downloadAudio = ref.read(downloaderRepositoryProvider);
var audioPath = await downloadAudio.getDownloadedFile(fileName);
audioDownloadState[fileName] = audioPath != null
@ -81,6 +81,6 @@ class AudioDownloaderProvider extends ChangeNotifier {
enum AUDIO_DOWNLOAD_STATE {
DOWNLOAD,
DOWNLOADIING,
DOWNLOADING,
DOWNLOADED,
}

View File

@ -1,10 +1,17 @@
import 'dart:async';
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/utils/utils.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:workmanager/workmanager.dart';
import '../../constants/types/type_constants.dart';
import '../../src/audio_pigeon.g.dart';
import '../../utils/utils.dart';
import '../../utils/workmanager.dart';
import '../events/events_provider.dart';
import 'download/audio_downloader_provider.dart';
final _api = MeditoAudioServiceApi();
final playerProvider =
StateNotifierProvider<PlayerProvider, TrackModel?>((ref) {
@ -33,84 +40,78 @@ class PlayerProvider extends StateNotifier<TrackModel?> {
}
});
final audioPlayerNotifier = ref.read(audioPlayerNotifierProvider);
state = track;
_optionallyLoadBackgroundSound(
ref,
audioPlayerNotifier,
track,
track.audio.first.files.first,
);
await _playTrack(
ref,
audioPlayerNotifier,
track,
file,
);
}
void _optionallyLoadBackgroundSound(
Ref ref,
AudioPlayerNotifier audioPlayerNotifier,
TrackModel trackModel,
TrackFilesModel file,
) {
var isPlaying = audioPlayerNotifier.trackAudioPlayer.playerState.playing;
var currentPlayingFileId = audioPlayerNotifier.currentlyPlayingTrack?.id;
if (!isPlaying || currentPlayingFileId != file.id) {
_optionallyPlayBackgroundSound(
ref,
audioPlayerNotifier,
trackModel.hasBackgroundSound,
);
}
state = track;
}
Future<void> _playTrack(
Ref ref,
AudioPlayerNotifier audioPlayerNotifier,
TrackModel trackModel,
TrackFilesModel file,
) async {
var checkDownloadedFile = ref.read(audioDownloaderProvider).getTrackAudio(
await startBackgroundThreadForAudioCompleteEvent(
file.id,
trackModel.id,
file.duration,
);
var downloadPath = await ref.read(audioDownloaderProvider).getTrackPath(
_constructFileName(trackModel, file),
);
var filePath = await checkDownloadedFile;
audioPlayerNotifier.setTrackAudio(
trackModel,
file,
filePath: filePath,
await _api.playAudio(
AudioData(
url: downloadPath ?? file.path,
track: Track(
title: trackModel.title,
artist: trackModel.artist?.name ?? '',
artistUrl: trackModel.artist?.path ?? '',
description: trackModel.description,
imageUrl: trackModel.coverUrl,
),
),
);
audioPlayerNotifier.currentlyPlayingTrack = file;
unawaited(audioPlayerNotifier.play());
}
Future<void> startBackgroundThreadForAudioCompleteEvent(
String fileId,
String trackModelId,
int duration,
) async {
await Workmanager().initialize(
callbackDispatcher,
isInDebugMode: true,
);
await Workmanager().registerOneOffTask(
audioCompletedTaskKey,
audioCompletedTaskKey,
initialDelay: Duration(milliseconds: duration),
constraints: Constraints(
networkType: NetworkType.connected,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresDeviceIdle: false,
requiresStorageNotLow: false,
),
inputData: {
TypeConstants.fileIdKey: fileId,
TypeConstants.trackIdKey: trackModelId,
},
);
}
void _cancelBackgroundThreadForAudioCompleteEvent() async {
await Workmanager().cancelAll();
}
String _constructFileName(TrackModel trackModel, TrackFilesModel file) =>
'${trackModel.id}-${file.id}${getAudioFileExtension(file.path)}';
void _optionallyPlayBackgroundSound(
Ref ref,
AudioPlayerNotifier audioPlayerNotifier,
bool hasBackgroundSound,
) {
if (hasBackgroundSound) {
final _provider = ref.read(backgroundSoundsNotifierProvider);
_provider.getBackgroundSoundFromPref();
if (_provider.selectedBgSound != null &&
_provider.selectedBgSound?.title != StringConstants.none) {
audioPlayerNotifier.setBackgroundAudio(_provider.selectedBgSound!);
audioPlayerNotifier.playBackgroundSound();
}
_provider.getVolumeFromPref();
audioPlayerNotifier.setBackgroundSoundVolume(_provider.volume);
} else {
audioPlayerNotifier.pauseBackgroundSound();
}
}
void handleAudioStartedEvent(
String trackId,
String audioFileId,
@ -123,19 +124,28 @@ class PlayerProvider extends StateNotifier<TrackModel?> {
ref.read(eventsProvider(event: event.toJson()));
}
void handleAudioCompletionEvent(
String audioFileId,
String trackId,
) {
var audio = AudioCompletedModel(
audioFileId: audioFileId,
trackId: trackId,
updateStats: true,
);
var event = EventsModel(
name: EventTypes.audioCompleted,
payload: audio.toJson(),
);
ref.read(eventsProvider(event: event.toJson()));
Future<void> seekToPosition(int position) async {
await _api.seekToPosition(position);
}
void stop() {
_cancelBackgroundThreadForAudioCompleteEvent();
_api.stopAudio();
}
void setSpeed(double speed) {
_api.setSpeed(speed);
}
void skip10SecondsForward() {
_api.skip10SecondsForward();
}
void skip10SecondsBackward() {
_api.skip10SecondsBackward();
}
void playPause() {
_api.playPauseAudio();
}
}

View File

@ -1,23 +1,16 @@
export 'auth/auth_provider.dart';
export 'auth/initialize_user_provider.dart';
export 'me/me_provider.dart';
export 'home/home_provider.dart';
export 'player/audio_player_provider.dart';
export 'background_sounds/background_sounds_provider.dart';
export 'connectivity/connectivity_provider.dart';
export 'device_and_app_info/device_and_app_info_provider.dart';
export 'events/app_opened_event_provider.dart';
export 'events/events_provider.dart';
export 'explore/explore_provider.dart';
export 'meditation/download_track_provider.dart';
export 'meditation/track_provider.dart';
export 'notification/notification_provider.dart';
export 'pack/pack_provider.dart';
export 'page_view/bottom_padding_provider.dart';
export 'player/audio_play_pause_provider.dart';
export 'player/audio_position_provider.dart';
export 'player/audio_speed_provider.dart';
export 'player/audio_state_provider.dart';
export 'player/download/audio_downloader_provider.dart';
export 'player/player_provider.dart';
export 'explore/explore_provider.dart';
export 'shared_preference/shared_preference_provider.dart';
export 'events/app_opened_event_provider.dart';
export 'stats/stats_provider.dart';

View File

@ -1,11 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:just_audio/just_audio.dart';
final rootCombineProvider = Provider.family<void, BuildContext>((ref, context) {
ref.read(remoteStatsProvider);
@ -13,54 +8,4 @@ final rootCombineProvider = Provider.family<void, BuildContext>((ref, context) {
ref.read(postLocalStatsProvider);
ref.read(deviceAppAndUserInfoProvider);
ref.read(audioDownloaderProvider).deleteDownloadedFileFromPreviousVersion();
var audioPlayerProvider = ref.read(audioPlayerNotifierProvider);
var streamEvent = audioPlayerProvider.trackAudioPlayer.playerStateStream
.map((event) => event.processingState)
.distinct();
streamEvent.forEach((element) {
if (element == ProcessingState.completed) {
_handleAudioCompletion(ref, context);
_handleUserNotSignedIn(ref, context);
}
});
});
void _handleAudioCompletion(Ref ref, BuildContext context) {
final audioProvider = ref.read(audioPlayerNotifierProvider);
final bgSoundProvider = ref.read(backgroundSoundsNotifierProvider);
var extras = audioProvider.mediaItem.value?.extras;
if (extras != null) {
ref.read(playerProvider.notifier).handleAudioCompletionEvent(
extras[TypeConstants.fileIdKey],
extras[TypeConstants.trackIdKey],
);
audioProvider.seekValueFromSlider(0);
audioProvider.pause();
audioProvider.setBackgroundSoundVolume(bgSoundProvider.volume);
audioProvider.stop();
ref.invalidate(packProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(audioPlayPauseStateProvider.notifier).state =
PLAY_PAUSE_AUDIO.PAUSE;
});
var currentlyPlayingTrack = ref.read(playerProvider);
var endScreen = currentlyPlayingTrack?.endScreen;
if (endScreen != null) {
context.push(RouteConstants.endScreenPath, extra: endScreen);
}
}
}
void _handleUserNotSignedIn(Ref ref, BuildContext context) {
var _user =
ref.read(authProvider.notifier).userResponse.body as UserTokenModel;
if (_user.email == null) {
var params = JoinRouteParamsModel(screen: Screen.track);
context.push(
RouteConstants.joinIntroPath,
extra: params,
);
}
}

View File

@ -5,7 +5,6 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -78,5 +77,5 @@ class AuthRepositoryImpl extends AuthRepository {
@riverpod
AuthRepository authRepository(AuthRepositoryRef ref) {
return AuthRepositoryImpl(ref: ref, client: ref.watch(dioClientProvider));
return AuthRepositoryImpl(ref: ref, client: DioApiService());
}

View File

@ -5,7 +5,6 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@ -159,7 +158,7 @@ BackgroundSoundsRepositoryImpl backgroundSoundsRepository(
BackgroundSoundsRepositoryRef ref,
) {
return BackgroundSoundsRepositoryImpl(
client: ref.watch(dioClientProvider),
client: DioApiService(),
ref: ref,
);
}

View File

@ -5,7 +5,6 @@ import 'dart:io';
import 'package:Medito/constants/strings/shared_preference_constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:Medito/utils/utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
@ -115,7 +114,7 @@ class DownloaderRepositoryImpl extends DownloaderRepository {
@riverpod
DownloaderRepositoryImpl downloaderRepository(DownloaderRepositoryRef ref) {
return DownloaderRepositoryImpl(
client: ref.watch(dioClientProvider),
client: DioApiService(),
ref: ref,
);
}

View File

@ -1,6 +1,5 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'events_repository.g.dart';
@ -35,5 +34,5 @@ class EventsRepositoryImpl extends EventsRepository {
@riverpod
EventsRepository eventsRepository(EventsRepositoryRef ref) {
return EventsRepositoryImpl(client: ref.watch(dioClientProvider));
return EventsRepositoryImpl(client: DioApiService());
}

View File

@ -1,7 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'explore_repository.g.dart';
@ -36,6 +35,6 @@ class ExploreRepositoryImpl extends ExploreRepository {
}
@riverpod
ExploreRepositoryImpl exploreRepository(ref) {
return ExploreRepositoryImpl(client: ref.watch(dioClientProvider));
ExploreRepositoryImpl exploreRepository(ExploreRepositoryRef ref) {
return ExploreRepositoryImpl(client: DioApiService());
}

View File

@ -2,7 +2,6 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -114,5 +113,5 @@ class HomeRepositoryImpl extends HomeRepository {
@riverpod
HomeRepositoryImpl homeRepository(HomeRepositoryRef ref) {
return HomeRepositoryImpl(ref: ref, client: ref.watch(dioClientProvider));
return HomeRepositoryImpl(ref: ref, client: DioApiService());
}

View File

@ -1,7 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'me_repository.g.dart';
@ -25,5 +24,5 @@ class MeRepositoryImpl extends MeRepository {
@riverpod
MeRepository meRepository(MeRepositoryRef ref) {
return MeRepositoryImpl(client: ref.watch(dioClientProvider));
return MeRepositoryImpl(client: DioApiService());
}

View File

@ -1,7 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'packs_repository.g.dart';
@ -35,5 +34,5 @@ class PackRepositoryImpl extends PacksRepository {
@riverpod
PackRepositoryImpl packRepository(PackRepositoryRef ref) {
return PackRepositoryImpl(client: ref.watch(dioClientProvider));
return PackRepositoryImpl(client: DioApiService());
}

View File

@ -3,9 +3,9 @@ export 'background_sounds/background_sounds_repository.dart';
export 'device_and_app_info/device_and_app_info_repository.dart';
export 'downloader/downloader_repository.dart';
export 'events/events_repository.dart';
export 'explore/explore_repository.dart';
export 'home/home_repository.dart';
export 'me/me_repository.dart';
export 'pack/packs_repository.dart';
export 'explore/explore_repository.dart';
export 'stats/stats_repository.dart';
export 'track/track_repository.dart';

View File

@ -1,7 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:Medito/utils/stats_utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@ -98,6 +97,6 @@ class StatsRepositoryImpl extends StatsRepository {
}
@riverpod
StatsRepository statsRepository(StatsRepositoryRef ref) {
return StatsRepositoryImpl(client: ref.watch(dioClientProvider));
StatsRepository statsRepository(StatsRepositoryRef _) {
return StatsRepositoryImpl(client: DioApiService());
}

View File

@ -4,7 +4,6 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:Medito/services/network/dio_client_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -97,5 +96,5 @@ class TrackRepositoryImpl extends TrackRepository {
@riverpod
TrackRepository trackRepository(TrackRepositoryRef ref) {
return TrackRepositoryImpl(ref: ref, client: ref.watch(dioClientProvider));
return TrackRepositoryImpl(ref: ref, client: DioApiService());
}

View File

@ -12,13 +12,13 @@ import 'package:Medito/views/background_sound/background_sound_view.dart';
import 'package:Medito/views/bottom_navigation/bottom_navigation_bar_view.dart';
import 'package:Medito/views/downloads/downloads_view.dart';
import 'package:Medito/views/end_screen/end_screen_view.dart';
import 'package:Medito/views/explore/explore_view.dart';
import 'package:Medito/views/home/home_view.dart';
import 'package:Medito/views/missing_route/missing_route_view.dart';
import 'package:Medito/views/notifications/notification_permission_view.dart';
import 'package:Medito/views/pack/pack_view.dart';
import 'package:Medito/views/player/player_view.dart';
import 'package:Medito/views/root/root_page_view.dart';
import 'package:Medito/views/explore/explore_view.dart';
import 'package:Medito/views/splash_view.dart';
import 'package:Medito/views/track/track_view.dart';
import 'package:Medito/widgets/widgets.dart';

View File

@ -1,10 +1,72 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../constants/http/http_constants.dart';
// ignore: avoid_dynamic_calls
class DioApiService {
Dio dio;
static final DioApiService _instance = DioApiService._internal();
late Dio dio;
DioApiService({required this.dio});
factory DioApiService() {
return _instance;
}
// Private constructor
DioApiService._internal() {
dio = Dio();
dio.options = BaseOptions(
connectTimeout: Duration(milliseconds: 30000),
baseUrl: HTTPConstants.BASE_URL,
headers: {
HttpHeaders.accessControlAllowOriginHeader: '*',
HttpHeaders.accessControlAllowHeadersHeader:
'Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale',
HttpHeaders.accessControlAllowCredentialsHeader: 'true',
HttpHeaders.accessControlAllowMethodsHeader: 'POST, OPTIONS, HEAD, GET',
HttpHeaders.contentTypeHeader: ContentType.json.value,
HttpHeaders.refererHeader: 'no-referrer-when-downgrade',
HttpHeaders.acceptHeader: '*/*',
},
);
if (kDebugMode) {
dio.interceptors.add(LogInterceptor(
request: true,
responseBody: true,
requestBody: true,
error: true,
));
}
dio.interceptors.add(
InterceptorsWrapper(onError: (e, handler) => _onError(e, handler)),
);
}
Future<void> _onError(
DioException err,
ErrorInterceptorHandler handler,
) async {
await _captureException(err);
handler.reject(err);
}
Future<void> _captureException(
DioException err,
) async {
await Sentry.captureException(
{
'error': err.toString(),
'endpoint': err.requestOptions.path.toString(),
'response': err.response.toString(),
'serverMessage': err.message.toString(),
},
stackTrace: err.stackTrace,
);
}
// ignore: avoid-dynamic
Future<dynamic> getRequest(

View File

@ -1,74 +1,14 @@
import 'dart:async';
import 'dart:io';
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/services/network/dio_api_service.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
final dioClientProvider = Provider<DioApiService>((ref) {
var dio = Dio();
dio.options = BaseOptions(
connectTimeout: Duration(milliseconds: 30000),
baseUrl: HTTPConstants.BASE_URL,
headers: {
HttpHeaders.accessControlAllowOriginHeader: '*',
HttpHeaders.accessControlAllowHeadersHeader:
'Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale',
HttpHeaders.accessControlAllowCredentialsHeader: 'true',
HttpHeaders.accessControlAllowMethodsHeader: 'POST, OPTIONS, HEAD, GET',
HttpHeaders.contentTypeHeader: ContentType.json.value,
HttpHeaders.refererHeader: 'no-referrer-when-downgrade',
HttpHeaders.acceptHeader: '*/*',
},
);
if (kDebugMode) {
dio.interceptors.add(LogInterceptor(
request: true,
responseBody: true,
requestBody: true,
error: true,
));
}
dio.interceptors.add(
InterceptorsWrapper(onError: (e, handler) => _onError(e, handler, ref)),
);
var dioApiService = DioApiService(dio: dio);
return dioApiService;
});
Future<void> _onError(
DioException err,
ErrorInterceptorHandler handler,
Ref _,
) async {
await _captureException(err);
handler.reject(err);
}
Future<void> _captureException(
DioException err,
) async {
await Sentry.captureException(
{
'error': err.toString(),
'endpoint': err.requestOptions.path.toString(),
'response': err.response.toString(),
'serverMessage': err.message.toString(),
},
stackTrace: err.stackTrace,
);
}
var assignDioHeadersProvider = FutureProvider<void>((ref) async {
var auth = ref.read(authProvider);
var deviceInfo = await ref.read(deviceAndAppInfoProvider.future);
var headers = ref.read(dioClientProvider).dio.options.headers;
var headers = DioApiService().dio.options.headers;
var user = auth.userResponse.body as UserTokenModel;

View File

@ -80,7 +80,7 @@ Future<bool> updateMinuteCounter(int additionalSecs) async {
var prefs = await SharedPreferences.getInstance();
var current = await _getSecondsListened();
var plusOne = current + (additionalSecs);
var plusOne = current + additionalSecs;
await prefs.setInt(SharedPreferenceConstants.secsListened, plusOne);
return true;

View File

@ -110,8 +110,6 @@ Future<void> launchEmailSubmission(
if (await canLaunchUrl(params)) {
await launchUrl(params);
} else {
var url = params.toString();
}
}

View File

@ -0,0 +1,33 @@
import 'package:workmanager/workmanager.dart';
import '../constants/types/type_constants.dart';
import '../models/events/audio_completed/audio_completed_model.dart';
import '../models/events/events_model.dart';
import '../repositories/events/events_repository.dart';
import '../services/network/dio_api_service.dart';
const audioCompletedTaskKey = 'com.AVFoundation.medito.audioCompletedTask';
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
switch (task) {
case audioCompletedTaskKey:
var audio = AudioCompletedModel(
audioFileId: inputData?[TypeConstants.fileIdKey],
trackId: inputData?[TypeConstants.trackIdKey],
updateStats: true,
);
var event = EventsModel(
name: EventTypes.audioCompleted,
payload: audio.toJson(),
);
var eventsRpo = EventsRepositoryImpl(client: DioApiService());
if (inputData != null) eventsRpo.trackEvent(event.toJson());
break;
}
return Future.value(true);
});
}

View File

@ -13,6 +13,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
import '../../providers/me/me_provider.dart';
import '../../utils/utils.dart';
class JoinVerifyOTPView extends ConsumerStatefulWidget {

View File

@ -5,6 +5,7 @@ import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/background_sounds/background_sounds_notifier.dart';
import 'widgets/sound_listtile_widget.dart';
import 'widgets/volume_slider_widget.dart';
@ -23,36 +24,6 @@ class _BackgroundSoundViewState extends ConsumerState<BackgroundSoundView> {
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) {
setInitStateValues();
});
}
@override
void deactivate() {
final _audioPlayerNotifier = ref.read(audioPlayerNotifierProvider);
if (!_audioPlayerNotifier.trackAudioPlayer.playerState.playing) {
_audioPlayerNotifier.stopBackgroundSound();
}
super.deactivate();
}
void setInitStateValues() {
final _provider = ref.read(backgroundSoundsNotifierProvider);
final _audioPlayerNotifier = ref.read(audioPlayerNotifierProvider);
if (!_audioPlayerNotifier.backgroundSoundAudioPlayer.playerState.playing) {
_provider.getBackgroundSoundFromPref();
if (_provider.selectedBgSound != null &&
_provider.selectedBgSound?.title != StringConstants.none) {
_audioPlayerNotifier.setBackgroundAudio(
_provider.selectedBgSound!,
);
_audioPlayerNotifier.playBackgroundSound();
}
_provider.getVolumeFromPref();
_audioPlayerNotifier.setBackgroundSoundVolume(_provider.volume);
}
}
void _scrollListener() {
@ -151,8 +122,7 @@ class _BackgroundSoundViewState extends ConsumerState<BackgroundSoundView> {
))
.toList(),
),
height16,
height16,
height32,
]),
),
],

View File

@ -1,9 +1,9 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import '../../../providers/background_sounds/background_sounds_notifier.dart';
class SoundListTileWidget extends ConsumerWidget {
const SoundListTileWidget({required this.sound}) : super();
@ -12,7 +12,6 @@ class SoundListTileWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bgSoundNotifierProvider = ref.watch(backgroundSoundsNotifierProvider);
final audioPlayerNotifier = ref.watch(audioPlayerNotifierProvider);
var selectedSoundId = bgSoundNotifierProvider.selectedBgSound?.id;
var id = selectedSoundId ?? '0';
var isSelected = id == sound.id;
@ -20,7 +19,6 @@ class SoundListTileWidget extends ConsumerWidget {
return InkWell(
onTap: () => _handleItemTap(
bgSoundNotifierProvider,
audioPlayerNotifier,
),
child: Container(
decoration: BoxDecoration(
@ -66,17 +64,10 @@ class SoundListTileWidget extends ConsumerWidget {
void _handleItemTap(
BackgroundSoundsNotifier bgSoundNotifierProvider,
AudioPlayerNotifier audioPlayerNotifier,
) {
if (sound.title == StringConstants.none) {
bgSoundNotifierProvider.handleOnChangeSound(sound);
audioPlayerNotifier.stopBackgroundSound();
audioPlayerNotifier.backgroundSoundAudioPlayer.dispose();
audioPlayerNotifier.backgroundSoundAudioPlayer = AudioPlayer();
} else {
audioPlayerNotifier.setBackgroundAudio(sound);
audioPlayerNotifier.playBackgroundSound();
bgSoundNotifierProvider.handleOnChangeSound(sound);
bgSoundNotifierProvider.stopBackgroundSound();
}
bgSoundNotifierProvider.handleOnChangeSound(sound);
}
}

View File

@ -1,8 +1,8 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../providers/background_sounds/background_sounds_notifier.dart';
import 'background_sound_volume_track_shape_widget.dart';
class VolumeSliderWidget extends ConsumerWidget {
@ -11,14 +11,13 @@ class VolumeSliderWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bgSoundNotifierProvider = ref.watch(backgroundSoundsNotifierProvider);
final audioPlayerNotifier = ref.watch(audioPlayerNotifierProvider);
var currentVolume = bgSoundNotifierProvider.volume;
final currentVolume = bgSoundNotifierProvider.volume;
return SliderTheme(
data: SliderThemeData(
trackShape: BackgroundSoundVolumeTrackShapeWidget(
leadingTitle: StringConstants.volume,
tralingText: currentVolume.toString().split('.').first + '%',
tralingText: currentVolume.toString().split('.').first + '%',
),
overlayShape: RoundSliderOverlayShape(overlayRadius: 0.0),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 0.0),
@ -33,7 +32,6 @@ class VolumeSliderWidget extends ConsumerWidget {
inactiveColor: ColorConstants.greyIsTheNewGrey,
onChanged: (double newValue) {
bgSoundNotifierProvider.handleOnChangeVolume(newValue);
audioPlayerNotifier.setBackgroundSoundVolume(newValue);
},
semanticFormatterCallback: (double newValue) {
return '${newValue.round()} ';

View File

@ -1,7 +1,7 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/views/home/home_view.dart';
import 'package:Medito/views/explore/explore_view.dart';
import 'package:Medito/views/home/home_view.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

View File

@ -13,6 +13,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../providers/background_sounds/background_sounds_notifier.dart';
import '../../providers/home/home_provider.dart';
class DownloadsView extends ConsumerStatefulWidget {
@override
ConsumerState<DownloadsView> createState() => _DownloadsViewState();
@ -148,8 +151,8 @@ class _DownloadsViewState extends ConsumerState<DownloadsView>
WidgetRef ref,
TrackModel trackModel,
) async {
final audioProvider = ref.read(audioPlayerNotifierProvider);
await audioProvider.stop();
final bgSoundNotifier = ref.read(backgroundSoundsNotifierProvider);
bgSoundNotifier.getVolumeFromPref();
await ref.read(playerProvider.notifier).loadSelectedTrack(
trackModel: trackModel,
file: trackModel.audio.first.files.first,

View File

@ -2,11 +2,12 @@ import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/widgets/headers/medito_app_bar_small.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'widgets/donation_widget.dart';
import 'widgets/feedback_widget.dart';
class EndScreenView extends StatelessWidget {
class EndScreenView extends ConsumerStatefulWidget {
final List<EndScreenModel> endScreenModel;
const EndScreenView({
@ -14,6 +15,12 @@ class EndScreenView extends StatelessWidget {
required this.endScreenModel,
});
@override
ConsumerState<EndScreenView> createState() => _EndScreenViewState();
}
class _EndScreenViewState extends ConsumerState<EndScreenView> {
@override
Widget build(BuildContext context) {
return Scaffold(
@ -35,7 +42,7 @@ class EndScreenView extends StatelessWidget {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: padding16),
child: Column(
children: endScreenModel.map((e) {
children: widget.endScreenModel.map((e) {
if (e.name == TypeConstants.donationAskCard) {
return Padding(
padding: const EdgeInsets.only(bottom: 20),
@ -58,4 +65,5 @@ class EndScreenView extends StatelessWidget {
),
);
}
}

View File

@ -1,6 +1,5 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -18,33 +17,33 @@ class _FeedbackWidgetState extends ConsumerState<FeedbackWidget> {
bool isFeedbackAdded = false;
void _handleFeedbackPress(String feedback) async {
final audioProvider = ref.read(audioPlayerNotifierProvider);
var extras = audioProvider.mediaItem.value?.extras;
// final audioProvider = ref.read(audioPlayerNotifierProvider);
// var extras = audioProvider.mediaItem.value?.extras;
setState(() {
isLoading = true;
});
var payload = FeedbackTappedModel(
trackId: extras?[TypeConstants.trackIdKey] ?? '',
audioFileId: extras?[TypeConstants.fileIdKey] ?? '',
emoji: feedback,
);
var event = EventsModel(
name: EventTypes.trackFeedback,
payload: payload.toJson(),
);
try {
await ref.read(eventsProvider(event: event.toJson()).future);
setState(() {
isLoading = false;
isFeedbackAdded = true;
});
} catch (e) {
setState(() {
isLoading = false;
isFeedbackAdded = false;
});
}
// var payload = FeedbackTappedModel(
// trackId: extras?[TypeConstants.trackIdKey] ?? '',
// audioFileId: extras?[TypeConstants.fileIdKey] ?? '',
// emoji: feedback,
// );
// var event = EventsModel(
// name: EventTypes.trackFeedback,
// payload: payload.toJson(),
// );
// try {
// await ref.read(eventsProvider(event: event.toJson()).future);
// setState(() {
// isLoading = false;
// isFeedbackAdded = true;
// });
// } catch (e) {
// setState(() {
// isLoading = false;
// isFeedbackAdded = false;
// });
// }
}
@override

View File

@ -6,6 +6,8 @@ import 'package:Medito/views/home/widgets/header_and_announcement_widget.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/home/home_provider.dart';
import 'widgets/editorial/editorial_widget.dart';
import 'widgets/quote/quote_widget.dart';
import 'widgets/shortcuts/shortcuts_widget.dart';

View File

@ -5,6 +5,7 @@ import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../../providers/me/me_provider.dart';
import '../share_btn/share_btn_widget.dart';
class DebugBottomSheetWidget extends ConsumerWidget {

View File

@ -1,13 +1,13 @@
import 'package:Medito/constants/colors/color_constants.dart';
import 'package:Medito/constants/styles/widget_styles.dart';
import 'package:Medito/models/home/editorial/editorial_model.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/routes/routes.dart';
import 'package:Medito/utils/utils.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../providers/home/home_provider.dart';
import '../animated_scale_widget.dart';
class EditorialWidget extends ConsumerWidget {

View File

@ -1,9 +1,9 @@
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../providers/home/home_provider.dart';
import 'announcement/announcement_widget.dart';
import 'header/home_header_widget.dart';

View File

@ -1,10 +1,11 @@
import 'package:Medito/constants/colors/color_constants.dart';
import 'package:Medito/constants/styles/widget_styles.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../providers/home/home_provider.dart';
class QuoteWidget extends ConsumerWidget {
const QuoteWidget({super.key});

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reorderables/reorderables.dart';
import '../../../../providers/home/home_provider.dart';
import '../animated_scale_widget.dart';
class ShortcutsItemsWidget extends ConsumerStatefulWidget {

View File

@ -1,8 +1,8 @@
import 'package:Medito/providers/providers.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../providers/home/home_provider.dart';
import 'shortcuts_items_widget.dart';
class ShortcutsWidget extends ConsumerStatefulWidget {

View File

@ -1,16 +1,18 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/widgets/headers/medito_app_bar_small.dart';
import 'package:Medito/widgets/widgets.dart';
import 'package:Medito/views/player/widgets/artist_title_widget.dart';
import 'package:Medito/views/player/widgets/bottom_actions/bottom_action_widget.dart';
import 'package:Medito/views/player/widgets/duration_indicator_widget.dart';
import 'package:Medito/views/player/widgets/overlay_cover_image_widget.dart';
import 'package:Medito/views/player/widgets/player_buttons/player_buttons_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'widgets/artist_title_widget.dart';
import 'widgets/bottom_actions/bottom_action_widget.dart';
import 'widgets/duration_indicator_widget.dart';
import 'widgets/overlay_cover_image_widget.dart';
import 'widgets/player_buttons/player_buttons_widget.dart';
import '../../constants/strings/route_constants.dart';
import '../../constants/strings/string_constants.dart';
import '../../providers/background_sounds/background_sounds_notifier.dart';
import '../../widgets/errors/medito_error_widget.dart';
import '../../widgets/headers/medito_app_bar_small.dart';
class PlayerView extends ConsumerStatefulWidget {
const PlayerView({
@ -21,11 +23,17 @@ class PlayerView extends ConsumerStatefulWidget {
ConsumerState<PlayerView> createState() => _PlayerViewState();
}
class _PlayerViewState extends ConsumerState<PlayerView>
with AutomaticKeepAliveClientMixin {
class _PlayerViewState extends ConsumerState<PlayerView> {
bool _endScreenOpened = false;
@override
Widget build(BuildContext context) {
super.build(context);
var playbackState = ref.watch(audioStateProvider);
if (playbackState.isCompleted && playbackState.position > 5000) {
_resetState();
_openEndScreen();
}
var currentlyPlayingTrack = ref.watch(playerProvider);
if (currentlyPlayingTrack == null) {
@ -34,9 +42,6 @@ class _PlayerViewState extends ConsumerState<PlayerView>
message: StringConstants.unableToLoadAudio,
);
}
var coverUrl = currentlyPlayingTrack.coverUrl;
var artist = currentlyPlayingTrack.artist;
var file = currentlyPlayingTrack.audio.first.files.first;
var size = MediaQuery.of(context).size.width;
@ -59,33 +64,46 @@ class _PlayerViewState extends ConsumerState<PlayerView>
child: Column(
children: [
SizedBox(height: spacerHeight20),
OverlayCoverImageWidget(imageUrl: coverUrl),
OverlayCoverImageWidget(imageUrl: playbackState.track.imageUrl),
SizedBox(height: spacerHeight48),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: ArtistTitleWidget(
trackTitle: currentlyPlayingTrack.title,
artistName: artist?.name,
artistUrlPath: artist?.path,
trackTitle: playbackState.track.title,
artistName: playbackState.track.artist,
artistUrlPath: playbackState.track.artistUrl,
isPlayerScreen: true,
),
),
DurationIndicatorWidget(
file: file,
trackId: currentlyPlayingTrack.id,
totalDuration: playbackState.duration,
currentPosition: playbackState.position,
onSeekEnd: (value) {
ref.read(playerProvider.notifier).seekToPosition(value);
},
),
SizedBox(height: spacerHeight24),
Transform.translate(
offset: Offset(0, -10),
child: PlayerButtonsWidget(
file: file,
trackModel: currentlyPlayingTrack,
isPlaying: playbackState.isPlaying,
onPlayPause: () =>
ref.read(playerProvider.notifier).playPause(),
onSkip10SecondsBackward: () => ref
.read(playerProvider.notifier)
.skip10SecondsBackward(),
onSkip10SecondsForward: () => ref
.read(playerProvider.notifier)
.skip10SecondsForward(),
),
),
SizedBox(height: spacerHeight24),
BottomActionWidget(
trackModel: currentlyPlayingTrack,
file: file,
onSpeedChanged: (speed) =>
ref.read(playerProvider.notifier).setSpeed(speed),
isBackgroundSoundSelected: _isBackgroundSoundSelected(),
),
SizedBox(height: 40),
],
@ -96,15 +114,40 @@ class _PlayerViewState extends ConsumerState<PlayerView>
);
}
Future<bool> _handleClose() async {
final audioProvider = ref.read(audioPlayerNotifierProvider);
await audioProvider.stop();
bool _isBackgroundSoundSelected() {
var bgSoundNotifier = ref.read(backgroundSoundsNotifierProvider);
return bgSoundNotifier.selectedBgSound != null &&
bgSoundNotifier.selectedBgSound?.title != StringConstants.none;
}
Future<bool> _handleClose() async {
_resetState();
context.pop();
return true;
}
@override
bool get wantKeepAlive => true;
void _resetState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(audioStateProvider.notifier).resetState();
ref.read(playerProvider.notifier).stop();
});
}
void _openEndScreen() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_endScreenOpened) {
var currentlyPlayingTrack = ref.read(playerProvider);
var endScreen = currentlyPlayingTrack?.endScreen;
if (endScreen != null) {
context.pushReplacement(
RouteConstants.endScreenPath,
extra: endScreen,
);
}
_endScreenOpened = true;
}
});
}
}

View File

@ -1,5 +1,6 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/routes/routes.dart';
import 'package:Medito/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:marquee/marquee.dart';
@ -17,7 +18,7 @@ class ArtistTitleWidget extends ConsumerWidget {
this.titleHeight = 35,
});
final String trackTitle;
final String? trackTitle;
final String? artistName, artistUrlPath;
final double trackTitleFontSize;
final double artistNameFontSize;
@ -31,16 +32,20 @@ class ArtistTitleWidget extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_title(context),
if (artistName != null) _subtitle(context),
if (artistName.isNotNullAndNotEmpty()) _subtitle(context),
],
);
}
Widget _title(BuildContext context) {
if (trackTitle?.isEmpty == true || trackTitle == null) {
return SizedBox(height: titleHeight);
}
return SizedBox(
height: titleHeight,
child: Marquee(
text: trackTitle,
text: trackTitle ?? 'Title',
style: Theme.of(context).primaryTextTheme.headlineMedium?.copyWith(
fontFamily: SourceSerif,
color: ColorConstants.walterWhite,

View File

@ -1,10 +1,10 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/views/player/widgets/bottom_actions/widgets/audio_download_widget.dart';
import 'package:Medito/views/player/widgets/bottom_actions/widgets/audio_speed_widget.dart';
import 'package:Medito/views/player/widgets/bottom_actions/widgets/bg_sound_widget.dart';
import 'package:flutter/material.dart';
import 'widgets/audio_download_widget.dart';
import 'widgets/bg_sound_widget.dart';
import '../../../../models/track/track_model.dart';
import 'widgets/mark_favourite_widget.dart';
class BottomActionWidget extends StatelessWidget {
@ -12,10 +12,14 @@ class BottomActionWidget extends StatelessWidget {
super.key,
required this.trackModel,
required this.file,
required this.isBackgroundSoundSelected,
required this.onSpeedChanged,
});
final bool isBackgroundSoundSelected;
final TrackModel trackModel;
final TrackFilesModel file;
final Function(double) onSpeedChanged;
@override
Widget build(BuildContext context) {
@ -35,18 +39,16 @@ class BottomActionWidget extends StatelessWidget {
if (trackModel.hasBackgroundSound)
Expanded(
child: BgSoundWidget(
isBackgroundSoundSelected: isBackgroundSoundSelected,
trackModel: trackModel,
file: file,
),
),
if (trackModel.hasBackgroundSound) width8,
Expanded(child: AudioSpeedWidget()),
Expanded(child: AudioSpeedWidget(onSpeedChanged: onSpeedChanged)),
width8,
Expanded(
child: MarkFavouriteWidget(
trackModel: trackModel,
file: file,
),
child: MarkFavouriteWidget(trackModel: trackModel, file: file),
),
],
),

View File

@ -32,7 +32,7 @@ class AudioDownloadWidget extends ConsumerWidget {
),
);
} else if (downloadAudioProvider.audioDownloadState[downloadFileKey] ==
AUDIO_DOWNLOAD_STATE.DOWNLOADIING) {
AUDIO_DOWNLOAD_STATE.DOWNLOADING) {
return showDownloadProgress(downloadAudioProvider, downloadFileKey);
} else {
return IconButton(

View File

@ -1,45 +1,44 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AudioSpeedWidget extends ConsumerStatefulWidget {
const AudioSpeedWidget({super.key});
const AudioSpeedWidget({super.key, required this.onSpeedChanged});
final Function(double) onSpeedChanged;
@override
ConsumerState<AudioSpeedWidget> createState() => _AudioSpeedComponentState();
}
class _AudioSpeedComponentState extends ConsumerState<AudioSpeedWidget> {
@override
void initState() {
super.initState();
final _provider = ref.read(audioSpeedProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
_provider.getAudioTrackSpeedFromPref();
ref
.read(audioPlayerNotifierProvider)
.setTrackAudioSpeed(_provider.audioSpeedModel.speed);
});
}
String _label = 'x1';
@override
Widget build(BuildContext context) {
final _provider = ref.watch(audioSpeedProvider);
var audioSpeedModel = _provider.audioSpeedModel;
var textColor = audioSpeedModel.label != StringConstants.x1
var textColor = _label != StringConstants.x1
? ColorConstants.lightPurple
: ColorConstants.walterWhite;
ColorConstants.walterWhite;
return GestureDetector(
onTap: () {
_provider.setAudioTrackSpeed();
ref
.read(audioPlayerNotifierProvider)
.setTrackAudioSpeed(_provider.audioSpeedModel.speed);
if (_label == StringConstants.x06) {
_label = StringConstants.x07;
} else if (_label == StringConstants.x07) {
_label = StringConstants.x08;
} else if (_label == StringConstants.x08) {
_label = StringConstants.x09;
} else if (_label == StringConstants.x09) {
_label = StringConstants.x1;
} else if (_label == StringConstants.x1) {
_label = StringConstants.x06;
}
widget.onSpeedChanged(_label.toDouble);
},
child: Text(
audioSpeedModel.label,
_label,
style: Theme.of(context)
.textTheme
.bodyMedium
@ -49,3 +48,7 @@ class _AudioSpeedComponentState extends ConsumerState<AudioSpeedWidget> {
);
}
}
extension on String {
double get toDouble => double.parse(substring(1));
}

View File

@ -1,27 +1,24 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../../models/track/track_model.dart';
class BgSoundWidget extends ConsumerWidget {
const BgSoundWidget({
super.key,
required this.trackModel,
required this.file,
required this.isBackgroundSoundSelected,
});
final TrackModel trackModel;
final TrackFilesModel file;
final bool isBackgroundSoundSelected;
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedBgSound =
ref.watch(backgroundSoundsNotifierProvider).selectedBgSound;
var checkIsBgSoundSelected = selectedBgSound != null &&
selectedBgSound.title != StringConstants.none;
return IconButton(
onPressed: () {
context.push(
@ -31,7 +28,7 @@ class BgSoundWidget extends ConsumerWidget {
},
icon: Icon(
Icons.music_note,
color: checkIsBgSoundSelected
color: isBackgroundSoundSelected
? ColorConstants.lightPurple
: ColorConstants.walterWhite,
),

View File

@ -1,8 +1,4 @@
import 'dart:math';
import 'package:Medito/constants/constants.dart';
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:Medito/utils/duration_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -10,12 +6,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
class DurationIndicatorWidget extends ConsumerStatefulWidget {
const DurationIndicatorWidget({
super.key,
required this.file,
required this.trackId,
required this.totalDuration,
required this.currentPosition,
required this.onSeekEnd,
});
final TrackFilesModel file;
final String trackId;
final int totalDuration;
final int currentPosition;
final void Function(int) onSeekEnd;
@override
ConsumerState<DurationIndicatorWidget> createState() =>
@ -24,61 +22,23 @@ class DurationIndicatorWidget extends ConsumerStatefulWidget {
class _DurationIndicatorWidgetState
extends ConsumerState<DurationIndicatorWidget> {
final _minSeconds = 0.0;
double? _dragSeekbarValue;
double _maxDuration = 0.0;
bool _draggingSeekbar = false;
bool _isSeekbarBeingDragged = false;
double _dragSeekbarValue = 0;
@override
Widget build(BuildContext context) {
final audioPositionAndPlayerState =
ref.watch(audioPositionAndPlayerStateProvider);
final audioPlayerNotifier = ref.watch(audioPlayerNotifierProvider);
_maxDuration = widget.file.duration.toDouble();
return audioPositionAndPlayerState.when(
data: (data) {
final value = min(
_dragSeekbarValue ?? data.position.inMilliseconds,
_maxDuration,
);
if (_dragSeekbarValue != null && !_draggingSeekbar) {
_dragSeekbarValue = null;
}
audioPlayerNotifier.handleFadeAtEnd(
data.position,
Duration(milliseconds: _maxDuration.round()),
);
return _durationBar(context, ref, value, data);
},
error: (error, stackTrace) => SizedBox(),
loading: () => SizedBox(),
return _durationBar(
context,
// 99 is to avoid rounding errors which would result in the current
// position being larger that the total duration
widget.totalDuration / 99,
widget.currentPosition / 100,
);
}
void onChangeEnd(
WidgetRef ref,
PositionAndPlayerStateState data,
double val,
) {
ref.read(slideAudioPositionProvider(
duration: val.round(),
));
_draggingSeekbar = false;
ref.read(audioPlayerNotifierProvider).handleFadeAtEnd(
data.position,
Duration(milliseconds: _maxDuration.round()),
);
}
Padding _durationBar(
BuildContext context,
WidgetRef ref,
num currentDuration,
PositionAndPlayerStateState data,
) {
Padding _durationBar(BuildContext context,
double totalDuration,
double currentDuration,) {
return Padding(
padding: const EdgeInsets.only(left: 32, right: 32, top: 0, bottom: 0),
child: Column(
@ -92,28 +52,27 @@ class _DurationIndicatorWidgetState
),
),
child: Slider(
min: _minSeconds,
min: 0.0,
max: totalDuration > 0.0 ? totalDuration : 1.0,
activeColor: ColorConstants.walterWhite,
inactiveColor: ColorConstants.onyx,
max: _maxDuration,
value: currentDuration.toDouble(),
value:
_isSeekbarBeingDragged ? _dragSeekbarValue : currentDuration,
onChanged: (val) {
if (!_draggingSeekbar) {
_draggingSeekbar = true;
if (!_isSeekbarBeingDragged) {
_isSeekbarBeingDragged = true;
}
setState(() {
_dragSeekbarValue = val;
});
},
onChangeEnd: (val) => onChangeEnd(ref, data, val),
onChangeEnd: _onChangeEnd,
),
),
Transform.translate(
offset: Offset(0, -14),
child: _durationLabels(
context,
currentDuration.round(),
_maxDuration.round(),
),
),
],
@ -121,37 +80,42 @@ class _DurationIndicatorWidgetState
);
}
Row _durationLabels(
BuildContext context,
int position,
int duration,
) {
void _onChangeEnd(double val) {
_isSeekbarBeingDragged = false;
widget.onSeekEnd((val * 100).toInt());
}
Row _durationLabels(BuildContext context,) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_durationLabel(
context,
Duration(milliseconds: position).toMinutesSeconds(),
Duration(milliseconds: widget.currentPosition.round())
.toMinutesSeconds(),
),
_durationLabel(
context,
Duration(milliseconds: duration).toMinutesSeconds(),
Duration(milliseconds: widget.totalDuration.round())
.toMinutesSeconds(),
),
],
);
}
Text _durationLabel(
BuildContext context,
String label,
) {
Text _durationLabel(BuildContext context,
String label,) {
return Text(
label,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: ColorConstants.graphite,
fontFamily: DmMono,
fontSize: 12,
),
style: Theme
.of(context)
.textTheme
.titleSmall
?.copyWith(
color: ColorConstants.graphite,
fontFamily: DmMono,
fontSize: 12,
),
);
}
}
@ -182,19 +146,18 @@ class CustomTrackShape extends RoundedRectSliderTrackShape {
}
@override
void paint(
PaintingContext context,
Offset offset, {
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required TextDirection textDirection,
required Offset thumbCenter,
Offset? secondaryOffset,
bool isDiscrete = false,
bool isEnabled = false,
double additionalActiveTrackHeight = 0,
}) {
void paint(PaintingContext context,
Offset offset, {
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required TextDirection textDirection,
required Offset thumbCenter,
Offset? secondaryOffset,
bool isDiscrete = false,
bool isEnabled = false,
double additionalActiveTrackHeight = 0,
}) {
super.paint(
context,
offset,

View File

@ -1,51 +1,40 @@
import 'package:Medito/constants/constants.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PlayPauseButtonWidget extends ConsumerWidget {
const PlayPauseButtonWidget({super.key, this.iconSize = 72});
const PlayPauseButtonWidget({
super.key,
this.iconSize = 72,
required this.isPlaying, required this.onPlayPause,
});
final double iconSize;
final bool isPlaying;
final Function() onPlayPause;
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(audioPlayerNotifierProvider);
return StreamBuilder<bool>(
stream: provider.playbackState.map((state) => state.playing).distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
return InkWell(
onTap: () => _handleTap(ref, playing),
borderRadius: BorderRadius.circular(iconSize / 2),
child: AnimatedCrossFade(
firstChild: Icon(
Icons.play_circle_fill,
size: iconSize,
color: ColorConstants.walterWhite,
),
secondChild: Icon(
Icons.pause_circle_outlined,
size: iconSize,
color: ColorConstants.walterWhite,
),
crossFadeState:
playing ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: Duration(milliseconds: 500),
),
);
},
return InkWell(
onTap: onPlayPause,
borderRadius: BorderRadius.circular(iconSize / 2),
child: AnimatedCrossFade(
firstChild: Icon(
Icons.play_circle_fill,
size: iconSize,
color: ColorConstants.walterWhite,
),
secondChild: Icon(
Icons.pause_circle_outlined,
size: iconSize,
color: ColorConstants.walterWhite,
),
crossFadeState:
isPlaying ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: Duration(milliseconds: 250),
),
);
}
void _handleTap(WidgetRef ref, bool isPlaying) {
final provider = ref.read(audioPlayerNotifierProvider);
if (isPlaying) {
provider.pause();
} else {
provider.play();
}
}
}

View File

@ -1,5 +1,3 @@
import 'package:Medito/models/models.dart';
import 'package:Medito/providers/providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -7,13 +5,17 @@ import 'play_pause_button_widget.dart';
class PlayerButtonsWidget extends ConsumerWidget {
const PlayerButtonsWidget({
required this.onSkip10SecondsBackward,
required this.onSkip10SecondsForward,
required this.isPlaying,
super.key,
required this.trackModel,
required this.file,
required this.onPlayPause,
});
final TrackFilesModel file;
final TrackModel trackModel;
final Function() onSkip10SecondsBackward;
final Function() onSkip10SecondsForward;
final bool isPlaying;
final Function() onPlayPause;
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -21,19 +23,21 @@ class PlayerButtonsWidget extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_rewindButton(ref),
_rewindButton(),
SizedBox(width: 32),
PlayPauseButtonWidget(),
PlayPauseButtonWidget(
isPlaying: isPlaying,
onPlayPause: onPlayPause,
),
SizedBox(width: 32),
_forwardButton(ref),
_forwardButton(),
],
);
}
IconButton _rewindButton(WidgetRef ref) {
IconButton _rewindButton() {
return IconButton(
onPressed: () =>
_handleForwardAndRewind(ref, SKIP_AUDIO.SKIP_BACKWARD_10),
onPressed: onSkip10SecondsBackward,
icon: Icon(
Icons.replay_10_rounded,
size: 40,
@ -41,30 +45,13 @@ class PlayerButtonsWidget extends ConsumerWidget {
);
}
IconButton _forwardButton(WidgetRef ref) {
IconButton _forwardButton() {
return IconButton(
onPressed: () => _handleForwardAndRewind(ref, SKIP_AUDIO.SKIP_FORWARD_10),
onPressed: onSkip10SecondsForward,
icon: Icon(
Icons.forward_10_rounded,
size: 40,
),
);
}
void _handleForwardAndRewind(WidgetRef ref, SKIP_AUDIO skip) {
var audioProvider = ref.read(audioPlayerNotifierProvider);
final audioPositionAndPlayerState =
ref.read(audioPositionAndPlayerStateProvider);
var maxDuration = audioProvider.mediaItem.value?.duration ?? Duration();
ref.read(
skipAudioProvider(skip: skip),
);
audioProvider.handleFadeAtEnd(
audioPositionAndPlayerState.value?.position ?? Duration(),
maxDuration,
);
}
}

View File

@ -6,6 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import '../providers/auth/initialize_user_provider.dart';
class SplashView extends ConsumerStatefulWidget {
const SplashView({super.key});

View File

@ -26,6 +26,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../providers/background_sounds/background_sounds_notifier.dart';
class TrackView extends ConsumerStatefulWidget {
final String id;
@ -90,6 +92,8 @@ class _TrackViewState extends ConsumerState<TrackView>
TrackModel trackModel,
TrackFilesModel file,
) async {
final bgSoundNotifier = ref.read(backgroundSoundsNotifierProvider);
bgSoundNotifier.getVolumeFromPref();
await ref.read(playerProvider.notifier).loadSelectedTrack(
trackModel: trackModel,
file: file,

View File

@ -51,7 +51,7 @@ class MeditoAppBarSmall extends StatelessWidget implements PreferredSizeWidget {
Widget getTitleWidget(BuildContext context) {
return titleWidget == null
? Text(title ?? '', style: Theme.of(context).textTheme.displayLarge)
: Row(children: [titleWidget ?? Container()]);
: Row(children: [titleWidget!]);
}
@override

View File

@ -9,14 +9,14 @@ export 'markdown_widget.dart';
export 'network_image_widget.dart';
export 'pack_card_widget.dart';
export 'shimmers/background_sounds_shimmer_widget.dart';
export 'shimmers/explore_initial_page_shimmer_widget.dart';
export 'shimmers/explore_result_shimmer_widget.dart';
export 'shimmers/folder_shimmer_widget.dart';
export 'shimmers/home/editorial_shimmer_widget.dart';
export 'shimmers/home/header_and_announcement_shimmer_widget.dart';
export 'shimmers/home/home_shimmer_widget.dart';
export 'shimmers/home/quote_shimmer_widget.dart';
export 'shimmers/home/shortcuts_shimmer_widget.dart';
export 'shimmers/home/tiles_shimmer_widget.dart';
export 'shimmers/home/editorial_shimmer_widget.dart';
export 'shimmers/explore_initial_page_shimmer_widget.dart';
export 'shimmers/explore_result_shimmer_widget.dart';
export 'shimmers/track_shimmer_widget.dart';
export 'snackbar_widget.dart';

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/device_info_plus-9.1.1/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/package_info_plus-4.2.0/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/sentry_flutter-7.13.2/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/share_plus-7.2.1/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.2/

View File

@ -0,0 +1 @@
/Users/mike/.pub-cache/hosted/pub.dev/url_launcher_linux-3.1.0/

View File

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@ -0,0 +1,25 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
sentry_flutter
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@ -0,0 +1,34 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import connectivity_macos
import device_info_plus
import firebase_core
import firebase_messaging
import flutter_local_notifications
import package_info_plus
import path_provider_foundation
import sentry_flutter
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@ -0,0 +1,11 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/mike/flutter
FLUTTER_APPLICATION_PATH=/Users/mike/Projects/meditoapp
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=3.0.11
FLUTTER_BUILD_NUMBER=30011
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View File

@ -0,0 +1,12 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/mike/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/mike/Projects/meditoapp"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=3.0.11"
export "FLUTTER_BUILD_NUMBER=30011"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

43
macos/Podfile Normal file
View File

@ -0,0 +1,43 @@
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

121
pigeon_conf.dart Normal file
View File

@ -0,0 +1,121 @@
import 'package:pigeon/pigeon.dart';
// to build the classes: flutter pub run pigeon --input pigeon_conf.dart
// #docregion config
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/audio_pigeon.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/app/src/main/kotlin/com/meditofoundation/medito/AudioPigeon.g.kt',
kotlinOptions: KotlinOptions(),
// swiftOut: 'ios/Runner/Messages.g.swift',
// swiftOptions: SwiftOptions(),
))
// #enddocregion config
// #docregion host-definitions
//ignore:prefer-match-file-name
class AudioData {
AudioData({
required this.url,
required this.track,
});
String url;
Track track;
}
@HostApi()
abstract class MeditoAudioServiceApi {
bool playAudio(AudioData audioData);
void playPauseAudio();
void stopAudio();
void setSpeed(double speed);
void seekToPosition(int position);
void skip10SecondsForward();
void skip10SecondsBackward();
void setBackgroundSound(String? uri);
void setBackgroundSoundVolume(double volume);
void stopBackgroundSound();
void playBackgroundSound();
}
// #enddocregion host-definitions
// #docregion flutter-definitions
class PlaybackState {
bool isPlaying;
bool isBuffering;
bool isSeeking;
bool isCompleted;
int position;
int duration;
Speed speed;
int volume;
Track track;
BackgroundSound? backgroundSound;
PlaybackState({
required this.isPlaying,
required this.isBuffering,
required this.isSeeking,
required this.isCompleted,
required this.position,
required this.duration,
required this.speed,
required this.volume,
required this.track,
this.backgroundSound,
});
}
class BackgroundSound {
String? uri;
String title;
BackgroundSound({
required this.uri,
required this.title,
});
}
class Speed {
double speed;
Speed({required this.speed});
}
class Track {
String title;
String description;
String imageUrl;
String? artist;
String? artistUrl;
Track({
required this.title,
required this.description,
required this.imageUrl,
required this.artist,
this.artistUrl,
});
}
@FlutterApi()
abstract class MeditoAudioServiceCallbackApi {
void updatePlaybackState(PlaybackState state);
}
// #enddocregion flutter-definitions

View File

@ -73,38 +73,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
audio_service:
dependency: "direct main"
description:
name: audio_service
sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4
url: "https://pub.dev"
source: hosted
version: "0.18.12"
audio_service_platform_interface:
dependency: transitive
description:
name: audio_service_platform_interface
sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
audio_service_web:
dependency: transitive
description:
name: audio_service_web
sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
audio_session:
dependency: "direct main"
description:
name: audio_session
sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f"
url: "https://pub.dev"
source: hosted
version: "0.1.18"
auto_size_text:
dependency: "direct main"
description:
@ -285,10 +253,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.17.2"
connectivity:
dependency: "direct main"
description:
@ -776,31 +744,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.7.1"
just_audio:
dependency: "direct main"
description:
path: just_audio
ref: "feature/treadmill"
resolved-ref: "0b8047c2b5ef29a69f189b622a83b1d263fe1abb"
url: "https://github.com/meditohq/just_audio.git"
source: git
version: "0.9.35"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
url: "https://pub.dev"
source: hosted
version: "4.2.2"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
url: "https://pub.dev"
source: hosted
version: "0.4.9"
list_counter:
dependency: transitive
description:
@ -837,18 +780,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.5.0"
meta:
dependency: transitive
description:
@ -1041,6 +984,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.4.0"
pigeon:
dependency: "direct dev"
description:
name: pigeon
sha256: "614d9926d887e8486b487de19d0ea082b7e91228f9e7e6ba86837daf5d6e71cb"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
pin_code_fields:
dependency: "direct main"
description:
@ -1338,10 +1289,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
sqflite:
dependency: transitive
description:
@ -1418,26 +1369,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4"
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
url: "https://pub.dev"
source: hosted
version: "1.24.1"
version: "1.24.3"
test_api:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.0"
test_core:
dependency: transitive
description:
name: test_core
sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93"
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.5.3"
timezone:
dependency: transitive
description:
@ -1466,10 +1417,10 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
url: "https://pub.dev"
source: hosted
version: "6.1.14"
version: "6.2.1"
url_launcher_android:
dependency: transitive
description:
@ -1514,10 +1465,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: ba140138558fcc3eead51a1c42e92a9fb074a1b1149ed3c73e66035b2ccd94f2
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
url: "https://pub.dev"
source: hosted
version: "2.0.19"
version: "2.2.0"
url_launcher_windows:
dependency: transitive
description:
@ -1566,6 +1517,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel:
dependency: transitive
description:
@ -1586,18 +1545,26 @@ packages:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
url: "https://pub.dev"
source: hosted
version: "5.0.9"
version: "5.1.1"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.1.2"
workmanager:
dependency: "direct main"
description:
name: workmanager
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
url: "https://pub.dev"
source: hosted
version: "0.5.2"
xdg_directories:
dependency: transitive
description:
@ -1623,5 +1590,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <3.7.0-13.0"
flutter: ">=3.10.0"
dart: ">=3.1.2 <3.7.0-13.0"
flutter: ">=3.13.0"

View File

@ -11,7 +11,7 @@ description: A meditation learning tool
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.0.11+30011
version: 3.1.0+30012
environment:
sdk: ">=2.18.0 <3.7.0-13.0"
flutter: ">=3.0.0"
@ -27,13 +27,6 @@ dependencies:
flutter_markdown: ^0.6.7
shared_preferences: ^2.0.5
auto_size_text: ^3.0.0-nullsafety.0
just_audio:
git:
url: https://github.com/meditohq/just_audio.git
ref: feature/treadmill
path: just_audio
audio_service: ^0.18.10
audio_session: ^0.1.18
rxdart: ^0.27.1
share: ^2.0.1
flutter_html: ^3.0.0-alpha.2
@ -67,6 +60,7 @@ dependencies:
vibration: ^1.7.3
reorderables: ^0.6.0
google_api_availability: ^5.0.0
workmanager: ^0.5.2
dependency_overrides:
firebase_core_platform_interface: 4.5.1
@ -74,6 +68,7 @@ dependency_overrides:
dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^15.0.2
mocktail: ^0.3.0
build_runner: ^2.3.3
riverpod_generator: ^2.1.0