diff --git a/build.gradle b/build.gradle index b0be9299ab..d003f75977 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'witness' apply plugin: 'io.fabric' +apply plugin: 'kotlin-kapt' repositories { mavenLocal() @@ -112,6 +113,7 @@ dependencies { implementation 'com.github.chrisbanes:PhotoView:2.1.3' implementation 'com.github.bumptech.glide:glide:4.5.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0' + kapt 'com.github.bumptech.glide:compiler:4.5.0' implementation 'com.makeramen:roundedimageview:2.1.0' implementation 'com.pnikosis:materialish-progress:1.5' implementation 'org.greenrobot:eventbus:3.0.0' diff --git a/res/drawable/ic_circle_check.xml b/res/drawable/ic_circle_check.xml new file mode 100644 index 0000000000..a258b5c30c --- /dev/null +++ b/res/drawable/ic_circle_check.xml @@ -0,0 +1,7 @@ + + + + diff --git a/res/drawable/ic_circle_dot_dot_dot.xml b/res/drawable/ic_circle_dot_dot_dot.xml new file mode 100644 index 0000000000..28ecaa1fbe --- /dev/null +++ b/res/drawable/ic_circle_dot_dot_dot.xml @@ -0,0 +1,7 @@ + + + + diff --git a/res/drawable/ic_filled_circle_check.xml b/res/drawable/ic_filled_circle_check.xml new file mode 100644 index 0000000000..c45e603d4c --- /dev/null +++ b/res/drawable/ic_filled_circle_check.xml @@ -0,0 +1,5 @@ + + + + diff --git a/res/layout/view_conversation.xml b/res/layout/view_conversation.xml index 531313a97c..07d499dfb3 100644 --- a/res/layout/view_conversation.xml +++ b/res/layout/view_conversation.xml @@ -1,8 +1,8 @@ - @@ -70,22 +70,35 @@ android:orientation="horizontal" android:gravity="center_vertical"> - + android:layout_height="wrap_content"> + + + + + + - { val result = ScanQRCodeWrapperFragment() result.delegate = activity - result.message = "Users can share their QR code by going into their account settings and tapping "Share QR Code"" + result.message = "Users can share their QR code by going into their account settings and tapping \"Share QR Code\"" result } else -> throw IllegalStateException() diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt index da88fbef8b..ab740cfc2f 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.loki.redesign.activities +import android.arch.lifecycle.Observer import android.content.Intent import android.os.Bundle import android.support.v7.widget.LinearLayoutManager @@ -14,9 +15,12 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.loki.redesign.utilities.push import org.thoughtcrime.securesms.loki.redesign.views.ConversationView +import org.thoughtcrime.securesms.mms.GlideApp +import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.TextSecurePreferences class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener { + private lateinit var glide: GlideRequests // region Lifecycle constructor() : super() @@ -27,14 +31,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe setContentView(R.layout.activity_home) // Set title supportActionBar!!.title = "Messages" + // Set up Glide + glide = GlideApp.with(this) // Set up recycler view val cursor = DatabaseFactory.getThreadDatabase(this).conversationList - val conversationAdapter = HomeAdapter(this, cursor) - conversationAdapter.conversationClickListener = this - recyclerView.adapter = conversationAdapter + val homeAdapter = HomeAdapter(this, cursor) + homeAdapter.glide = glide + homeAdapter.conversationClickListener = this + recyclerView.adapter = homeAdapter recyclerView.layoutManager = LinearLayoutManager(this) // Set up new conversation button newConversationButton.setOnClickListener { createPrivateChat() } + // Set up typing observer + ApplicationContext.getInstance(this).typingStatusRepository.typingThreads.observe(this, object : Observer> { + + override fun onChanged(threadIDs: Set?) { + val adapter = recyclerView.adapter as HomeAdapter + adapter.typingThreadIDs = threadIDs ?: setOf() + } + }) // Set up public chats and RSS feeds if needed if (TextSecurePreferences.getLocalNumber(this) != null) { val application = ApplicationContext.getInstance(this) diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeAdapter.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeAdapter.kt index 1ebf829b04..b5eb5885d7 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeAdapter.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeAdapter.kt @@ -8,9 +8,13 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.loki.redesign.views.ConversationView +import org.thoughtcrime.securesms.mms.GlideRequests class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter(context, cursor) { private val threadDatabase = DatabaseFactory.getThreadDatabase(context) + lateinit var glide: GlideRequests + var typingThreadIDs = setOf() + set(value) { field = value; notifyDataSetChanged() } var conversationClickListener: ConversationClickListener? = null class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) @@ -26,7 +30,9 @@ class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter< } override fun onBindItemViewHolder(viewHolder: ViewHolder, cursor: Cursor) { - viewHolder.view.bind(getThread(cursor)!!) + val thread = getThread(cursor)!! + val isTyping = typingThreadIDs.contains(thread.threadId) + viewHolder.view.bind(thread, isTyping, glide) } private fun getThread(cursor: Cursor): ThreadRecord? { diff --git a/src/org/thoughtcrime/securesms/loki/redesign/views/ConversationView.kt b/src/org/thoughtcrime/securesms/loki/redesign/views/ConversationView.kt index c40c313984..f145b4a9f5 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/views/ConversationView.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/views/ConversationView.kt @@ -11,6 +11,7 @@ import network.loki.messenger.R import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.loki.LokiAPIUtilities.populateUserHexEncodedPublicKeyCacheIfNeeded import org.thoughtcrime.securesms.loki.MentionUtilities.highlightMentions +import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.DateUtils import org.whispersystems.signalservice.loki.api.LokiAPI import java.util.* @@ -43,7 +44,7 @@ class ConversationView : LinearLayout { // endregion // region Updating - fun bind(thread: ThreadRecord) { + fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) { this.thread = thread populateUserHexEncodedPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a terrible place to do this unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE @@ -58,6 +59,7 @@ class ConversationView : LinearLayout { profilePictureView.additionalHexEncodedPublicKey = null profilePictureView.isRSSFeed = false } + profilePictureView.glide = glide profilePictureView.update() val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else thread.recipient.name displayNameTextView.text = senderDisplayName @@ -66,6 +68,21 @@ class ConversationView : LinearLayout { val snippet = highlightMentions(rawSnippet, thread.threadId, context) snippetTextView.text = snippet snippetTextView.typeface = if (thread.unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT + snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE + if (isTyping) { + typingIndicatorView.startAnimation() + } else { + typingIndicatorView.stopAnimation() + } + typingIndicatorView.visibility = if (isTyping) View.VISIBLE else View.GONE + statusIndicatorImageView.visibility = View.VISIBLE + when { + !thread.isOutgoing || thread.isVerificationStatusChange -> statusIndicatorImageView.visibility = View.GONE + thread.isFailed -> statusIndicatorImageView.setImageResource(R.drawable.ic_error) + thread.isPending -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot) + thread.isRemoteRead -> statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) + else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) + } } // endregion } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/redesign/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/redesign/views/ProfilePictureView.kt index f2b0b237f1..351d314bdb 100644 --- a/src/org/thoughtcrime/securesms/loki/redesign/views/ProfilePictureView.kt +++ b/src/org/thoughtcrime/securesms/loki/redesign/views/ProfilePictureView.kt @@ -4,11 +4,17 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View +import android.widget.ImageView import android.widget.RelativeLayout +import com.bumptech.glide.load.engine.DiskCacheStrategy import kotlinx.android.synthetic.main.view_profile_picture.view.* import network.loki.messenger.R +import org.thoughtcrime.securesms.database.Address +import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.recipients.Recipient class ProfilePictureView : RelativeLayout { + lateinit var glide: GlideRequests var hexEncodedPublicKey: String? = null var additionalHexEncodedPublicKey: String? = null var isRSSFeed = false @@ -44,6 +50,22 @@ class ProfilePictureView : RelativeLayout { doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE singleModeImageView.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed) View.VISIBLE else View.INVISIBLE rssTextView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE + fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String) { + glide.clear(imageView) + if (hexEncodedPublicKey.isNotEmpty()) { + val signalProfilePicture = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).contactPhoto + if (signalProfilePicture != null) { + glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) + } else { + imageView.setImageDrawable(null) + } + } else { + imageView.setImageDrawable(null) + } + } + setProfilePictureIfNeeded(singleModeImageView, hexEncodedPublicKey) + setProfilePictureIfNeeded(doubleModeImageView1, hexEncodedPublicKey) + setProfilePictureIfNeeded(doubleModeImageView2, additionalHexEncodedPublicKey ?: "") } // endregion } \ No newline at end of file