8000 Thumbnail image on item view (fixes #70) by stoyicker · Pull Request #78 · stoyicker/master-slave-clean-store · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Thumbnail image on item view (fixes #70) #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our 8000 terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/lint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<ignore regexp="error" />
<ignore regexp="root" />
<ignore regexp="search" />
<ignore regexp="scroll_guide" />
<ignore regexp="thumbnail" />
</issue>
<issue id="MergeRootFrame">
<ignore regexp="include_top_posts_view" />
Expand Down
2 changes: 2 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@
-dontwarn android.test.**
-dontwarn kotlin.jvm.internal.Reflection
-dontwarn com.google.errorprone.annotations.*
-dontwarn com.squareup.okhttp.**

5 changes: 3 additions & 2 deletions app/src/androidTest/kotlin/app/AndroidTestApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ internal open class AndroidTestApplication : MainApplication() {
* @see app.gaming.TopGamingAllTimePostsActivity
*/
override fun buildTopGamingAllTimePostsFeatureComponent(
contentView: RecyclerView, errorView: View, progressView: View, activity: Activity) =
contentView: RecyclerView, errorView: View, progressView: View, guideView: View,
activity: Activity) =
DaggerTopGamingAllTimePostsFeatureInstrumentationComponent.builder()
.topGamingAllTimePostsFeatureInstrumentationModule(
TopGamingAllTimePostsFeatureInstrumentationModule(
contentView, errorView, progressView, activity))
contentView, errorView, progressView, guideView, activity))
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ internal class TopGamingActivityInstrumentation {
@Test
fun onLoadItemsAreShown() {
SUBJECT = ReplaySubject.create()
SUBJECT.onNext(Post("0", "Bananas title", "r/bananas", 879, "bananaLink"))
SUBJECT.onNext(Post("0", "Bananas title", "r/bananas", 879, "bananaLink", "tb"))
SUBJECT.onCompleted()
launchActivity()
onView(withId(R.id.progress)).check { view, _ ->
Expand Down Expand Up @@ -123,7 +123,7 @@ internal class TopGamingActivityInstrumentation {
@Test
fun onItemClickIntentIsFired() {
SUBJECT = ReplaySubject.create()
SUBJECT.onNext(Post("0", "Bananas title", "r/bananas", 879, "http://www.banan.as"))
SUBJECT.onNext(Post("0", "Bananas title", "r/bananas", 879, "http://www.banan.as", "tb"))
SUBJECT.onCompleted()
launchActivity()
Intents.init()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal class TopGamingAllTimePostsFeatureInstrumentationModule(
private val contentView: RecyclerView,
private val errorView: View,
private val progressView: View,
private val guideView: View,
private val activity: Activity) {
@Provides
@Singleton
Expand Down Expand Up @@ -84,7 +85,7 @@ internal class TopGamingAllTimePostsFeatureInstrumentationModule(
@Provides
@Singleton
fun topGamingAllTimePostsView() = TopGamingAllTimePostsView(
contentView, errorView, progressView)
contentView, errorView, progressView, guideView)

@Provides
@Singleton
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/kotlin/app/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ internal open class MainApplication : Application() {
* @see app.gaming.TopGamingAllTimePostsActivity
*/
internal open fun buildTopGamingAllTimePostsFeatureComponent(
contentView: RecyclerView, errorView: View, progressView: View, activity: Activity) =
contentView: RecyclerView, errorView: View, progressView: View, guideView: View,
activity: Activity) =
DaggerTopGamingAllTimePostsFeatureComponent.builder()
.topGamingAllTimePostsFeatureModule(
TopGamingAllTimePostsFeatureModule(
contentView, errorView, progressView, activity))
contentView, errorView, progressView, guideView, activity))
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class TopGamingAllTimePostsActivity : AppCompatActivity() {
private fun inject() {
(application as MainApplication).buildTopGamingAllTimePostsFeatureComponent(
// https://kotlinlang.org/docs/tutorials/android-plugin.html#using-kotlin-android-extensions
content, error, progress, this).inject(this)
content, error, progress, scroll_guide, this).inject(this)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal class TopGamingAllTimePostsFeatureModule(
private val contentView: RecyclerView,
private val errorView: View,
private val progressView: View,
private val guideView: View,
private val activity: Activity) {
@Provides
@Singleton
Expand Down Expand Up @@ -93,7 +94,7 @@ internal class TopGamingAllTimePostsFeatureModule(
@Provides
@Singleton
fun topGamingAllTimePostsView() = TopGamingAllTimePostsView(
contentView, errorView, progressView)
contentView, errorView, progressView, guideView)

@Provides
@Singleton
Expand Down
95 changes: 76 additions & 19 deletions app/src/main/kotlin/app/gaming/TopGamingAllTimePostsFeatureView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import domain.entity.Post
import org.jorge.ms.app.R
import util.android.HtmlCompat
import util.android.ext.getDimension

/**
* Configuration for the recycler view holding the post list.
Expand Down Expand Up @@ -82,7 +83,7 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
= ViewHolder(LayoutInflater.from(parent.context).inflate(
R.layout.item_post, parent, false), recyclerView, { callback.onItemClicked(it) })
R.layout.item_post, parent, false), { callback.onItemClicked(it) })

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.render(shownItems[position])
Expand All @@ -95,7 +96,7 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
return
}
// This is used to take the latest valid value in the given payload list
val combinator: (Bundle, String) -> Unit = { bundle, key ->
val folder: (Bundle, String) -> Unit = { bundle, key ->
@Suppress("UNCHECKED_CAST")
bundle.putString(key, (payloads as List<Bundle>).fold(Bundle(), { old, new ->
val oldTitle = old.getString(key)
Expand All @@ -104,8 +105,8 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
}).getString(key))
}
val combinedBundle = Bundle().also { bundle ->
arrayOf(KEY_TITLE, KEY_SUBREDDIT, KEY_SCORE).forEach {
combinator(bundle, it)
arrayOf(KEY_TITLE, KEY_SUBREDDIT, KEY_SCORE, KEY_THUMBNAIL).forEach {
folder(bundle, it)
}
}
// Now combinedBundle contains the latest version of each of the fields that can be updated
Expand Down Expand Up @@ -183,9 +184,10 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
}

override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) =
shownItems[oldItemPosition].let { (_, oldTitle, oldSubreddit, oldScore) ->
shownItems[oldItemPosition].let {
(_, oldTitle, oldSubreddit, oldScore, oldThumbnail) ->
filteredItems[newItemPosition].let {
(_, newTitle, newSubreddit, newScore) ->
(_, newTitle, newSubreddit, newScore, newThumbnail) ->
Bundle().apply {
putString(KEY_TITLE, newTitle.takeIf {
!it.contentEquals(oldTitle)
Expand All @@ -196,6 +198,9 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
putString(KEY_SCORE, "${newScore.takeIf {
it != oldScore
}}")
putString(KEY_THUMBNAIL, newThumbnail.takeIf {
it != oldThumbnail
})
}
}
}
Expand All @@ -209,7 +214,6 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
@Suppress("UNCHECKED_CAST")
shownItems = results?.values as List<Post>? ?: items
(recyclerView.layoutParams as FrameLayout.LayoutParams).bottomMargin = 0
diff.dispatchUpdatesTo(this@Adapter)
}

Expand All @@ -222,23 +226,25 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
/**
* Very simple viewholder that sets text and click event handling.
* @param itemView The view to dump the held data.
* @param onItemClicked What to run when a click happens.
*/
internal class ViewHolder internal constructor(
itemView: View,
private val recyclerView: RecyclerView,
private val onItemClicked: (Post) -> Unit): RecyclerView.ViewHolder(itemView) {
private val titleView: TextView = itemView.findViewById(R.id.text_title) as TextView
private val scoreView: TextView = itemView.findViewById(R.id.text_score) as TextView
private val subredditView: TextView = itemView.findViewById(R.id.text_subreddit) as TextView
private val thumbnailView: ImageView = itemView.findViewById(R.id.thumbnail) as ImageView

/**
* Draw an item.
* @title The item to draw.
*/
internal fun render(item: Post) {
titleView.text = HtmlCompat.fromHtml(item.title)
subredditView.text = item.subreddit
scoreView.text = item.score.toString()
setTitle(item.title)
setSubreddit(item.subreddit)
setScore(item.score)
setThumbnail(item.thumbnailLink)
itemView.setOnClickListener { onItemClicked(item) }
}

Expand All @@ -248,26 +254,77 @@ internal class Adapter(private val callback: TopGamingAllTimePostsActivity.Behav
* @param item The item these updates correspond to.
*/
internal fun renderPartial(bundle: Bundle, item: Post) {
bundle.getString(KEY_TITLE).takeIf { it != null }.let { titleView.text =
HtmlCompat.fromHtml(it!!) }
bundle.getString(KEY_SUBREDDIT).takeIf { it != null }.let { subredditView.text = it }
bundle.getString(KEY_SCORE).takeIf { it != null }.let { scoreView.text = it }
bundle.getString(KEY_TITLE).takeIf { it != null }.let { setTitle(it!!) }
bundle.getString(KEY_SUBREDDIT).takeIf { it != null }.let { setSubreddit(it!!) }
bundle.getString(KEY_SCORE).takeIf { it != null }.let {
setScore(Integer.valueOf(it!!))
}
setThumbnail(bundle.getString(KEY_THUMBNAIL))
itemView.setOnClickListener { onItemClicked(item) }
}

/**
* Adds a margin under the recycler view for the progress and error views to show.
*/
internal fun addBottomMargin() {
(recyclerView.layoutParams as FrameLayout.LayoutParams).bottomMargin =
itemView.context.getDimension(R.dimen.footer_padding).toInt()
}

/**
* Updates the layout according to the changes required by a new title.
* @param title The new title.
*/
private fun setTitle(title: String) {
val formattedTitle = HtmlCompat.fromHtml(title)
titleView.text = formattedTitle
thumbnailView.contentDescription = formattedTitle.toString()
}

/**
* Updates the layout according to the changes required by a new subreddit.
* @param name The new subreddit name.
*/
private fun setSubreddit(name: String) {
subredditView.text = name
}

/**
* Updates the layout according to the changes required by a new score.
* @param score The new score.
*/
private fun setScore(score: Int) {
scoreView.text = score.toString()
}

/**
* Updates the layout according to the changes required by a new thumbnail link.
* @param thumbnailLink The new thumbnail link, or <code>null</code> if none is applicable.
*/
private fun setThumbnail(thumbnailLink: String?) {
if (thumbnailLink != null) {
Picasso.with(thumbnailView.context)
.load(thumbnailLink)
.into(thumbnailView, object : Callback {
override fun onError() {
thumbnailView.visibility = View.GONE
thumbnailView.setImageDrawable(null)
}

override fun onSuccess() {
thumbnailView.visibility = View.VISIBLE
}
})
} else {
thumbnailView.visibility = View.GONE
thumbnailView.setImageDrawable(null)
}
}
}

private companion object {
private val KEY_TITLE = "KEY_TITLE"
private val KEY_SUBREDDIT = "KEY_SUBREDDIT"
private val KEY_SCORE = "KEY_SCORE"
private val KEY_THUMBNAIL = "KEY_THUMBNAIL"
}
}

Expand Down
16 changes: 15 additions & 1 deletion app/src/main/kotlin/app/gaming/TopGamingAllTimePostsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ package app.gaming

import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.FrameLayout
import app.LoadableContentView
import domain.entity.Post
import org.jorge.ms.app.R
import util.android. 10000 ext.getDimension

/**
* Wraps UI behavior for top all time gaming posts scenario.
*/
internal class TopGamingAllTimePostsView(
internal val contentView: RecyclerView,
internal val errorView: View,
private val progressView: View) : LoadableContentView<Post> {
private val progressView: View,
private val guideView: View) : LoadableContentView<Post> {
override fun showLoadingLayout() {
pushInfoArea()
progressView.visibility = View.VISIBLE
guideView.visibility = View.INVISIBLE
}

override fun hideLoadingLayout() {
Expand All @@ -22,13 +28,21 @@ internal class TopGamingAllTimePostsView(

override fun updateContent(actionResult: List<Post>) {
(contentView.adapter as Adapter).addItems(actionResult)
guideView.visibility = View.VISIBLE
}

override fun showErrorLayout() {
pushInfoArea()
errorView.visibility = View.VISIBLE
guideView.visibility = View.INVISIBLE
}

override fun hideErrorLayout() {
errorView.visibility = View.GONE
}

private fun pushInfoArea() {
(contentView.layoutParams as FrameLayout.LayoutParams).bottomMargin =
contentView.context.getDimension(R.dimen.footer_padding).toInt()
}
}
10 changes: 9 additions & 1 deletion app/src/main/res/layout-v21/item_post.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:padding="@dimen/appbar_horizontal_padding"
android:theme="@style/AppTheme.Card">
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/section_separator"
android:textIsSelectable="false"
android:clickable="false"
style="@style/AppTheme.TextTitle" />
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:visibility="gone"
tools:ignore="ContentDescription"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/section_separator"
android:layout_marginBottom="@dimen/section_separator"
android:clickable="false"
android:background="@color/windowBackground" />
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/layout/include_top_posts_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,14 @@
android:textStyle="bold"
android:text="@string/top_posts_error_action" />
</LinearLayout>
<TextView
android:id="@+id/scroll_guide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center"
android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:text="@string/top_posts_scroll_guide"
android:visibility="invisible" />
</FrameLayout>
Loading
0