forked from melod1n/fast-messenger
я не помню, что тут
This commit is contained in:
@@ -7,9 +7,9 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("dagger.hilt.android.plugin")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -38,6 +38,9 @@ android {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
|
||||
buildConfigField("String", "vkLogin", login)
|
||||
buildConfigField("String", "vkPassword", password)
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
@@ -68,7 +71,7 @@ android {
|
||||
kapt {
|
||||
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 {
|
||||
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
|
||||
}
|
||||
@@ -83,6 +86,8 @@ dependencies {
|
||||
|
||||
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("com.google.android.material:material:1.5.0-alpha03")
|
||||
implementation("androidx.core:core-ktx:1.7.0-beta01")
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.meloda.fast.common.AppGlobal
|
||||
|
||||
object UserConfig {
|
||||
|
||||
private const val FAST_TOKEN = "fast_token"
|
||||
private const val TOKEN = "token"
|
||||
private const val USER_ID = "user_id"
|
||||
|
||||
@@ -25,8 +26,16 @@ object UserConfig {
|
||||
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() {
|
||||
accessToken = ""
|
||||
fastToken = ""
|
||||
userId = -1
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ object VKConstants {
|
||||
const val USER_FIELDS =
|
||||
"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 VK_APP_ID = "2274003"
|
||||
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||
|
||||
@@ -42,6 +42,12 @@ object VkUtils {
|
||||
return forwards
|
||||
}
|
||||
|
||||
fun parseReplyMessage(baseReplyMessage: BaseVkMessage?): VkMessage? {
|
||||
if (baseReplyMessage == null) return null
|
||||
|
||||
return baseReplyMessage.asVkMessage()
|
||||
}
|
||||
|
||||
fun parseAttachments(baseAttachments: List<BaseVkAttachmentItem>?): List<VkAttachment>? {
|
||||
if (baseAttachments.isNullOrEmpty()) return null
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.meloda.fast.api.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||
import com.meloda.fast.base.adapter.SelectableItem
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Entity(tableName = "messages")
|
||||
@@ -24,9 +27,21 @@ data class VkMessage(
|
||||
val actionMessage: String? = null,
|
||||
val geoType: String? = null,
|
||||
val important: Boolean = false,
|
||||
|
||||
var forwards: List<VkMessage>? = null,
|
||||
var attachments: List<VkAttachment>? = null
|
||||
) : Parcelable {
|
||||
var attachments: List<VkAttachment>? = null,
|
||||
|
||||
// @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
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.meloda.fast.api.model.attachments
|
||||
|
||||
import androidx.room.Ignore
|
||||
import com.meloda.fast.api.model.base.attachments.BaseVkPhoto
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
|
||||
@Parcelize
|
||||
data class VkPhoto(
|
||||
@@ -17,13 +19,58 @@ data class VkPhoto(
|
||||
val userId: Int?
|
||||
) : 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
|
||||
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) {
|
||||
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 null
|
||||
|
||||
@@ -23,7 +23,8 @@ data class BaseVkMessage(
|
||||
val payload: String,
|
||||
val geo: Geo?,
|
||||
val action: Action?,
|
||||
val ttl: Int
|
||||
val ttl: Int,
|
||||
val reply_message: BaseVkMessage?
|
||||
) : Parcelable {
|
||||
|
||||
fun asVkMessage() = VkMessage(
|
||||
@@ -44,6 +45,7 @@ data class BaseVkMessage(
|
||||
).also {
|
||||
it.attachments = VkUtils.parseAttachments(attachments)
|
||||
it.forwards = VkUtils.parseForwards(fwd_messages)
|
||||
it.replyMessage = VkUtils.parseReplyMessage(reply_message)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -17,6 +17,7 @@ class AuthInterceptor : Interceptor {
|
||||
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())
|
||||
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ abstract class BaseAdapter<Item, VH : BaseHolder>(
|
||||
holder.bind(position)
|
||||
}
|
||||
|
||||
protected fun initListeners(itemView: View, position: Int) {
|
||||
protected open fun initListeners(itemView: View, position: Int) {
|
||||
if (itemView is AdapterView<*>) return
|
||||
|
||||
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,
|
||||
VkGroup::class
|
||||
],
|
||||
version = 24,
|
||||
version = 25,
|
||||
exportSchema = false,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.meloda.fast.extensions
|
||||
|
||||
import android.graphics.*
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import kotlin.math.min
|
||||
|
||||
fun Bitmap.borderedCircularBitmap(
|
||||
@@ -70,4 +72,6 @@ fun Bitmap.borderedCircularBitmap(
|
||||
diameter, // width
|
||||
diameter // height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val View.isNotVisible get() = !isVisible
|
||||
+85
-20
@@ -1,25 +1,31 @@
|
||||
package com.meloda.fast.screens.conversations
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.viewbinding.library.fragment.viewBinding
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import coil.load
|
||||
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.activity.MainActivity
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.base.BaseViewModelFragment
|
||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.VKEvent
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.common.AppSettings
|
||||
import com.meloda.fast.common.dataStore
|
||||
import com.meloda.fast.databinding.FragmentConversationsBinding
|
||||
@@ -29,6 +35,7 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@AndroidEntryPoint
|
||||
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 isExpanded = true
|
||||
|
||||
@@ -74,11 +99,7 @@ class ConversationsFragment :
|
||||
}.collect { }
|
||||
}
|
||||
|
||||
binding.createChat.setOnClickListener {
|
||||
Snackbar.make(it, "Test snackbar", Snackbar.LENGTH_SHORT)
|
||||
.setAction("Action") {}
|
||||
.show()
|
||||
}
|
||||
binding.createChat.setOnClickListener {}
|
||||
|
||||
UserConfig.vkUser.observe(viewLifecycleOwner) {
|
||||
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
|
||||
@@ -87,28 +108,49 @@ class ConversationsFragment :
|
||||
binding.appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
||||
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)
|
||||
|
||||
viewModel.loadProfileUser()
|
||||
viewModel.loadConversations()
|
||||
|
||||
binding.avatar.setOnClickListener {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
avatarPopupMenu.show()
|
||||
}
|
||||
|
||||
binding.avatar.setOnLongClickListener {
|
||||
lifecycleScope.launch {
|
||||
requireContext().dataStore.edit { settings ->
|
||||
val isMultilineEnabled = settings[AppSettings.keyIsMultilineEnabled] ?: true
|
||||
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
||||
@@ -117,7 +159,30 @@ class ConversationsFragment :
|
||||
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) {
|
||||
|
||||
@@ -3,13 +3,13 @@ package com.meloda.fast.screens.conversations
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.meloda.fast.api.UserConfig
|
||||
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.VkGroup
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.request.ConversationsGetRequest
|
||||
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.StartProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||
@@ -26,13 +26,15 @@ class ConversationsViewModel @Inject constructor(
|
||||
private val usersDataSource: UsersDataSource
|
||||
) : BaseViewModel() {
|
||||
|
||||
fun loadConversations() = viewModelScope.launch(Dispatchers.Default) {
|
||||
fun loadConversations(
|
||||
offset: Int? = null
|
||||
) = viewModelScope.launch(Dispatchers.Default) {
|
||||
makeJob({
|
||||
dataSource.getAllChats(
|
||||
ConversationsGetRequest(
|
||||
count = 30,
|
||||
// offset = 177,
|
||||
extended = true,
|
||||
offset = offset,
|
||||
fields = "${VKConstants.USER_FIELDS},${VKConstants.GROUP_FIELDS}"
|
||||
)
|
||||
)
|
||||
@@ -52,6 +54,7 @@ class ConversationsViewModel @Inject constructor(
|
||||
sendEvent(
|
||||
ConversationsLoaded(
|
||||
count = response.count,
|
||||
offset = offset,
|
||||
unreadCount = response.unreadCount ?: 0,
|
||||
conversations = response.items.map { items ->
|
||||
items.conversation.asVkConversation(
|
||||
@@ -73,9 +76,7 @@ class ConversationsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun loadProfileUser() = viewModelScope.launch {
|
||||
makeJob({
|
||||
usersDataSource.getById(UsersGetRequest(fields = "online,photo_200"))
|
||||
},
|
||||
makeJob({ usersDataSource.getById(UsersGetRequest(fields = VKConstants.USER_FIELDS)) },
|
||||
onAnswer = {
|
||||
it.response?.let { r ->
|
||||
val users = r.map { u -> u.asVkUser() }
|
||||
@@ -89,6 +90,7 @@ class ConversationsViewModel @Inject constructor(
|
||||
|
||||
data class ConversationsLoaded(
|
||||
val count: Int,
|
||||
val offset: Int?,
|
||||
val unreadCount: Int?,
|
||||
val conversations: List<VkConversation>,
|
||||
val profiles: HashMap<Int, VkUser>,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.meloda.fast.screens.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.viewbinding.library.fragment.viewBinding
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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.meloda.fast.BuildConfig
|
||||
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.viewmodel.*
|
||||
import com.meloda.fast.databinding.DialogCaptchaBinding
|
||||
@@ -29,7 +37,10 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -89,11 +100,91 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
||||
}
|
||||
|
||||
private fun prepareViews() {
|
||||
prepareWebView()
|
||||
prepareEmailEditText()
|
||||
preparePasswordEditText()
|
||||
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() {
|
||||
binding.loginInput.addTextChangedListener {
|
||||
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 {
|
||||
if (haveAuthorized) delay(500)
|
||||
|
||||
launchWebView()
|
||||
|
||||
findNavController().navigate(R.id.toMain)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// TODO: 9/29/2021 use recyclerview for viewing attachments
|
||||
class AttachmentInflater constructor(
|
||||
private val context: Context,
|
||||
private val container: LinearLayoutCompat,
|
||||
@@ -94,12 +95,15 @@ class AttachmentInflater constructor(
|
||||
}
|
||||
|
||||
private fun photo(photo: VkPhoto) {
|
||||
val size = photo.sizeOfType('m') ?: return
|
||||
val size = photo.getSizeOrSmaller('y') ?: return
|
||||
|
||||
val newPhoto = ShapeableImageView(context).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
AndroidUtils.px(size.width).roundToInt(),
|
||||
AndroidUtils.px(size.height).roundToInt()
|
||||
// ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
size.width,
|
||||
size.height
|
||||
// AndroidUtils.px(size.width).roundToInt(),
|
||||
// AndroidUtils.px(size.height).roundToInt()
|
||||
)
|
||||
|
||||
shapeAppearanceModel =
|
||||
@@ -222,14 +226,7 @@ class AttachmentInflater constructor(
|
||||
binding.caption.text = link.caption
|
||||
binding.caption.isVisible = !link.caption.isNullOrBlank()
|
||||
|
||||
binding.preview.shapeAppearanceModel.toBuilder()
|
||||
.setAllCornerSizes(AndroidUtils.px(20))
|
||||
.build()
|
||||
.let {
|
||||
binding.preview.shapeAppearanceModel = it
|
||||
}
|
||||
|
||||
link.photo?.sizeOfType('m')?.let {
|
||||
link.photo?.getMaxSize()?.let {
|
||||
binding.preview.load(it.url) { crossfade(150) }
|
||||
binding.preview.isVisible = true
|
||||
return
|
||||
@@ -245,8 +242,8 @@ class AttachmentInflater constructor(
|
||||
|
||||
with(binding.image) {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
AndroidUtils.px(180).roundToInt(),
|
||||
AndroidUtils.px(180).roundToInt()
|
||||
AndroidUtils.px(140).roundToInt(),
|
||||
AndroidUtils.px(140).roundToInt()
|
||||
)
|
||||
|
||||
load(url) { crossfade(150) }
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
@@ -29,7 +30,11 @@ class MessagesHistoryAdapter constructor(
|
||||
val conversation: VkConversation,
|
||||
val profiles: HashMap<Int, VkUser> = 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 {
|
||||
when {
|
||||
@@ -49,7 +54,7 @@ class MessagesHistoryAdapter constructor(
|
||||
private fun isPositionHeader(position: Int) = position == 0
|
||||
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) {
|
||||
// magick numbers is great!
|
||||
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 {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@@ -78,22 +90,22 @@ class MessagesHistoryAdapter constructor(
|
||||
isFocusable = false
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||
override fun onBindViewHolder(holder: BasicHolder, position: Int) {
|
||||
if (holder is Header || holder is Footer) return
|
||||
|
||||
initListeners(holder.itemView, 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(
|
||||
private val binding: ItemMessageInBinding
|
||||
) : Holder(binding.root) {
|
||||
) : BasicHolder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
@@ -103,6 +115,9 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
MessagesPreparator(
|
||||
context = context,
|
||||
|
||||
root = binding.root,
|
||||
|
||||
conversation = conversation,
|
||||
message = message,
|
||||
prevMessage = prevMessage,
|
||||
@@ -112,7 +127,6 @@ class MessagesHistoryAdapter constructor(
|
||||
bubble = binding.bubble,
|
||||
text = binding.text,
|
||||
spacer = binding.spacer,
|
||||
time = binding.time,
|
||||
unread = binding.unread,
|
||||
attachmentContainer = binding.attachmentContainer,
|
||||
attachmentSpacer = binding.attachmentSpacer,
|
||||
@@ -125,11 +139,7 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
inner class OutgoingMessage(
|
||||
private val binding: ItemMessageOutBinding
|
||||
) : Holder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.bubbleStroke.setOnClickListener { binding.bubble.performClick() }
|
||||
}
|
||||
) : BasicHolder(binding.root) {
|
||||
|
||||
override fun bind(position: Int) {
|
||||
val message = getItem(position)
|
||||
@@ -138,15 +148,14 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
MessagesPreparator(
|
||||
context = context,
|
||||
root = binding.root,
|
||||
conversation = conversation,
|
||||
message = message,
|
||||
prevMessage = prevMessage,
|
||||
|
||||
bubble = binding.bubble,
|
||||
bubbleStroke = binding.bubbleStroke,
|
||||
text = binding.text,
|
||||
spacer = binding.spacer,
|
||||
time = binding.time,
|
||||
unread = binding.unread,
|
||||
attachmentContainer = binding.attachmentContainer,
|
||||
attachmentSpacer = binding.attachmentSpacer,
|
||||
@@ -159,7 +168,7 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
inner class ServiceMessage(
|
||||
private val binding: ItemMessageServiceBinding
|
||||
) : Holder(binding.root) {
|
||||
) : BasicHolder(binding.root) {
|
||||
|
||||
private val youPrefix = context.getString(R.string.you_message_prefix)
|
||||
|
||||
@@ -198,7 +207,7 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
binding.photo.isVisible = true
|
||||
|
||||
val size = attachment.sizeOfType('m') ?: return@let
|
||||
val size = attachment.getSizeOrSmaller('y') ?: return@let
|
||||
|
||||
binding.photo.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
size.width,
|
||||
@@ -213,7 +222,7 @@ class MessagesHistoryAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val actualSize get() = values.size
|
||||
val actualSize get() = values.size
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (actualSize == 0) return 2
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.graphics.Color
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
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.setPadding
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@@ -16,6 +19,7 @@ import coil.load
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.meloda.fast.R
|
||||
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.VkGroup
|
||||
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.databinding.FragmentMessagesHistoryBinding
|
||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
||||
import com.meloda.fast.extensions.isNotVisible
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.util.TimeUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MessagesHistoryFragment :
|
||||
@@ -60,11 +66,14 @@ class MessagesHistoryFragment :
|
||||
|
||||
private val adapter: MessagesHistoryAdapter by lazy {
|
||||
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
|
||||
it.itemClickListener = this::onItemClick
|
||||
it.onItemClickListener = this::onItemClick
|
||||
it.itemLongClickListener = this::onItemLongClick
|
||||
}
|
||||
}
|
||||
|
||||
private val replyMessage = MutableLiveData<VkMessage?>()
|
||||
private val isAttachmentPanelVisible = MutableLiveData(false)
|
||||
|
||||
private var timestampTimer: Timer? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -98,19 +107,7 @@ class MessagesHistoryFragment :
|
||||
|
||||
binding.status.text = status ?: "..."
|
||||
|
||||
val avatar = when {
|
||||
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
|
||||
prepareAvatar()
|
||||
|
||||
prepareViews()
|
||||
|
||||
@@ -166,8 +163,12 @@ class MessagesHistoryFragment :
|
||||
})
|
||||
|
||||
binding.message.doAfterTextChanged {
|
||||
val newValue = if (it.toString().isNotBlank()) Action.SEND
|
||||
else Action.RECORD
|
||||
val canSend =
|
||||
it.toString().isNotBlank()
|
||||
|
||||
val newValue =
|
||||
if (canSend) Action.SEND
|
||||
else Action.RECORD
|
||||
|
||||
if (action.value != newValue) action.value = newValue
|
||||
}
|
||||
@@ -195,12 +196,117 @@ class MessagesHistoryFragment :
|
||||
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() {
|
||||
if (action.value == Action.RECORD) {
|
||||
|
||||
return
|
||||
} else if (action.value == Action.SEND) {
|
||||
val messageText = binding.message.text.toString().trim()
|
||||
if (messageText.isBlank()) return
|
||||
@@ -214,18 +320,25 @@ class MessagesHistoryFragment :
|
||||
peerId = conversation.id,
|
||||
fromId = UserConfig.userId,
|
||||
date = (date / 1000).toInt(),
|
||||
randomId = 0
|
||||
randomId = 0,
|
||||
replyMessage = replyMessage.value
|
||||
)
|
||||
|
||||
adapter.add(message)
|
||||
adapter.notifyDataSetChanged()
|
||||
adapter.notifyItemInserted(adapter.actualSize - 1)
|
||||
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
||||
binding.message.clear()
|
||||
|
||||
val replyMessage = replyMessage.value
|
||||
|
||||
this.replyMessage.value = null
|
||||
hideAttachmentPanel()
|
||||
|
||||
viewModel.sendMessage(
|
||||
peerId = conversation.id,
|
||||
message = messageText,
|
||||
randomId = 0
|
||||
randomId = 0,
|
||||
replyTo = replyMessage?.id
|
||||
) { message = message.copyMessage(id = it) }
|
||||
}
|
||||
}
|
||||
@@ -283,17 +396,22 @@ class MessagesHistoryFragment :
|
||||
|
||||
private fun markMessagesAsImportant(event: MessagesMarkAsImportant) {
|
||||
var changed = false
|
||||
val positions = mutableListOf<Int>()
|
||||
|
||||
for (i in adapter.values.indices) {
|
||||
val message = adapter.values[i]
|
||||
if (event.messagesIds.contains(message.id)) {
|
||||
if (!changed) changed = true
|
||||
|
||||
positions.add(i)
|
||||
|
||||
adapter.values[i] = message.copyMessage(
|
||||
important = event.important
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) adapter.notifyDataSetChanged()
|
||||
if (changed) positions.forEach { adapter.notifyItemChanged(it) }
|
||||
}
|
||||
|
||||
private fun refreshMessages(event: MessagesLoaded) {
|
||||
@@ -314,21 +432,111 @@ class MessagesHistoryFragment :
|
||||
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
||||
}
|
||||
|
||||
private fun onItemClick(position: Int) {
|
||||
private fun onItemClick(position: Int, view: View) {
|
||||
val message = adapter.values[position]
|
||||
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())
|
||||
.setItems(params) { _, which ->
|
||||
if (which == 0) {
|
||||
viewModel.markAsImportant(
|
||||
when (params[which]) {
|
||||
important -> viewModel.markAsImportant(
|
||||
messagesIds = listOf(message.id),
|
||||
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,
|
||||
message: String? = null,
|
||||
randomId: Int = 0,
|
||||
replyTo: Int? = null,
|
||||
setId: ((messageId: Int) -> Unit)? = null
|
||||
) = viewModelScope.launch {
|
||||
makeJob(
|
||||
@@ -99,7 +100,8 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
MessagesSendRequest(
|
||||
peerId = peerId,
|
||||
randomId = randomId,
|
||||
message = message
|
||||
message = message,
|
||||
replyTo = replyTo
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.meloda.fast.screens.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
@@ -10,7 +11,6 @@ import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import com.meloda.fast.R
|
||||
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.attachments.VkSticker
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.widget.BoundedLinearLayout
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -30,13 +29,14 @@ import kotlin.math.roundToInt
|
||||
class MessagesPreparator constructor(
|
||||
private val context: Context,
|
||||
|
||||
private val root: View? = null,
|
||||
|
||||
private val conversation: VkConversation,
|
||||
private val message: VkMessage,
|
||||
private val prevMessage: VkMessage? = null,
|
||||
private val nextMessage: VkMessage? = null,
|
||||
|
||||
private val bubble: BoundedLinearLayout? = null,
|
||||
private val bubbleStroke: View? = null,
|
||||
private val text: TextView? = null,
|
||||
private val avatar: ImageView? = null,
|
||||
private val title: TextView? = null,
|
||||
@@ -65,99 +65,43 @@ class MessagesPreparator constructor(
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||
private val backgroundMiddleOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
||||
private val backgroundStrokeOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||
private val backgroundMiddleStrokeOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||
// private val backgroundStrokeOut =
|
||||
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||
// private val backgroundMiddleStrokeOut =
|
||||
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||
|
||||
private val rootHighlightedColor =
|
||||
ContextCompat.getColor(context, R.color.n2_100)
|
||||
|
||||
fun prepare() {
|
||||
val messageUser: VkUser? = if (message.isUser()) {
|
||||
val messageUser: VkUser? = (if (message.isUser()) {
|
||||
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]
|
||||
} else null
|
||||
} else null).also { message.group.value = it }
|
||||
|
||||
if (unread != null) {
|
||||
unread.isVisible = message.isRead(conversation)
|
||||
}
|
||||
prepareRootBackground()
|
||||
|
||||
if (bubble != null && time != null) {
|
||||
bubble.setOnClickListener { time.isVisible = !time.isVisible }
|
||||
}
|
||||
prepareTime()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
prepareUnreadIndicator()
|
||||
|
||||
if (bubble != null) {
|
||||
val padding =
|
||||
AndroidUtils.px(if (!message.attachments.isNullOrEmpty()) 4 else 15).roundToInt()
|
||||
prepareSpacer()
|
||||
|
||||
bubble.setPadding(padding)
|
||||
prepareAttachments()
|
||||
|
||||
prepareAttachmentsSpacer()
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
prepareBubbleBackground()
|
||||
|
||||
// TODO: 9/23/2021 use external function
|
||||
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
|
||||
}
|
||||
prepareText()
|
||||
|
||||
if (bubble != null && text != null) {
|
||||
if (message.text == null) {
|
||||
text.isVisible = false
|
||||
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)
|
||||
prepareAvatar(
|
||||
messageUser = messageUser,
|
||||
messageGroup = messageGroup
|
||||
)
|
||||
|
||||
if (message.isPeerChat()) {
|
||||
|
||||
@@ -188,19 +132,98 @@ class MessagesPreparator constructor(
|
||||
|
||||
title.text = titleString
|
||||
title.measure(0, 0)
|
||||
|
||||
if (bubble != null) {
|
||||
if (title.isVisible) {
|
||||
bubble.minimumWidth = title.measuredWidth + 60
|
||||
} else {
|
||||
bubble.minimumWidth = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachmentSpacer?.isVisible =
|
||||
!message.attachments.isNullOrEmpty() && text?.isVisible == true
|
||||
private fun prepareRootBackground() {
|
||||
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)
|
||||
}
|
||||
|
||||
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"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
||||
@@ -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
|
||||
android:bottomLeftRadius="5dp"
|
||||
android:bottomRightRadius="40dp"
|
||||
android:bottomRightRadius="30dp"
|
||||
android:topLeftRadius="30dp"
|
||||
android:topRightRadius="40dp" />
|
||||
android:topRightRadius="30dp" />
|
||||
|
||||
</shape>
|
||||
@@ -2,6 +2,10 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/messageOutStrokeColor" />
|
||||
|
||||
<solid android:color="@color/messageOutColor" />
|
||||
|
||||
<corners
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/messageOutStrokeColor" />
|
||||
|
||||
<solid android:color="@color/messageOutColor" />
|
||||
|
||||
<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"
|
||||
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
||||
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
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
|
||||
android:id="@+id/expandedImage"
|
||||
android:layout_width="match_parent"
|
||||
@@ -43,11 +34,13 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="30dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="30dp"
|
||||
app:layout_collapseParallaxMultiplier="0.5">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/search"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
@@ -63,6 +56,42 @@
|
||||
|
||||
</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>
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
android:layout_height="match_parent"
|
||||
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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
@@ -49,21 +48,21 @@
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:src="@tools:sample/avatars"
|
||||
tools:visibility="visible" />
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/avatarPlaceholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.meloda.fast.widget.CircleImageView
|
||||
android:id="@+id/placeholderBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@color/n1_50" />
|
||||
|
||||
<com.meloda.fast.widget.CircleImageView
|
||||
android:id="@+id/placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_account_circle_cut"
|
||||
@@ -90,7 +89,34 @@
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
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>
|
||||
|
||||
@@ -182,6 +208,69 @@
|
||||
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
@@ -211,7 +300,8 @@
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/message_input_hint" />
|
||||
android:hint="@string/message_input_hint"
|
||||
android:singleLine="true" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/attach"
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<com.meloda.fast.widget.CircleImageView
|
||||
android:id="@+id/preview"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
|
||||
@@ -51,31 +51,39 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_message_in_background"
|
||||
android:backgroundTint="@color/n2_100"
|
||||
android:minWidth="60dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp"
|
||||
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
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<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>
|
||||
|
||||
@@ -88,19 +96,6 @@
|
||||
android:src="@color/a3_200" />
|
||||
|
||||
</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>
|
||||
</layout>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
@@ -30,28 +31,26 @@
|
||||
android:layout_height="10dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bubbleStroke"
|
||||
<com.meloda.fast.widget.BoundedLinearLayout
|
||||
android:id="@+id/bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_message_out_background_stroke"
|
||||
android:padding="1.5dp"
|
||||
tools:ignore="UselessParent">
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_message_out_background"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.meloda.fast.widget.BoundedLinearLayout
|
||||
android:id="@+id/bubble"
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_message_out_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<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|start"
|
||||
android:padding="15dp"
|
||||
android:textColor="@color/n1_900"
|
||||
tools:text="This is test" />
|
||||
|
||||
@@ -59,30 +58,21 @@
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:layout_below="@+id/text"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/attachmentSpacer"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
</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" />
|
||||
android:visibility="gone"
|
||||
app:layout_anchor="@+id/text"
|
||||
app:layout_anchorGravity="bottom" />
|
||||
|
||||
</RelativeLayout>
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</layout>
|
||||
@@ -109,4 +109,12 @@
|
||||
<string name="post_type_user">User post</string>
|
||||
<string name="post_type_unknown">Post</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>
|
||||
|
||||
Reference in New Issue
Block a user