diff --git a/README.md b/README.md
index 350af084..bd80fbbf 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ Don't forget to give the project a star! ⭐️
Marty Ulrich |
Cristhian Escobar |
- Jose Mateo |
+ Skywalker |
Jermaine Lara |
Walter Lara |
diff --git a/android/app-newm/build.gradle.kts b/android/app-newm/build.gradle.kts
index de9f2de6..52832c73 100644
--- a/android/app-newm/build.gradle.kts
+++ b/android/app-newm/build.gradle.kts
@@ -97,6 +97,8 @@ dependencies {
implementation(libs.firebase.analytics.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.material)
+ implementation(libs.androidx.media3.datasource)
+ implementation(libs.androidx.media3.database)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.launchdarkly.client)
implementation(libs.play.services.auth)
diff --git a/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt b/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
index d3472bef..32949845 100644
--- a/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
+++ b/android/app-newm/src/main/java/io/newm/di/android/Dependencies.kt
@@ -1,6 +1,12 @@
package io.newm.di.android
+import android.annotation.SuppressLint
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.media3.database.DatabaseProvider
+import androidx.media3.database.StandaloneDatabaseProvider
+import androidx.media3.datasource.cache.Cache
+import androidx.media3.datasource.cache.NoOpCacheEvictor
+import androidx.media3.datasource.cache.SimpleCache
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.Scopes
@@ -14,6 +20,8 @@ import io.newm.feature.login.screen.createaccount.CreateAccountScreenPresenter
import io.newm.feature.login.screen.login.LoginScreenPresenter
import io.newm.feature.login.screen.resetpassword.ResetPasswordScreenPresenter
import io.newm.feature.login.screen.welcome.WelcomeScreenPresenter
+import io.newm.feature.musicplayer.service.DownloadManager
+import io.newm.feature.musicplayer.service.DownloadManagerImpl
import io.newm.screens.forceupdate.ForceAppUpdatePresenter
import io.newm.screens.library.NFTLibraryPresenter
import io.newm.screens.profile.edit.ProfileEditPresenter
@@ -26,6 +34,7 @@ import io.newm.utils.ForceAppUpdateViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
+@SuppressLint("UnsafeOptInUsageError")
val viewModule = module {
single { AndroidFeatureFlagManager(get(), get()) }
single { ForceAppUpdateViewModel(get(), get()) }
@@ -86,7 +95,9 @@ val viewModule = module {
get(),
get(),
get(),
- get()
+ get(),
+ get(),
+ get(),
)
}
factory { params ->
@@ -111,6 +122,12 @@ val viewModule = module {
params.get(),
)
}
+ single { StandaloneDatabaseProvider(androidContext()) }
+ single {
+ val downloadDirectory = androidContext().getExternalFilesDir(null)!!
+ SimpleCache(downloadDirectory, NoOpCacheEvictor(), get())
+ }
+ single { DownloadManagerImpl(androidContext()) }
}
val androidModules = module {
diff --git a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryEvent.kt b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryEvent.kt
index e9eb1c44..d8118844 100644
--- a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryEvent.kt
+++ b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryEvent.kt
@@ -10,7 +10,7 @@ sealed interface NFTLibraryEvent : CircuitUiEvent {
data class OnQueryChange(val newQuery: String) : NFTLibraryEvent
- data class OnDownloadTrack(val tackId: String) : NFTLibraryEvent
+ data class OnDownloadTrack(val track: NFTTrack) : NFTLibraryEvent
data class OnApplyFilters(val filters: NFTLibraryFilters) : NFTLibraryEvent
}
diff --git a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryPresenter.kt b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryPresenter.kt
index ccf6cd70..da043625 100644
--- a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryPresenter.kt
+++ b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryPresenter.kt
@@ -16,9 +16,12 @@ import io.newm.feature.musicplayer.models.PlaybackState
import io.newm.feature.musicplayer.models.Playlist
import io.newm.feature.musicplayer.models.Track
import io.newm.feature.musicplayer.rememberMediaPlayer
+import io.newm.feature.musicplayer.service.DownloadManager
import io.newm.feature.musicplayer.service.MusicPlayer
import io.newm.shared.public.analytics.NewmAppEventLogger
import io.newm.shared.public.analytics.events.AppScreens
+import io.newm.shared.public.featureflags.FeatureFlagManager
+import io.newm.shared.public.featureflags.FeatureFlags
import io.newm.shared.public.models.NFTTrack
import io.newm.shared.public.usecases.ConnectWalletUseCase
import io.newm.shared.public.usecases.HasWalletConnectionsUseCase
@@ -35,12 +38,17 @@ class NFTLibraryPresenter(
private val syncWalletConnectionsUseCase: SyncWalletConnectionsUseCase,
private val walletNFTTracksUseCase: WalletNFTTracksUseCase,
private val scope: CoroutineScope,
- private val eventLogger: NewmAppEventLogger
+ private val eventLogger: NewmAppEventLogger,
+ private val downloadManager: DownloadManager,
+ private val featureFlagManager: FeatureFlagManager,
) : Presenter {
@Composable
override fun present(): NFTLibraryState {
val musicPlayer: MusicPlayer? = rememberMediaPlayer(eventLogger)
+ val downloadsEnabled =
+ remember { featureFlagManager.isEnabled(FeatureFlags.DownloadTracks) }
+
LaunchedEffect(Unit) {
syncWalletConnectionsUseCase.syncWalletConnectionsFromNetworkToDevice()
}
@@ -144,7 +152,13 @@ class NFTLibraryPresenter(
refreshing = refreshing,
eventSink = { event ->
when (event) {
- is NFTLibraryEvent.OnDownloadTrack -> TODO("Not implemented yet")
+ is NFTLibraryEvent.OnDownloadTrack -> {
+ downloadManager.download(
+ id = event.track.id,
+ url = event.track.audioUrl
+ )
+ }
+
is NFTLibraryEvent.OnQueryChange -> {
eventLogger.logEvent(
AppScreens.NFTLibraryScreen.SEARCH_BUTTON,
@@ -176,7 +190,8 @@ class NFTLibraryPresenter(
}
}
},
- currentTrackId = currentTrackId
+ currentTrackId = currentTrackId,
+ downloadsEnabled = downloadsEnabled,
)
}
}
diff --git a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryScreenUi.kt b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryScreenUi.kt
index c7007e18..a008ac23 100644
--- a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryScreenUi.kt
+++ b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryScreenUi.kt
@@ -157,12 +157,13 @@ fun NFTLibraryScreenUi(
filters = state.filters,
onQueryChange = { query -> eventSink(OnQueryChange(query)) },
onPlaySong = { track -> eventSink(PlaySong(track)) },
- onDownloadSong = { trackId -> eventSink(OnDownloadTrack(trackId)) },
+ onDownloadSong = { track -> eventSink(OnDownloadTrack(track)) },
refresh = { eventSink(NFTLibraryEvent.OnRefresh) },
refreshing = state.refreshing,
eventLogger = eventLogger,
onApplyFilters = { filters -> eventSink(OnApplyFilters(filters)) },
- currentTrackId = state.currentTrackId
+ currentTrackId = state.currentTrackId,
+ downloadsEnabled = state.downloadsEnabled,
)
}
}
@@ -180,12 +181,13 @@ private fun NFTTracks(
filters: NFTLibraryFilters,
onQueryChange: (String) -> Unit,
onPlaySong: (NFTTrack) -> Unit,
- onDownloadSong: (String) -> Unit,
+ onDownloadSong: (NFTTrack) -> Unit,
refresh: () -> Unit,
refreshing: Boolean,
eventLogger: NewmAppEventLogger,
onApplyFilters: (NFTLibraryFilters) -> Unit,
- currentTrackId: String?
+ currentTrackId: String?,
+ downloadsEnabled: Boolean,
) {
val scope = rememberCoroutineScope()
val filterSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
@@ -240,8 +242,9 @@ private fun NFTTracks(
TrackRowItemWrapper(
track = track,
onPlaySong = onPlaySong,
- onDownloadSong = { onDownloadSong(track.id) },
- isSelected = track.id == currentTrackId
+ onDownloadSong = { onDownloadSong(track) },
+ isSelected = track.id == currentTrackId,
+ downloadsEnabled = downloadsEnabled,
)
}
}
@@ -259,16 +262,14 @@ private fun NFTTracks(
}
}
-// TODO: Remove this flag once the download functionality is implemented
-private const val DOWNLOAD_UI_ENABLED = false
-
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun TrackRowItemWrapper(
track: NFTTrack,
onPlaySong: (NFTTrack) -> Unit,
onDownloadSong: () -> Unit,
- isSelected: Boolean
+ isSelected: Boolean,
+ downloadsEnabled: Boolean,
) {
val swipeableState = rememberSwipeableState(initialValue = false)
val deltaX = with(LocalDensity.current) { 82.dp.toPx() }
@@ -288,13 +289,13 @@ private fun TrackRowItemWrapper(
),
)
) {
- if (!track.isDownloaded && DOWNLOAD_UI_ENABLED) {
+ if (!track.isDownloaded && downloadsEnabled) {
RevealedPanel(onDownloadSong)
}
TrackRowItem(
track = track,
onClick = onPlaySong,
- modifier = if (DOWNLOAD_UI_ENABLED) Modifier.offset {
+ modifier = if (downloadsEnabled) Modifier.offset {
IntOffset(
x = -swipeableState.offset.value.roundToInt(),
y = 0
@@ -379,7 +380,8 @@ fun PreviewNftLibrary() {
),
refreshing = false,
eventSink = {},
- currentTrackId = null
+ currentTrackId = null,
+ downloadsEnabled = true,
),
eventLogger = NewmAppEventLogger()
)
diff --git a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryState.kt b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryState.kt
index a76c8171..76cb2d59 100644
--- a/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryState.kt
+++ b/android/app-newm/src/main/java/io/newm/screens/library/NFTLibraryState.kt
@@ -17,7 +17,8 @@ sealed interface NFTLibraryState : CircuitUiState {
val filters: NFTLibraryFilters,
val refreshing: Boolean,
val eventSink: (NFTLibraryEvent) -> Unit,
- val currentTrackId: String?
+ val currentTrackId: String?,
+ val downloadsEnabled: Boolean
) : NFTLibraryState
data class Error(val message: String) : NFTLibraryState
diff --git a/android/features/music-player/build.gradle.kts b/android/features/music-player/build.gradle.kts
index 6fb842fa..b896eb3e 100644
--- a/android/features/music-player/build.gradle.kts
+++ b/android/features/music-player/build.gradle.kts
@@ -33,6 +33,7 @@ dependencies {
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.ui)
+ implementation(libs.androidx.media3.workmanager)
implementation(libs.androidx.palette.ktx)
implementation(libs.koin.android)
implementation(libs.play.services.auth)
diff --git a/android/features/music-player/src/main/AndroidManifest.xml b/android/features/music-player/src/main/AndroidManifest.xml
index de2992db..2a3095b2 100644
--- a/android/features/music-player/src/main/AndroidManifest.xml
+++ b/android/features/music-player/src/main/AndroidManifest.xml
@@ -2,6 +2,8 @@
+
+
@@ -13,6 +15,15 @@
+
+
+
+
+
+
+
diff --git a/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/DownloadManager.kt b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/DownloadManager.kt
new file mode 100644
index 00000000..551ad167
--- /dev/null
+++ b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/DownloadManager.kt
@@ -0,0 +1,28 @@
+package io.newm.feature.musicplayer.service
+
+import android.content.Context
+import android.net.Uri
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.offline.DownloadRequest
+import androidx.media3.exoplayer.offline.DownloadService
+
+interface DownloadManager {
+ fun download(id: String, url: String)
+}
+
+class DownloadManagerImpl(
+ private val context: Context
+) : DownloadManager {
+ @UnstableApi
+ override fun download(id: String, url: String) {
+ val uri = Uri.parse(url)
+ val downloadRequest = DownloadRequest.Builder(id, uri).build()
+
+ DownloadService.sendAddDownload(
+ context,
+ NewmDownloadService::class.java,
+ downloadRequest,
+ true // isForeground
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/MusicService.kt b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/MusicService.kt
index 1c94e65c..74f10ddf 100644
--- a/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/MusicService.kt
+++ b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/MusicService.kt
@@ -6,10 +6,19 @@ import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
+import androidx.annotation.OptIn
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.datasource.DataSource
+import androidx.media3.datasource.DefaultHttpDataSource
+import androidx.media3.datasource.cache.Cache
+import androidx.media3.datasource.cache.CacheDataSource
import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
+import org.koin.android.ext.android.inject
+@UnstableApi
class MediaService : MediaSessionService() {
private lateinit var mediaSession: MediaSession
private lateinit var audioManager: AudioManager
@@ -20,6 +29,8 @@ class MediaService : MediaSessionService() {
private var playBackAuthorized = false
private var resumeOnFocusGain = false
+ private val downloadCache: Cache by inject()
+
companion object {
private const val DUCKING_VOLUME = 0.2f
private const val NORMAL_VOLUME = 1.0f
@@ -75,10 +86,14 @@ class MediaService : MediaSessionService() {
.build()
}
+ @OptIn(UnstableApi::class)
override fun onCreate() {
super.onCreate()
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
- player = ExoPlayer.Builder(this).build()
+
+ player = ExoPlayer.Builder(this)
+ .setMediaSourceFactory(buildMediaSourceFactory())
+ .build()
mediaSession = MediaSession.Builder(this, player)
.setSessionActivity()
@@ -87,6 +102,18 @@ class MediaService : MediaSessionService() {
}
+ private fun buildMediaSourceFactory(): DefaultMediaSourceFactory {
+ val httpDataSourceFactory = DefaultHttpDataSource.Factory()
+
+ val cacheDataSourceFactory: DataSource.Factory =
+ CacheDataSource.Factory()
+ .setCache(downloadCache)
+ .setUpstreamDataSourceFactory(httpDataSourceFactory)
+ .setCacheWriteDataSinkFactory(null) // Disable writing.
+
+ return DefaultMediaSourceFactory(this).setDataSourceFactory(cacheDataSourceFactory)
+ }
+
private fun MediaSession.Builder.setSessionActivity(): MediaSession.Builder {
val launchIntentForPackage = packageManager.getLaunchIntentForPackage(packageName)
diff --git a/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/NewmDownloadService.kt b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/NewmDownloadService.kt
new file mode 100644
index 00000000..6b27922c
--- /dev/null
+++ b/android/features/music-player/src/main/java/io/newm/feature/musicplayer/service/NewmDownloadService.kt
@@ -0,0 +1,128 @@
+package io.newm.feature.musicplayer.service
+
+import android.app.Notification
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.database.DatabaseProvider
+import androidx.media3.datasource.DefaultHttpDataSource
+import androidx.media3.datasource.cache.Cache
+import androidx.media3.exoplayer.offline.Download
+import androidx.media3.exoplayer.offline.DownloadManager
+import androidx.media3.exoplayer.offline.DownloadNotificationHelper
+import androidx.media3.exoplayer.offline.DownloadService
+import androidx.media3.exoplayer.scheduler.Requirements
+import androidx.media3.exoplayer.scheduler.Scheduler
+import androidx.media3.exoplayer.workmanager.WorkManagerScheduler
+import io.newm.feature.musicplayer.R
+import org.koin.android.ext.android.inject
+import java.util.concurrent.Executor
+
+private const val WORK_NAME: String = "NewmDownload"
+private const val FOREGROUND_NOTIFICATION_ID: Int = 1
+private const val NOTIFICATION_UPDATE_INTERVAL: Long = 1_000
+const val DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel"
+
+@UnstableApi
+internal class NewmDownloadService : DownloadService(
+ FOREGROUND_NOTIFICATION_ID,
+ NOTIFICATION_UPDATE_INTERVAL,
+ DOWNLOAD_NOTIFICATION_CHANNEL_ID,
+ R.string.musicplayer_exo_download_notification_channel_name,
+ 0
+) {
+ private val databaseProvider : DatabaseProvider by inject()
+ private val downloadCache : Cache by inject()
+
+ // Create a factory for reading the data from the network.
+ private val dataSourceFactory = DefaultHttpDataSource.Factory()
+
+ private val downloadExecutor = Executor(Runnable::run)
+
+ override fun getDownloadManager(): DownloadManager {
+ val downloadManager = DownloadManager(this, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor)
+
+ downloadManager.addListener(
+ object : DownloadManager.Listener {
+ override fun onInitialized(downloadManager: DownloadManager) {
+ super.onInitialized(downloadManager)
+ println("DownloadManager initialized")
+ }
+
+ override fun onDownloadsPausedChanged(
+ downloadManager: DownloadManager,
+ downloadsPaused: Boolean
+ ) {
+ super.onDownloadsPausedChanged(downloadManager, downloadsPaused)
+ println("Downloads paused: $downloadsPaused")
+ }
+
+ override fun onDownloadChanged(
+ downloadManager: DownloadManager,
+ download: Download,
+ finalException: Exception?
+ ) {
+ super.onDownloadChanged(downloadManager, download, finalException)
+ println("Download changed: ${download.request.uri}")
+ println("${download.percentDownloaded}% downloaded")
+ }
+
+ override fun onDownloadRemoved(
+ downloadManager: DownloadManager,
+ download: Download
+ ) {
+ super.onDownloadRemoved(downloadManager, download)
+ println("Download removed: $download")
+ }
+
+ override fun onIdle(downloadManager: DownloadManager) {
+ super.onIdle(downloadManager)
+ println("DownloadManager idle")
+ }
+
+ override fun onRequirementsStateChanged(
+ downloadManager: DownloadManager,
+ requirements: Requirements,
+ notMetRequirements: Int
+ ) {
+ super.onRequirementsStateChanged(
+ downloadManager,
+ requirements,
+ notMetRequirements
+ )
+ println("Requirements state changed: $requirements")
+ }
+
+ override fun onWaitingForRequirementsChanged(
+ downloadManager: DownloadManager,
+ waitingForRequirements: Boolean
+ ) {
+ super.onWaitingForRequirementsChanged(downloadManager, waitingForRequirements)
+ println("Waiting for requirements: $waitingForRequirements")
+ }
+ }
+ )
+ return downloadManager
+ }
+
+ override fun getScheduler(): Scheduler {
+ return WorkManagerScheduler(this, WORK_NAME)
+ }
+
+ override fun getForegroundNotification(
+ downloads: MutableList,
+ notMetRequirements: Int
+ ): Notification {
+ val downloadNotificationHelper = DownloadNotificationHelper(
+ this,
+ DOWNLOAD_NOTIFICATION_CHANNEL_ID
+ )
+
+ return downloadNotificationHelper.buildProgressNotification(
+ this,
+ R.drawable.musicplayer_ic_download,
+ null,
+ null,
+ downloads,
+ notMetRequirements
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/features/music-player/src/main/res/drawable/musicplayer_ic_download.xml b/android/features/music-player/src/main/res/drawable/musicplayer_ic_download.xml
new file mode 100644
index 00000000..a8b409b1
--- /dev/null
+++ b/android/features/music-player/src/main/res/drawable/musicplayer_ic_download.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/features/music-player/src/main/res/values/strings.xml b/android/features/music-player/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6ff30569
--- /dev/null
+++ b/android/features/music-player/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Download
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c7823147..171d8b16 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -60,9 +60,12 @@ androidx-datastore-preferences = { module = "androidx.datastore:datastore-prefer
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
+androidx-media3-database = { module = "androidx.media3:media3-database", version.ref = "media3" }
+androidx-media3-datasource = { module = "androidx.media3:media3-datasource", version.ref = "media3" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
+androidx-media3-workmanager = { module = "androidx.media3:media3-exoplayer-workmanager", version.ref = "media3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationCompose" }
androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteKtx" }
diff --git a/shared/src/commonMain/kotlin/io.newm.shared/public/featureflags/FeatureFlags.kt b/shared/src/commonMain/kotlin/io.newm.shared/public/featureflags/FeatureFlags.kt
index a31fdb56..b8417356 100644
--- a/shared/src/commonMain/kotlin/io.newm.shared/public/featureflags/FeatureFlags.kt
+++ b/shared/src/commonMain/kotlin/io.newm.shared/public/featureflags/FeatureFlags.kt
@@ -8,4 +8,8 @@ object FeatureFlags {
object ShowRecordStore : FeatureFlag {
override val key = "mobile-app-show-recordstore"
}
+
+ object DownloadTracks : FeatureFlag {
+ override val key = "mobile-app-track-downloads"
+ }
}
\ No newline at end of file