forked from melod1n/fast-messenger
я не помню, что тут
This commit is contained in:
@@ -7,9 +7,9 @@ plugins {
|
|||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
|
id("kotlin-parcelize")
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
id("dagger.hilt.android.plugin")
|
id("dagger.hilt.android.plugin")
|
||||||
id("kotlin-parcelize")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -38,6 +38,9 @@ android {
|
|||||||
getByName("release") {
|
getByName("release") {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
|
|
||||||
|
buildConfigField("String", "vkLogin", login)
|
||||||
|
buildConfigField("String", "vkPassword", password)
|
||||||
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
@@ -68,7 +71,7 @@ android {
|
|||||||
kapt {
|
kapt {
|
||||||
correctErrorTypes = true
|
correctErrorTypes = true
|
||||||
|
|
||||||
//use this shit if you don't want to have hilt errors
|
//use this shit if you don't want have hilt errors
|
||||||
javacOptions {
|
javacOptions {
|
||||||
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
|
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
|
||||||
}
|
}
|
||||||
@@ -83,6 +86,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
implementation("androidx.datastore:datastore-preferences:1.0.0")
|
||||||
|
|
||||||
|
implementation("androidx.paging:paging-runtime-ktx:3.0.1")
|
||||||
|
|
||||||
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
|
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
|
||||||
implementation("com.google.android.material:material:1.5.0-alpha03")
|
implementation("com.google.android.material:material:1.5.0-alpha03")
|
||||||
implementation("androidx.core:core-ktx:1.7.0-beta01")
|
implementation("androidx.core:core-ktx:1.7.0-beta01")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.meloda.fast.common.AppGlobal
|
|||||||
|
|
||||||
object UserConfig {
|
object UserConfig {
|
||||||
|
|
||||||
|
private const val FAST_TOKEN = "fast_token"
|
||||||
private const val TOKEN = "token"
|
private const val TOKEN = "token"
|
||||||
private const val USER_ID = "user_id"
|
private const val USER_ID = "user_id"
|
||||||
|
|
||||||
@@ -25,8 +26,16 @@ object UserConfig {
|
|||||||
AppGlobal.preferences.edit().putString(TOKEN, value).apply()
|
AppGlobal.preferences.edit().putString(TOKEN, value).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fastToken: String = ""
|
||||||
|
get() = AppGlobal.preferences.getString(FAST_TOKEN, "") ?: ""
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
AppGlobal.preferences.edit().putString(FAST_TOKEN, value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
accessToken = ""
|
accessToken = ""
|
||||||
|
fastToken = ""
|
||||||
userId = -1
|
userId = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ object VKConstants {
|
|||||||
const val USER_FIELDS =
|
const val USER_FIELDS =
|
||||||
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info"
|
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info"
|
||||||
|
|
||||||
|
const val ALL_FIELDS = "$USER_FIELDS,$GROUP_FIELDS"
|
||||||
|
|
||||||
const val API_VERSION = "5.132"
|
const val API_VERSION = "5.132"
|
||||||
const val VK_APP_ID = "2274003"
|
const val VK_APP_ID = "2274003"
|
||||||
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ object VkUtils {
|
|||||||
return forwards
|
return forwards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseReplyMessage(baseReplyMessage: BaseVkMessage?): VkMessage? {
|
||||||
|
if (baseReplyMessage == null) return null
|
||||||
|
|
||||||
|
return baseReplyMessage.asVkMessage()
|
||||||
|
}
|
||||||
|
|
||||||
fun parseAttachments(baseAttachments: List<BaseVkAttachmentItem>?): List<VkAttachment>? {
|
fun parseAttachments(baseAttachments: List<BaseVkAttachmentItem>?): List<VkAttachment>? {
|
||||||
if (baseAttachments.isNullOrEmpty()) return null
|
if (baseAttachments.isNullOrEmpty()) return null
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.meloda.fast.api.model
|
package com.meloda.fast.api.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||||
|
import com.meloda.fast.base.adapter.SelectableItem
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Entity(tableName = "messages")
|
@Entity(tableName = "messages")
|
||||||
@@ -24,9 +27,21 @@ data class VkMessage(
|
|||||||
val actionMessage: String? = null,
|
val actionMessage: String? = null,
|
||||||
val geoType: String? = null,
|
val geoType: String? = null,
|
||||||
val important: Boolean = false,
|
val important: Boolean = false,
|
||||||
|
|
||||||
var forwards: List<VkMessage>? = null,
|
var forwards: List<VkMessage>? = null,
|
||||||
var attachments: List<VkAttachment>? = null
|
var attachments: List<VkAttachment>? = null,
|
||||||
) : Parcelable {
|
|
||||||
|
// @Embedded(prefix = "replyMessage_")
|
||||||
|
var replyMessage: VkMessage? = null
|
||||||
|
) : SelectableItem() {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val user = MutableLiveData<VkUser?>()
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val group = MutableLiveData<VkGroup?>()
|
||||||
|
|
||||||
fun isPeerChat() = peerId > 2_000_000_000
|
fun isPeerChat() = peerId > 2_000_000_000
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import androidx.room.Ignore
|
||||||
import com.meloda.fast.api.model.base.attachments.BaseVkPhoto
|
import com.meloda.fast.api.model.base.attachments.BaseVkPhoto
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class VkPhoto(
|
data class VkPhoto(
|
||||||
@@ -17,13 +19,58 @@ data class VkPhoto(
|
|||||||
val userId: Int?
|
val userId: Int?
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
private val sizesChars = Stack<Char>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
sizesChars.push('s')
|
||||||
|
sizesChars.push('m')
|
||||||
|
sizesChars.push('x')
|
||||||
|
sizesChars.push('o')
|
||||||
|
sizesChars.push('p')
|
||||||
|
sizesChars.push('q')
|
||||||
|
sizesChars.push('r')
|
||||||
|
sizesChars.push('y')
|
||||||
|
sizesChars.push('z')
|
||||||
|
sizesChars.push('w')
|
||||||
|
}
|
||||||
|
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
val className: String = this::class.java.name
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
fun sizeOfType(type: Char): BaseVkPhoto.Size? {
|
fun getMaxSize(): BaseVkPhoto.Size? {
|
||||||
|
return getSizeOrSmaller(sizesChars.peek())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSizeOrNull(type: Char): BaseVkPhoto.Size? {
|
||||||
for (size in sizes) {
|
for (size in sizes) {
|
||||||
if (size.type == type.toString())
|
if (size.type == type.toString()) return size
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSizeOrSmaller(type: Char): BaseVkPhoto.Size? {
|
||||||
|
val photoStack = sizesChars.clone() as Stack<Char>
|
||||||
|
|
||||||
|
val sizeIndex = photoStack.search(type)
|
||||||
|
|
||||||
|
if (sizeIndex == -1) return null
|
||||||
|
|
||||||
|
for (i in 0 until sizeIndex) {
|
||||||
|
photoStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until photoStack.size) {
|
||||||
|
val size = getSizeOrNull(photoStack.peek())
|
||||||
|
|
||||||
|
if (size == null) {
|
||||||
|
photoStack.pop()
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
return size
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ data class BaseVkMessage(
|
|||||||
val payload: String,
|
val payload: String,
|
||||||
val geo: Geo?,
|
val geo: Geo?,
|
||||||
val action: Action?,
|
val action: Action?,
|
||||||
val ttl: Int
|
val ttl: Int,
|
||||||
|
val reply_message: BaseVkMessage?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
fun asVkMessage() = VkMessage(
|
fun asVkMessage() = VkMessage(
|
||||||
@@ -44,6 +45,7 @@ data class BaseVkMessage(
|
|||||||
).also {
|
).also {
|
||||||
it.attachments = VkUtils.parseAttachments(attachments)
|
it.attachments = VkUtils.parseAttachments(attachments)
|
||||||
it.forwards = VkUtils.parseForwards(fwd_messages)
|
it.forwards = VkUtils.parseForwards(fwd_messages)
|
||||||
|
it.replyMessage = VkUtils.parseReplyMessage(reply_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class AuthInterceptor : Interceptor {
|
|||||||
builder.addQueryParameter("access_token", URLEncoder.encode(it, "utf-8"))
|
builder.addQueryParameter("access_token", URLEncoder.encode(it, "utf-8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: 9/29/2021 crash on timeout
|
||||||
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
|
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ abstract class BaseAdapter<Item, VH : BaseHolder>(
|
|||||||
holder.bind(position)
|
holder.bind(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun initListeners(itemView: View, position: Int) {
|
protected open fun initListeners(itemView: View, position: Int) {
|
||||||
if (itemView is AdapterView<*>) return
|
if (itemView is AdapterView<*>) return
|
||||||
|
|
||||||
itemView.setOnClickListener { itemClickListener.invoke(position) }
|
itemView.setOnClickListener { itemClickListener.invoke(position) }
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.meloda.fast.base.adapter
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
open class SelectableItem : Parcelable {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
var isSelected: Boolean = false
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import com.meloda.fast.database.dao.UsersDao
|
|||||||
VkUser::class,
|
VkUser::class,
|
||||||
VkGroup::class
|
VkGroup::class
|
||||||
],
|
],
|
||||||
version = 24,
|
version = 25,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.meloda.fast.extensions
|
package com.meloda.fast.extensions
|
||||||
|
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
fun Bitmap.borderedCircularBitmap(
|
fun Bitmap.borderedCircularBitmap(
|
||||||
@@ -70,4 +72,6 @@ fun Bitmap.borderedCircularBitmap(
|
|||||||
diameter, // width
|
diameter, // width
|
||||||
diameter // height
|
diameter // height
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val View.isNotVisible get() = !isVisible
|
||||||
+85
-20
@@ -1,25 +1,31 @@
|
|||||||
package com.meloda.fast.screens.conversations
|
package com.meloda.fast.screens.conversations
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.activity.MainActivity
|
||||||
import com.meloda.fast.api.UserConfig
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.api.model.VkConversation
|
import com.meloda.fast.api.model.VkConversation
|
||||||
import com.meloda.fast.base.BaseViewModelFragment
|
import com.meloda.fast.base.BaseViewModelFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
|
import com.meloda.fast.common.AppGlobal
|
||||||
import com.meloda.fast.common.AppSettings
|
import com.meloda.fast.common.AppSettings
|
||||||
import com.meloda.fast.common.dataStore
|
import com.meloda.fast.common.dataStore
|
||||||
import com.meloda.fast.databinding.FragmentConversationsBinding
|
import com.meloda.fast.databinding.FragmentConversationsBinding
|
||||||
@@ -29,6 +35,7 @@ import kotlinx.coroutines.flow.collect
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ConversationsFragment :
|
class ConversationsFragment :
|
||||||
@@ -53,6 +60,24 @@ class ConversationsFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val avatarPopupMenu: PopupMenu
|
||||||
|
get() =
|
||||||
|
PopupMenu(
|
||||||
|
requireContext(),
|
||||||
|
binding.avatar,
|
||||||
|
Gravity.BOTTOM
|
||||||
|
).apply {
|
||||||
|
menu.add(getString(R.string.log_out))
|
||||||
|
setOnMenuItemClickListener { item ->
|
||||||
|
if (item.title == getString(R.string.log_out)) {
|
||||||
|
showLogOutDialog()
|
||||||
|
return@setOnMenuItemClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var isPaused = false
|
private var isPaused = false
|
||||||
private var isExpanded = true
|
private var isExpanded = true
|
||||||
|
|
||||||
@@ -74,11 +99,7 @@ class ConversationsFragment :
|
|||||||
}.collect { }
|
}.collect { }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.createChat.setOnClickListener {
|
binding.createChat.setOnClickListener {}
|
||||||
Snackbar.make(it, "Test snackbar", Snackbar.LENGTH_SHORT)
|
|
||||||
.setAction("Action") {}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
UserConfig.vkUser.observe(viewLifecycleOwner) {
|
UserConfig.vkUser.observe(viewLifecycleOwner) {
|
||||||
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
|
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
|
||||||
@@ -87,28 +108,49 @@ class ConversationsFragment :
|
|||||||
binding.appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
binding.appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
||||||
if (isPaused) return@OnOffsetChangedListener
|
if (isPaused) return@OnOffsetChangedListener
|
||||||
|
|
||||||
if (verticalOffset <= -100) {
|
|
||||||
binding.avatarContainer.alpha = 0f
|
|
||||||
return@OnOffsetChangedListener
|
|
||||||
}
|
|
||||||
|
|
||||||
val alpha = 1 - abs(verticalOffset * 0.01).toFloat()
|
// if (verticalOffset <= -100) {
|
||||||
|
// binding.avatarContainer.alpha = 0f
|
||||||
|
// return@OnOffsetChangedListener
|
||||||
|
// }
|
||||||
|
|
||||||
binding.avatarContainer.alpha = alpha
|
// from 0 to -294
|
||||||
|
// from 0 to 29
|
||||||
|
|
||||||
|
// if -147
|
||||||
|
// 30 - value
|
||||||
|
|
||||||
|
var value = 30 - (abs(verticalOffset) * 0.1).roundToInt()
|
||||||
|
|
||||||
|
val bottomPadding = 0
|
||||||
|
// if (verticalOffset > -150) AndroidUtils.px(30).roundToInt()
|
||||||
|
// else (30 + abs(verticalOffset) * 0.1).roundToInt()
|
||||||
|
|
||||||
|
val endPadding = 0
|
||||||
|
// if (verticalOffset > 30) 30
|
||||||
|
// else (abs(verticalOffset) * 0.1).roundToInt()
|
||||||
|
|
||||||
|
binding.avatarContainer.updatePadding(
|
||||||
|
bottom = value,
|
||||||
|
right = endPadding
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
println("Fast::ConversationsFragment::onOffset verticalOffset = $verticalOffset; bottomPadding = $value; endPadding = $endPadding")
|
||||||
|
|
||||||
|
|
||||||
|
// binding.avatarContainer.alpha = alpha
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isPaused) {
|
|
||||||
isPaused = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.toolbar.overflowIcon = ContextCompat.getDrawable(requireContext(), R.drawable.test)
|
binding.toolbar.overflowIcon = ContextCompat.getDrawable(requireContext(), R.drawable.test)
|
||||||
|
|
||||||
viewModel.loadProfileUser()
|
|
||||||
viewModel.loadConversations()
|
|
||||||
|
|
||||||
binding.avatar.setOnClickListener {
|
binding.avatar.setOnClickListener {
|
||||||
lifecycleScope.launchWhenResumed {
|
avatarPopupMenu.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.avatar.setOnLongClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
requireContext().dataStore.edit { settings ->
|
requireContext().dataStore.edit { settings ->
|
||||||
val isMultilineEnabled = settings[AppSettings.keyIsMultilineEnabled] ?: true
|
val isMultilineEnabled = settings[AppSettings.keyIsMultilineEnabled] ?: true
|
||||||
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
||||||
@@ -117,7 +159,30 @@ class ConversationsFragment :
|
|||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPaused) {
|
||||||
|
isPaused = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadProfileUser()
|
||||||
|
viewModel.loadConversations()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLogOutDialog() {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.confirm)
|
||||||
|
.setMessage(R.string.log_out_confirm)
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
UserConfig.clear()
|
||||||
|
AppGlobal.appDatabase.clearAllTables()
|
||||||
|
requireActivity().finishAffinity()
|
||||||
|
requireActivity().startActivity(Intent(requireContext(), MainActivity::class.java))
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VKEvent) {
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package com.meloda.fast.screens.conversations
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.meloda.fast.api.UserConfig
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.api.VKConstants
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.api.network.datasource.ConversationsDataSource
|
|
||||||
import com.meloda.fast.api.network.datasource.UsersDataSource
|
|
||||||
import com.meloda.fast.api.model.VkConversation
|
import com.meloda.fast.api.model.VkConversation
|
||||||
import com.meloda.fast.api.model.VkGroup
|
import com.meloda.fast.api.model.VkGroup
|
||||||
import com.meloda.fast.api.model.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.model.request.ConversationsGetRequest
|
import com.meloda.fast.api.model.request.ConversationsGetRequest
|
||||||
import com.meloda.fast.api.model.request.UsersGetRequest
|
import com.meloda.fast.api.model.request.UsersGetRequest
|
||||||
|
import com.meloda.fast.api.network.datasource.ConversationsDataSource
|
||||||
|
import com.meloda.fast.api.network.datasource.UsersDataSource
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||||
@@ -26,13 +26,15 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
private val usersDataSource: UsersDataSource
|
private val usersDataSource: UsersDataSource
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
fun loadConversations() = viewModelScope.launch(Dispatchers.Default) {
|
fun loadConversations(
|
||||||
|
offset: Int? = null
|
||||||
|
) = viewModelScope.launch(Dispatchers.Default) {
|
||||||
makeJob({
|
makeJob({
|
||||||
dataSource.getAllChats(
|
dataSource.getAllChats(
|
||||||
ConversationsGetRequest(
|
ConversationsGetRequest(
|
||||||
count = 30,
|
count = 30,
|
||||||
// offset = 177,
|
|
||||||
extended = true,
|
extended = true,
|
||||||
|
offset = offset,
|
||||||
fields = "${VKConstants.USER_FIELDS},${VKConstants.GROUP_FIELDS}"
|
fields = "${VKConstants.USER_FIELDS},${VKConstants.GROUP_FIELDS}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -52,6 +54,7 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
sendEvent(
|
sendEvent(
|
||||||
ConversationsLoaded(
|
ConversationsLoaded(
|
||||||
count = response.count,
|
count = response.count,
|
||||||
|
offset = offset,
|
||||||
unreadCount = response.unreadCount ?: 0,
|
unreadCount = response.unreadCount ?: 0,
|
||||||
conversations = response.items.map { items ->
|
conversations = response.items.map { items ->
|
||||||
items.conversation.asVkConversation(
|
items.conversation.asVkConversation(
|
||||||
@@ -73,9 +76,7 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadProfileUser() = viewModelScope.launch {
|
fun loadProfileUser() = viewModelScope.launch {
|
||||||
makeJob({
|
makeJob({ usersDataSource.getById(UsersGetRequest(fields = VKConstants.USER_FIELDS)) },
|
||||||
usersDataSource.getById(UsersGetRequest(fields = "online,photo_200"))
|
|
||||||
},
|
|
||||||
onAnswer = {
|
onAnswer = {
|
||||||
it.response?.let { r ->
|
it.response?.let { r ->
|
||||||
val users = r.map { u -> u.asVkUser() }
|
val users = r.map { u -> u.asVkUser() }
|
||||||
@@ -89,6 +90,7 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
|
|
||||||
data class ConversationsLoaded(
|
data class ConversationsLoaded(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
|
val offset: Int?,
|
||||||
val unreadCount: Int?,
|
val unreadCount: Int?,
|
||||||
val conversations: List<VkConversation>,
|
val conversations: List<VkConversation>,
|
||||||
val profiles: HashMap<Int, VkUser>,
|
val profiles: HashMap<Int, VkUser>,
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
package com.meloda.fast.screens.login
|
package com.meloda.fast.screens.login
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@@ -19,6 +25,8 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.meloda.fast.BuildConfig
|
import com.meloda.fast.BuildConfig
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.api.UserConfig
|
||||||
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.base.BaseViewModelFragment
|
import com.meloda.fast.base.BaseViewModelFragment
|
||||||
import com.meloda.fast.base.viewmodel.*
|
import com.meloda.fast.base.viewmodel.*
|
||||||
import com.meloda.fast.databinding.DialogCaptchaBinding
|
import com.meloda.fast.databinding.DialogCaptchaBinding
|
||||||
@@ -29,7 +37,10 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.regex.Pattern
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -89,11 +100,91 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareViews() {
|
private fun prepareViews() {
|
||||||
|
prepareWebView()
|
||||||
prepareEmailEditText()
|
prepareEmailEditText()
|
||||||
preparePasswordEditText()
|
preparePasswordEditText()
|
||||||
prepareAuthButton()
|
prepareAuthButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
private fun prepareWebView() {
|
||||||
|
with(binding.webView) {
|
||||||
|
settings.javaScriptEnabled = true
|
||||||
|
settings.domStorageEnabled = true
|
||||||
|
|
||||||
|
clearCache(true)
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageStarted(view: WebView?, url: String, favicon: Bitmap?) {
|
||||||
|
super.onPageStarted(view, url, favicon)
|
||||||
|
parseAuthUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
|
||||||
|
val a = Jsoup.parse(url)
|
||||||
|
|
||||||
|
val b = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CookieManager.getInstance().apply {
|
||||||
|
removeAllCookies(null)
|
||||||
|
flush()
|
||||||
|
setAcceptCookie(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchWebView() {
|
||||||
|
binding.webView.loadUrl(
|
||||||
|
"https://oauth.vk.com/authorize?client_id=${UserConfig.FAST_APP_ID}&" +
|
||||||
|
"display=mobile&scope=136297695&" +
|
||||||
|
"redirect_uri=${
|
||||||
|
URLEncoder.encode(
|
||||||
|
"https://oauth.vk.com/blank.html",
|
||||||
|
Charsets.UTF_8.toString()
|
||||||
|
)
|
||||||
|
}&response_type=token&v=${VKConstants.API_VERSION}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseAuthUrl(url: String) {
|
||||||
|
if (url.isBlank()) return
|
||||||
|
|
||||||
|
if (url.startsWith("https://oauth.vk.com/blank.html")) {
|
||||||
|
if (url.contains("error")) {
|
||||||
|
Log.e("Fast::Login", "errorUrl: $url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val authData = parseRedirectUrl(url)
|
||||||
|
if (authData == null) {
|
||||||
|
Log.e("Fast::Login", "errorUrl: $url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = authData.first
|
||||||
|
|
||||||
|
UserConfig.fastToken = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseRedirectUrl(url: String): Pair<String, Int>? {
|
||||||
|
val accessToken = extractPattern(url, "access_token=(.*?)&") ?: return null
|
||||||
|
val userId = extractPattern(url, "id=(\\d*)")?.toIntOrNull() ?: return null
|
||||||
|
|
||||||
|
return accessToken to userId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractPattern(string: String, pattern: String): String? {
|
||||||
|
val p = Pattern.compile(pattern)
|
||||||
|
val m = p.matcher(string)
|
||||||
|
return if (m.find()) {
|
||||||
|
m.group(1)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
private fun prepareEmailEditText() {
|
private fun prepareEmailEditText() {
|
||||||
binding.loginInput.addTextChangedListener {
|
binding.loginInput.addTextChangedListener {
|
||||||
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
|
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
|
||||||
@@ -296,6 +387,8 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
private fun goToMain(haveAuthorized: Boolean) = lifecycleScope.launch {
|
private fun goToMain(haveAuthorized: Boolean) = lifecycleScope.launch {
|
||||||
if (haveAuthorized) delay(500)
|
if (haveAuthorized) delay(500)
|
||||||
|
|
||||||
|
launchWebView()
|
||||||
|
|
||||||
findNavController().navigate(R.id.toMain)
|
findNavController().navigate(R.id.toMain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
// TODO: 9/29/2021 use recyclerview for viewing attachments
|
||||||
class AttachmentInflater constructor(
|
class AttachmentInflater constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val container: LinearLayoutCompat,
|
private val container: LinearLayoutCompat,
|
||||||
@@ -94,12 +95,15 @@ class AttachmentInflater constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun photo(photo: VkPhoto) {
|
private fun photo(photo: VkPhoto) {
|
||||||
val size = photo.sizeOfType('m') ?: return
|
val size = photo.getSizeOrSmaller('y') ?: return
|
||||||
|
|
||||||
val newPhoto = ShapeableImageView(context).apply {
|
val newPhoto = ShapeableImageView(context).apply {
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||||
AndroidUtils.px(size.width).roundToInt(),
|
// ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
AndroidUtils.px(size.height).roundToInt()
|
size.width,
|
||||||
|
size.height
|
||||||
|
// AndroidUtils.px(size.width).roundToInt(),
|
||||||
|
// AndroidUtils.px(size.height).roundToInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
shapeAppearanceModel =
|
shapeAppearanceModel =
|
||||||
@@ -222,14 +226,7 @@ class AttachmentInflater constructor(
|
|||||||
binding.caption.text = link.caption
|
binding.caption.text = link.caption
|
||||||
binding.caption.isVisible = !link.caption.isNullOrBlank()
|
binding.caption.isVisible = !link.caption.isNullOrBlank()
|
||||||
|
|
||||||
binding.preview.shapeAppearanceModel.toBuilder()
|
link.photo?.getMaxSize()?.let {
|
||||||
.setAllCornerSizes(AndroidUtils.px(20))
|
|
||||||
.build()
|
|
||||||
.let {
|
|
||||||
binding.preview.shapeAppearanceModel = it
|
|
||||||
}
|
|
||||||
|
|
||||||
link.photo?.sizeOfType('m')?.let {
|
|
||||||
binding.preview.load(it.url) { crossfade(150) }
|
binding.preview.load(it.url) { crossfade(150) }
|
||||||
binding.preview.isVisible = true
|
binding.preview.isVisible = true
|
||||||
return
|
return
|
||||||
@@ -245,8 +242,8 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
with(binding.image) {
|
with(binding.image) {
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||||
AndroidUtils.px(180).roundToInt(),
|
AndroidUtils.px(140).roundToInt(),
|
||||||
AndroidUtils.px(180).roundToInt()
|
AndroidUtils.px(140).roundToInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
load(url) { crossfade(150) }
|
load(url) { crossfade(150) }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.graphics.Color
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
import androidx.appcompat.widget.LinearLayoutCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
@@ -29,7 +30,11 @@ class MessagesHistoryAdapter constructor(
|
|||||||
val conversation: VkConversation,
|
val conversation: VkConversation,
|
||||||
val profiles: HashMap<Int, VkUser> = hashMapOf(),
|
val profiles: HashMap<Int, VkUser> = hashMapOf(),
|
||||||
val groups: HashMap<Int, VkGroup> = hashMapOf()
|
val groups: HashMap<Int, VkGroup> = hashMapOf()
|
||||||
) : BaseAdapter<VkMessage, MessagesHistoryAdapter.Holder>(context, values, COMPARATOR) {
|
) : BaseAdapter<VkMessage, MessagesHistoryAdapter.BasicHolder>(context, values, COMPARATOR) {
|
||||||
|
|
||||||
|
private var highlightTimer: Timer? = null
|
||||||
|
|
||||||
|
var onItemClickListener: ((position: Int, view: View) -> Unit)? = null
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
when {
|
when {
|
||||||
@@ -49,7 +54,7 @@ class MessagesHistoryAdapter constructor(
|
|||||||
private fun isPositionHeader(position: Int) = position == 0
|
private fun isPositionHeader(position: Int) = position == 0
|
||||||
private fun isPositionFooter(position: Int) = position >= actualSize
|
private fun isPositionFooter(position: Int) = position >= actualSize
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BasicHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
// magick numbers is great!
|
// magick numbers is great!
|
||||||
HEADER -> Header(createEmptyView(60))
|
HEADER -> Header(createEmptyView(60))
|
||||||
@@ -67,6 +72,13 @@ class MessagesHistoryAdapter constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun initListeners(itemView: View, position: Int) {
|
||||||
|
if (itemView is AdapterView<*>) return
|
||||||
|
|
||||||
|
itemView.setOnClickListener { onItemClickListener?.invoke(position, itemView) }
|
||||||
|
itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun createEmptyView(size: Int) = View(context).apply {
|
private fun createEmptyView(size: Int) = View(context).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
@@ -78,22 +90,22 @@ class MessagesHistoryAdapter constructor(
|
|||||||
isFocusable = false
|
isFocusable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: Holder, position: Int) {
|
override fun onBindViewHolder(holder: BasicHolder, position: Int) {
|
||||||
if (holder is Header || holder is Footer) return
|
if (holder is Header || holder is Footer) return
|
||||||
|
|
||||||
initListeners(holder.itemView, position)
|
initListeners(holder.itemView, position)
|
||||||
holder.bind(position)
|
holder.bind(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
open inner class Holder(v: View = View(context)) : BaseHolder(v)
|
open inner class BasicHolder(v: View = View(context)) : BaseHolder(v)
|
||||||
|
|
||||||
inner class Header(v: View) : Holder(v)
|
inner class Header(v: View) : BasicHolder(v)
|
||||||
|
|
||||||
inner class Footer(v: View) : Holder(v)
|
inner class Footer(v: View) : BasicHolder(v)
|
||||||
|
|
||||||
inner class IncomingMessage(
|
inner class IncomingMessage(
|
||||||
private val binding: ItemMessageInBinding
|
private val binding: ItemMessageInBinding
|
||||||
) : Holder(binding.root) {
|
) : BasicHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
val message = getItem(position)
|
||||||
@@ -103,6 +115,9 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
MessagesPreparator(
|
MessagesPreparator(
|
||||||
context = context,
|
context = context,
|
||||||
|
|
||||||
|
root = binding.root,
|
||||||
|
|
||||||
conversation = conversation,
|
conversation = conversation,
|
||||||
message = message,
|
message = message,
|
||||||
prevMessage = prevMessage,
|
prevMessage = prevMessage,
|
||||||
@@ -112,7 +127,6 @@ class MessagesHistoryAdapter constructor(
|
|||||||
bubble = binding.bubble,
|
bubble = binding.bubble,
|
||||||
text = binding.text,
|
text = binding.text,
|
||||||
spacer = binding.spacer,
|
spacer = binding.spacer,
|
||||||
time = binding.time,
|
|
||||||
unread = binding.unread,
|
unread = binding.unread,
|
||||||
attachmentContainer = binding.attachmentContainer,
|
attachmentContainer = binding.attachmentContainer,
|
||||||
attachmentSpacer = binding.attachmentSpacer,
|
attachmentSpacer = binding.attachmentSpacer,
|
||||||
@@ -125,11 +139,7 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
inner class OutgoingMessage(
|
inner class OutgoingMessage(
|
||||||
private val binding: ItemMessageOutBinding
|
private val binding: ItemMessageOutBinding
|
||||||
) : Holder(binding.root) {
|
) : BasicHolder(binding.root) {
|
||||||
|
|
||||||
init {
|
|
||||||
binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
val message = getItem(position)
|
||||||
@@ -138,15 +148,14 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
MessagesPreparator(
|
MessagesPreparator(
|
||||||
context = context,
|
context = context,
|
||||||
|
root = binding.root,
|
||||||
conversation = conversation,
|
conversation = conversation,
|
||||||
message = message,
|
message = message,
|
||||||
prevMessage = prevMessage,
|
prevMessage = prevMessage,
|
||||||
|
|
||||||
bubble = binding.bubble,
|
bubble = binding.bubble,
|
||||||
bubbleStroke = binding.bubbleStroke,
|
|
||||||
text = binding.text,
|
text = binding.text,
|
||||||
spacer = binding.spacer,
|
spacer = binding.spacer,
|
||||||
time = binding.time,
|
|
||||||
unread = binding.unread,
|
unread = binding.unread,
|
||||||
attachmentContainer = binding.attachmentContainer,
|
attachmentContainer = binding.attachmentContainer,
|
||||||
attachmentSpacer = binding.attachmentSpacer,
|
attachmentSpacer = binding.attachmentSpacer,
|
||||||
@@ -159,7 +168,7 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
inner class ServiceMessage(
|
inner class ServiceMessage(
|
||||||
private val binding: ItemMessageServiceBinding
|
private val binding: ItemMessageServiceBinding
|
||||||
) : Holder(binding.root) {
|
) : BasicHolder(binding.root) {
|
||||||
|
|
||||||
private val youPrefix = context.getString(R.string.you_message_prefix)
|
private val youPrefix = context.getString(R.string.you_message_prefix)
|
||||||
|
|
||||||
@@ -198,7 +207,7 @@ class MessagesHistoryAdapter constructor(
|
|||||||
|
|
||||||
binding.photo.isVisible = true
|
binding.photo.isVisible = true
|
||||||
|
|
||||||
val size = attachment.sizeOfType('m') ?: return@let
|
val size = attachment.getSizeOrSmaller('y') ?: return@let
|
||||||
|
|
||||||
binding.photo.layoutParams = LinearLayoutCompat.LayoutParams(
|
binding.photo.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||||
size.width,
|
size.width,
|
||||||
@@ -213,7 +222,7 @@ class MessagesHistoryAdapter constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val actualSize get() = values.size
|
val actualSize get() = values.size
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
if (actualSize == 0) return 2
|
if (actualSize == 0) return 2
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -16,6 +19,7 @@ import coil.load
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.api.UserConfig
|
import com.meloda.fast.api.UserConfig
|
||||||
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.api.model.VkConversation
|
import com.meloda.fast.api.model.VkConversation
|
||||||
import com.meloda.fast.api.model.VkGroup
|
import com.meloda.fast.api.model.VkGroup
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
@@ -26,12 +30,14 @@ import com.meloda.fast.base.viewmodel.StopProgressEvent
|
|||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
||||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
import com.meloda.fast.extensions.TextViewExtensions.clear
|
||||||
|
import com.meloda.fast.extensions.isNotVisible
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import com.meloda.fast.util.TimeUtils
|
import com.meloda.fast.util.TimeUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MessagesHistoryFragment :
|
class MessagesHistoryFragment :
|
||||||
@@ -60,11 +66,14 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
private val adapter: MessagesHistoryAdapter by lazy {
|
private val adapter: MessagesHistoryAdapter by lazy {
|
||||||
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
|
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
|
||||||
it.itemClickListener = this::onItemClick
|
it.onItemClickListener = this::onItemClick
|
||||||
it.itemLongClickListener = this::onItemLongClick
|
it.itemLongClickListener = this::onItemLongClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val replyMessage = MutableLiveData<VkMessage?>()
|
||||||
|
private val isAttachmentPanelVisible = MutableLiveData(false)
|
||||||
|
|
||||||
private var timestampTimer: Timer? = null
|
private var timestampTimer: Timer? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -98,19 +107,7 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
binding.status.text = status ?: "..."
|
binding.status.text = status ?: "..."
|
||||||
|
|
||||||
val avatar = when {
|
prepareAvatar()
|
||||||
conversation.isChat() -> conversation.photo200
|
|
||||||
conversation.isUser() -> user?.photo200
|
|
||||||
conversation.isGroup() -> group?.photo200
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.avatar.load(avatar) {
|
|
||||||
crossfade(false)
|
|
||||||
error(ColorDrawable(Color.RED))
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.online.isVisible = user?.online == true
|
|
||||||
|
|
||||||
prepareViews()
|
prepareViews()
|
||||||
|
|
||||||
@@ -166,8 +163,12 @@ class MessagesHistoryFragment :
|
|||||||
})
|
})
|
||||||
|
|
||||||
binding.message.doAfterTextChanged {
|
binding.message.doAfterTextChanged {
|
||||||
val newValue = if (it.toString().isNotBlank()) Action.SEND
|
val canSend =
|
||||||
else Action.RECORD
|
it.toString().isNotBlank()
|
||||||
|
|
||||||
|
val newValue =
|
||||||
|
if (canSend) Action.SEND
|
||||||
|
else Action.RECORD
|
||||||
|
|
||||||
if (action.value != newValue) action.value = newValue
|
if (action.value != newValue) action.value = newValue
|
||||||
}
|
}
|
||||||
@@ -195,12 +196,117 @@ class MessagesHistoryFragment :
|
|||||||
else -> return@observe
|
else -> return@observe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAttachmentPanelVisible.observe(viewLifecycleOwner) {
|
||||||
|
val layoutParams = binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
layoutParams.bottomMargin =
|
||||||
|
if (it) (binding.attachmentPanel.height / 1.5).roundToInt() else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAttachmentPanel(duration = 1)
|
||||||
|
|
||||||
|
binding.avatar.setOnClickListener {
|
||||||
|
val isShown = binding.attachmentPanel.isVisible
|
||||||
|
|
||||||
|
if (isShown) {
|
||||||
|
hideAttachmentPanel()
|
||||||
|
} else {
|
||||||
|
showAttachmentPanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.attachmentPanel.setOnClickListener c@{
|
||||||
|
val message = replyMessage.value ?: return@c
|
||||||
|
|
||||||
|
val index = adapter.values.indexOf(message)
|
||||||
|
if (index == -1) return@c
|
||||||
|
|
||||||
|
binding.recyclerView.smoothScrollToPosition(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.dismissReply.setOnClickListener {
|
||||||
|
if (replyMessage.value != null) replyMessage.value = null
|
||||||
|
|
||||||
|
hideAttachmentPanel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prepareAvatar() {
|
||||||
|
val avatar = when {
|
||||||
|
conversation.ownerId == VKConstants.FAST_GROUP_ID -> null
|
||||||
|
conversation.isUser() -> user?.photo200
|
||||||
|
conversation.isGroup() -> group?.photo200
|
||||||
|
conversation.isChat() -> conversation.photo200
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.avatar.isVisible = avatar != null
|
||||||
|
|
||||||
|
if (avatar == null) {
|
||||||
|
binding.avatarPlaceholder.isVisible = true
|
||||||
|
|
||||||
|
if (conversation.ownerId == VKConstants.FAST_GROUP_ID) {
|
||||||
|
binding.placeholderBack.setImageDrawable(
|
||||||
|
ColorDrawable(
|
||||||
|
ContextCompat.getColor(requireContext(), R.color.a1_400)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.placeholder.imageTintList =
|
||||||
|
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.a1_0))
|
||||||
|
binding.placeholder.setImageResource(R.drawable.ic_fast_logo)
|
||||||
|
binding.placeholder.setPadding(18)
|
||||||
|
} else {
|
||||||
|
binding.placeholderBack.setImageDrawable(
|
||||||
|
ColorDrawable(
|
||||||
|
ContextCompat.getColor(requireContext(), R.color.n1_50)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.placeholder.imageTintList =
|
||||||
|
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.n2_500))
|
||||||
|
binding.placeholder.setImageResource(R.drawable.ic_account_circle_cut)
|
||||||
|
binding.placeholder.setPadding(0)
|
||||||
|
binding.avatar.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.avatar.load(avatar) {
|
||||||
|
crossfade(200)
|
||||||
|
target {
|
||||||
|
binding.avatarPlaceholder.isVisible = false
|
||||||
|
binding.avatar.setImageDrawable(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.phantomIcon.isVisible = conversation.isPhantom
|
||||||
|
binding.online.isVisible = user?.online == true
|
||||||
|
binding.pin.isVisible = conversation.isPinned
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAttachmentPanel(duration: Long = 250) {
|
||||||
|
if (isAttachmentPanelVisible.value == false) isAttachmentPanelVisible.value = true
|
||||||
|
|
||||||
|
binding.attachmentPanel.animate()
|
||||||
|
.translationY(0f)
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.withStartAction { binding.attachmentPanel.isVisible = true }
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideAttachmentPanel(duration: Long = 250) {
|
||||||
|
if (isAttachmentPanelVisible.value == true) isAttachmentPanelVisible.value = false
|
||||||
|
|
||||||
|
binding.attachmentPanel.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.translationY(50f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.withEndAction { binding.attachmentPanel.isVisible = false }
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
private fun performAction() {
|
private fun performAction() {
|
||||||
if (action.value == Action.RECORD) {
|
if (action.value == Action.RECORD) {
|
||||||
|
return
|
||||||
} else if (action.value == Action.SEND) {
|
} else if (action.value == Action.SEND) {
|
||||||
val messageText = binding.message.text.toString().trim()
|
val messageText = binding.message.text.toString().trim()
|
||||||
if (messageText.isBlank()) return
|
if (messageText.isBlank()) return
|
||||||
@@ -214,18 +320,25 @@ class MessagesHistoryFragment :
|
|||||||
peerId = conversation.id,
|
peerId = conversation.id,
|
||||||
fromId = UserConfig.userId,
|
fromId = UserConfig.userId,
|
||||||
date = (date / 1000).toInt(),
|
date = (date / 1000).toInt(),
|
||||||
randomId = 0
|
randomId = 0,
|
||||||
|
replyMessage = replyMessage.value
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter.add(message)
|
adapter.add(message)
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyItemInserted(adapter.actualSize - 1)
|
||||||
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
||||||
binding.message.clear()
|
binding.message.clear()
|
||||||
|
|
||||||
|
val replyMessage = replyMessage.value
|
||||||
|
|
||||||
|
this.replyMessage.value = null
|
||||||
|
hideAttachmentPanel()
|
||||||
|
|
||||||
viewModel.sendMessage(
|
viewModel.sendMessage(
|
||||||
peerId = conversation.id,
|
peerId = conversation.id,
|
||||||
message = messageText,
|
message = messageText,
|
||||||
randomId = 0
|
randomId = 0,
|
||||||
|
replyTo = replyMessage?.id
|
||||||
) { message = message.copyMessage(id = it) }
|
) { message = message.copyMessage(id = it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,17 +396,22 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
private fun markMessagesAsImportant(event: MessagesMarkAsImportant) {
|
private fun markMessagesAsImportant(event: MessagesMarkAsImportant) {
|
||||||
var changed = false
|
var changed = false
|
||||||
|
val positions = mutableListOf<Int>()
|
||||||
|
|
||||||
for (i in adapter.values.indices) {
|
for (i in adapter.values.indices) {
|
||||||
val message = adapter.values[i]
|
val message = adapter.values[i]
|
||||||
if (event.messagesIds.contains(message.id)) {
|
if (event.messagesIds.contains(message.id)) {
|
||||||
if (!changed) changed = true
|
if (!changed) changed = true
|
||||||
|
|
||||||
|
positions.add(i)
|
||||||
|
|
||||||
adapter.values[i] = message.copyMessage(
|
adapter.values[i] = message.copyMessage(
|
||||||
important = event.important
|
important = event.important
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) adapter.notifyDataSetChanged()
|
if (changed) positions.forEach { adapter.notifyItemChanged(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshMessages(event: MessagesLoaded) {
|
private fun refreshMessages(event: MessagesLoaded) {
|
||||||
@@ -314,21 +432,111 @@ class MessagesHistoryFragment :
|
|||||||
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onItemClick(position: Int) {
|
private fun onItemClick(position: Int, view: View) {
|
||||||
val message = adapter.values[position]
|
val message = adapter.values[position]
|
||||||
if (message.action != null) return
|
if (message.action != null) return
|
||||||
|
|
||||||
val important = if (message.important) "Unmark as important" else "Mark as important"
|
// val popupMenu = PopupMenu(requireContext(), view)
|
||||||
|
//
|
||||||
|
// val reply = popupMenu.menu.add(
|
||||||
|
// getString(R.string.message_context_action_reply)
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// reply.icon =
|
||||||
|
// ContextCompat.getDrawable(
|
||||||
|
// requireContext(),
|
||||||
|
// R.drawable.ic_attachment_wall_reply
|
||||||
|
// )?.constantState?.newDrawable()?.also {
|
||||||
|
// it.setTint(
|
||||||
|
// ContextCompat.getColor(
|
||||||
|
// requireContext(),
|
||||||
|
// R.color.textColorSecondaryVariant
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val important = popupMenu.menu.add(
|
||||||
|
// getString(
|
||||||
|
// if (message.important) R.string.message_context_action_unmark_as_important
|
||||||
|
// else R.string.message_context_action_mark_as_important
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// important.icon =
|
||||||
|
// ContextCompat.getDrawable(
|
||||||
|
// requireContext(),
|
||||||
|
// R.drawable.ic_star_border
|
||||||
|
// )?.constantState?.newDrawable()?.also {
|
||||||
|
// it.setTint(
|
||||||
|
// ContextCompat.getColor(
|
||||||
|
// requireContext(),
|
||||||
|
// R.color.textColorSecondaryVariant
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// popupMenu.setForceShowIcon(true)
|
||||||
|
// popupMenu.setOnMenuItemClickListener {
|
||||||
|
// when (it) {
|
||||||
|
// reply -> {
|
||||||
|
// val title = when {
|
||||||
|
// message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||||
|
// message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||||
|
// else -> null
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (replyMessage.value != message) replyMessage.value = message
|
||||||
|
//
|
||||||
|
// binding.replyMessageTitle.text = title
|
||||||
|
// binding.replyMessageText.text = message.text ?: "[no_message]"
|
||||||
|
//
|
||||||
|
// if (binding.attachmentPanel.isNotVisible) binding.avatar.performClick()
|
||||||
|
// true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// important -> {
|
||||||
|
// viewModel.markAsImportant(
|
||||||
|
// messagesIds = listOf(message.id),
|
||||||
|
// important = !message.important
|
||||||
|
// )
|
||||||
|
// true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// popupMenu.show()
|
||||||
|
|
||||||
val params = arrayOf(important)
|
val reply = getString(R.string.message_context_action_reply)
|
||||||
|
|
||||||
|
val important = getString(
|
||||||
|
if (message.important) R.string.message_context_action_unmark_as_important
|
||||||
|
else R.string.message_context_action_mark_as_important
|
||||||
|
)
|
||||||
|
|
||||||
|
val params = arrayOf(reply, important)
|
||||||
|
|
||||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setItems(params) { _, which ->
|
.setItems(params) { _, which ->
|
||||||
if (which == 0) {
|
when (params[which]) {
|
||||||
viewModel.markAsImportant(
|
important -> viewModel.markAsImportant(
|
||||||
messagesIds = listOf(message.id),
|
messagesIds = listOf(message.id),
|
||||||
important = !message.important
|
important = !message.important
|
||||||
)
|
)
|
||||||
|
reply -> {
|
||||||
|
val title = when {
|
||||||
|
message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||||
|
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replyMessage.value != message) replyMessage.value = message
|
||||||
|
|
||||||
|
binding.replyMessageTitle.text = title
|
||||||
|
binding.replyMessageText.text = message.text ?: "[no_message]"
|
||||||
|
|
||||||
|
if (binding.attachmentPanel.isNotVisible) binding.avatar.performClick()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
peerId: Int,
|
peerId: Int,
|
||||||
message: String? = null,
|
message: String? = null,
|
||||||
randomId: Int = 0,
|
randomId: Int = 0,
|
||||||
|
replyTo: Int? = null,
|
||||||
setId: ((messageId: Int) -> Unit)? = null
|
setId: ((messageId: Int) -> Unit)? = null
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch {
|
||||||
makeJob(
|
makeJob(
|
||||||
@@ -99,7 +100,8 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
MessagesSendRequest(
|
MessagesSendRequest(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
randomId = randomId,
|
randomId = randomId,
|
||||||
message = message
|
message = message,
|
||||||
|
replyTo = replyTo
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@@ -10,7 +11,6 @@ import androidx.appcompat.widget.LinearLayoutCompat
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.setPadding
|
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.api.VkUtils
|
import com.meloda.fast.api.VkUtils
|
||||||
@@ -20,7 +20,6 @@ import com.meloda.fast.api.model.VkMessage
|
|||||||
import com.meloda.fast.api.model.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.model.attachments.VkSticker
|
import com.meloda.fast.api.model.attachments.VkSticker
|
||||||
import com.meloda.fast.common.AppGlobal
|
import com.meloda.fast.common.AppGlobal
|
||||||
import com.meloda.fast.util.AndroidUtils
|
|
||||||
import com.meloda.fast.widget.BoundedLinearLayout
|
import com.meloda.fast.widget.BoundedLinearLayout
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -30,13 +29,14 @@ import kotlin.math.roundToInt
|
|||||||
class MessagesPreparator constructor(
|
class MessagesPreparator constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
|
||||||
|
private val root: View? = null,
|
||||||
|
|
||||||
private val conversation: VkConversation,
|
private val conversation: VkConversation,
|
||||||
private val message: VkMessage,
|
private val message: VkMessage,
|
||||||
private val prevMessage: VkMessage? = null,
|
private val prevMessage: VkMessage? = null,
|
||||||
private val nextMessage: VkMessage? = null,
|
private val nextMessage: VkMessage? = null,
|
||||||
|
|
||||||
private val bubble: BoundedLinearLayout? = null,
|
private val bubble: BoundedLinearLayout? = null,
|
||||||
private val bubbleStroke: View? = null,
|
|
||||||
private val text: TextView? = null,
|
private val text: TextView? = null,
|
||||||
private val avatar: ImageView? = null,
|
private val avatar: ImageView? = null,
|
||||||
private val title: TextView? = null,
|
private val title: TextView? = null,
|
||||||
@@ -65,99 +65,43 @@ class MessagesPreparator constructor(
|
|||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||||
private val backgroundMiddleOut =
|
private val backgroundMiddleOut =
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
||||||
private val backgroundStrokeOut =
|
// private val backgroundStrokeOut =
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||||
private val backgroundMiddleStrokeOut =
|
// private val backgroundMiddleStrokeOut =
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||||
|
|
||||||
|
private val rootHighlightedColor =
|
||||||
|
ContextCompat.getColor(context, R.color.n2_100)
|
||||||
|
|
||||||
fun prepare() {
|
fun prepare() {
|
||||||
val messageUser: VkUser? = if (message.isUser()) {
|
val messageUser: VkUser? = (if (message.isUser()) {
|
||||||
profiles[message.fromId]
|
profiles[message.fromId]
|
||||||
} else null
|
} else null).also { message.user.value = it }
|
||||||
|
|
||||||
val messageGroup: VkGroup? = if (message.isGroup()) {
|
val messageGroup: VkGroup? = (if (message.isGroup()) {
|
||||||
groups[message.fromId]
|
groups[message.fromId]
|
||||||
} else null
|
} else null).also { message.group.value = it }
|
||||||
|
|
||||||
if (unread != null) {
|
prepareRootBackground()
|
||||||
unread.isVisible = message.isRead(conversation)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bubble != null && time != null) {
|
prepareTime()
|
||||||
bubble.setOnClickListener { time.isVisible = !time.isVisible }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachmentContainer != null) {
|
prepareUnreadIndicator()
|
||||||
if (message.attachments.isNullOrEmpty()) {
|
|
||||||
attachmentContainer.isVisible = false
|
|
||||||
attachmentContainer.removeAllViews()
|
|
||||||
} else {
|
|
||||||
attachmentContainer.isVisible = true
|
|
||||||
AttachmentInflater(
|
|
||||||
context = context,
|
|
||||||
container = attachmentContainer,
|
|
||||||
message = message,
|
|
||||||
groups = groups,
|
|
||||||
profiles = profiles
|
|
||||||
).inflate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bubble != null) {
|
prepareSpacer()
|
||||||
val padding =
|
|
||||||
AndroidUtils.px(if (!message.attachments.isNullOrEmpty()) 4 else 15).roundToInt()
|
|
||||||
|
|
||||||
bubble.setPadding(padding)
|
prepareAttachments()
|
||||||
|
|
||||||
|
prepareAttachmentsSpacer()
|
||||||
|
|
||||||
// TODO: 9/23/2021 use external function
|
prepareBubbleBackground()
|
||||||
bubble.background =
|
|
||||||
if (!message.attachments.isNullOrEmpty() && message.attachments!![0] is VkSticker) null
|
|
||||||
else {
|
|
||||||
if (message.isOut) {
|
|
||||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalOut
|
|
||||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleOut
|
|
||||||
else backgroundNormalOut
|
|
||||||
} else {
|
|
||||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalIn
|
|
||||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleIn
|
|
||||||
else backgroundNormalIn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 9/23/2021 use external function
|
prepareText()
|
||||||
bubbleStroke?.background =
|
|
||||||
if (bubble?.background == null) null else {
|
|
||||||
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundStrokeOut
|
|
||||||
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleStrokeOut
|
|
||||||
else backgroundStrokeOut
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bubble != null && text != null) {
|
prepareAvatar(
|
||||||
if (message.text == null) {
|
messageUser = messageUser,
|
||||||
text.isVisible = false
|
messageGroup = messageGroup
|
||||||
bubble.isVisible = !message.attachments.isNullOrEmpty()
|
)
|
||||||
bubbleStroke?.isVisible = bubble.isVisible
|
|
||||||
} else {
|
|
||||||
text.isVisible = true
|
|
||||||
bubble.isVisible = true
|
|
||||||
bubbleStroke?.isVisible = true
|
|
||||||
text.text = VkUtils.prepareMessageText(message.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatar != null) {
|
|
||||||
val avatarUrl = when {
|
|
||||||
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
|
||||||
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
avatar.load(avatarUrl) { crossfade(100) }
|
|
||||||
}
|
|
||||||
|
|
||||||
spacer?.isVisible = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
|
||||||
|
|
||||||
if (message.isPeerChat()) {
|
if (message.isPeerChat()) {
|
||||||
|
|
||||||
@@ -188,19 +132,98 @@ class MessagesPreparator constructor(
|
|||||||
|
|
||||||
title.text = titleString
|
title.text = titleString
|
||||||
title.measure(0, 0)
|
title.measure(0, 0)
|
||||||
|
|
||||||
if (bubble != null) {
|
|
||||||
if (title.isVisible) {
|
|
||||||
bubble.minimumWidth = title.measuredWidth + 60
|
|
||||||
} else {
|
|
||||||
bubble.minimumWidth = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
attachmentSpacer?.isVisible =
|
private fun prepareRootBackground() {
|
||||||
!message.attachments.isNullOrEmpty() && text?.isVisible == true
|
if (root != null) {
|
||||||
|
root.background =
|
||||||
|
if (message.isSelected) ColorDrawable(rootHighlightedColor)
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareTime() {
|
||||||
time?.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
|
time?.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prepareUnreadIndicator() {
|
||||||
|
if (unread != null) {
|
||||||
|
unread.isVisible = message.isRead(conversation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareSpacer() {
|
||||||
|
spacer?.isVisible = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAttachments() {
|
||||||
|
if (attachmentContainer != null) {
|
||||||
|
if (message.attachments.isNullOrEmpty()) {
|
||||||
|
attachmentContainer.isVisible = false
|
||||||
|
attachmentContainer.removeAllViews()
|
||||||
|
} else {
|
||||||
|
attachmentContainer.isVisible = true
|
||||||
|
AttachmentInflater(
|
||||||
|
context = context,
|
||||||
|
container = attachmentContainer,
|
||||||
|
message = message,
|
||||||
|
groups = groups,
|
||||||
|
profiles = profiles
|
||||||
|
).inflate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAttachmentsSpacer() {
|
||||||
|
attachmentSpacer?.isVisible =
|
||||||
|
!message.attachments.isNullOrEmpty() && text?.isVisible == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareBubbleBackground() {
|
||||||
|
if (bubble != null) {
|
||||||
|
// TODO: 9/23/2021 use external function
|
||||||
|
bubble.background =
|
||||||
|
if (!message.attachments.isNullOrEmpty() && message.attachments!![0] is VkSticker) null
|
||||||
|
else {
|
||||||
|
if (message.isOut) {
|
||||||
|
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalOut
|
||||||
|
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleOut
|
||||||
|
else backgroundNormalOut
|
||||||
|
} else {
|
||||||
|
if (prevMessage == null || prevMessage.fromId != message.fromId) backgroundNormalIn
|
||||||
|
else if (prevMessage.fromId == message.fromId && message.date - prevMessage.date < 60) backgroundMiddleIn
|
||||||
|
else backgroundNormalIn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareText() {
|
||||||
|
if (bubble != null && text != null) {
|
||||||
|
if (message.text == null) {
|
||||||
|
text.isVisible = false
|
||||||
|
bubble.isVisible = !message.attachments.isNullOrEmpty()
|
||||||
|
} else {
|
||||||
|
text.isVisible = true
|
||||||
|
bubble.isVisible = true
|
||||||
|
text.text = VkUtils.prepareMessageText(message.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAvatar(
|
||||||
|
messageUser: VkUser? = null,
|
||||||
|
messageGroup: VkGroup? = null
|
||||||
|
) {
|
||||||
|
if (avatar != null) {
|
||||||
|
val avatarUrl = when {
|
||||||
|
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
||||||
|
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
avatar.load(avatarUrl) { crossfade(100) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="#FF000000" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
android:tint="#FFFFFF"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners
|
||||||
|
android:topLeftRadius="30dp"
|
||||||
|
android:topRightRadius="30dp" />
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
|
||||||
|
<corners android:radius="100dp" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="5dp"
|
android:bottomLeftRadius="5dp"
|
||||||
android:bottomRightRadius="40dp"
|
android:bottomRightRadius="30dp"
|
||||||
android:topLeftRadius="30dp"
|
android:topLeftRadius="30dp"
|
||||||
android:topRightRadius="40dp" />
|
android:topRightRadius="30dp" />
|
||||||
|
|
||||||
</shape>
|
</shape>
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/messageOutStrokeColor" />
|
||||||
|
|
||||||
<solid android:color="@color/messageOutColor" />
|
<solid android:color="@color/messageOutColor" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/messageOutStrokeColor" />
|
||||||
|
|
||||||
<solid android:color="@color/messageOutColor" />
|
<solid android:color="@color/messageOutColor" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z" />
|
||||||
|
</vector>
|
||||||
@@ -20,18 +20,9 @@
|
|||||||
android:elevation="0dp"
|
android:elevation="0dp"
|
||||||
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
||||||
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
||||||
app:layout_scrollFlags="scroll|enterAlways|snap"
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||||
app:title="Messages">
|
app:title="Messages">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?actionBarSize"
|
|
||||||
android:background="?android:windowBackground"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:layout_collapseMode="parallax"
|
|
||||||
app:menu="@menu/fragment_conversations" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/expandedImage"
|
android:id="@+id/expandedImage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -43,11 +34,13 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="30dp"
|
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
app:layout_collapseParallaxMultiplier="0.5">
|
app:layout_collapseParallaxMultiplier="0.5">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/search"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
@@ -63,6 +56,42 @@
|
|||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:background="?android:windowBackground"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:layout_collapseMode="parallax"
|
||||||
|
app:menu="@menu/fragment_conversations">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/toolbarAvatarContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="30dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_collapseParallaxMultiplier="0.5">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/toolbarSearch"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_search"
|
||||||
|
android:tint="?colorSecondary3Variant" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/toolbarAvatar"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,14 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/refreshLayout"
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -49,21 +48,21 @@
|
|||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:src="@tools:sample/avatars"
|
tools:src="@tools:sample/avatars" />
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/avatarPlaceholder"
|
android:id="@+id/avatarPlaceholder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/placeholderBack"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:src="@color/n1_50" />
|
android:src="@color/n1_50" />
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/placeholder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:src="@drawable/ic_account_circle_cut"
|
android:src="@drawable/ic_account_circle_cut"
|
||||||
@@ -90,7 +89,34 @@
|
|||||||
android:layout_height="14dp"
|
android:layout_height="14dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_online_pc"
|
android:src="@drawable/ic_online_pc"
|
||||||
app:tint="@color/a3_200" />
|
app:tint="?colorSecondary2" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/pin"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="start|top"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/ic_back"
|
||||||
|
android:backgroundTint="@color/n2_500"
|
||||||
|
android:elevation="0.5dp" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/pinIcon"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:src="@drawable/ic_round_push_pin_24"
|
||||||
|
app:tint="@color/n2_0" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
@@ -182,6 +208,69 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/attachmentPanel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="12dp"
|
||||||
|
android:layout_marginBottom="35dp"
|
||||||
|
android:background="@drawable/ic_chat_attachment_panel_background"
|
||||||
|
android:backgroundTint="@color/n2_100"
|
||||||
|
android:minHeight="105dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
app:layout_anchor="@+id/messagePanel"
|
||||||
|
app:layout_anchorGravity="center_vertical|top">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/replyMessage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/replyMessageTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?textColorPrimary"
|
||||||
|
app:fontFamily="@font/google_sans_regular"
|
||||||
|
tools:text="Michael Bae" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/dismissReply"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/ic_image_button_circle_background"
|
||||||
|
android:backgroundTint="@color/n1_50"
|
||||||
|
android:src="@drawable/ic_round_close_20"
|
||||||
|
android:tint="?colorSecondary3" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/replyMessageText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?textColorPrimary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:fontFamily="@font/roboto_regular"
|
||||||
|
tools:text="Short Message." />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
@@ -211,7 +300,8 @@
|
|||||||
android:layout_marginHorizontal="20dp"
|
android:layout_marginHorizontal="20dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:hint="@string/message_input_hint" />
|
android:hint="@string/message_input_hint"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
android:id="@+id/attach"
|
android:id="@+id/attach"
|
||||||
|
|||||||
@@ -9,12 +9,11 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="4dp">
|
android:padding="4dp">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/preview"
|
android:id="@+id/preview"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
|||||||
@@ -51,31 +51,39 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/ic_message_in_background"
|
android:background="@drawable/ic_message_in_background"
|
||||||
android:backgroundTint="@color/n2_100"
|
android:backgroundTint="@color/n2_100"
|
||||||
|
android:minWidth="60dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="15dp"
|
|
||||||
tools:ignore="UselessParent">
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:autoLink="all"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
tools:text="This" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:id="@+id/attachmentSpacer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="5dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/attachmentContainer"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:visibility="gone" />
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:textColor="@color/n1_800"
|
||||||
|
tools:text="This" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/attachmentSpacer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="5dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/attachmentContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||||
|
|
||||||
@@ -88,19 +96,6 @@
|
|||||||
android:src="@color/a3_200" />
|
android:src="@color/a3_200" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/time"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:textColor="?textColorSecondaryVariant"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:layout_height="18dp"
|
|
||||||
tools:text="12:00"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
@@ -30,28 +31,26 @@
|
|||||||
android:layout_height="10dp"
|
android:layout_height="10dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<FrameLayout
|
<com.meloda.fast.widget.BoundedLinearLayout
|
||||||
android:id="@+id/bubbleStroke"
|
android:id="@+id/bubble"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/ic_message_out_background_stroke"
|
android:layout_gravity="center"
|
||||||
android:padding="1.5dp"
|
android:background="@drawable/ic_message_out_background"
|
||||||
tools:ignore="UselessParent">
|
android:clipChildren="true"
|
||||||
|
android:clipToPadding="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.meloda.fast.widget.BoundedLinearLayout
|
<RelativeLayout
|
||||||
android:id="@+id/bubble"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_gravity="center"
|
|
||||||
android:background="@drawable/ic_message_out_background"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="15dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical|start"
|
android:layout_gravity="center_vertical|start"
|
||||||
|
android:padding="15dp"
|
||||||
android:textColor="@color/n1_900"
|
android:textColor="@color/n1_900"
|
||||||
tools:text="This is test" />
|
tools:text="This is test" />
|
||||||
|
|
||||||
@@ -59,30 +58,21 @@
|
|||||||
android:id="@+id/attachmentSpacer"
|
android:id="@+id/attachmentSpacer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="5dp"
|
android:layout_height="5dp"
|
||||||
|
android:layout_below="@+id/text"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/attachmentContainer"
|
android:id="@+id/attachmentContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/attachmentSpacer"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
app:layout_anchor="@+id/text"
|
||||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
app:layout_anchorGravity="bottom" />
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/time"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:textColor="?textColorSecondaryVariant"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:layout_height="18dp"
|
|
||||||
tools:text="12:00"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -109,4 +109,12 @@
|
|||||||
<string name="post_type_user">User post</string>
|
<string name="post_type_user">User post</string>
|
||||||
<string name="post_type_unknown">Post</string>
|
<string name="post_type_unknown">Post</string>
|
||||||
<string name="message_attachments_story">Story</string>
|
<string name="message_attachments_story">Story</string>
|
||||||
|
<string name="log_out">Log out</string>
|
||||||
|
<string name="confirm">Confirmation</string>
|
||||||
|
<string name="log_out_confirm">Are you really want to log out?</string>
|
||||||
|
<string name="yes">Yes</string>
|
||||||
|
<string name="no">No</string>
|
||||||
|
<string name="message_context_action_reply">Reply</string>
|
||||||
|
<string name="message_context_action_mark_as_important">Mark as important</string>
|
||||||
|
<string name="message_context_action_unmark_as_important">Unmark as important</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+4
-18
@@ -1,21 +1,7 @@
|
|||||||
# Project-wide Gradle settings.
|
org.gradle.jvmargs=-Xmx4096M -XX:MaxPermSize=4096m -Dfile.encoding=UTF-8
|
||||||
# IDE (e.g. Android Studio) users:
|
org.gradle.daemon=true
|
||||||
# Gradle settings configured through the IDE *will override*
|
org.gradle.parallel=true
|
||||||
# any settings specified in this file.
|
org.gradle.configureondemand=false
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
||||||
# Android operating system, and which are packaged with your app"s APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
Reference in New Issue
Block a user