diff --git a/MediaPicker/build.gradle b/MediaPicker/build.gradle index 4cc94d57..5f6834f1 100644 --- a/MediaPicker/build.gradle +++ b/MediaPicker/build.gradle @@ -32,6 +32,8 @@ android { } dependencies { + implementation project(':MediaPicker:domain') + implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' diff --git a/MediaPicker/domain/.gitignore b/MediaPicker/domain/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/MediaPicker/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/MediaPicker/domain/build.gradle b/MediaPicker/domain/build.gradle new file mode 100644 index 00000000..9b10423f --- /dev/null +++ b/MediaPicker/domain/build.gradle @@ -0,0 +1,11 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} +dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' + + testImplementation "org.mockito:mockito-inline:3.11.2" + testImplementation "org.assertj:assertj-core:3.19.0" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + testImplementation 'junit:junit:4.13.2' +} \ No newline at end of file diff --git a/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt new file mode 100644 index 00000000..df18930f --- /dev/null +++ b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt @@ -0,0 +1,29 @@ +package org.wordpress.android.mediapicker + +import org.wordpress.android.mediapicker.MediaItem.IdentifierType.LOCAL_ID +import org.wordpress.android.mediapicker.MediaItem.IdentifierType.LOCAL_URI +import org.wordpress.android.mediapicker.MediaItem.IdentifierType.REMOTE_ID +import org.wordpress.android.mediapicker.util.MediaUri + +data class MediaItem( + val identifier: Identifier, + val url: String, + val name: String? = null, + val type: MediaType, + val mimeType: String? = null, + val dataModified: Long +) { + enum class IdentifierType { + LOCAL_URI, + REMOTE_ID, + LOCAL_ID, + } + + sealed class Identifier(val type: IdentifierType) { + data class LocalUri(val value: MediaUri, val queued: Boolean = false) : Identifier(LOCAL_URI) + + data class RemoteId(val value: Long) : Identifier(REMOTE_ID) + + data class LocalId(val value: Int) : Identifier(LOCAL_ID) + } +} diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaType.kt b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/MediaType.kt similarity index 100% rename from MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaType.kt rename to MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/MediaType.kt diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt similarity index 74% rename from MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt rename to MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt index 5eebf61a..2b313c0f 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt +++ b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaLoader.kt @@ -16,7 +16,6 @@ import org.wordpress.android.mediapicker.loader.MediaSource.MediaLoadingResult import org.wordpress.android.mediapicker.loader.MediaSource.MediaLoadingResult.Empty import org.wordpress.android.mediapicker.loader.MediaSource.MediaLoadingResult.Failure import org.wordpress.android.mediapicker.loader.MediaSource.MediaLoadingResult.Success -import org.wordpress.android.util.UiString data class MediaLoader(private val mediaSource: MediaSource) { suspend fun loadMedia(actions: Channel): Flow { @@ -53,8 +52,8 @@ data class MediaLoader(private val mediaSource: MediaSource) { is Start -> { if (state.domainItems.isEmpty()) { buildDomainModel( - mediaSource.load(filter = loadAction.filter), - state.copy(filter = loadAction.filter) + mediaSource.load(filter = loadAction.filter), + state.copy(filter = loadAction.filter) ) } else { state @@ -63,10 +62,11 @@ data class MediaLoader(private val mediaSource: MediaSource) { is Refresh -> { if (loadAction.forced || state.domainItems.isEmpty()) { buildDomainModel( - mediaSource.load( - filter = state.filter, - forced = loadAction.forced - ), state + mediaSource.load( + filter = state.filter, + forced = loadAction.forced + ), + state ) } else { state @@ -103,34 +103,34 @@ data class MediaLoader(private val mediaSource: MediaSource) { ): DomainModel { return when (partialResult) { is Success -> state.copy( - isLoading = false, - hasMore = partialResult.hasMore, - domainItems = partialResult.data, - emptyState = null + isLoading = false, + hasMore = partialResult.hasMore, + domainItems = partialResult.data, + emptyState = null ) is Empty -> state.copy( - isLoading = false, - hasMore = false, - domainItems = listOf(), - emptyState = EmptyState( - partialResult.title, - partialResult.htmlSubtitle, - partialResult.image, - partialResult.bottomImage, - partialResult.bottomImageContentDescription, - isError = false - ) + isLoading = false, + hasMore = false, + domainItems = listOf(), + emptyState = EmptyState( + partialResult.title, + partialResult.htmlSubtitle, + partialResult.image, + partialResult.bottomImage, + partialResult.bottomImageContentDescription, + isError = false + ) ) is Failure -> state.copy( - isLoading = false, - hasMore = partialResult.data.isNotEmpty(), - domainItems = partialResult.data, - emptyState = EmptyState( - partialResult.title, - partialResult.htmlSubtitle, - partialResult.image, - isError = true - ) + isLoading = false, + hasMore = partialResult.data.isNotEmpty(), + domainItems = partialResult.data, + emptyState = EmptyState( + partialResult.title, + partialResult.htmlSubtitle, + partialResult.image, + isError = true + ) ) } } @@ -153,11 +153,11 @@ data class MediaLoader(private val mediaSource: MediaSource) { val emptyState: EmptyState? = null ) { data class EmptyState( - val title: UiString, - val htmlSubtitle: UiString? = null, + val title: String, + val htmlSubtitle: String? = null, val image: Int? = null, val bottomImage: Int? = null, - val bottomImageDescription: UiString? = null, + val bottomImageDescription: String? = null, val isError: Boolean = false ) } diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt similarity index 74% rename from MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt rename to MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt index 70db9cf8..067449e0 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt +++ b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/loader/MediaSource.kt @@ -1,7 +1,6 @@ package org.wordpress.android.mediapicker.loader import org.wordpress.android.mediapicker.MediaItem -import org.wordpress.android.util.UiString interface MediaSource { suspend fun load( @@ -11,18 +10,20 @@ interface MediaSource { ): MediaLoadingResult sealed class MediaLoadingResult(open val data: List) { + data class Success(override val data: List, val hasMore: Boolean = false) : MediaLoadingResult(data) + data class Empty( - val title: UiString, - val htmlSubtitle: UiString? = null, + val title: String, + val htmlSubtitle: String? = null, val image: Int? = null, val bottomImage: Int? = null, - val bottomImageContentDescription: UiString? = null + val bottomImageContentDescription: String? = null ) : MediaLoadingResult(listOf()) data class Failure( - val title: UiString, - val htmlSubtitle: UiString? = null, + val title: String, + val htmlSubtitle: String? = null, val image: Int? = null, override val data: List = listOf() ) : MediaLoadingResult(data) diff --git a/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/util/MediaUri.kt b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/util/MediaUri.kt new file mode 100644 index 00000000..161b8c4d --- /dev/null +++ b/MediaPicker/domain/src/main/java/org/wordpress/android/mediapicker/util/MediaUri.kt @@ -0,0 +1,4 @@ +package org.wordpress.android.mediapicker.util + +@JvmInline +value class MediaUri(val s: String) diff --git a/MediaPicker/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt b/MediaPicker/domain/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt similarity index 62% rename from MediaPicker/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt rename to MediaPicker/domain/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt index a7711e3d..73a4c388 100644 --- a/MediaPicker/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt +++ b/MediaPicker/domain/src/test/java/org/wordpress/android/mediapicker/loader/MediaLoaderTest.kt @@ -7,12 +7,13 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock -import org.wordpress.android.BaseUnitTest -import org.wordpress.android.test +import org.mockito.junit.MockitoJUnitRunner import org.wordpress.android.mediapicker.MediaItem import org.wordpress.android.mediapicker.MediaItem.Identifier import org.wordpress.android.mediapicker.MediaType.IMAGE @@ -20,34 +21,36 @@ import org.wordpress.android.mediapicker.MediaType.VIDEO import org.wordpress.android.mediapicker.loader.MediaLoader.DomainModel import org.wordpress.android.mediapicker.loader.MediaLoader.LoadAction import org.wordpress.android.mediapicker.loader.MediaSource.MediaLoadingResult -import org.wordpress.android.ui.utils.UiString.UiStringText -import org.wordpress.android.util.NetworkUtilsWrapper - -class MediaLoaderTest : BaseUnitTest() { - @Mock lateinit var mediaSource: MediaSource - @Mock lateinit var networkUtilsWrapper: NetworkUtilsWrapper - @Mock lateinit var identifier1: Identifier - @Mock lateinit var identifier2: Identifier + +@RunWith(MockitoJUnitRunner::class) +class MediaLoaderTest { + @Mock + lateinit var mediaSource: MediaSource + @Mock + lateinit var identifier1: Identifier + @Mock + lateinit var identifier2: Identifier private lateinit var mediaLoader: MediaLoader - private val mediaTypes = setOf(IMAGE, VIDEO) private lateinit var firstMediaItem: MediaItem private lateinit var secondMediaItem: MediaItem @Before fun setUp() { - mediaLoader = MediaLoader(mediaSource, networkUtilsWrapper) - firstMediaItem = MediaItem(identifier1, "url://first_item", "first item", IMAGE, "image/jpeg", 1) - secondMediaItem = MediaItem(identifier2, "url://second_item", "second item", VIDEO, "video/mpeg", 2) + mediaLoader = MediaLoader(mediaSource) + firstMediaItem = + MediaItem(identifier1, "url://first_item", "first item", IMAGE, "image/jpeg", 1) + secondMediaItem = + MediaItem(identifier2, "url://second_item", "second item", VIDEO, "video/mpeg", 2) } @Test fun `loads media items on start`() = withMediaLoader { resultModel, performAction -> val mediaItems = listOf(firstMediaItem) whenever( - mediaSource.load( - forced = false, - loadMore = false - ) + mediaSource.load( + forced = false, + loadMore = false + ) ).thenReturn(MediaLoadingResult.Success(mediaItems, hasMore = false)) performAction(LoadAction.Start(), true) @@ -59,9 +62,7 @@ class MediaLoaderTest : BaseUnitTest() { fun `shows an error when loading fails`() = withMediaLoader { resultModel, performAction -> val errorMessage = "error" whenever(mediaSource.load(forced = false, loadMore = false)).thenReturn( - MediaLoadingResult.Failure( - UiStringText(errorMessage) - ) + MediaLoadingResult.Failure(errorMessage) ) performAction(LoadAction.Start(), true) @@ -86,21 +87,23 @@ class MediaLoaderTest : BaseUnitTest() { } @Test - fun `shows an error when loading next page fails`() = withMediaLoader { resultModel, performAction -> - val firstPage = MediaLoadingResult.Success(listOf(firstMediaItem), hasMore = true) - val message = "error" - val secondPage = MediaLoadingResult.Failure(UiStringText(message), data = listOf(firstMediaItem)) - whenever(mediaSource.load(forced = false, loadMore = false)).thenReturn(firstPage) - whenever(mediaSource.load(forced = false, loadMore = true)).thenReturn(secondPage) + fun `shows an error when loading next page fails`() = + withMediaLoader { resultModel, performAction -> + val firstPage = MediaLoadingResult.Success(listOf(firstMediaItem), hasMore = true) + val message = "error" + val secondPage = + MediaLoadingResult.Failure(message, data = listOf(firstMediaItem)) + whenever(mediaSource.load(forced = false, loadMore = false)).thenReturn(firstPage) + whenever(mediaSource.load(forced = false, loadMore = true)).thenReturn(secondPage) - performAction(LoadAction.Start(), true) + performAction(LoadAction.Start(), true) - resultModel.assertModel(listOf(firstMediaItem), hasMore = true) + resultModel.assertModel(listOf(firstMediaItem), hasMore = true) - performAction(LoadAction.NextPage, true) + performAction(LoadAction.NextPage, true) - resultModel.assertModel(listOf(firstMediaItem), errorMessage = message, hasMore = true) - } + resultModel.assertModel(listOf(firstMediaItem), errorMessage = message, hasMore = true) + } @Test fun `refresh overrides data`() = withMediaLoader { resultModel, performAction -> @@ -121,13 +124,18 @@ class MediaLoaderTest : BaseUnitTest() { fun `filters out media item`() = withMediaLoader { resultModel, performAction -> val mediaItems = listOf(firstMediaItem, secondMediaItem) val filter = "second" - whenever(mediaSource.load(forced = false, loadMore = false)).thenReturn(MediaLoadingResult.Success(mediaItems)) whenever( - mediaSource.load( - forced = false, - loadMore = false, - filter = filter - ) + mediaSource.load( + forced = false, + loadMore = false + ) + ).thenReturn(MediaLoadingResult.Success(mediaItems)) + whenever( + mediaSource.load( + forced = false, + loadMore = false, + filter = filter + ) ).thenReturn(MediaLoadingResult.Success(listOf(secondMediaItem))) performAction(LoadAction.Start(), true) @@ -146,17 +154,17 @@ class MediaLoaderTest : BaseUnitTest() { val mediaItems = listOf(firstMediaItem, secondMediaItem) val filter = "second" whenever( - mediaSource.load( - forced = false, - loadMore = false, - filter = filter - ) + mediaSource.load( + forced = false, + loadMore = false, + filter = filter + ) ).thenReturn(MediaLoadingResult.Success(listOf(secondMediaItem))) whenever( - mediaSource.load( - forced = false, - loadMore = false - ) + mediaSource.load( + forced = false, + loadMore = false + ) ).thenReturn(MediaLoadingResult.Success(mediaItems)) performAction(LoadAction.Start(), true) @@ -175,7 +183,7 @@ class MediaLoaderTest : BaseUnitTest() { this.last().apply { assertThat(this.domainItems).isEqualTo(mediaItems) if (errorMessage != null) { - assertThat(this.emptyState?.title).isEqualTo(UiStringText(errorMessage)) + assertThat(this.emptyState?.title).isEqualTo(errorMessage) assertThat(this.emptyState?.isError).isTrue() } else { assertThat(this.emptyState?.title).isNull() @@ -193,23 +201,23 @@ class MediaLoaderTest : BaseUnitTest() { ) -> Unit ) -> Unit ) = - test { - val loadActions: Channel = Channel() - val domainModels: MutableList = mutableListOf() - val job = launch { - mediaLoader.loadMedia(loadActions).collect { - domainModels.add(it) - } + runBlocking { + val loadActions: Channel = Channel() + val domainModels: MutableList = mutableListOf() + val job = launch { + mediaLoader.loadMedia(loadActions).collect { + domainModels.add(it) } - assertFunction(domainModels) { action, awaitResult -> - val currentCount = domainModels.size - loadActions.send(action) - if (awaitResult) { - domainModels.awaitResult(currentCount + 1) - } + } + assertFunction(domainModels) { action, awaitResult -> + val currentCount = domainModels.size + loadActions.send(action) + if (awaitResult) { + domainModels.awaitResult(currentCount + 1) } - job.cancel() } + job.cancel() + } private suspend fun List.awaitResult(count: Int) { val limit = 10 diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt deleted file mode 100644 index 4dceb8dd..00000000 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaItem.kt +++ /dev/null @@ -1,113 +0,0 @@ -package org.wordpress.android.mediapicker - -import android.net.Uri -import android.os.Parcel -import android.os.Parcelable -import android.os.Parcelable.Creator -import org.wordpress.android.mediapicker.MediaItem.IdentifierType.GIF_MEDIA_IDENTIFIER -import org.wordpress.android.mediapicker.MediaItem.IdentifierType.LOCAL_ID -import org.wordpress.android.mediapicker.MediaItem.IdentifierType.LOCAL_URI -import org.wordpress.android.mediapicker.MediaItem.IdentifierType.REMOTE_ID -import org.wordpress.android.mediapicker.MediaItem.IdentifierType.STOCK_MEDIA_IDENTIFIER -import org.wordpress.android.util.UriWrapper - -data class MediaItem( - val identifier: Identifier, - val url: String, - val name: String? = null, - val type: MediaType, - val mimeType: String? = null, - val dataModified: Long -) { - enum class IdentifierType { - LOCAL_URI, - REMOTE_ID, - LOCAL_ID, - STOCK_MEDIA_IDENTIFIER, - GIF_MEDIA_IDENTIFIER - } - - sealed class Identifier(val type: IdentifierType) : Parcelable { - data class LocalUri(val value: UriWrapper, val queued: Boolean = false) : Identifier(LOCAL_URI) - - data class RemoteId(val value: Long) : Identifier(REMOTE_ID) - - data class LocalId(val value: Int) : Identifier(LOCAL_ID) - - data class StockMediaIdentifier( - val url: String?, - val name: String?, - val title: String? - ) : Identifier(STOCK_MEDIA_IDENTIFIER) - - data class GifMediaIdentifier( - val largeImageUri: UriWrapper, - val title: String? - ) : Identifier(GIF_MEDIA_IDENTIFIER) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(this.type.name) - when (this) { - is LocalUri -> { - parcel.writeParcelable(this.value.uri, flags) - parcel.writeInt(if (this.queued) 1 else 0) - } - is RemoteId -> { - parcel.writeLong(this.value) - } - is LocalId -> { - parcel.writeInt(this.value) - } - is StockMediaIdentifier -> { - parcel.writeString(this.url) - parcel.writeString(this.name) - parcel.writeString(this.title) - } - is GifMediaIdentifier -> { - parcel.writeParcelable(this.largeImageUri.uri, flags) - parcel.writeString(this.title) - } - } - } - - override fun describeContents(): Int { - return 0 - } - - companion object { - @JvmField - val CREATOR: Creator = object : Creator { - override fun createFromParcel(parcel: Parcel): Identifier { - val type = IdentifierType.valueOf(requireNotNull(parcel.readString())) - return when (type) { - LOCAL_URI -> { - LocalUri( - UriWrapper(requireNotNull(parcel.readParcelable(Uri::class.java.classLoader))), - parcel.readInt() != 0 - ) - } - REMOTE_ID -> { - RemoteId(parcel.readLong()) - } - LOCAL_ID -> { - LocalId(parcel.readInt()) - } - STOCK_MEDIA_IDENTIFIER -> { - StockMediaIdentifier(parcel.readString(), parcel.readString(), parcel.readString()) - } - GIF_MEDIA_IDENTIFIER -> { - GifMediaIdentifier( - UriWrapper(requireNotNull(parcel.readParcelable(Uri::class.java.classLoader))), - parcel.readString() - ) - } - } - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } - } -} diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaNavigationEvent.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaNavigationEvent.kt index 89c5c4ab..3416ab8e 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaNavigationEvent.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaNavigationEvent.kt @@ -2,12 +2,12 @@ package org.wordpress.android.mediapicker import org.wordpress.android.mediapicker.MediaItem.Identifier import org.wordpress.android.mediapicker.MediaPickerFragment.MediaPickerAction -import org.wordpress.android.util.UriWrapper +import org.wordpress.android.mediapicker.util.MediaUri sealed class MediaNavigationEvent { data class PreviewUrl(val url: String) : MediaNavigationEvent() data class PreviewMedia(val mediaId: Long) : MediaNavigationEvent() - data class EditMedia(val uris: List) : MediaNavigationEvent() + data class EditMedia(val uris: List) : MediaNavigationEvent() data class InsertMedia(val identifiers: List) : MediaNavigationEvent() data class IconClickEvent(val action: MediaPickerAction) : MediaNavigationEvent() object Exit : MediaNavigationEvent() diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActionModeCallback.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActionModeCallback.kt index 454929ba..3af32b7c 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActionModeCallback.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActionModeCallback.kt @@ -14,8 +14,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.Observer import org.wordpress.android.mediapicker.MediaPickerViewModel.ActionModeUiModel -import org.wordpress.android.util.UiString.UiStringText -import org.wordpress.android.util.UiString.UiStringRes class MediaPickerActionModeCallback(private val viewModel: MediaPickerViewModel) : Callback, LifecycleOwner { @@ -59,11 +57,7 @@ class MediaPickerActionModeCallback(private val viewModel: MediaPickerViewModel) editItem.isVisible = false } - if (uiModel.actionModeTitle is UiStringText) { - actionMode.title = uiModel.actionModeTitle.text - } else if (uiModel.actionModeTitle is UiStringRes) { - actionMode.setTitle(uiModel.actionModeTitle.stringRes) - } + actionMode.title = uiModel.actionModeTitle } } }) diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActivity.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActivity.kt index 1d4fdaab..b79ab886 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActivity.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerActivity.kt @@ -32,9 +32,12 @@ import org.wordpress.android.mediapicker.MediaPickerRequestCodes.TAKE_PHOTO import org.wordpress.android.mediapicker.MediaPickerSetup.DataSource import org.wordpress.android.mediapicker.MediaPickerSetup.DataSource.DEVICE import org.wordpress.android.mediapicker.databinding.PhotoPickerActivityBinding +import org.wordpress.android.mediapicker.util.MediaUri import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T.MEDIA import org.wordpress.android.util.WPMediaUtils +import org.wordpress.android.util.asAndroidUri +import org.wordpress.android.util.asMediaUri import java.io.File class MediaPickerActivity : AppCompatActivity(), MediaPickerListener { @@ -152,7 +155,7 @@ class MediaPickerActivity : AppCompatActivity(), MediaPickerListener { mediaCapturePath!!.let { WPMediaUtils.scanMediaFile(this, it) val f = File(it) - val capturedImageUri = listOf(Uri.fromFile(f)) + val capturedImageUri = listOf(Uri.fromFile(f).asMediaUri()) if (mediaPickerSetup.queueResults) { intent.putQueuedUris(capturedImageUri) } else { @@ -208,15 +211,15 @@ class MediaPickerActivity : AppCompatActivity(), MediaPickerListener { } private fun Intent.putUris( - mediaUris: List + mediaUris: List ) { - this.putExtra(EXTRA_MEDIA_URIS, mediaUris.toStringArray()) + this.putExtra(EXTRA_MEDIA_URIS, mediaUris.map(MediaUri::asAndroidUri).toStringArray()) } private fun Intent.putQueuedUris( - mediaUris: List + mediaUris: List ) { - this.putExtra(EXTRA_MEDIA_QUEUED_URIS, mediaUris.toStringArray()) + this.putExtra(EXTRA_MEDIA_QUEUED_URIS, mediaUris.map(MediaUri::asAndroidUri).toStringArray()) } private fun Intent.putMediaIds( @@ -237,8 +240,8 @@ class MediaPickerActivity : AppCompatActivity(), MediaPickerListener { override fun onItemsChosen(identifiers: List) { val chosenLocalUris = identifiers.mapNotNull { (it as? Identifier.LocalUri) } - val chosenUris = chosenLocalUris.filter { !it.queued }.map { it.value.uri } - val queuedUris = chosenLocalUris.filter { it.queued }.map { it.value.uri } + val chosenUris = chosenLocalUris.filter { !it.queued }.map { it.value } + val queuedUris = chosenLocalUris.filter { it.queued }.map { it.value } val chosenIds = identifiers.mapNotNull { (it as? Identifier.RemoteId)?.value } val chosenLocalIds = identifiers.mapNotNull { (it as? Identifier.LocalId)?.value } diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerFragment.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerFragment.kt index a584619d..861235dd 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerFragment.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerFragment.kt @@ -69,7 +69,6 @@ import org.wordpress.android.util.SnackbarItem import org.wordpress.android.util.SnackbarItem.Action import org.wordpress.android.util.SnackbarItem.Info import org.wordpress.android.util.SnackbarSequencer -import org.wordpress.android.util.UriWrapper import org.wordpress.android.util.WPLinkMovementMethod import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils @@ -96,14 +95,14 @@ class MediaPickerFragment : Fragment() { enum class ChooserContext( val intentAction: String, - val title: UiStringRes, + val title: Int, val mediaTypeFilter: String ) { - PHOTO(ACTION_GET_CONTENT, UiStringRes(R.string.pick_photo), "image/*"), - VIDEO(ACTION_GET_CONTENT, UiStringRes(R.string.pick_video), "video/*"), - PHOTO_OR_VIDEO(ACTION_GET_CONTENT, UiStringRes(R.string.pick_media), "*/*"), - AUDIO(ACTION_GET_CONTENT, UiStringRes(R.string.pick_audio), "*/*"), - MEDIA_FILE(ACTION_OPEN_DOCUMENT, UiStringRes(R.string.pick_file), "*/*"); + PHOTO(ACTION_GET_CONTENT, R.string.pick_photo, "image/*"), + VIDEO(ACTION_GET_CONTENT, R.string.pick_video, "video/*"), + PHOTO_OR_VIDEO(ACTION_GET_CONTENT, R.string.pick_media, "*/*"), + AUDIO(ACTION_GET_CONTENT, R.string.pick_audio, "*/*"), + MEDIA_FILE(ACTION_OPEN_DOCUMENT, R.string.pick_file, "*/*"); } sealed class MediaPickerAction { diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerViewModel.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerViewModel.kt index 5169f658..83c8a086 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerViewModel.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/MediaPickerViewModel.kt @@ -629,7 +629,7 @@ class MediaPickerViewModel constructor( sealed class ActionModeUiModel { data class Visible( - val actionModeTitle: UiString? = null, + val actionModeTitle: String? = null, val editActionUiModel: EditActionUiModel = EditActionUiModel() ) : ActionModeUiModel() diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/insert/DeviceListInsertUseCase.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/insert/DeviceListInsertUseCase.kt index 4e627e37..c7200d1b 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/insert/DeviceListInsertUseCase.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/insert/DeviceListInsertUseCase.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.flow import org.wordpress.android.mediapicker.MediaItem.Identifier import org.wordpress.android.mediapicker.MediaItem.Identifier.LocalUri import org.wordpress.android.mediapicker.insert.MediaInsertHandler.InsertModel -import org.wordpress.android.util.UriWrapper +import org.wordpress.android.mediapicker.util.MediaUri import org.wordpress.android.util.WPMediaUtilsWrapper class DeviceListInsertUseCase( @@ -16,16 +16,16 @@ class DeviceListInsertUseCase( emit(InsertModel.Progress(actionTitle)) var failed = false val fetchedUris = localUris.mapNotNull { localUri -> - val fetchedUri = wpMediaUtilsWrapper.fetchMedia(localUri.value.uri) + val fetchedUri = wpMediaUtilsWrapper.fetchMedia(localUri.value) if (fetchedUri == null) { failed = true } - fetchedUri + fetchedUri?.toString() } if (failed) { emit(InsertModel.Error("Failed to fetch local media")) } else { - emit(InsertModel.Success(fetchedUris.map { LocalUri(UriWrapper(it), queueResults) })) + emit(InsertModel.Success(fetchedUris.map { LocalUri(MediaUri(it), queueResults) })) } } diff --git a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/DeviceMediaLoader.kt b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/DeviceMediaLoader.kt index c0b515f5..3c842fca 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/DeviceMediaLoader.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/mediapicker/loader/DeviceMediaLoader.kt @@ -15,8 +15,10 @@ import org.wordpress.android.mediapicker.MediaType.AUDIO import org.wordpress.android.mediapicker.MediaType.IMAGE import org.wordpress.android.mediapicker.MediaType.VIDEO import org.wordpress.android.mediapicker.api.MimeTypeSupportProvider +import org.wordpress.android.mediapicker.util.MediaUri import org.wordpress.android.util.SqlUtils -import org.wordpress.android.util.UriWrapper +import org.wordpress.android.util.asAndroidUri +import org.wordpress.android.util.asMediaUri import java.io.File class DeviceMediaLoader( @@ -69,7 +71,7 @@ class DeviceMediaLoader( val title = cursor.getString(titleIndex) val uri = Uri.withAppendedPath(baseUri, "" + id) val item = DeviceMediaItem( - UriWrapper(uri), + uri.asMediaUri(), title, dateModified ) @@ -101,7 +103,7 @@ class DeviceMediaLoader( val result = nextPage.take(pageSize).map { file -> val uri = Uri.parse(file.toURI().toString()) DeviceMediaItem( - UriWrapper(uri), + uri.asMediaUri(), file.name, file.lastModifiedInSecs() ) @@ -111,18 +113,19 @@ class DeviceMediaLoader( private fun File.lastModifiedInSecs() = this.lastModified() / 1000 - fun getMimeType(uri: UriWrapper): String? { - return if (uri.uri.scheme == ContentResolver.SCHEME_CONTENT) { - context.contentResolver.getType(uri.uri) + fun getMimeType(mediaUri: MediaUri): String? { + val uri = mediaUri.asAndroidUri() + return if (uri.scheme == ContentResolver.SCHEME_CONTENT) { + context.contentResolver.getType(uri) } else { - val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(uri.uri.toString()) + val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(uri.toString()) mimeTypeSupportProvider.getMimeTypeForExtension(fileExtension) } } data class DeviceMediaList(val items: List, val next: Long? = null) - data class DeviceMediaItem(val uri: UriWrapper, val title: String, val dateModified: Long) + data class DeviceMediaItem(val uri: MediaUri, val title: String, val dateModified: Long) companion object { private const val ID_COL = Media._ID diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/MediaUriExt.kt b/MediaPicker/src/main/java/org/wordpress/android/util/MediaUriExt.kt new file mode 100644 index 00000000..b264b0b3 --- /dev/null +++ b/MediaPicker/src/main/java/org/wordpress/android/util/MediaUriExt.kt @@ -0,0 +1,12 @@ +package org.wordpress.android.util + +import android.net.Uri +import org.wordpress.android.mediapicker.util.MediaUri + +fun MediaUri.asAndroidUri(): Uri { + return Uri.parse(this.s) +} + +fun Uri.asMediaUri(): MediaUri{ + return MediaUri(this.toString()) +} \ No newline at end of file diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/UiHelper.kt b/MediaPicker/src/main/java/org/wordpress/android/util/UiHelper.kt deleted file mode 100644 index 37bfe6f0..00000000 --- a/MediaPicker/src/main/java/org/wordpress/android/util/UiHelper.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.wordpress.android.util - -import android.content.Context - -object UiHelper { - fun getTextOfUiString(context: Context, uiString: UiString): CharSequence = - when (uiString) { - is UiString.UiStringRes -> context.getString(uiString.stringRes) - is UiString.UiStringText -> uiString.text - is UiString.UiStringResWithParams -> context.getString( - uiString.stringRes, - *uiString.params.map { value -> - getTextOfUiString( - context, - value - ) - }.toTypedArray() - ) - } -} \ No newline at end of file diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/UiString.kt b/MediaPicker/src/main/java/org/wordpress/android/util/UiString.kt deleted file mode 100644 index dacc1b21..00000000 --- a/MediaPicker/src/main/java/org/wordpress/android/util/UiString.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.wordpress.android.util - -import androidx.annotation.StringRes - -/** - * [UiString] is a utility sealed class that represents a string to be used in the UI. It allows a string to be - * represented as both string resource and text. - */ -sealed class UiString { - data class UiStringText(val text: CharSequence) : UiString() - data class UiStringRes(@StringRes val stringRes: Int) : UiString() - data class UiStringResWithParams(@StringRes val stringRes: Int, val params: List) : UiString() -} diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/UriWrapper.kt b/MediaPicker/src/main/java/org/wordpress/android/util/UriWrapper.kt deleted file mode 100644 index 6bbbea15..00000000 --- a/MediaPicker/src/main/java/org/wordpress/android/util/UriWrapper.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.wordpress.android.util - -import android.net.Uri - -/** - * This class is necessary because standard Uri doesn't work in unit tests (it's always null) - */ -data class UriWrapper(val uri: Uri) { - constructor(uriString: String) : this(Uri.parse(uriString)) - - val lastPathSegment: String? = uri.lastPathSegment - val pathSegments: List = uri.pathSegments - val host: String? = uri.host - - override fun toString() = uri.toString() - fun getQueryParameter(key: String): String? = uri.getQueryParameter(key) - fun copy(path: String): UriWrapper { - val newUri = uri.buildUpon().path(path).build() - return this.copy(uri = newUri) - } -} diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtils.java b/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtils.java index 94247415..e8a0ade3 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtils.java +++ b/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtils.java @@ -17,6 +17,7 @@ import org.wordpress.android.mediapicker.MediaPickerFragment; import org.wordpress.android.mediapicker.R; import org.wordpress.android.mediapicker.model.EditImageData; +import org.wordpress.android.mediapicker.util.MediaUri; import java.io.File; import java.io.IOException; @@ -63,7 +64,7 @@ private static Intent prepareChooserIntent( if (openSystemPicker.getAllowMultipleSelection()) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } - return Intent.createChooser(intent, UiHelper.INSTANCE.getTextOfUiString(context, chooserContext.getTitle())); + return Intent.createChooser(intent, context.getString(chooserContext.getTitle())); } public static void launchCamera(Activity activity, String applicationId, LaunchCameraCallback callback) { @@ -187,7 +188,7 @@ Uri fetchMedia(@NonNull Context context, @NonNull Uri mediaUri) { } } - public static List retrieveImageEditorResult(Intent data) { + public static List retrieveImageEditorResult(Intent data) { if (data != null && data.hasExtra(ARG_EDIT_IMAGE_DATA)) { return convertEditImageOutputToListOfUris( data.getParcelableArrayListExtra(ARG_EDIT_IMAGE_DATA) @@ -197,10 +198,12 @@ public static List retrieveImageEditorResult(Intent data) { } } - private static List convertEditImageOutputToListOfUris(List data) { - List uris = new ArrayList<>(data.size()); + private static List convertEditImageOutputToListOfUris(List data) { + List uris = new ArrayList<>(data.size()); for (EditImageData.OutputData item : data) { - uris.add(Uri.parse(item.getOutputFilePath())); + final Uri uri = Uri.parse(item.getOutputFilePath()); + final String s = MediaUriExtKt.asMediaUri(uri); + uris.add(new MediaUri(s)); } return uris; } diff --git a/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtilsWrapper.kt b/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtilsWrapper.kt index 6b584f5b..315ee634 100644 --- a/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtilsWrapper.kt +++ b/MediaPicker/src/main/java/org/wordpress/android/util/WPMediaUtilsWrapper.kt @@ -2,13 +2,10 @@ package org.wordpress.android.util import android.content.Context import android.net.Uri +import org.wordpress.android.mediapicker.util.MediaUri class WPMediaUtilsWrapper(private val context: Context) { - fun fetchMedia(mediaUri: Uri): Uri? { - return WPMediaUtils.fetchMedia(context, mediaUri) - } - - fun fetchMediaToUriWrapper(mediaUri: UriWrapper): UriWrapper? { - return WPMediaUtils.fetchMedia(context, mediaUri.uri)?.let { UriWrapper(it) } + fun fetchMedia(mediaUri: MediaUri): Uri? { + return WPMediaUtils.fetchMedia(context, Uri.parse(mediaUri.s)) } } diff --git a/build.gradle b/build.gradle index 1ab02755..ba09e6ae 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,6 @@ subprojects { apply from: "$project.rootDir/ktlint.gradle" dependencies { - ktlint "com.pinterest:ktlint:0.40.0" + ktlint "com.pinterest:ktlint:0.41.0" } } diff --git a/settings.gradle b/settings.gradle index 153e4011..c2460dce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ pluginManagement { plugins { id 'org.jetbrains.kotlin.android' version gradle.ext.kotlinVersion + id 'org.jetbrains.kotlin.jvm' version gradle.ext.kotlinVersion id 'org.jetbrains.kotlin.plugin.parcelize' version gradle.ext.kotlinVersion } repositories { @@ -20,6 +21,7 @@ pluginManagement { rootProject.name = "MediaPicker" include ':MediaPicker', + ':MediaPicker:domain', ':sampleapp' // Build cache is only enabled for CI, at least for now