Skip to content
Open
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
8 changes: 8 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ dependencies {
implementation libs.firebaseDatabase
implementation libs.firebaseStorage
implementation libs.kotlinStdlib
implementation libs.kotlinCoroutineJdk
implementation libs.kotlinCoroutineAndroid
implementation libs.multilineToolbar
implementation libs.playServicesMaps
implementation libs.recyclerView
Expand All @@ -78,4 +80,10 @@ dependencies {
testImplementation libs.junit4
}

kotlin {
experimental {
coroutines 'enable'
}
}

apply plugin: 'com.google.gms.google-services'
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class DataModule {
return FirebaseSessionProvider(db)
}

@Provides @Singleton fun provideSpeakerProvider(db: DatabaseReference): SpeakerProvider {
return FirebaseSpeakerProvider(db)
@Provides @Singleton fun provideSpeakerProvider(db: DatabaseReference, avatarProvider: AvatarProvider): SpeakerProvider {
return FirebaseSpeakerProvider(db, avatarProvider)
}

@Provides @Singleton fun provideVenueProvider(db: DatabaseReference): VenueProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ package com.chicagoroboto.data
import com.chicagoroboto.model.Speaker
import com.google.android.gms.tasks.OnSuccessListener
import com.google.firebase.storage.StorageReference
import kotlin.coroutines.experimental.suspendCoroutine

class FirebaseAvatarProvider(storage: StorageReference) : AvatarProvider {

private val ref = storage.child("profiles")

override fun getAvatarUri(speaker: Speaker, callback: (String) -> Unit) {
val id = speaker.id ?: return
ref.child(id).downloadUrl.addOnSuccessListener(OnSuccessListener {
callback(it.toString())
})
override suspend fun getAvatarUri(speaker: Speaker) = suspendCoroutine<String> { c ->
val id = speaker.id
if (id == null) {
c.resume("")
}
else {
ref.child(id).downloadUrl.addOnSuccessListener({
c.resume(it.toString())
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,104 @@ import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.GenericTypeIndicator
import com.google.firebase.database.Query
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import java.util.HashMap
import kotlin.coroutines.experimental.suspendCoroutine

class FirebaseSpeakerProvider(private val database: DatabaseReference) : SpeakerProvider {
class FirebaseSpeakerProvider(private val database: DatabaseReference,
private val avatarProvider: AvatarProvider) : SpeakerProvider {

private val queries: MutableMap<Any, Query> = mutableMapOf()
private val listeners: MutableMap<Any, ValueEventListener> = mutableMapOf()
private val queries: MutableMap<Any, Query> = mutableMapOf()
private val listeners: MutableMap<Any, ValueEventListener> = mutableMapOf()

override fun addSpeakerListener(key: Any, onComplete: (Map<String, Speaker>?) -> Unit) {
if (queries[key] != null) {
removeSpeakerListener(key)
}
override suspend fun getSpeakersMap(key: Any) : Map<String, Speaker> {
if (queries[key] != null) {
removeSpeakerListener(key)
}
val speakersWithoutUrl = getSpeakers(key)
return populateAvatarUrls(speakersWithoutUrl)
}

private suspend fun getSpeakers(key: Any) = suspendCoroutine<Map<String, Speaker>> {
val listener = object : ValueEventListener {
override fun onDataChange(data: DataSnapshot?) {
val typeIndicator = object : GenericTypeIndicator<HashMap<String, Speaker>>() {}
val speakersWithoutUrl = data?.getValue(typeIndicator)!!
it.resume(speakersWithoutUrl)
}

override fun onCancelled(e: DatabaseError) {
it.resumeWithException(e.toException())
}
}
listeners[key] = listener

val query = database.child("speakers")
query.addValueEventListener(listener)
queries[key] = query

val listener = object : ValueEventListener {
override fun onDataChange(data: DataSnapshot?) {
val typeIndicator = object : GenericTypeIndicator<HashMap<String, Speaker>>() {}
onComplete(data?.getValue(typeIndicator))
}
}

override fun onCancelled(e: DatabaseError?) {
onComplete(null)
}
}
listeners[key] = listener
private suspend fun populateAvatarUrls(speakerMap: Map<String, Speaker>): Map<String, Speaker> {
val speakerList = speakerMap.values
return speakerList.map { speaker ->
speaker to async {
avatarProvider.getAvatarUri(speaker)
}
}.map { (s, deferred) ->
val avatarUrl = deferred.await()
Speaker(s.id, s.name, s.title, s.company, s.email, s.twitter, s.github, s.bio, avatarUrl)
}.associateBy { it.id!! }
}

val query = database.child("speakers")
query.addValueEventListener(listener)
queries[key] = query
override suspend fun getSpeaker(id: String): Speaker {
if (queries[id] != null) {
removeSpeakerListener(id)
}

override fun addSpeakerListener(id: String, onComplete: (Speaker?) -> Unit) {
if (queries[id] != null) {
removeSpeakerListener(id)
}

val listener = object : ValueEventListener {
override fun onDataChange(data: DataSnapshot?) {
onComplete(data?.getValue(Speaker::class.java))
}

override fun onCancelled(e: DatabaseError?) {
onComplete(null)
}
}
listeners[id] = listener

val query = database.child("speakers").child(id)
query.addValueEventListener(listener)
queries[id] = query
val s = getSpeakerFromFirebase(id)
val avatarUri = avatarProvider.getAvatarUri(s)
return Speaker(s.id, s.name, s.title, s.company, s.email, s.twitter, s.github, s.bio, avatarUri)
}

private suspend fun getSpeakerFromFirebase(id: String) = suspendCoroutine<Speaker> { c ->
addSpeakerListener(id, {
if (it != null) {
c.resume(it)
}
else {
c.resumeWithException(IllegalStateException("No Speaker"))
}
})
}

override fun addSpeakerListener(id: String, onComplete: (Speaker?) -> Unit) {
if (queries[id] != null) {
removeSpeakerListener(id)
}

override fun removeSpeakerListener(key: Any) {
val query = queries[key]
query?.removeEventListener(listeners[key])
val listener = object : ValueEventListener {
override fun onDataChange(data: DataSnapshot?) {
onComplete(data?.getValue(Speaker::class.java))
}

queries.remove(key)
listeners.remove(key)
override fun onCancelled(e: DatabaseError?) {
onComplete(null)
}
}
listeners[id] = listener

val query = database.child("speakers").child(id)
query.addValueEventListener(listener)
queries[id] = query
}

override fun removeSpeakerListener(key: Any) {
val query = queries[key]
query?.removeEventListener(listeners[key])

queries.remove(key)
listeners.remove(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.text.format.DateUtils
import android.util.AttributeSet
import android.view.LayoutInflater
import com.chicagoroboto.R
import com.chicagoroboto.data.AvatarProvider
import com.chicagoroboto.ext.getComponent
import com.chicagoroboto.features.sessiondetail.feedback.FeedbackDialog
import com.chicagoroboto.features.speakerdetail.SpeakerNavigator
Expand All @@ -24,7 +23,6 @@ class SessionDetailView(context: Context, attrs: AttributeSet? = null, defStyle:

@Inject lateinit var speakerNavigator: SpeakerNavigator
@Inject lateinit var presenter: SessionDetailMvp.Presenter
@Inject lateinit var avatarProvider: AvatarProvider

private val speakerAdapter: SpeakerAdapter
private var sessionId: String? = null
Expand All @@ -42,7 +40,7 @@ class SessionDetailView(context: Context, attrs: AttributeSet? = null, defStyle:
}
}

speakerAdapter = SpeakerAdapter(avatarProvider, true, { speaker, image ->
speakerAdapter = SpeakerAdapter(true, { speaker, image ->
speakerNavigator.navigateToSpeaker(speaker.id!!, image)
})
speakers.adapter = speakerAdapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import com.chicagoroboto.R
import com.chicagoroboto.data.AvatarProvider
import com.chicagoroboto.model.Speaker
import com.chicagoroboto.utils.DrawableUtils
import kotlinx.android.synthetic.main.item_speaker.view.*

internal class SpeakerAdapter(private val avatarProvider: AvatarProvider,
val wrapsWidth: Boolean = true,
val onSpeakerClickedListener: ((speaker: Speaker, view: View) -> Unit)) :
internal class SpeakerAdapter(val wrapsWidth: Boolean = true,
val onSpeakerClickedListener: (speaker: Speaker, view: View) -> Unit) :
RecyclerView.Adapter<SpeakerAdapter.ViewHolder>() {

val speakers: MutableList<Speaker> = mutableListOf()
Expand All @@ -25,7 +23,7 @@ internal class SpeakerAdapter(private val avatarProvider: AvatarProvider,
if (!wrapsWidth) {
v.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
}
return ViewHolder(v, avatarProvider, onSpeakerClickedListener)
return ViewHolder(v, onSpeakerClickedListener)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Expand All @@ -37,13 +35,13 @@ internal class SpeakerAdapter(private val avatarProvider: AvatarProvider,
}

internal class ViewHolder(itemView: View,
private val avatarProvider: AvatarProvider,
private val onSpeakerClickedListener: ((speaker: Speaker, view: View) -> Unit)) :
RecyclerView.ViewHolder(itemView), View.OnClickListener {
private var speaker: Speaker? = null
val image: ImageView
val name: TextView
val title: TextView
val placeHolder = DrawableUtils.create(itemView.context, R.drawable.ph_speaker)

init {
image = itemView.image
Expand All @@ -56,13 +54,17 @@ internal class SpeakerAdapter(private val avatarProvider: AvatarProvider,
this.speaker = speaker
name.text = speaker.name

avatarProvider.getAvatarUri(speaker) {
if (speaker.avatarUrl != null) {
Glide.with(itemView.context)
.load(it)
.load(speaker.avatarUrl)
.asBitmap()
.placeholder(DrawableUtils.create(itemView.context, R.drawable.ph_speaker))
.placeholder(placeHolder)
.into(image)
}
else {
Glide.clear(image)
image.setImageDrawable(placeHolder)
}
}

override fun onClick(v: View?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal class SessionAdapter(val onSessionSelectedListener: ((session: Session)
holder.speakers.visibility = View.GONE
} else {
holder.speakers.visibility = View.VISIBLE
holder.speakers.text = sessionSpeakers.map { it?.name }.joinToString()
holder.speakers.text = sessionSpeakers.joinToString { it?.name ?: "" }
holder.room.setCompoundDrawablesWithIntrinsicBounds(
DrawableUtils.create(context, R.drawable.ic_speaker),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import android.view.LayoutInflater
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.chicagoroboto.R
import com.chicagoroboto.data.AvatarProvider
import com.chicagoroboto.ext.getActivity
import com.chicagoroboto.ext.getComponent
import com.chicagoroboto.model.Speaker
Expand All @@ -26,7 +25,6 @@ class SpeakerDetailView(context: Context, attrs: AttributeSet? = null, defStyle:
ConstraintLayout(context, attrs, defStyle), SpeakerDetailMvp.View {

@Inject lateinit var presenter: SpeakerDetailMvp.Presenter
@Inject lateinit var avatarProvider: AvatarProvider

constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)

Expand Down Expand Up @@ -103,9 +101,9 @@ class SpeakerDetailView(context: Context, attrs: AttributeSet? = null, defStyle:
github.visibility = GONE
}

avatarProvider.getAvatarUri(speaker) {
if (speaker.avatarUrl != null) {
Glide.with(context)
.load(it)
.load(speaker.avatarUrl)
.asBitmap()
.centerCrop()
.dontAnimate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.ViewGroup
import com.chicagoroboto.R
import com.chicagoroboto.data.AvatarProvider
import com.chicagoroboto.ext.getComponent
import com.chicagoroboto.features.main.MainView
import com.chicagoroboto.features.main.MainComponent
Expand All @@ -26,15 +25,14 @@ class SpeakerListView(context: Context, attrs: AttributeSet? = null, defStyle: I

@Inject lateinit var presenter: SpeakerListMvp.Presenter
@Inject lateinit var speakerNavigator: SpeakerNavigator
@Inject lateinit var avatarProvider: AvatarProvider

init {
context.getComponent<MainComponent>().speakerListComponent().inject(this)

layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
layoutManager = LinearLayoutManager(context, VERTICAL, false)
addItemDecoration(DividerItemDecoration(context))
adapter = SpeakerAdapter(avatarProvider, false, { speaker, view ->
adapter = SpeakerAdapter(false, { speaker, view ->
speakerNavigator.navigateToSpeaker(speaker.id!!, view)
})
super.setAdapter(adapter)
Expand Down
7 changes: 7 additions & 0 deletions android/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ sourceSets {

dependencies {
compile libs.kotlinStdlib
compile libs.kotlinCoroutineJdk
compile libs.kotlinCoroutineAndroid

testCompile libs.junit4
testCompile libs.truth
Expand All @@ -21,3 +23,8 @@ dependencies {

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
kotlin {
experimental {
coroutines "enable"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.chicagoroboto.data
import com.chicagoroboto.model.Speaker

interface AvatarProvider {
fun getAvatarUri(speaker: Speaker, callback: (String) -> Unit)
suspend fun getAvatarUri(speaker: Speaker): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package com.chicagoroboto.data
import com.chicagoroboto.model.Speaker

interface SpeakerProvider {
fun addSpeakerListener(key: Any, onComplete: (speakers: Map<String, Speaker>?) -> Unit)
suspend fun getSpeakersMap(key: Any): Map<String, Speaker>
suspend fun getSpeaker(id: String) : Speaker
fun addSpeakerListener(id: String, onComplete: (speaker: Speaker?) -> Unit)
fun removeSpeakerListener(key: Any)
}
Loading