diff --git a/build.gradle b/build.gradle index 85c6c894..40cfc266 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,11 @@ allprojects { tasks.withType(KotlinCompile).all { kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 allWarningsAsErrors = true + freeCompilerArgs += [ + "-opt-in=kotlin.RequiresOptIn" + ] } } } @@ -47,27 +51,27 @@ subprojects { ext { minSdkVersion = 24 - compileSdkVersion = 33 - targetSdkVersion = 33 + compileSdkVersion = 34 + targetSdkVersion = 34 } ext { // libs - wordPressFluxCVersion = 'develop-fa819801570505a0e9b4f7f226bb704b88ccc1d2' - wordpressUtilsVersion = "2.4.0" + wordPressFluxCVersion = 'trunk-7ea7465680431fa4df5f0433d4c8b8395055b997' + wordpressUtilsVersion = "2.6.0" // main - androidxAppcompatVersion = '1.4.1' - androidxCoreVersion = '1.7.0' + androidxAppcompatVersion = '1.4.2' + androidxCoreVersion = '1.12.0' androidxDatastoreVersion = '1.0.0' - androidxConstraintlayoutVersion = '2.1.1' - androidxLifecycleVersion = '2.4.0' - androidxNavigationVersion = '2.3.5' + androidxConstraintlayoutVersion = '2.1.4' + androidxLifecycleVersion = '2.6.2' + androidxNavigationVersion = '2.5.3' androidxSwipeToRefreshVersion = '1.1.0' chrisbanesPhotoviewVersion = '2.3.0' - glideVersion = '4.12.0' - googleMaterialVersion = '1.5.0' - kotlinxCoroutinesVersion = '1.5.2' + glideVersion = '4.13.2' + googleMaterialVersion = '1.6.1' + kotlinxCoroutinesVersion = '1.6.4' squareupRetrofitVersion = "2.9.0" // other diff --git a/mediapicker/build.gradle b/mediapicker/build.gradle index de03b5e9..82d1c912 100644 --- a/mediapicker/build.gradle +++ b/mediapicker/build.gradle @@ -11,7 +11,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -26,9 +26,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 - } lint { warningsAsErrors true diff --git a/mediapicker/domain/build.gradle b/mediapicker/domain/build.gradle index 579aadb1..81d02989 100644 --- a/mediapicker/domain/build.gradle +++ b/mediapicker/domain/build.gradle @@ -10,7 +10,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker.api" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -20,6 +20,11 @@ android { lint { warningsAsErrors true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/mediapicker/domain/src/main/java/org/wordpress/android/mediapicker/model/MediaItem.kt b/mediapicker/domain/src/main/java/org/wordpress/android/mediapicker/model/MediaItem.kt index 853ed491..58582180 100644 --- a/mediapicker/domain/src/main/java/org/wordpress/android/mediapicker/model/MediaItem.kt +++ b/mediapicker/domain/src/main/java/org/wordpress/android/mediapicker/model/MediaItem.kt @@ -30,9 +30,9 @@ data class MediaItem( @Parcelize data class RemoteMedia( val id: Long, - val name: String, + val name: String?, val url: String, - val date: String + val date: String? ) : Identifier(REMOTE_MEDIA) @Parcelize diff --git a/mediapicker/source-camera/build.gradle b/mediapicker/source-camera/build.gradle index d05bdded..28108608 100644 --- a/mediapicker/source-camera/build.gradle +++ b/mediapicker/source-camera/build.gradle @@ -8,7 +8,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker.source.camera" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -18,6 +18,11 @@ android { lint { warningsAsErrors true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/mediapicker/source-camera/src/main/java/org/wordpress/android/mediapicker/source/camera/CameraMediaPickerSetup.kt b/mediapicker/source-camera/src/main/java/org/wordpress/android/mediapicker/source/camera/CameraMediaPickerSetup.kt index a55b8730..d56fd8a8 100644 --- a/mediapicker/source-camera/src/main/java/org/wordpress/android/mediapicker/source/camera/CameraMediaPickerSetup.kt +++ b/mediapicker/source-camera/src/main/java/org/wordpress/android/mediapicker/source/camera/CameraMediaPickerSetup.kt @@ -4,15 +4,13 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.CAMERA import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.HIDDEN -class CameraMediaPickerSetup private constructor() { - companion object { - fun build(): MediaPickerSetup { - return MediaPickerSetup( - primaryDataSource = CAMERA, - isMultiSelectEnabled = false, - areResultsQueued = false, - searchMode = HIDDEN - ) - } +object CameraMediaPickerSetup { + fun build(): MediaPickerSetup { + return MediaPickerSetup( + primaryDataSource = CAMERA, + isMultiSelectEnabled = false, + areResultsQueued = false, + searchMode = HIDDEN + ) } } diff --git a/mediapicker/source-device/build.gradle b/mediapicker/source-device/build.gradle index ad2c9063..ccc8359d 100644 --- a/mediapicker/source-device/build.gradle +++ b/mediapicker/source-device/build.gradle @@ -11,7 +11,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker.source.device" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -21,6 +21,11 @@ android { lint { warningsAsErrors true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/mediapicker/source-device/src/main/java/org/wordpress/android/mediapicker/source/device/DeviceMediaPickerSetup.kt b/mediapicker/source-device/src/main/java/org/wordpress/android/mediapicker/source/device/DeviceMediaPickerSetup.kt index 3b1679c9..f5482cd0 100644 --- a/mediapicker/source-device/src/main/java/org/wordpress/android/mediapicker/source/device/DeviceMediaPickerSetup.kt +++ b/mediapicker/source-device/src/main/java/org/wordpress/android/mediapicker/source/device/DeviceMediaPickerSetup.kt @@ -8,28 +8,16 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.HIDDEN import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.VISIBLE_UNTOGGLED import org.wordpress.android.mediapicker.model.MediaTypes -class DeviceMediaPickerSetup private constructor() { - companion object { - fun buildMediaPicker(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { - return MediaPickerSetup( - primaryDataSource = DEVICE, - isMultiSelectEnabled = canMultiSelect, - areResultsQueued = false, - searchMode = VISIBLE_UNTOGGLED, - availableDataSources = setOf(SYSTEM_PICKER), - allowedTypes = mediaTypes.allowedTypes, - title = R.string.photo_picker_title - ) - } - - fun buildSystemPicker(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { - return MediaPickerSetup( - primaryDataSource = SYSTEM_PICKER, - isMultiSelectEnabled = canMultiSelect, - areResultsQueued = false, - searchMode = HIDDEN, - allowedTypes = mediaTypes.allowedTypes - ) - } +object DeviceMediaPickerSetup { + fun build(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { + return MediaPickerSetup( + primaryDataSource = DEVICE, + isMultiSelectEnabled = canMultiSelect, + areResultsQueued = false, + searchMode = VISIBLE_UNTOGGLED, + availableDataSources = setOf(SYSTEM_PICKER), + allowedTypes = mediaTypes.allowedTypes, + title = R.string.photo_picker_title + ) } } diff --git a/mediapicker/source-gif/build.gradle b/mediapicker/source-gif/build.gradle index b2f7621e..47dc3342 100644 --- a/mediapicker/source-gif/build.gradle +++ b/mediapicker/source-gif/build.gradle @@ -11,7 +11,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker.source.gif" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -21,6 +21,11 @@ android { lint { warningsAsErrors true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/mediapicker/source-gif/src/main/java/org/wordpress/android/mediapicker/source/gif/GifMediaPickerSetup.kt b/mediapicker/source-gif/src/main/java/org/wordpress/android/mediapicker/source/gif/GifMediaPickerSetup.kt index 4a8fc90b..27715ca5 100644 --- a/mediapicker/source-gif/src/main/java/org/wordpress/android/mediapicker/source/gif/GifMediaPickerSetup.kt +++ b/mediapicker/source-gif/src/main/java/org/wordpress/android/mediapicker/source/gif/GifMediaPickerSetup.kt @@ -5,17 +5,15 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.GIF_LIB import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.VISIBLE_TOGGLED import org.wordpress.android.mediapicker.model.MediaType.IMAGE -class GifMediaPickerSetup private constructor() { - companion object { - fun build(canMultiSelect: Boolean): MediaPickerSetup { - return MediaPickerSetup( - primaryDataSource = GIF_LIBRARY, - isMultiSelectEnabled = canMultiSelect, - allowedTypes = setOf(IMAGE), - areResultsQueued = false, - searchMode = VISIBLE_TOGGLED, - title = R.string.photo_picker_gif - ) - } +object GifMediaPickerSetup { + fun build(canMultiSelect: Boolean): MediaPickerSetup { + return MediaPickerSetup( + primaryDataSource = GIF_LIBRARY, + isMultiSelectEnabled = canMultiSelect, + allowedTypes = setOf(IMAGE), + areResultsQueued = false, + searchMode = VISIBLE_TOGGLED, + title = R.string.photo_picker_gif + ) } } diff --git a/mediapicker/source-wordpress/build.gradle b/mediapicker/source-wordpress/build.gradle index f0bb0c4f..86eb790a 100644 --- a/mediapicker/source-wordpress/build.gradle +++ b/mediapicker/source-wordpress/build.gradle @@ -11,7 +11,7 @@ plugins { android { namespace "org.wordpress.android.mediapicker.source.wordpress" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { minSdkVersion rootProject.minSdkVersion @@ -21,6 +21,11 @@ android { lint { warningsAsErrors true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibraryPickerSetup.kt b/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibraryPickerSetup.kt index be3ccb8c..300436d0 100644 --- a/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibraryPickerSetup.kt +++ b/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibraryPickerSetup.kt @@ -5,17 +5,15 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.WP_MEDI import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.HIDDEN import org.wordpress.android.mediapicker.model.MediaTypes -class MediaLibraryPickerSetup private constructor() { - companion object { - fun build(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { - return MediaPickerSetup( - primaryDataSource = WP_MEDIA_LIBRARY, - isMultiSelectEnabled = canMultiSelect, - allowedTypes = mediaTypes.allowedTypes, - areResultsQueued = false, - searchMode = HIDDEN, - title = R.string.media_library_title - ) - } +object MediaLibraryPickerSetup { + fun build(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { + return MediaPickerSetup( + primaryDataSource = WP_MEDIA_LIBRARY, + isMultiSelectEnabled = canMultiSelect, + allowedTypes = mediaTypes.allowedTypes, + areResultsQueued = false, + searchMode = HIDDEN, + title = R.string.media_library_title + ) } } diff --git a/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibrarySource.kt b/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibrarySource.kt index b9074587..1e638667 100644 --- a/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibrarySource.kt +++ b/mediapicker/source-wordpress/src/main/java/org/wordpress/android/mediapicker/source/wordpress/MediaLibrarySource.kt @@ -122,7 +122,7 @@ class MediaLibrarySource( } private fun List.toMediaItems(mediaType: MediaType): List { - return this.filter { it.url != null }.map { mediaModel -> + return this.map { mediaModel -> MediaItem( RemoteMedia( mediaModel.mediaId, diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/setup/SystemMediaPickerSetup.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/setup/SystemMediaPickerSetup.kt new file mode 100644 index 00000000..19addb8c --- /dev/null +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/setup/SystemMediaPickerSetup.kt @@ -0,0 +1,18 @@ +package org.wordpress.android.mediapicker.setup + +import org.wordpress.android.mediapicker.api.MediaPickerSetup +import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.SYSTEM_PICKER +import org.wordpress.android.mediapicker.api.MediaPickerSetup.SearchMode.HIDDEN +import org.wordpress.android.mediapicker.model.MediaTypes + +object SystemMediaPickerSetup { + fun build(mediaTypes: MediaTypes, canMultiSelect: Boolean): MediaPickerSetup { + return MediaPickerSetup( + primaryDataSource = SYSTEM_PICKER, + isMultiSelectEnabled = canMultiSelect, + areResultsQueued = false, + searchMode = HIDDEN, + allowedTypes = mediaTypes.allowedTypes + ) + } +} diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerActionModeCallback.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerActionModeCallback.kt index 97e5941c..5b31f236 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerActionModeCallback.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerActionModeCallback.kt @@ -74,5 +74,6 @@ internal class MediaPickerActionModeCallback(private val viewModel: MediaPickerV lifecycleRegistry.handleLifecycleEvent(ON_STOP) } - override fun getLifecycle(): Lifecycle = lifecycleRegistry + override val lifecycle: Lifecycle + get() = lifecycleRegistry } diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerFragment.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerFragment.kt index 767cd7c2..5d4a8982 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerFragment.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaPickerFragment.kt @@ -19,6 +19,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.core.text.HtmlCompat +import androidx.core.view.MenuProvider import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -72,7 +73,7 @@ import org.wordpress.android.mediapicker.viewmodel.observeEvent import javax.inject.Inject @AndroidEntryPoint -internal class MediaPickerFragment : Fragment() { +internal class MediaPickerFragment : Fragment(), MenuProvider { companion object { private const val KEY_SELECTED_IDS = "selected_ids" private const val KEY_LIST_STATE = "list_state" @@ -130,11 +131,6 @@ internal class MediaPickerFragment : Fragment() { } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -204,6 +200,8 @@ internal class MediaPickerFragment : Fragment() { recycler.setEmptyView(actionableEmptyView) recycler.setHasFixedSize(true) + requireActivity().addMenuProvider(this@MediaPickerFragment, viewLifecycleOwner) + pullToRefresh.apply { setOnRefreshListener { viewModel.onPullToRefresh() @@ -303,60 +301,6 @@ internal class MediaPickerFragment : Fragment() { binding = null } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.media_picker_lib_menu, menu) - - val searchMenuItem = checkNotNull(menu.findItem(R.id.action_search)) { - "Menu does not contain mandatory search item" - } - val browseMenuItem = checkNotNull(menu.findItem(R.id.mnu_browse_item)) { - "Menu does not contain mandatory browse item" - } - val deviceMenuItem = checkNotNull(menu.findItem(R.id.mnu_choose_from_device)) { - "Menu does not contain device library item" - } - val tenorLibraryMenuItem = checkNotNull(menu.findItem(R.id.mnu_choose_from_tenor_library)) { - "Menu does not contain mandatory tenor library item" - } - - initializeSearchView(searchMenuItem) - viewModel.uiState.observe(viewLifecycleOwner) { uiState -> - val searchView = searchMenuItem.actionView as SearchView - - if (uiState.searchUiModel is SearchUiModel.Expanded && !searchMenuItem.isActionViewExpanded) { - searchMenuItem.expandActionView() - searchView.maxWidth = Integer.MAX_VALUE - searchView.setQuery(uiState.searchUiModel.filter, true) - searchView.setOnCloseListener { !uiState.searchUiModel.closeable } - } else if (uiState.searchUiModel is SearchUiModel.Collapsed && searchMenuItem.isActionViewExpanded) { - searchMenuItem.collapseActionView() - } - - searchMenuItem.isVisible = uiState.searchUiModel !is SearchUiModel.Hidden - - val shownActions = uiState.browseMenuUiModel.shownActions - browseMenuItem.isVisible = shownActions.contains(SYSTEM_PICKER) - deviceMenuItem.isVisible = shownActions.contains(DEVICE) - tenorLibraryMenuItem.isVisible = shownActions.contains(GIF_LIBRARY) - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.mnu_browse_item -> { - viewModel.onMenuItemClicked(SYSTEM_PICKER) - } - R.id.mnu_choose_from_device -> { - viewModel.onMenuItemClicked(DEVICE) - } - R.id.mnu_choose_from_tenor_library -> { - viewModel.onMenuItemClicked(GIF_LIBRARY) - } - } - return true - } - private fun initializeSearchView(actionMenuItem: MenuItem) { var isExpanding = false actionMenuItem.setOnActionExpandListener(object : OnActionExpandListener { @@ -588,4 +532,63 @@ internal class MediaPickerFragment : Fragment() { private fun requestMediaPermissions(permissions: List) { mediaPermissionRequest.launch(permissions.map { it.toString() }.toTypedArray()) } + + override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { + menu.clear() + + inflater.inflate(R.menu.media_picker_lib_menu, menu) + } + + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + + val searchMenuItem = checkNotNull(menu.findItem(R.id.action_search)) { + "Menu does not contain mandatory search item" + } + val browseMenuItem = checkNotNull(menu.findItem(R.id.mnu_browse_item)) { + "Menu does not contain mandatory browse item" + } + val deviceMenuItem = checkNotNull(menu.findItem(R.id.mnu_choose_from_device)) { + "Menu does not contain device library item" + } + val tenorLibraryMenuItem = checkNotNull(menu.findItem(R.id.mnu_choose_from_tenor_library)) { + "Menu does not contain mandatory tenor library item" + } + + initializeSearchView(searchMenuItem) + viewModel.uiState.observe(viewLifecycleOwner) { uiState -> + val searchView = searchMenuItem.actionView as SearchView + + if (uiState.searchUiModel is SearchUiModel.Expanded && !searchMenuItem.isActionViewExpanded) { + searchMenuItem.expandActionView() + searchView.maxWidth = Integer.MAX_VALUE + searchView.setQuery(uiState.searchUiModel.filter, true) + searchView.setOnCloseListener { !uiState.searchUiModel.closeable } + } else if (uiState.searchUiModel is SearchUiModel.Collapsed && searchMenuItem.isActionViewExpanded) { + searchMenuItem.collapseActionView() + } + + searchMenuItem.isVisible = uiState.searchUiModel !is SearchUiModel.Hidden + + val shownActions = uiState.browseMenuUiModel.shownActions + browseMenuItem.isVisible = shownActions.contains(SYSTEM_PICKER) + deviceMenuItem.isVisible = shownActions.contains(DEVICE) + tenorLibraryMenuItem.isVisible = shownActions.contains(GIF_LIBRARY) + } + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + when (menuItem.itemId) { + R.id.mnu_browse_item -> { + viewModel.onMenuItemClicked(SYSTEM_PICKER) + } + R.id.mnu_choose_from_device -> { + viewModel.onMenuItemClicked(DEVICE) + } + R.id.mnu_choose_from_tenor_library -> { + viewModel.onMenuItemClicked(GIF_LIBRARY) + } + } + return true + } } diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaViewerFragment.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaViewerFragment.kt index 990c9956..1df6421b 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaViewerFragment.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/ui/MediaViewerFragment.kt @@ -14,6 +14,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.mediapicker.R import org.wordpress.android.mediapicker.databinding.MediaPickerLibViewerFragmentBinding @@ -105,7 +106,7 @@ internal class MediaViewerFragment : override fun onLoadFailed( e: GlideException?, model: Any?, - target: com.bumptech.glide.request.target.Target?, + target: Target, isFirstResource: Boolean ): Boolean { binding.progressBar.isVisible = false @@ -116,10 +117,10 @@ internal class MediaViewerFragment : * Glide has loaded the image, hide the progress bar */ override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: com.bumptech.glide.request.target.Target?, - dataSource: DataSource?, + resource: Drawable, + model: Any, + target: Target?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { binding.progressBar.isVisible = false diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/ContextExtensions.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/ContextExtensions.kt index 11855f7f..ce583c15 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/ContextExtensions.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/ContextExtensions.kt @@ -37,7 +37,3 @@ fun Context.getColorStateListFromAttribute(@AttrRes attribute: Int): ColorStateL getColorResIdFromAttribute(attribute).let { AppCompatResources.getColorStateList(this, it) } - -// https://developer.android.com/reference/android/content/res/Configuration.html#locale -val Context.currentLocale: Locale - get() = ConfigurationCompat.getLocales(resources.configuration)[0] diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/LiveDataUtils.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/LiveDataUtils.kt index 4f9f5246..54157de2 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/LiveDataUtils.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/util/LiveDataUtils.kt @@ -2,116 +2,6 @@ package org.wordpress.android.mediapicker.util import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.Transformations -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * Merges two LiveData sources using a given function. The function returns an object of a new type. - * @param sourceA first source - * @param sourceB second source - * @return new data source - */ -fun mergeAsyncNotNull( - scope: CoroutineScope, - sourceA: LiveData, - sourceB: LiveData, - distinct: Boolean = true, - merger: suspend (T, U) -> V -): LiveData { - val mediator = MediatorLiveData>() - mediator.addSource(sourceA) { - if (!distinct || mediator.value?.first != it) { - mediator.value = it to mediator.value?.second - } - } - mediator.addSource(sourceB) { - if (!distinct || mediator.value?.second != it) { - mediator.value = mediator.value?.first to it - } - } - return mediator.mapAsync(scope) { (dataA, dataB) -> - if (dataA != null && dataB != null) { - merger(dataA, dataB) - } else { - null - } - } -} - -/** - * Merges two LiveData sources using a given function. The function returns an object of a new type. - * @param sourceA first source - * @param sourceB second source - * @return new data source - */ -fun merge(sourceA: LiveData, sourceB: LiveData, merger: (T?, U?) -> V?): LiveData { - val mediator = MediatorLiveData>() - mediator.addSource(sourceA) { - mediator.value = Pair(it, mediator.value?.second) - } - mediator.addSource(sourceB) { - mediator.value = Pair(mediator.value?.first, it) - } - return mediator.map { (dataA, dataB) -> merger(dataA, dataB) } -} - -/** - * Merges LiveData sources using a given function. The function returns an object of a new type. - * @param sources all source - * @return new data source - */ -fun merge(vararg sources: LiveData?): MediatorLiveData { - val mediator = MediatorLiveData() - for (source in sources) { - if (source != null) { - mediator.addSource(source) { - mediator.value = it - } - } - } - return mediator -} - -/** - * Merges three LiveData sources using a given function. The function returns an object of a new type. - * @param sourceA first source - * @param sourceB second source - * @param sourceC third source - * @return new data source - */ -fun merge( - sourceA: LiveData, - sourceB: LiveData, - sourceC: LiveData, - distinct: Boolean = false, - merger: (S?, T?, U?) -> V -): MediatorLiveData { - data class TripleContainer(val first: S? = null, val second: T? = null, val third: U? = null) - - val mediator = MediatorLiveData() - mediator.value = TripleContainer() - mediator.addSource(sourceA) { - val container = mediator.value - if (container?.first != it || !distinct) { - mediator.value = container?.copy(first = it) - } - } - mediator.addSource(sourceB) { - val container = mediator.value - if (container?.second != it || !distinct) { - mediator.value = container?.copy(second = it) - } - } - mediator.addSource(sourceC) { - val container = mediator.value - if (container?.third != it || !distinct) { - mediator.value = container?.copy(third = it) - } - } - return mediator.map { (first, second, third) -> merger(first, second, third) } -} /** * Merges four LiveData sources using a given function. The function returns an object of a new type. @@ -165,92 +55,6 @@ fun merge( return mediator.map { (first, second, third, fourth) -> merger(first, second, third, fourth) } } -/** - * Merges five LiveData sources using a given function. The function returns an object of a new type. - * @param sourceA first source - * @param sourceB second source - * @param sourceC third source - * @param sourceD fourth source - * @param sourceE fifth source - * @return new data source - */ -@Suppress("LongParameterList") -fun merge( - sourceA: LiveData, - sourceB: LiveData, - sourceC: LiveData, - sourceD: LiveData, - sourceE: LiveData, - distinct: Boolean = false, - merger: (S?, T?, U?, V?, W?) -> X? -): LiveData { - data class FiveItemContainer( - val first: S? = null, - val second: T? = null, - val third: U? = null, - val fourth: V? = null, - val fifth: W? = null, - ) - - val mediator = MediatorLiveData() - mediator.value = FiveItemContainer() - mediator.addSource(sourceA) { - val container = mediator.value - if (container?.first != it || !distinct) { - mediator.value = container?.copy(first = it) - } - } - mediator.addSource(sourceB) { - val container = mediator.value - if (container?.second != it || !distinct) { - mediator.value = container?.copy(second = it) - } - } - mediator.addSource(sourceC) { - val container = mediator.value - if (container?.third != it || !distinct) { - mediator.value = container?.copy(third = it) - } - } - mediator.addSource(sourceD) { - val container = mediator.value - if (container?.fourth != it || !distinct) { - mediator.value = container?.copy(fourth = it) - } - } - mediator.addSource(sourceE) { - val container = mediator.value - if (container?.fifth != it || !distinct) { - mediator.value = container?.copy(fifth = it) - } - } - return mediator.map { (first, second, third, fourth, fifth) -> merger(first, second, third, fourth, fifth) } -} - -/** - * Combines all the LiveData values in the given Map into one LiveData with the map of values. - * @param sources is a map of all the live data sources in a map by a given key - * @return one livedata instance that combines all the values into one map - */ -fun combineMap(sources: Map>): MediatorLiveData> { - val mediator = MediatorLiveData>() - mediator.value = mutableMapOf() - for (source in sources) { - mediator.addSource(source.value) { updatedValue -> - val value = mediator.value ?: mutableMapOf() - if (value[source.key] != updatedValue) { - if (updatedValue != null) { - value[source.key] = updatedValue - } else { - value.remove(source.key) - } - mediator.value = value - } - } - } - return mediator.map { it.toMap() } -} - /** * Simple wrapper of the map utility method that is null safe */ @@ -260,38 +64,6 @@ fun LiveData.map(mapper: (T) -> U?): MediatorLiveData { return result } -/** - * A wrapper of the map utility method that is null safe and runs the mapping on a background thread - * @param scope defines the scope to run mapping in - */ -fun LiveData.mapAsync(scope: CoroutineScope, mapper: suspend (T) -> U?): MediatorLiveData { - val result = MediatorLiveData() - result.addSource(this) { x -> - scope.launch { - val mappedValue = x?.let { mapper(x) } - result.postValue(mappedValue) - } - } - return result -} - -/** - * Calls the specified function [block] with `this` value as its receiver and returns new instance of LiveData. - */ -fun LiveData.perform(block: LiveData.(T) -> Unit): LiveData { - return Transformations.map(this) { - block(it) - return@map it - } -} - -/** - * Simple wrapper of the map utility method that is null safe - */ -fun LiveData.mapNullable(mapper: (T?) -> U?): LiveData { - return Transformations.map(this) { mapper(it) } -} - /** * This method ensures that the LiveData instance doesn't emit the same item twice */ @@ -304,88 +76,3 @@ fun LiveData.distinct(): MediatorLiveData { } return mediatorLiveData } - -/** - * This method folds previous and updated value in a result - */ -fun LiveData.fold(action: (previous: T, current: T) -> T): MediatorLiveData { - val mediatorLiveData: MediatorLiveData = MediatorLiveData() - mediatorLiveData.addSource(this) { - if (it != null) { - val previous = mediatorLiveData.value - mediatorLiveData.value = if (previous != null) action(previous, it) else it - } - } - return mediatorLiveData -} - -/** - * A helper function that filters data and only emits what fits the predicate - * @param predicate - * @return filtered result - */ -fun LiveData.filter(predicate: (T) -> Boolean): LiveData { - val mediator = MediatorLiveData() - mediator.addSource(this) { - if (it != null && predicate(it)) { - mediator.value = it - } - } - return mediator -} - -/** - * Suppresses the first n items by this [LiveData]. - * - * Consider this for example: - * - * ``` - * val connectionStatusLiveData = getConnectionStatusLiveData() - * connectionStatusLiveData.skip(1).observe(this, Observer { - * refresh() - * }) - * ``` - * - * The first value emitted by `connectionStatusLiveData` would be ignored and [Observer] will not be called. - */ -fun LiveData.skip(times: Int): LiveData { - check(times > 0) { "The number of times to skip must be greater than 0" } - - var skipped = 0 - val mediator = MediatorLiveData() - mediator.addSource(this) { value -> - skipped += 1 - - if (skipped > times) { - mediator.value = value - } - } - - return mediator -} - -/** - * A helper function that scans sources into a single state - * @param initialState the initial state passed into the scan function - * @param sources producing partial states to be merged into a single state - * @param distinct true if all the emitted items should be distinct - * @param scanFunction merges the partial state into the single state - * @return merged partial states into the single state - */ -fun scan( - initialState: U, - vararg sources: LiveData, - distinct: Boolean = true, - scanFunction: (U, T) -> U -): MediatorLiveData { - val mediator = MediatorLiveData().also { it.value = initialState } - for (source in sources) { - mediator.addSource(source) { - val currentState = mediator.value ?: initialState - if (it != null && currentState != it || !distinct) { - mediator.value = scanFunction(currentState, it) - } - } - } - return mediator -} diff --git a/mediapicker/src/main/java/org/wordpress/android/mediapicker/viewmodel/MediaPickerViewModel.kt b/mediapicker/src/main/java/org/wordpress/android/mediapicker/viewmodel/MediaPickerViewModel.kt index 9bfa0cc4..7fe97b2f 100644 --- a/mediapicker/src/main/java/org/wordpress/android/mediapicker/viewmodel/MediaPickerViewModel.kt +++ b/mediapicker/src/main/java/org/wordpress/android/mediapicker/viewmodel/MediaPickerViewModel.kt @@ -9,7 +9,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.wordpress.android.mediapicker.MediaManager import org.wordpress.android.mediapicker.MediaPickerTracker @@ -206,7 +205,7 @@ internal class MediaPickerViewModel @Inject constructor( val data = domainModel?.domainItems return if (softAskRequest?.show == true) { PhotoListUiModel.Hidden - } else if (data != null && data.isNotEmpty()) { + } else if (!data.isNullOrEmpty()) { val uiItems = data.map { val showOrderCounter = mediaPickerSetup.isMultiSelectEnabled val toggleAction = ToggleAction(it.identifier, showOrderCounter, ::onItemToggled) diff --git a/sampleapp/build.gradle b/sampleapp/build.gradle index 7f36e892..6f7a73a2 100644 --- a/sampleapp/build.gradle +++ b/sampleapp/build.gradle @@ -14,7 +14,7 @@ repositories { android { namespace "org.wordpress.android.sampleapp" - compileSdkVersion rootProject.compileSdkVersion + compileSdk rootProject.compileSdkVersion defaultConfig { applicationId "org.wordpress.android.sampleapp" @@ -31,9 +31,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } lint { warningsAsErrors true @@ -42,7 +39,7 @@ android { } packagingOptions { - exclude 'META-INF/gradle/incremental.annotation.processors' + exclude("META-INF/gradle/incremental.annotation.processors") } kapt { @@ -69,6 +66,5 @@ dependencies { implementation "com.google.android.material:material:$googleMaterialVersion" implementation "com.google.dagger:hilt-android:$gradle.ext.daggerVersion" - implementation "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion" kapt "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion" } diff --git a/sampleapp/src/main/java/org/wordpress/android/sampleapp/MediaPickerSetupFactory.kt b/sampleapp/src/main/java/org/wordpress/android/sampleapp/MediaPickerSetupFactory.kt index 151eccd4..94ea0c85 100644 --- a/sampleapp/src/main/java/org/wordpress/android/sampleapp/MediaPickerSetupFactory.kt +++ b/sampleapp/src/main/java/org/wordpress/android/sampleapp/MediaPickerSetupFactory.kt @@ -7,6 +7,7 @@ import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.DEVICE import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.GIF_LIBRARY import org.wordpress.android.mediapicker.api.MediaPickerSetup.DataSource.SYSTEM_PICKER import org.wordpress.android.mediapicker.model.MediaTypes +import org.wordpress.android.mediapicker.setup.SystemMediaPickerSetup import org.wordpress.android.mediapicker.source.camera.CameraMediaPickerSetup import org.wordpress.android.mediapicker.source.device.DeviceMediaPickerSetup import org.wordpress.android.mediapicker.source.gif.GifMediaPickerSetup @@ -18,11 +19,11 @@ class MediaPickerSetupFactory @Inject constructor() : MediaPickerSetup.Factory { return when (source) { GIF_LIBRARY -> GifMediaPickerSetup.build(canMultiSelect = true) CAMERA -> CameraMediaPickerSetup.build() - DEVICE -> DeviceMediaPickerSetup.buildMediaPicker( + DEVICE -> DeviceMediaPickerSetup.build( mediaTypes = mediaTypes, canMultiSelect = true ) - SYSTEM_PICKER -> DeviceMediaPickerSetup.buildSystemPicker( + SYSTEM_PICKER -> SystemMediaPickerSetup.build( mediaTypes = mediaTypes, canMultiSelect = false ) diff --git a/settings.gradle b/settings.gradle index 8a2393c2..534eccd6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ pluginManagement { - gradle.ext.kotlinVersion = '1.6.10' - gradle.ext.daggerVersion = '2.42' + gradle.ext.kotlinVersion = '1.8.21' + gradle.ext.daggerVersion = '2.45' gradle.ext.agpVersion = '8.1.0' gradle.ext.detektVersion = '1.19.0' gradle.ext.automatticPublishToS3Version = '0.8.0'