Skip to content
Snippets Groups Projects
Commit 384a365d authored by Adrien Béraud's avatar Adrien Béraud
Browse files

presence: support new presence status

Change-Id: I186ef585bcdb36ee4a6ce7955f888fbc4a4ce921
parent 057d60bc
No related branches found
No related tags found
No related merge requests found
Showing
with 74 additions and 41 deletions
...@@ -107,7 +107,7 @@ class AccountAdapter( ...@@ -107,7 +107,7 @@ class AccountAdapter(
profile.first, profile.first,
profile.second, profile.second,
true, true,
profile.first.isRegistered profile.first.presenceStatus
) )
) )
holder.binding.title.text = getTitle(profile.first, profile.second) holder.binding.title.text = getTitle(profile.first, profile.second)
......
...@@ -524,7 +524,7 @@ class HomeFragment: BaseSupportFragment<HomePresenter, HomeView>(), ...@@ -524,7 +524,7 @@ class HomeFragment: BaseSupportFragment<HomePresenter, HomeView>(),
profile.first, profile.first,
profile.second, profile.second,
true, true,
profile.first.isRegistered profile.first.presenceStatus
), ),
TypedValue.applyDimension( TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, TypedValue.COMPLEX_UNIT_DIP,
......
...@@ -27,6 +27,7 @@ import cx.ring.tv.cards.CardView ...@@ -27,6 +27,7 @@ import cx.ring.tv.cards.CardView
import cx.ring.views.AvatarDrawable import cx.ring.views.AvatarDrawable
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import net.jami.model.Contact
import net.jami.services.ConversationFacade import net.jami.services.ConversationFacade
class ContactCardPresenter(context: Context, val conversationFacade: ConversationFacade, resId: Int) : class ContactCardPresenter(context: Context, val conversationFacade: ConversationFacade, resId: Int) :
...@@ -42,7 +43,7 @@ class ContactCardPresenter(context: Context, val conversationFacade: Conversatio ...@@ -42,7 +43,7 @@ class ContactCardPresenter(context: Context, val conversationFacade: Conversatio
val title: String, val title: String,
val uri: String, val uri: String,
val avatar: Drawable, val avatar: Drawable,
val isOnline: Boolean val presenceStatus: Contact.PresenceStatus
) )
override fun onBindViewHolder(card: Card, cardView: CardView, disposable: CompositeDisposable) { override fun onBindViewHolder(card: Card, cardView: CardView, disposable: CompositeDisposable) {
...@@ -52,12 +53,12 @@ class ContactCardPresenter(context: Context, val conversationFacade: Conversatio ...@@ -52,12 +53,12 @@ class ContactCardPresenter(context: Context, val conversationFacade: Conversatio
.withViewModel(vm) .withViewModel(vm)
.withPresence(false) .withPresence(false)
.withCircleCrop(false) .withCircleCrop(false)
.build(context), vm.isOnline) } .build(context), vm.presenceStatus) }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { vm -> cardView.apply { .subscribe { vm -> cardView.apply {
titleText = vm.title titleText = vm.title
contentText = vm.uri contentText = vm.uri
badgeImage = if (vm.isOnline) badge else null badgeImage = if (vm.presenceStatus != Contact.PresenceStatus.OFFLINE) badge else null
setMainImage(vm.avatar, true) setMainImage(vm.avatar, true)
} }
}) })
......
...@@ -21,6 +21,7 @@ import android.graphics.* ...@@ -21,6 +21,7 @@ import android.graphics.*
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable import android.graphics.drawable.VectorDrawable
import android.util.TypedValue import android.util.TypedValue
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import cx.ring.R import cx.ring.R
...@@ -65,9 +66,13 @@ class AvatarDrawable : Drawable { ...@@ -65,9 +66,13 @@ class AvatarDrawable : Drawable {
} }
private val presenceFillPaint: Paint private val presenceFillPaint: Paint
private val presenceStrokePaint: Paint private val presenceStrokePaint: Paint
@ColorInt
private val presenceConnectedColor: Int
@ColorInt
private val presenceAvailableColor: Int
private val checkedPaint: Paint private val checkedPaint: Paint
private val cropCircle: Boolean private val cropCircle: Boolean
private var isOnline = false private var presenceStatus = Contact.PresenceStatus.OFFLINE
private var isChecked = false private var isChecked = false
private var showPresence = false private var showPresence = false
...@@ -76,10 +81,6 @@ class AvatarDrawable : Drawable { ...@@ -76,10 +81,6 @@ class AvatarDrawable : Drawable {
private const val SIZE_AB = 36 private const val SIZE_AB = 36
private const val SIZE_BORDER = 2 private const val SIZE_BORDER = 2
private const val DEFAULT_TEXT_SIZE_PERCENTAGE = 0.5f private const val DEFAULT_TEXT_SIZE_PERCENTAGE = 0.5f
private const val PLACEHOLDER_ICON = R.drawable.baseline_account_crop_24
private const val PLACEHOLDER_ICON_GROUP = R.drawable.baseline_group_24
private const val CHECKED_ICON = R.drawable.baseline_check_circle_24
private const val PRESENCE_COLOR = R.color.online_indicator
private val contactColors = intArrayOf( private val contactColors = intArrayOf(
R.color.red_500, R.color.pink_500, R.color.red_500, R.color.pink_500,
R.color.purple_500, R.color.deep_purple_500, R.color.purple_500, R.color.deep_purple_500,
...@@ -99,13 +100,13 @@ class AvatarDrawable : Drawable { ...@@ -99,13 +100,13 @@ class AvatarDrawable : Drawable {
VCardServiceImpl.loadProfile(context, account) VCardServiceImpl.loadProfile(context, account)
.map { profile -> build(context, account, profile, crop) } .map { profile -> build(context, account, profile, crop) }
fun build(context: Context, account: Account, profile: Profile, crop: Boolean = true, isOnline: Boolean = false) = fun build(context: Context, account: Account, profile: Profile, crop: Boolean = true, presenceStatus: Contact.PresenceStatus = Contact.PresenceStatus.OFFLINE) =
Builder() Builder()
.withPhoto(profile.avatar as Bitmap?) .withPhoto(profile.avatar as Bitmap?)
.withNameData(profile.displayName, account.registeredName) .withNameData(profile.displayName, account.registeredName)
.withId(if (account.isSip) account.uri else Uri(Uri.JAMI_URI_SCHEME, account.username!!).rawUriString) .withId(if (account.isSip) account.uri else Uri(Uri.JAMI_URI_SCHEME, account.username!!).rawUriString)
.withCircleCrop(crop) .withCircleCrop(crop)
.withOnlineState(isOnline) .withOnlineState(presenceStatus)
.build(context) .build(context)
private fun getSubBounds(bounds: Rect, total: Int, i: Int): Rect? { private fun getSubBounds(bounds: Rect, total: Int, i: Int): Rect? {
...@@ -171,7 +172,7 @@ class AvatarDrawable : Drawable { ...@@ -171,7 +172,7 @@ class AvatarDrawable : Drawable {
private var name: String? = null private var name: String? = null
private var id: String? = null private var id: String? = null
private var circleCrop = false private var circleCrop = false
private var isOnline = false private var presenceStatus = Contact.PresenceStatus.OFFLINE
private var showPresence = true private var showPresence = true
private var isChecked = false private var isChecked = false
private var isGroup = false private var isGroup = false
...@@ -200,8 +201,8 @@ class AvatarDrawable : Drawable { ...@@ -200,8 +201,8 @@ class AvatarDrawable : Drawable {
return this return this
} }
fun withOnlineState(isOnline: Boolean): Builder { fun withOnlineState(status: Contact.PresenceStatus): Builder {
this.isOnline = isOnline this.presenceStatus = status
return this return this
} }
...@@ -221,7 +222,7 @@ class AvatarDrawable : Drawable { ...@@ -221,7 +222,7 @@ class AvatarDrawable : Drawable {
fun withContact(contact: ContactViewModel?) = if (contact == null) this else fun withContact(contact: ContactViewModel?) = if (contact == null) this else
withPhoto(contact.profile.avatar as? Bitmap?) withPhoto(contact.profile.avatar as? Bitmap?)
.withId(contact.contact.uri.toString()) .withId(contact.contact.uri.toString())
.withPresence(contact.presence) .withPresence(contact.presence != Contact.PresenceStatus.OFFLINE)
.withOnlineState(contact.presence) .withOnlineState(contact.presence)
.withNameData(contact.profile.displayName, contact.registeredName) .withNameData(contact.profile.displayName, contact.registeredName)
...@@ -278,12 +279,12 @@ class AvatarDrawable : Drawable { ...@@ -278,12 +279,12 @@ class AvatarDrawable : Drawable {
.setGroup() .setGroup()
else withContact(ConversationItemViewModel.getContact(vm.contacts)) else withContact(ConversationItemViewModel.getContact(vm.contacts))
.withPresence(vm.showPresence) .withPresence(vm.showPresence)
.withOnlineState(vm.isOnline) .withOnlineState(vm.presenceStatus)
.withCheck(vm.isChecked) .withCheck(vm.isChecked)
fun build(context: Context): AvatarDrawable = fun build(context: Context): AvatarDrawable =
AvatarDrawable(context, photos, name, id, circleCrop, isGroup).also { AvatarDrawable(context, photos, name, id, circleCrop, isGroup).also {
it.setOnline(isOnline) it.setPresenceStatus(presenceStatus)
it.setChecked(isChecked) it.setChecked(isChecked)
it.showPresence = showPresence it.showPresence = showPresence
} }
...@@ -296,7 +297,7 @@ class AvatarDrawable : Drawable { ...@@ -296,7 +297,7 @@ class AvatarDrawable : Drawable {
contact.profile.avatar?.let { photo -> contact.profile.avatar?.let { photo ->
bitmaps?.set(0, photo as Bitmap) bitmaps?.set(0, photo as Bitmap)
} }
isOnline = contact.presence presenceStatus = contact.presence
update = true update = true
} }
...@@ -310,8 +311,9 @@ class AvatarDrawable : Drawable { ...@@ -310,8 +311,9 @@ class AvatarDrawable : Drawable {
update = true update = true
} }
fun setOnline(online: Boolean) { fun setPresenceStatus(status: Contact.PresenceStatus) {
isOnline = online presenceStatus = status
presenceFillPaint.color = if (status == Contact.PresenceStatus.CONNECTED) presenceConnectedColor else presenceAvailableColor
} }
fun setChecked(checked: Boolean) { fun setChecked(checked: Boolean) {
...@@ -363,17 +365,18 @@ class AvatarDrawable : Drawable { ...@@ -363,17 +365,18 @@ class AvatarDrawable : Drawable {
clipPaint = if (cropCircle) arrayOf(Paint()) else null clipPaint = if (cropCircle) arrayOf(Paint()) else null
if (avatarText == null) { if (avatarText == null) {
placeholder = placeholder =
context.getDrawable(if (isGroup) PLACEHOLDER_ICON_GROUP else PLACEHOLDER_ICON) as VectorDrawable? context.getDrawable(if (isGroup) R.drawable.baseline_group_24 else R.drawable.baseline_account_crop_24) as VectorDrawable?
} else { } else {
textPaint.color = Color.WHITE textPaint.color = Color.WHITE
textPaint.typeface = Typeface.SANS_SERIF textPaint.typeface = Typeface.SANS_SERIF
} }
} }
presenceAvailableColor = ContextCompat.getColor(context, R.color.available_indicator)
presenceConnectedColor = ContextCompat.getColor(context, R.color.online_indicator)
presenceFillPaint = Paint().apply { presenceFillPaint = Paint().apply {
color = ContextCompat.getColor(context, PRESENCE_COLOR)
style = Paint.Style.FILL style = Paint.Style.FILL
isAntiAlias = true isAntiAlias = true
color = ContextCompat.getColor(context, PRESENCE_COLOR) color = presenceConnectedColor
} }
val typedValue = TypedValue() val typedValue = TypedValue()
...@@ -388,7 +391,7 @@ class AvatarDrawable : Drawable { ...@@ -388,7 +391,7 @@ class AvatarDrawable : Drawable {
style = Paint.Style.STROKE style = Paint.Style.STROKE
isAntiAlias = true isAntiAlias = true
} }
checkedIcon = context.getDrawable(CHECKED_ICON) as VectorDrawable? checkedIcon = context.getDrawable(R.drawable.baseline_check_circle_24) as VectorDrawable?
checkedIcon?.setTint(ContextCompat.getColor(context, R.color.colorPrimary)) checkedIcon?.setTint(ContextCompat.getColor(context, R.color.colorPrimary))
checkedPaint = Paint().apply { checkedPaint = Paint().apply {
color = ContextCompat.getColor(context, R.color.background) color = ContextCompat.getColor(context, R.color.background)
...@@ -415,9 +418,11 @@ class AvatarDrawable : Drawable { ...@@ -415,9 +418,11 @@ class AvatarDrawable : Drawable {
clipPaint = if (other.clipPaint == null) null else Array(other.clipPaint.size) { i -> Paint(other.clipPaint[i]).apply { clipPaint = if (other.clipPaint == null) null else Array(other.clipPaint.size) { i -> Paint(other.clipPaint[i]).apply {
shader = null shader = null
} } } }
isOnline = other.isOnline presenceStatus = other.presenceStatus
isChecked = other.isChecked isChecked = other.isChecked
showPresence = other.showPresence showPresence = other.showPresence
presenceConnectedColor = other.presenceConnectedColor
presenceAvailableColor = other.presenceAvailableColor
presenceFillPaint = other.presenceFillPaint presenceFillPaint = other.presenceFillPaint
presenceStrokePaint = other.presenceStrokePaint presenceStrokePaint = other.presenceStrokePaint
checkedPaint = other.checkedPaint checkedPaint = other.checkedPaint
...@@ -468,7 +473,7 @@ class AvatarDrawable : Drawable { ...@@ -468,7 +473,7 @@ class AvatarDrawable : Drawable {
} else { } else {
finalCanvas.drawBitmap(firstWorkspace, null, bounds, drawPaint) finalCanvas.drawBitmap(firstWorkspace, null, bounds, drawPaint)
} }
if (showPresence && isOnline) { if (showPresence && presenceStatus != Contact.PresenceStatus.OFFLINE) {
drawPresence(finalCanvas) drawPresence(finalCanvas)
} }
if (isChecked) { if (isChecked) {
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
<color name="background_status_recommended">#8ee0d0</color> <color name="background_status_recommended">#8ee0d0</color>
<color name="background_status_required">#fee4e9</color> <color name="background_status_required">#fee4e9</color>
<color name="available_indicator">#E59028</color>
<color name="online_indicator">#0B8271</color> <color name="online_indicator">#0B8271</color>
<color name="menu_icon">#0A84FF</color> <color name="menu_icon">#0A84FF</color>
......
...@@ -494,6 +494,11 @@ class Account( ...@@ -494,6 +494,11 @@ class Account(
get() = registrationState == AccountConfig.RegistrationState.TRYING get() = registrationState == AccountConfig.RegistrationState.TRYING
val isRegistered: Boolean val isRegistered: Boolean
get() = registrationState == AccountConfig.RegistrationState.REGISTERED get() = registrationState == AccountConfig.RegistrationState.REGISTERED
val presenceStatus: Contact.PresenceStatus
get() = if (isRegistered) Contact.PresenceStatus.CONNECTED
else if (isTrying) Contact.PresenceStatus.AVAILABLE
else Contact.PresenceStatus.OFFLINE
val isInError: Boolean val isInError: Boolean
get() { get() {
val state = registrationState val state = registrationState
...@@ -787,9 +792,13 @@ class Account( ...@@ -787,9 +792,13 @@ class Account(
} }
fun presenceUpdate(contactUri: String, status: Int) { fun presenceUpdate(contactUri: String, status: Int) {
//Log.w(TAG, "presenceUpdate " + contactUri + " " + isOnline); //Log.w(TAG, "presenceUpdate $contactUri $status");
val contact = getContactFromCache(contactUri) val contact = getContactFromCache(contactUri)
contact.setPresence(status > 0) contact.setPresence(when (status) {
0 -> Contact.PresenceStatus.OFFLINE
1 -> Contact.PresenceStatus.AVAILABLE
else -> Contact.PresenceStatus.CONNECTED
})
synchronized(conversations) { synchronized(conversations) {
conversations[contactUri]?.let { conversationRefreshed(it) } conversations[contactUri]?.let { conversationRefreshed(it) }
} }
......
...@@ -29,8 +29,8 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) { ...@@ -29,8 +29,8 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) {
} }
var username: Single<String>? = null var username: Single<String>? = null
var presenceUpdates: Observable<Boolean>? = null var presenceUpdates: Observable<PresenceStatus>? = null
private var mContactPresenceEmitter: Emitter<Boolean>? = null private var mContactPresenceEmitter: Emitter<PresenceStatus>? = null
private val profileSubject: Subject<Single<Profile>> = BehaviorSubject.create() private val profileSubject: Subject<Single<Profile>> = BehaviorSubject.create()
val profile: Observable<Profile> = profileSubject.switchMapSingle { single -> single } val profile: Observable<Profile> = profileSubject.switchMapSingle { single -> single }
var loadedProfile: Single<Profile>? = null var loadedProfile: Single<Profile>? = null
...@@ -63,7 +63,7 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) { ...@@ -63,7 +63,7 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) {
val updatesSubject: Observable<Contact> val updatesSubject: Observable<Contact>
get() = mContactUpdates get() = mContactUpdates
fun setPresenceEmitter(emitter: Emitter<Boolean>?) { fun setPresenceEmitter(emitter: Emitter<PresenceStatus>?) {
mContactPresenceEmitter?.let { e -> mContactPresenceEmitter?.let { e ->
if (e != emitter) if (e != emitter)
e.onComplete() e.onComplete()
...@@ -71,7 +71,13 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) { ...@@ -71,7 +71,13 @@ class Contact constructor(val uri: Uri, val isUser: Boolean = false) {
mContactPresenceEmitter = emitter mContactPresenceEmitter = emitter
} }
fun setPresence(present: Boolean) { enum class PresenceStatus {
OFFLINE,
AVAILABLE,
CONNECTED
}
fun setPresence(present: PresenceStatus) {
mContactPresenceEmitter?.onNext(present) mContactPresenceEmitter?.onNext(present)
} }
......
...@@ -26,7 +26,7 @@ open class Profile(val displayName: String?, val avatar: Any?, val description: ...@@ -26,7 +26,7 @@ open class Profile(val displayName: String?, val avatar: Any?, val description:
} }
} }
class ContactViewModel(val contact: Contact, val profile: Profile, val registeredName: String? = null, val presence: Boolean = false) { class ContactViewModel(val contact: Contact, val profile: Profile, val registeredName: String? = null, val presence: Contact.PresenceStatus = Contact.PresenceStatus.OFFLINE) {
val displayUri: String val displayUri: String
get() = registeredName ?: contact.uri.toString() get() = registeredName ?: contact.uri.toString()
val displayName: String val displayName: String
......
...@@ -75,14 +75,14 @@ abstract class ContactService( ...@@ -75,14 +75,14 @@ abstract class ContactService(
val uriString = contact.uri.rawRingId val uriString = contact.uri.rawRingId
synchronized(contact) { synchronized(contact) {
val presenceUpdates = contact.presenceUpdates ?: run { val presenceUpdates = contact.presenceUpdates ?: run {
Observable.create { emitter: ObservableEmitter<Boolean> -> Observable.create { emitter: ObservableEmitter<Contact.PresenceStatus> ->
emitter.onNext(false) emitter.onNext(Contact.PresenceStatus.OFFLINE)
contact.setPresenceEmitter(emitter) contact.setPresenceEmitter(emitter)
mAccountService.subscribeBuddy(accountId, uriString, true) mAccountService.subscribeBuddy(accountId, uriString, true)
emitter.setCancellable { emitter.setCancellable {
mAccountService.subscribeBuddy(accountId, uriString, false) mAccountService.subscribeBuddy(accountId, uriString, false)
contact.setPresenceEmitter(null) contact.setPresenceEmitter(null)
emitter.onNext(false) emitter.onNext(Contact.PresenceStatus.OFFLINE)
} }
} }
.replay(1) .replay(1)
...@@ -105,7 +105,7 @@ abstract class ContactService( ...@@ -105,7 +105,7 @@ abstract class ContactService(
return if (contact.isUser) { return if (contact.isUser) {
mAccountService.getObservableAccountProfile(accountId).map { profile -> mAccountService.getObservableAccountProfile(accountId).map { profile ->
ContactViewModel(contact, profile.second, profile.first.registeredName.ifEmpty { null }, withPresence && profile.first.isRegistered) } ContactViewModel(contact, profile.second, profile.first.registeredName.ifEmpty { null }, if (withPresence) profile.first.presenceStatus else Contact.PresenceStatus.OFFLINE) }
} else { } else {
if (contact.loadedProfile == null) { if (contact.loadedProfile == null) {
contact.loadedProfile = loadContactData(contact, accountId).cache() contact.loadedProfile = loadContactData(contact, accountId).cache()
...@@ -116,7 +116,7 @@ abstract class ContactService( ...@@ -116,7 +116,7 @@ abstract class ContactService(
{ profile, name, presence -> ContactViewModel(contact, profile, name.ifEmpty { null }, presence) } { profile, name, presence -> ContactViewModel(contact, profile, name.ifEmpty { null }, presence) }
else else
Observable.combineLatest(contact.profile, username.toObservable()) Observable.combineLatest(contact.profile, username.toObservable())
{ profile, name -> ContactViewModel(contact, profile, name.ifEmpty { null }, false) } { profile, name -> ContactViewModel(contact, profile, name.ifEmpty { null }, Contact.PresenceStatus.OFFLINE) }
} }
} }
} }
......
...@@ -31,7 +31,18 @@ class ConversationItemViewModel( ...@@ -31,7 +31,18 @@ class ConversationItemViewModel(
val mode: Conversation.Mode = conversation.mode.blockingFirst() val mode: Conversation.Mode = conversation.mode.blockingFirst()
val uuid: String = uri.rawUriString val uuid: String = uri.rawUriString
val title: String = getTitle(conversation, conversationProfile, contacts) val title: String = getTitle(conversation, conversationProfile, contacts)
val isOnline: Boolean = showPresence && contacts.firstOrNull { it.presence } != null val presenceStatus: Contact.PresenceStatus = if (showPresence)
contacts.let {
var status = Contact.PresenceStatus.OFFLINE
for (contact in it) {
if (contact.presence == Contact.PresenceStatus.CONNECTED)
return@let Contact.PresenceStatus.CONNECTED
else if (contact.presence == Contact.PresenceStatus.AVAILABLE)
status = Contact.PresenceStatus.AVAILABLE
}
status
} else Contact.PresenceStatus.OFFLINE
var isChecked = false var isChecked = false
var selected: Observable<Boolean>? = conversation.getVisible() var selected: Observable<Boolean>? = conversation.getVisible()
private set private set
...@@ -79,7 +90,7 @@ class ConversationItemViewModel( ...@@ -79,7 +90,7 @@ class ConversationItemViewModel(
if (other !is ConversationItemViewModel) return false if (other !is ConversationItemViewModel) return false
return contacts === other.contacts return contacts === other.contacts
&& title == other.title && title == other.title
&& isOnline == other.isOnline && presenceStatus == other.presenceStatus
} }
companion object { companion object {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment