Skip to content

Commit

Permalink
Merge pull request #5 from shiki/issue/8627-giphy-empty-view
Browse files Browse the repository at this point in the history
Giphy Picker: Add Empty View
  • Loading branch information
kwonye authored Dec 13, 2018
2 parents c40e2c5 + d58c9c9 commit a489172
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ import org.wordpress.android.widgets.WPTextView
* - Title describing cause for empty state (required)
* - Subtitle detailing cause for empty state (optional)
* - Button providing action to take (optional)
* - Bottom Image which can be used for attribution logos (optional)
*/
class ActionableEmptyView : LinearLayout {
lateinit var button: AppCompatButton
lateinit var image: ImageView
lateinit var layout: View
lateinit var subtitle: WPTextView
lateinit var title: WPTextView
/**
* Image shown at the bottom after the subtitle.
*
* This can be used for attribution logos. This is [View.GONE] by default.
*/
lateinit var bottomImage: ImageView

constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initView(context, attrs)
Expand All @@ -45,6 +52,7 @@ class ActionableEmptyView : LinearLayout {
title = layout.findViewById(R.id.title)
subtitle = layout.findViewById(R.id.subtitle)
button = layout.findViewById(R.id.button)
bottomImage = layout.findViewById(R.id.bottom_image)

attrs.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.ActionableEmptyView, 0, 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,22 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.media_picker_activity.*
import org.wordpress.android.R
import org.wordpress.android.R.string
import org.wordpress.android.WordPress
import org.wordpress.android.ui.ActionableEmptyView
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.ui.giphy.GiphyMediaViewHolder.ThumbnailViewDimensions
import org.wordpress.android.ui.media.MediaPreviewActivity
import org.wordpress.android.util.AniUtils
import org.wordpress.android.util.DisplayUtils
import org.wordpress.android.util.getDistinct
import org.wordpress.android.util.ToastUtils
import org.wordpress.android.util.image.ImageManager
import org.wordpress.android.viewmodel.ViewModelFactory
import org.wordpress.android.viewmodel.giphy.GiphyMediaViewModel
import org.wordpress.android.viewmodel.giphy.GiphyPickerViewModel
import org.wordpress.android.viewmodel.giphy.GiphyPickerViewModel.EmptyDisplayMode
import org.wordpress.android.viewmodel.giphy.GiphyPickerViewModel.State
import javax.inject.Inject

Expand Down Expand Up @@ -69,6 +73,7 @@ class GiphyPickerActivity : AppCompatActivity() {
initializeRecyclerView()
initializeSearchView()
initializeSelectionBar()
initializeEmptyView()
initializePreviewHandlers()
initializeDownloadHandlers()
initializeStateChangeHandlers()
Expand Down Expand Up @@ -108,7 +113,7 @@ class GiphyPickerActivity : AppCompatActivity() {
* Configure the search view to execute search when the keyboard's Done button is pressed.
*/
private fun initializeSearchView() {
search_view.queryHint = getString(R.string.giphy_search_hint)
search_view.queryHint = getString(R.string.giphy_picker_search_hint)

search_view.setOnQueryTextListener(object : OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
Expand Down Expand Up @@ -167,6 +172,46 @@ class GiphyPickerActivity : AppCompatActivity() {
})
}

/**
* Set up showing and hiding of the empty view depending on the search results
*/
private fun initializeEmptyView() {
val emptyView: ActionableEmptyView = actionable_empty_view
emptyView.run {
image.setImageResource(R.drawable.img_illustration_media_105dp)
bottomImage.setImageResource(R.drawable.giphy_attribution_100dp)
bottomImage.contentDescription = getString(string.giphy_powered_by_giphy)
}

viewModel.emptyDisplayMode.getDistinct().observe(this, Observer { emptyDisplayMode ->
when (emptyDisplayMode) {
EmptyDisplayMode.HIDDEN -> {
emptyView.visibility = View.GONE
}
EmptyDisplayMode.VISIBLE_NO_SEARCH_RESULTS -> {
with(emptyView) {
updateLayoutForSearch(isSearching = true, topMargin = 0)

visibility = View.VISIBLE
title.setText(R.string.giphy_picker_empty_search_list)
image.visibility = View.GONE
bottomImage.visibility = View.GONE
}
}
EmptyDisplayMode.VISIBLE_NO_SEARCH_QUERY -> {
with(emptyView) {
updateLayoutForSearch(isSearching = false, topMargin = 0)

visibility = View.VISIBLE
title.setText(R.string.giphy_picker_initial_empty_text)
image.visibility = View.VISIBLE
bottomImage.visibility = View.VISIBLE
}
}
}
})
}

/**
* Set up listener for the Preview button
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ class GiphyPickerDataSourceFactory @Inject constructor() : Factory<Int, GiphyMed
*/
private val apiClient: GPHApiClient by lazy { GPHApiClient(BuildConfig.GIPHY_API_KEY) }

private var searchQuery: String = ""
/**
* The active search query.
*
* When changed, the current [GiphyPickerDataSource] will be invalidated. A new API search will be performed.
*/
var searchQuery: String = ""
set(value) {
field = value
dataSource?.invalidate()
}

/**
* The last [dataSource] that was created
Expand All @@ -32,14 +41,6 @@ class GiphyPickerDataSourceFactory @Inject constructor() : Factory<Int, GiphyMed
*/
private var dataSource: DataSource<Int, GiphyMediaViewModel>? = null

/**
* Set the current [searchQuery] and invalidate the current [GiphyPickerDataSource]
*/
fun setSearchQuery(searchQuery: String) {
this.searchQuery = searchQuery
dataSource?.invalidate()
}

override fun create(): DataSource<Int, GiphyMediaViewModel> {
val dataSource = GiphyPickerDataSource(apiClient, searchQuery)
this.dataSource = dataSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import android.arch.paging.PagedList.BoundaryCallback
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.launch
import org.wordpress.android.R
Expand All @@ -31,6 +32,11 @@ class GiphyPickerViewModel @Inject constructor(
*/
private val dataSourceFactory: GiphyPickerDataSourceFactory
) : CoroutineScopedViewModel() {
/**
* A result of [downloadSelected] observed using the [downloadResult] LiveData
*/
data class DownloadResult(val mediaModels: List<MediaModel>? = null, val errorMessageStringResId: Int? = null)

enum class State {
/**
* The default state where interaction with the UI like selecting and searching is allowed
Expand All @@ -51,9 +57,27 @@ class GiphyPickerViewModel @Inject constructor(
}

/**
* A result of [downloadSelected] observed using the [downloadResult] LiveData
* Describes how an empty view UI should be displayed
*/
data class DownloadResult(val mediaModels: List<MediaModel>? = null, val errorMessageStringResId: Int? = null)
enum class EmptyDisplayMode {
HIDDEN,
/**
* Visible because the user has not performed a search or the search string is blank.
*/
VISIBLE_NO_SEARCH_QUERY,
/**
* Visible because the user has performed a search but there are no search results
*/
VISIBLE_NO_SEARCH_RESULTS
}

private val _emptyDisplayMode = MutableLiveData<EmptyDisplayMode>().apply {
value = EmptyDisplayMode.VISIBLE_NO_SEARCH_QUERY
}
/**
* Describes how the empty view UI should be displayed
*/
val emptyDisplayMode: LiveData<EmptyDisplayMode> = _emptyDisplayMode

private lateinit var site: SiteModel

Expand Down Expand Up @@ -90,7 +114,27 @@ class GiphyPickerViewModel @Inject constructor(
*/
val mediaViewModelPagedList: LiveData<PagedList<GiphyMediaViewModel>> by lazy {
val pagedListConfig = PagedList.Config.Builder().setEnablePlaceholders(true).setPageSize(30).build()
LivePagedListBuilder(dataSourceFactory, pagedListConfig).build()
LivePagedListBuilder(dataSourceFactory, pagedListConfig).setBoundaryCallback(pagedListBoundaryCallback).build()
}

/**
* Update the [emptyDisplayMode] depending on the number of API search results
*/
private val pagedListBoundaryCallback = object : BoundaryCallback<GiphyMediaViewModel>() {
override fun onZeroItemsLoaded() {
val visibility = if (dataSourceFactory.searchQuery.isBlank()) {
EmptyDisplayMode.VISIBLE_NO_SEARCH_QUERY
} else {
EmptyDisplayMode.VISIBLE_NO_SEARCH_RESULTS
}
_emptyDisplayMode.postValue(visibility)
super.onZeroItemsLoaded()
}

override fun onItemAtFrontLoaded(itemAtFront: GiphyMediaViewModel) {
_emptyDisplayMode.postValue(EmptyDisplayMode.HIDDEN)
super.onItemAtFrontLoaded(itemAtFront)
}
}

/**
Expand All @@ -114,7 +158,11 @@ class GiphyPickerViewModel @Inject constructor(
}

_selectedMediaViewModelList.postValue(LinkedHashMap())
dataSourceFactory.setSearchQuery(searchQuery)

// The empty view should be hidden while the user is searching
_emptyDisplayMode.postValue(EmptyDisplayMode.HIDDEN)

dataSourceFactory.searchQuery = searchQuery
}

/**
Expand Down
31 changes: 31 additions & 0 deletions WordPress/src/main/res/drawable/giphy_attribution_100dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<vector android:height="34dp" android:viewportHeight="223"
android:viewportWidth="641" android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#CFDCE4"
android:pathData="M111,9.9c-11.9,3.6 -18.9,12.8 -19.8,25.7 -1.1,17.9 7.3,29.5 23.6,32.4 12.5,2.2 23.4,-1 30.6,-9.1 5.2,-5.8 7.1,-11.7 7,-21.4 -0.1,-12.8 -6.8,-22.8 -18,-26.8 -5.1,-1.8 -18.5,-2.2 -23.4,-0.8zM131.4,23.4c6.4,3.5 9.7,12.5 7.5,20.3 -3.5,12.5 -18.9,17.1 -29,8.6 -3.9,-3.3 -5.9,-7.9 -5.9,-13.5 0.1,-14.6 14.3,-22.6 27.4,-15.4z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M36,38.4l0,28.6 7,-0 7,-0 0,-7.9 0,-7.9 10.8,-0.4c12.5,-0.4 17,-2.1 20.9,-7.9 6.9,-10.5 4.1,-24.2 -6.2,-30.1 -3.7,-2.1 -5.4,-2.3 -21.7,-2.6l-17.8,-0.4 0,28.6zM69.7,25.2c3.2,3 3.1,8.1 -0.2,11.3 -2.2,2.3 -3.2,2.5 -11,2.5l-8.5,-0 0,-8 0,-8 8.7,-0c7.5,-0 9,0.3 11,2.2z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M155.3,11.7c0.5,1.8 13.2,35.7 18,48.3l2.5,6.5 5.7,-0 5.7,-0 3,-8c6.1,-16.2 8.1,-21.5 8.3,-21.5 0.1,-0 2.8,6.7 6,15l5.8,15 5.6,-0 5.6,-0 9.9,-27.3c5.5,-14.9 10.1,-27.8 10.4,-28.5 0.3,-0.9 -1.5,-1.2 -7.2,-1.2 -4.2,-0 -7.6,0.1 -7.6,0.2 0,0.2 -2,6.2 -4.4,13.4 -2.5,7.2 -4.8,14.9 -5.2,17 -0.3,2.2 -0.9,4.3 -1.2,4.8 -0.3,0.5 -2.7,-5.4 -5.3,-13 -8.7,-24.8 -7.4,-22.5 -12.6,-22.2l-4.6,0.3 -5.8,17.4c-3.2,9.6 -6,17.6 -6.2,17.8 -0.2,0.2 -1.4,-3.4 -2.6,-7.9 -1.3,-4.6 -3.9,-12.7 -5.9,-18.1l-3.6,-9.7 -7.4,-0c-6.6,-0 -7.4,0.2 -6.9,1.7z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M249,38.5l0,28.5 22,-0 22,-0 0,-6 0,-6 -15.5,-0 -15.5,-0 0,-5.5 0,-5.5 14,-0 14,-0 0,-5.5 0,-5.5 -14,-0 -14,-0 0,-5.5 0,-5.5 15,-0 15,-0 0,-6 0,-6 -21.5,-0 -21.5,-0 0,28.5z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M303,38.5l0,28.5 6.5,-0 6.5,-0 0,-9 0,-9 4.8,-0 4.7,-0 6.8,9 6.9,9 7.4,-0c4.1,-0 7.4,-0.3 7.4,-0.8 -0.1,-0.4 -3.4,-4.7 -7.5,-9.7l-7.3,-8.9 3.6,-2.1c13.4,-7.5 11.2,-28.9 -3.5,-33.9 -3.5,-1.2 -8.7,-1.6 -20.5,-1.6l-15.8,-0 0,28.5zM336.2,23.7c3.1,3.1 3,8.9 -0.1,11.4 -1.9,1.5 -4,1.9 -11.2,1.9l-8.9,-0 0,-7.3c0,-4.1 0.3,-7.7 0.8,-8.1 0.4,-0.4 4.4,-0.6 8.9,-0.4 6.9,0.2 8.6,0.7 10.5,2.5z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M363,38.5l0,28.5 22,-0 22,-0 0,-6 0,-6 -15.5,-0 -15.5,-0 0,-5.5 0,-5.5 14,-0 14,-0 0,-5.5 0,-5.5 -14,-0 -14,-0 0,-5.5 0,-5.5 15,-0 15,-0 0,-6 0,-6 -21.5,-0 -21.5,-0 0,28.5z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M417,38.5l0,28.5 15.8,-0c10.1,-0.1 17,-0.5 19.3,-1.4 5.9,-2.1 10.7,-6.4 13.6,-12.4 2.5,-5 2.8,-6.8 2.8,-15.2 0,-11 -1.7,-15.2 -8.4,-21.3 -5.9,-5.3 -11.8,-6.7 -28.8,-6.7l-14.3,-0 0,28.5zM447.9,23.8c5.9,2.9 7.6,6.3 7.6,14.7 0,5.9 -0.4,7.9 -2.1,10.2 -3.3,4.5 -7.9,6.3 -16.1,6.3l-7.3,-0 0,-16.5 0,-16.5 7.3,-0c4.9,-0 8.3,0.6 10.6,1.8z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M499,38.5l0,28.5 18.5,-0c20.5,-0 22.9,-0.5 28,-5.9 2.7,-3 3,-3.8 3,-10.2 0,-6.8 -0.1,-7.2 -3.7,-10.4 -3.6,-3.4 -3.6,-3.4 -1.6,-5.2 5.7,-5 5.8,-14.2 0.3,-19.8 -4.6,-4.5 -9.8,-5.5 -28.4,-5.5l-16.1,-0 0,28.5zM532.4,22.9c2.3,1.9 2.1,6.8 -0.3,8.1 -1.1,0.5 -6,1 -11,1l-9.1,-0 0,-5.6 0,-5.6 9.4,0.4c5.8,0.2 10,0.9 11,1.7zM533.6,45.2c0.9,0.9 1.7,2.8 1.7,4.3 0,4.9 -2.4,6 -13.3,6.3l-10,0.4 0,-6.7 0,-6.7 10,0.4c7.6,0.2 10.3,0.7 11.6,2z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#CFDCE4"
android:pathData="M550,10.5c0,0.2 5,8.1 11,17.3l11,16.9 0,11.1 0,11.2 6.5,-0 6.5,-0 0,-11.1 0,-11 11.5,-16.9c6.3,-9.2 11.5,-17.1 11.5,-17.4 0,-0.3 -3.4,-0.6 -7.5,-0.6l-7.6,-0 -6.6,10.2c-3.6,5.7 -6.8,10.6 -7.2,11 -0.4,0.4 -3.6,-4.2 -7.1,-10.2l-6.5,-11 -7.7,-0c-4.3,-0 -7.8,0.2 -7.8,0.5z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#B1C7D5"
android:pathData="M87.1,87c-15.1,2.1 -28.1,8.2 -37.5,17.7 -24.6,24.8 -24.3,70.6 0.6,93.9 11.8,10.9 23.9,15.4 44.4,16.1 24.4,0.9 43.3,-5.6 53.1,-18.5l3.3,-4.4 0,-27.4 0,-27.4 -30,-0 -30,-0 0,13.5 0,13.5 15,-0 15,-0 0,8.1 0,8 -4.2,1.9c-5.8,2.5 -17.1,4.4 -22.1,3.6 -12.7,-2 -20.6,-7.5 -25.3,-17.8 -2.6,-5.7 -2.9,-7.4 -2.9,-16.8 0,-13.8 1.9,-19.6 8.9,-26.5 3.7,-3.7 6.6,-5.5 10.7,-6.8 11.4,-3.5 26.8,-1.2 35.9,5.4l4.3,3.1 10.1,-10.1 10.1,-10.2 -5,-4.3c-6.4,-5.6 -16.4,-11.1 -24,-13.1 -7.5,-1.9 -22.1,-2.6 -30.4,-1.5z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#B1C7D5"
android:pathData="M170,151l0,61 17,-0 17,-0 0,-61 0,-61 -17,-0 -17,-0 0,61z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#B1C7D5"
android:pathData="M229,151l0,61 17,-0 17,-0 0,-16.5 0,-16.5 15.8,-0c8.7,-0 18.5,-0.5 21.7,-1.1 14.8,-2.7 26.7,-11.6 32.6,-24.2 3.3,-6.9 3.4,-7.7 3.4,-19.2 0,-11.5 -0.1,-12.3 -3.4,-19.2 -5.5,-11.9 -16,-20.2 -29.8,-23.8 -4.5,-1.2 -12.8,-1.5 -40,-1.5l-34.3,-0 0,61zM294.6,121.6c6.4,3 9.2,13.3 5.6,20.3 -3.4,6.9 -5.6,7.6 -22,7.6l-14.7,-0 -0.3,-14.8 -0.3,-14.7 14.2,-0c10.8,-0 14.9,0.4 17.5,1.6z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#B1C7D5"
android:pathData="M352,151l0,61 17.5,-0 17.5,-0 0,-23 0,-23 21.5,-0 21.5,-0 0,23 0,23 17.5,-0 17.5,-0 0,-61 0,-61 -17.5,-0 -17.5,-0 0,23 0,23 -21.5,-0 -21.5,-0 0,-23 0,-23 -17.5,-0 -17.5,-0 0,61z" android:strokeColor="#CFDCE4"/>
<path android:fillColor="#B1C7D5"
android:pathData="M475,90.6c0,0.5 9.1,14.7 36.7,57.2l10.2,15.7 0.1,24.2 0,24.3 17.5,-0 17.5,-0 0,-24.5 0,-24.6 11.7,-17.2c6.4,-9.5 17.2,-25.4 24,-35.4 6.7,-10 12.3,-18.6 12.3,-19.2 0,-0.8 -6.3,-1.1 -19.7,-1.1l-19.8,0.1 -11.5,18.4c-6.3,10.1 -12.1,19.3 -13,20.4 -1.4,1.9 -1.9,1.3 -10,-12.2 -4.6,-7.8 -10.2,-17 -12.4,-20.5l-4,-6.2 -19.8,-0c-10.9,-0 -19.8,0.2 -19.8,0.6z" android:strokeColor="#CFDCE4"/>
</vector>
10 changes: 10 additions & 0 deletions WordPress/src/main/res/layout/actionable_empty_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@
android:visibility="gone" >
</android.support.v7.widget.AppCompatButton>

<ImageView
android:id="@+id/bottom_image"
android:adjustViewBounds="true"
android:contentDescription="@string/content_description_person_reading_device_notification"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_extra_large"
android:layout_width="wrap_content"
android:visibility="gone" >
</ImageView>

</LinearLayout>

</android.support.v4.widget.NestedScrollView>
Expand Down
5 changes: 4 additions & 1 deletion WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2002,7 +2002,10 @@
<string name="stock_media_picker_initial_empty_text">Search to find free photos to add to your Media Library</string>
<string name="stock_media_picker_initial_empty_subtext">Photos provided by %s</string>

<string name="giphy_search_hint">Search Giphy</string>
<string name="giphy_picker_search_hint">Search Giphy</string>
<string name="giphy_picker_initial_empty_text">Search to find GIFs to add to your Media Library!</string>
<string name="giphy_picker_empty_search_list">No media matching your search</string>
<string name="giphy_powered_by_giphy">Powered by GIPHY</string>

<!-- E-mail verification reminder dialog -->
<string name="toast_saving_post_as_draft">Saving post as draft</string>
Expand Down
Loading

0 comments on commit a489172

Please sign in to comment.