chats: base chat UI

main
Alibek Omarov 4 years ago
parent 9f57699a3d
commit a002a76b92
  1. 3
      app/src/husky/res/values/strings.xml
  2. 3
      app/src/main/AndroidManifest.xml
  3. 6
      app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt
  4. 11
      app/src/main/java/com/keylesspalace/tusky/adapter/ChatsAdapter.kt
  5. 96
      app/src/main/java/com/keylesspalace/tusky/components/chat/ChatActivity.kt
  6. 4
      app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt
  7. 29
      app/src/main/java/com/keylesspalace/tusky/entity/Emoji.kt
  8. 9
      app/src/main/java/com/keylesspalace/tusky/fragment/ChatsFragment.kt
  9. 2
      app/src/main/java/com/keylesspalace/tusky/interfaces/ChatActionListener.kt
  10. 193
      app/src/main/res/layout/activity_chat.xml

@ -87,7 +87,8 @@
<string name="title_scheduled_toot">Scheduled posts</string>
<string name="title_reblogged_by">Repeated by</string>
<string name="title_view_thread">Post</string>
<string name="chat_message_hint_text">Just landed in L.A.</string>
<!--
<string name="about_tusky_version">Husky %s</string>
<string name="about_powered_by_tusky">Powered by Husky</string>

@ -106,6 +106,9 @@
android:name=".components.compose.ComposeActivity"
android:theme="@style/TuskyDialogActivityTheme"
android:windowSoftInputMode="stateVisible|adjustResize"/>
<activity
android:name=".components.chat.ChatActivity"
android:windowSoftInputMode="stateVisible|adjustResize"/>
<activity
android:name=".ViewThreadActivity"
android:configChanges="orientation|screenSize" />

@ -23,6 +23,8 @@ import android.widget.Toast
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.keylesspalace.tusky.components.chat.ChatActivity
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.LinkHelper
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
@ -112,6 +114,10 @@ abstract class BottomSheetActivity : BaseActivity() {
startActivityWithSlideInAnimation(intent)
}
open fun openChat(chat: Chat) {
startActivityWithSlideInAnimation(ChatActivity.getIntent(this, chat))
}
protected open fun performUrlFallbackAction(url: String, fallbackBehavior: PostLookupFallbackBehavior) {
when (fallbackBehavior) {
PostLookupFallbackBehavior.OPEN_IN_BROWSER -> openLink(url)

@ -42,7 +42,7 @@ class ChatsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
if(payload == null) {
displayName.text = chat.account.displayName?.emojify(chat.account.emojis, displayName, true)
?: ""
userName.text = userName.context.getString(R.string.status_username_format, chat.account.localUsername)
userName.text = userName.context.getString(R.string.status_username_format, chat.account.username)
setUpdatedAt(chat.updatedAt, statusDisplayOptions)
setAvatar(chat.account.avatar, chat.account.bot, statusDisplayOptions)
if(chat.unread <= 0) {
@ -57,11 +57,16 @@ class ChatsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
listener.onMore(chat.id, it)
true
}
val onClickListener = View.OnClickListener {
val pos = adapterPosition
if(pos != RecyclerView.NO_POSITION)
listener.openChat(pos)
}
content.setOnLongClickListener(onLongClickListener)
itemView.setOnLongClickListener(onLongClickListener)
content.setOnClickListener { }
itemView.setOnClickListener { }
content.setOnClickListener(onClickListener)
itemView.setOnClickListener(onClickListener)
chat.lastMessage?.let {
content.text = it.content.emojify(it.emojis, content, true)

@ -0,0 +1,96 @@
package com.keylesspalace.tusky.components.chat
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.repository.ChatRepository
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.loadAvatar
import kotlinx.android.synthetic.main.activity_chat.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import kotlinx.android.synthetic.main.toolbar_basic.toolbar
import javax.inject.Inject
class ChatActivity: BaseActivity(),
Injectable {
@Inject
lateinit var eventHub: EventHub
@Inject
lateinit var api: MastodonApi
@Inject
lateinit var chatsRepo: ChatRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val chatId = intent.getStringExtra(ID)
val avatarUrl = intent.getStringExtra(AVATAR_URL)
val displayName = intent.getStringExtra(DISPLAY_NAME)
val username = intent.getStringExtra(USERNAME)
val emojis = intent.getParcelableArrayListExtra<Emoji>(EMOJIS)
if(chatId == null || avatarUrl == null || displayName == null || username == null || emojis == null) {
throw IllegalArgumentException("Can't open ChatActivity without chat id")
}
setContentView(R.layout.activity_chat)
setSupportActionBar(toolbar)
supportActionBar?.run {
title = ""
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
loadAvatar(avatarUrl, chatAvatar,
resources.getDimensionPixelSize(R.dimen.avatar_radius_24dp),true)
chatTitle.text = displayName.emojify(emojis, chatTitle, true)
chatUsername.text = username
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
companion object {
fun getIntent(context: Context, chat: Chat) : Intent {
val intent = Intent(context, ChatActivity::class.java)
intent.putExtra(ID, chat.id)
intent.putExtra(AVATAR_URL, chat.account.avatar)
intent.putExtra(DISPLAY_NAME, chat.account.displayName ?: chat.account.localUsername)
intent.putParcelableArrayListExtra(EMOJIS, ArrayList(chat.account.emojis ?: emptyList<Emoji>()))
intent.putExtra(USERNAME, chat.account.username)
return intent
}
const val ID = "id"
const val AVATAR_URL = "avatar_url"
const val DISPLAY_NAME = "display_name"
const val USERNAME = "username"
const val EMOJIS = "emojis"
}
}

@ -16,6 +16,7 @@
package com.keylesspalace.tusky.di
import com.keylesspalace.tusky.*
import com.keylesspalace.tusky.components.chat.ChatActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.instancemute.InstanceListActivity
import com.keylesspalace.tusky.components.report.ReportActivity
@ -46,6 +47,9 @@ abstract class ActivitiesModule {
@ContributesAndroidInjector
abstract fun contributesComposeActivity(): ComposeActivity
@ContributesAndroidInjector
abstract fun contributesChatActivity(): ChatActivity
@ContributesAndroidInjector
abstract fun contributesEditProfileActivity(): EditProfileActivity

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.entity
import android.os.Parcel
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.android.parcel.Parcelize
@ -23,7 +24,33 @@ data class Emoji(
val shortcode: String,
val url: String,
@SerializedName("visible_in_picker") val visibleInPicker: Boolean?
)
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString()!!,
parcel.readString()!!,
parcel.readValue(Boolean::class.java.classLoader) as? Boolean) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(shortcode)
parcel.writeString(url)
parcel.writeValue(visibleInPicker)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Emoji> {
override fun createFromParcel(parcel: Parcel): Emoji {
return Emoji(parcel)
}
override fun newArray(size: Int): Array<Emoji?> {
return arrayOfNulls(size)
}
}
}
data class EmojiReaction(
val name: String,

@ -1,6 +1,7 @@
package com.keylesspalace.tusky.fragment
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -19,6 +20,7 @@ import com.keylesspalace.tusky.adapter.ChatsAdapter
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
import com.keylesspalace.tusky.adapter.TimelineAdapter
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.chat.ChatActivity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Chat
@ -719,4 +721,11 @@ class ChatsFragment : BaseFragment(), Injectable, RefreshableFragment, Reselecta
}
popup.show()
}
override fun openChat(position: Int) {
val chat = chats[position].asRightOrNull()
chat?.let {
bottomSheetActivity.openChat(it)
}
}
}

@ -7,4 +7,6 @@ interface ChatActionListener: LinkListener {
fun onLoadMore(position: Int)
fun onMore(chatId: String, v: View)
fun openChat(position: Int)
}

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/actionbar_elevation"
app:layout_collapseMode="pin">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
>
<ImageView
android:id="@+id/chatAvatar"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:padding="8dp"
android:contentDescription="@string/action_view_profile"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/avatar_default" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/chatTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:importantForAccessibility="no"
android:maxLines="1"
android:paddingEnd="@dimen/status_display_name_padding_end"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
android:textStyle="normal|bold"
tools:text="Ente r the void you foooooo" />
<TextView
android:id="@+id/chatUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:importantForAccessibility="no"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_large"
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/composeLayout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/composeLayout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<com.keylesspalace.tusky.view.BackgroundMessageView
android:id="@+id/messageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/composeLayout"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:visibility="visible"
app:layout_constrainedHeight="true" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/composeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?attr/colorSurface"
android:animateLayoutChanges="true"
android:paddingTop="4dp"
android:paddingBottom="4dp"
app:layout_constraintTop_toBottomOf="@+id/recycler"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<FrameLayout
android:id="@+id/attachmentLayout"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="8dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:visibility="visible">
<com.keylesspalace.tusky.components.compose.view.ProgressImageView
android:id="@+id/imageAttachment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible"
/>
<com.keylesspalace.tusky.components.compose.view.ProgressTextView
android:id="@+id/textAttachment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:ellipsize="marquee"
android:marqueeRepeatLimit="-1"
android:singleLine="true"
android:textSize="?attr/status_text_small"
tools:visibility="visible"
/>
</FrameLayout>
<ImageButton
android:id="@+id/attachmentButton"
style="@style/TuskyImageButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_add_media"
android:tooltipText="@string/action_add_media"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/attachmentLayout"
app:srcCompat="@drawable/ic_attach_file_24dp" />
<androidx.emoji.widget.EmojiEditText
android:id="@+id/editText"
android:layout_width="0dp"
android:layout_height="48dp"
android:textSize="?attr/status_text_large"
android:singleLine="false"
android:background="@null"
android:hint="@string/chat_message_hint_text"
app:layout_constraintEnd_toStartOf="@+id/emojiButton"
app:layout_constraintRight_toLeftOf="@id/emojiButton"
app:layout_constraintStart_toEndOf="@+id/attachmentButton"
app:layout_constraintTop_toBottomOf="@id/attachmentLayout"
tools:text="Just landed in L.A." />
<ImageButton
android:id="@+id/emojiButton"
style="@style/TuskyImageButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_emoji_keyboard"
android:tooltipText="@string/action_emoji_keyboard"
app:srcCompat="@drawable/ic_emoji_24dp"
app:layout_constraintTop_toBottomOf="@id/attachmentLayout"
app:layout_constraintRight_toLeftOf="@id/sendButton"
/>
<ImageButton
android:id="@+id/sendButton"
style="@style/TuskyImageButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_send"
android:tooltipText="@string/action_send"
app:srcCompat="@drawable/ic_send_24dp"
app:layout_constraintTop_toBottomOf="@id/attachmentLayout"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save