+34
-36
@@ -1,14 +1,17 @@
|
|||||||
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
val login: String = gradleLocalProperties(rootDir).getProperty("vklogin")
|
val login: String = gradleLocalProperties(rootDir).getProperty("vkLogin")
|
||||||
val password: String = gradleLocalProperties(rootDir).getProperty("vkpassword")
|
val password: String = gradleLocalProperties(rootDir).getProperty("vkPassword")
|
||||||
|
|
||||||
|
val sdkPackage: String = gradleLocalProperties(rootDir).getProperty("sdkPackage")
|
||||||
|
val sdkFingerprint: String = gradleLocalProperties(rootDir).getProperty("sdkFingerprint")
|
||||||
|
|
||||||
plugins {
|
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("kotlin-parcelize")
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
|
||||||
id("dagger.hilt.android.plugin")
|
id("dagger.hilt.android.plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +37,9 @@ android {
|
|||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
buildConfigField("String", "vkLogin", login)
|
buildConfigField("String", "vkLogin", login)
|
||||||
buildConfigField("String", "vkPassword", password)
|
buildConfigField("String", "vkPassword", password)
|
||||||
|
|
||||||
|
buildConfigField("String", "sdkPackage", sdkPackage)
|
||||||
|
buildConfigField("String", "sdkFingerprint", sdkFingerprint)
|
||||||
}
|
}
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
@@ -41,6 +47,9 @@ android {
|
|||||||
buildConfigField("String", "vkLogin", login)
|
buildConfigField("String", "vkLogin", login)
|
||||||
buildConfigField("String", "vkPassword", password)
|
buildConfigField("String", "vkPassword", password)
|
||||||
|
|
||||||
|
buildConfigField("String", "sdkPackage", sdkPackage)
|
||||||
|
buildConfigField("String", "sdkFingerprint", sdkFingerprint)
|
||||||
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
@@ -49,23 +58,13 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
dataBinding = true
|
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
@@ -78,44 +77,42 @@ kapt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31")
|
// Cicerone - Navigation
|
||||||
|
implementation("com.github.terrakok:cicerone:7.1")
|
||||||
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
|
||||||
|
|
||||||
implementation("com.github.massoudss:waveformSeekBar:3.1.0")
|
implementation("com.github.massoudss:waveformSeekBar:3.1.0")
|
||||||
|
|
||||||
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
implementation("androidx.core:core-splashscreen:1.0.0-beta02")
|
||||||
|
|
||||||
implementation("androidx.work:work-runtime-ktx:2.6.0")
|
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||||
|
|
||||||
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.paging:paging-runtime-ktx:3.1.1")
|
||||||
|
|
||||||
implementation("androidx.appcompat:appcompat:1.4.0-beta01")
|
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||||
implementation("com.google.android.material:material:1.5.0-alpha04")
|
implementation("com.google.android.material:material:1.6.0-beta01")
|
||||||
implementation("androidx.core:core-ktx:1.7.0-beta02")
|
implementation("androidx.core:core-ktx:1.7.0")
|
||||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.3.6")
|
implementation("androidx.fragment:fragment-ktx:1.4.1")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
|
||||||
|
|
||||||
implementation("androidx.room:room-ktx:2.3.0")
|
implementation("androidx.room:room-ktx:2.4.2")
|
||||||
implementation("androidx.room:room-runtime:2.3.0")
|
implementation("androidx.room:room-runtime:2.4.2")
|
||||||
kapt("androidx.room:room-compiler:2.3.0")
|
kapt("androidx.room:room-compiler:2.4.2")
|
||||||
|
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.3.1")
|
implementation("androidx.lifecycle:lifecycle-common-java8:2.4.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-common-java8:2.3.1")
|
|
||||||
|
|
||||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.2")
|
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.2")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2")
|
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2")
|
||||||
@@ -124,9 +121,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation("com.google.dagger:hilt-android:2.39.1")
|
implementation("com.google.dagger:hilt-android:2.39.1")
|
||||||
kapt("com.google.dagger:hilt-android-compiler:2.39.1")
|
kapt("com.google.dagger:hilt-android-compiler:2.39.1")
|
||||||
implementation("androidx.hilt:hilt-navigation-fragment:1.0.0")
|
|
||||||
|
|
||||||
implementation("com.github.yogacp:android-viewbinding:1.0.3")
|
implementation("com.github.yogacp:android-viewbinding:1.0.4")
|
||||||
|
|
||||||
implementation("io.coil-kt:coil:1.4.0")
|
implementation("io.coil-kt:coil:1.4.0")
|
||||||
|
|
||||||
@@ -134,4 +130,6 @@ dependencies {
|
|||||||
implementation("org.jsoup:jsoup:1.14.3")
|
implementation("org.jsoup:jsoup:1.14.3")
|
||||||
implementation("ch.acra:acra:4.11.1")
|
implementation("ch.acra:acra:4.11.1")
|
||||||
|
|
||||||
|
implementation("com.github.bumptech.glide:glide:4.13.0")
|
||||||
|
kapt("com.github.bumptech.glide:compiler:4.13.0")
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
android:testOnly="false"
|
android:testOnly="false"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:replace="android:allowBackup">
|
tools:replace="android:allowBackup">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.MainActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -29,6 +28,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.MessagesUpdateService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
|||||||
@@ -1,17 +1,54 @@
|
|||||||
package com.meloda.fast.activity
|
package com.meloda.fast.activity
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import com.github.terrakok.cicerone.NavigatorHolder
|
||||||
|
import com.github.terrakok.cicerone.Router
|
||||||
|
import com.github.terrakok.cicerone.androidx.AppNavigator
|
||||||
|
import com.github.terrakok.cicerone.androidx.FragmentScreen
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseActivity
|
import com.meloda.fast.base.BaseActivity
|
||||||
|
import com.meloda.fast.common.Screens
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : BaseActivity(R.layout.activity_main) {
|
class MainActivity : BaseActivity(R.layout.activity_main) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
private val navigator = object : AppNavigator(this, R.id.root_fragment_container) {
|
||||||
installSplashScreen()
|
override fun setupFragmentTransaction(
|
||||||
super.onCreate(savedInstanceState)
|
screen: FragmentScreen,
|
||||||
|
fragmentTransaction: FragmentTransaction,
|
||||||
|
currentFragment: Fragment?,
|
||||||
|
nextFragment: Fragment
|
||||||
|
) {
|
||||||
|
// fragmentTransaction.setCustomAnimations(
|
||||||
|
// R.anim.activity_open_enter, R.anim.activity_close_exit,
|
||||||
|
// R.anim.activity_close_enter, R.anim.activity_open_exit
|
||||||
|
// )
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var navigatorHolder: NavigatorHolder
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var router: Router
|
||||||
|
|
||||||
|
override fun onResumeFragments() {
|
||||||
|
navigatorHolder.setNavigator(navigator)
|
||||||
|
super.onResumeFragments()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
navigatorHolder.removeNavigator()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
router.newRootScreen(Screens.Main())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
enum class ApiEvent(val value: Int) {
|
||||||
|
MESSAGE_SET_FLAGS(2),
|
||||||
|
MESSAGE_CLEAR_FLAGS(3),
|
||||||
|
MESSAGE_NEW(4),
|
||||||
|
MESSAGE_EDIT(5),
|
||||||
|
MESSAGE_READ_INCOMING(6),
|
||||||
|
MESSAGE_READ_OUTGOING(7),
|
||||||
|
FRIEND_ONLINE(8),
|
||||||
|
FRIEND_OFFLINE(9),
|
||||||
|
MESSAGES_DELETED(13),
|
||||||
|
PIN_UNPIN_CONVERSATION(20),
|
||||||
|
PRIVATE_TYPING(61),
|
||||||
|
CHAT_TYPING(62),
|
||||||
|
ONE_MORE_TYPING(63),
|
||||||
|
VOICE_RECORDING(64),
|
||||||
|
PHOTO_UPLOADING(65),
|
||||||
|
VIDEO_UPLOADING(66),
|
||||||
|
FILE_UPLOADING(67),
|
||||||
|
UNREAD_COUNT_UPDATE(80)
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parse(value: Int) = values().firstOrNull { it.value == value }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
import com.meloda.fast.api.model.VkGroup
|
||||||
|
import com.meloda.fast.api.model.VkMessage
|
||||||
|
import com.meloda.fast.api.model.VkUser
|
||||||
|
|
||||||
|
sealed class LongPollEvent {
|
||||||
|
|
||||||
|
data class VkMessageNewEvent(
|
||||||
|
val message: VkMessage,
|
||||||
|
val profiles: HashMap<Int, VkUser>,
|
||||||
|
val groups: HashMap<Int, VkGroup>
|
||||||
|
) : LongPollEvent()
|
||||||
|
|
||||||
|
data class VkMessageEditEvent(val message: VkMessage) : LongPollEvent()
|
||||||
|
|
||||||
|
data class VkMessageReadIncomingEvent(val peerId: Int, val messageId: Int) : LongPollEvent()
|
||||||
|
data class VkMessageReadOutgoingEvent(val peerId: Int, val messageId: Int) : LongPollEvent()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.meloda.fast.api.model.VkGroup
|
||||||
|
import com.meloda.fast.api.model.VkUser
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import com.meloda.fast.api.network.messages.MessagesDataSource
|
||||||
|
import com.meloda.fast.api.network.messages.MessagesGetByIdRequest
|
||||||
|
import com.meloda.fast.base.viewmodel.VkEventCallback
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class LongPollUpdatesParser(
|
||||||
|
private val messagesDataSource: MessagesDataSource
|
||||||
|
) : CoroutineScope {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "LongPollUpdatesParser"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val job = SupervisorJob()
|
||||||
|
|
||||||
|
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
Log.d(TAG, "error: $throwable")
|
||||||
|
throwable.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Default + job + exceptionHandler
|
||||||
|
|
||||||
|
private val listenersMap: MutableMap<ApiEvent, MutableCollection<VkEventCallback<*>>> =
|
||||||
|
mutableMapOf()
|
||||||
|
|
||||||
|
fun parseNextUpdate(event: JsonArray) {
|
||||||
|
val eventType: ApiEvent? =
|
||||||
|
try {
|
||||||
|
ApiEvent.parse(event[0].asInt)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType != null) {
|
||||||
|
println("$TAG: $eventType: $event")
|
||||||
|
} else {
|
||||||
|
println("$TAG: unknown event: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
when (eventType) {
|
||||||
|
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
|
||||||
|
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
|
||||||
|
ApiEvent.MESSAGE_NEW -> parseMessageNew(eventType, event)
|
||||||
|
ApiEvent.MESSAGE_EDIT -> parseMessageEdit(eventType, event)
|
||||||
|
ApiEvent.MESSAGE_READ_INCOMING -> parseMessageReadIncoming(eventType, event)
|
||||||
|
ApiEvent.MESSAGE_READ_OUTGOING -> parseMessageReadOutgoing(eventType, event)
|
||||||
|
ApiEvent.FRIEND_ONLINE -> parseFriendOnline(eventType, event)
|
||||||
|
ApiEvent.FRIEND_OFFLINE -> parseFriendOffline(eventType, event)
|
||||||
|
ApiEvent.MESSAGES_DELETED -> parseMessagesDeleted(eventType, event)
|
||||||
|
// ApiEvent.PIN_UNPIN_CONVERSATION -> TODO()
|
||||||
|
// ApiEvent.TYPING -> TODO()
|
||||||
|
// ApiEvent.VOICE_RECORDING -> TODO()
|
||||||
|
// ApiEvent.PHOTO_UPLOADING -> TODO()
|
||||||
|
// ApiEvent.VIDEO_UPLOADING -> TODO()
|
||||||
|
// ApiEvent.FILE_UPLOADING -> TODO()
|
||||||
|
// ApiEvent.UNREAD_COUNT_UPDATE -> TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageSetFlags(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageClearFlags(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageNew(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
|
||||||
|
val messageId = event[1].asInt
|
||||||
|
|
||||||
|
launch {
|
||||||
|
val newMessageEvent: LongPollEvent.VkMessageNewEvent =
|
||||||
|
loadNormalMessage(
|
||||||
|
eventType,
|
||||||
|
messageId
|
||||||
|
)
|
||||||
|
|
||||||
|
listenersMap[ApiEvent.MESSAGE_NEW]?.let {
|
||||||
|
it.map { vkEventCallback ->
|
||||||
|
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageNewEvent>)
|
||||||
|
.onEvent(newMessageEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageEdit(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
|
||||||
|
val messageId = event[1].asInt
|
||||||
|
|
||||||
|
launch {
|
||||||
|
val editedMessageEvent: LongPollEvent.VkMessageEditEvent =
|
||||||
|
loadNormalMessage(
|
||||||
|
eventType,
|
||||||
|
messageId
|
||||||
|
)
|
||||||
|
|
||||||
|
listenersMap[ApiEvent.MESSAGE_EDIT]?.let {
|
||||||
|
it.map { vkEventCallback ->
|
||||||
|
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageEditEvent>)
|
||||||
|
.onEvent(editedMessageEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageReadIncoming(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
val peerId = event[1].asInt
|
||||||
|
val messageId = event[2].asInt
|
||||||
|
|
||||||
|
launch {
|
||||||
|
listenersMap[ApiEvent.MESSAGE_READ_INCOMING]?.let { listeners ->
|
||||||
|
listeners.map { vkEventCallback ->
|
||||||
|
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>)
|
||||||
|
.onEvent(
|
||||||
|
LongPollEvent.VkMessageReadIncomingEvent(
|
||||||
|
peerId = peerId,
|
||||||
|
messageId = messageId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessageReadOutgoing(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
val peerId = event[1].asInt
|
||||||
|
val messageId = event[2].asInt
|
||||||
|
|
||||||
|
launch {
|
||||||
|
listenersMap[ApiEvent.MESSAGE_READ_OUTGOING]?.let { listeners ->
|
||||||
|
listeners.map { vkEventCallback ->
|
||||||
|
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>)
|
||||||
|
.onEvent(
|
||||||
|
LongPollEvent.VkMessageReadOutgoingEvent(
|
||||||
|
peerId = peerId,
|
||||||
|
messageId = messageId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFriendOnline(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFriendOffline(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMessagesDeleted(eventType: ApiEvent, event: JsonArray) {
|
||||||
|
// println("$TAG: $eventType: $event")
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T : LongPollEvent> loadNormalMessage(eventType: ApiEvent, messageId: Int) =
|
||||||
|
coroutineScope {
|
||||||
|
suspendCoroutine<T> {
|
||||||
|
launch {
|
||||||
|
val normalMessageResponse = messagesDataSource.getById(
|
||||||
|
MessagesGetByIdRequest(
|
||||||
|
messagesIds = listOf(messageId),
|
||||||
|
extended = true,
|
||||||
|
fields = VKConstants.ALL_FIELDS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (normalMessageResponse !is Answer.Success) {
|
||||||
|
(normalMessageResponse as Answer.Error).throwable.let { throw it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesResponse = normalMessageResponse.data.response ?: return@launch
|
||||||
|
|
||||||
|
val messagesList = messagesResponse.items
|
||||||
|
if (messagesList.isEmpty()) return@launch
|
||||||
|
|
||||||
|
val normalMessage = messagesList[0].asVkMessage()
|
||||||
|
messagesDataSource.store(listOf(normalMessage))
|
||||||
|
|
||||||
|
val profiles = hashMapOf<Int, VkUser>()
|
||||||
|
messagesResponse.profiles?.forEach { baseUser ->
|
||||||
|
baseUser.asVkUser().let { user -> profiles[user.id] = user }
|
||||||
|
}
|
||||||
|
|
||||||
|
val groups = hashMapOf<Int, VkGroup>()
|
||||||
|
messagesResponse.groups?.forEach { baseGroup ->
|
||||||
|
baseGroup.asVkGroup().let { group -> groups[group.id] = group }
|
||||||
|
}
|
||||||
|
|
||||||
|
val resumeValue: LongPollEvent? = when (eventType) {
|
||||||
|
ApiEvent.MESSAGE_NEW ->
|
||||||
|
LongPollEvent.VkMessageNewEvent(
|
||||||
|
normalMessage,
|
||||||
|
profiles,
|
||||||
|
groups
|
||||||
|
)
|
||||||
|
ApiEvent.MESSAGE_EDIT -> LongPollEvent.VkMessageEditEvent(normalMessage)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeValue?.let { value -> it.resume(value as T) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun <T : Any> registerListener(eventType: ApiEvent, listener: VkEventCallback<T>) {
|
||||||
|
listenersMap.let { map ->
|
||||||
|
map[eventType] = (map[eventType] ?: mutableListOf()).also {
|
||||||
|
it.add(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageIncomingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>) {
|
||||||
|
registerListener(ApiEvent.MESSAGE_READ_INCOMING, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageIncomingRead(block: (LongPollEvent.VkMessageReadIncomingEvent) -> Unit) {
|
||||||
|
onMessageIncomingRead(assembleEventCallback(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageOutgoingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>) {
|
||||||
|
registerListener(ApiEvent.MESSAGE_READ_OUTGOING, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageOutgoingRead(block: (LongPollEvent.VkMessageReadOutgoingEvent) -> Unit) {
|
||||||
|
onMessageOutgoingRead(assembleEventCallback(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNewMessage(listener: VkEventCallback<LongPollEvent.VkMessageNewEvent>) {
|
||||||
|
registerListener(ApiEvent.MESSAGE_NEW, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNewMessage(block: (LongPollEvent.VkMessageNewEvent) -> Unit) {
|
||||||
|
onNewMessage(assembleEventCallback(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageEdited(listener: VkEventCallback<LongPollEvent.VkMessageEditEvent>) {
|
||||||
|
registerListener(ApiEvent.MESSAGE_EDIT, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageEdited(block: (LongPollEvent.VkMessageEditEvent) -> Unit) {
|
||||||
|
onMessageEdited(assembleEventCallback(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearListeners() {
|
||||||
|
listenersMap.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline fun <R : Any> assembleEventCallback(
|
||||||
|
crossinline block: (R) -> Unit
|
||||||
|
): VkEventCallback<R> {
|
||||||
|
return object : VkEventCallback<R> {
|
||||||
|
override fun onEvent(event: R) = block.invoke(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.meloda.fast.api
|
|||||||
|
|
||||||
import com.meloda.fast.api.model.attachments.*
|
import com.meloda.fast.api.model.attachments.*
|
||||||
|
|
||||||
|
@Suppress("RemoveExplicitTypeArguments")
|
||||||
object VKConstants {
|
object VKConstants {
|
||||||
|
|
||||||
const val GROUP_FIELDS = "description,members_count,counters,status,verified"
|
const val GROUP_FIELDS = "description,members_count,counters,status,verified"
|
||||||
@@ -12,10 +13,13 @@ object VKConstants {
|
|||||||
const val ALL_FIELDS = "$USER_FIELDS,$GROUP_FIELDS"
|
const val ALL_FIELDS = "$USER_FIELDS,$GROUP_FIELDS"
|
||||||
|
|
||||||
const val API_VERSION = "5.132"
|
const val API_VERSION = "5.132"
|
||||||
|
const val LP_VERSION = 10
|
||||||
|
|
||||||
const val VK_APP_ID = "2274003"
|
const val VK_APP_ID = "2274003"
|
||||||
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||||
|
|
||||||
const val FAST_GROUP_ID = -119516304
|
const val FAST_GROUP_ID = -119516304
|
||||||
|
const val FAST_APP_ID = "6964679"
|
||||||
|
|
||||||
object Auth {
|
object Auth {
|
||||||
const val SCOPE = "notify," +
|
const val SCOPE = "notify," +
|
||||||
@@ -38,7 +42,7 @@ object VKConstants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val restrictedToEditAttachments = listOf(
|
val restrictedToEditAttachments = listOf<Class<out VkAttachment>>(
|
||||||
VkCall::class.java,
|
VkCall::class.java,
|
||||||
VkCurator::class.java,
|
VkCurator::class.java,
|
||||||
VkEvent::class.java,
|
VkEvent::class.java,
|
||||||
@@ -46,6 +50,25 @@ object VKConstants {
|
|||||||
VkGraffiti::class.java,
|
VkGraffiti::class.java,
|
||||||
VkGroupCall::class.java,
|
VkGroupCall::class.java,
|
||||||
VkStory::class.java,
|
VkStory::class.java,
|
||||||
VkVoiceMessage::class.java
|
VkVoiceMessage::class.java,
|
||||||
|
VkWidget::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
val separatedFromTextAttachments = listOf<Class<out VkAttachment>>(
|
||||||
|
VkPhoto::class.java,
|
||||||
|
VkVideo::class.java,
|
||||||
|
VkSticker::class.java,
|
||||||
|
VkStory::class.java,
|
||||||
|
VkWidget::class.java,
|
||||||
|
VkGroupCall::class.java,
|
||||||
|
VkGroupCall::class.java,
|
||||||
|
VkCurator::class.java,
|
||||||
|
VkEvent::class.java,
|
||||||
|
VkGift::class.java,
|
||||||
|
VkGraffiti::class.java,
|
||||||
|
VkPoll::class.java,
|
||||||
|
VkWall::class.java,
|
||||||
|
VkWallReply::class.java,
|
||||||
|
VkLink::class.java
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -139,86 +139,82 @@ object VkUtils {
|
|||||||
|
|
||||||
for (baseAttachment in baseAttachments) {
|
for (baseAttachment in baseAttachments) {
|
||||||
when (baseAttachment.getPreparedType()) {
|
when (baseAttachment.getPreparedType()) {
|
||||||
BaseVkAttachmentItem.AttachmentType.PHOTO -> {
|
BaseVkAttachmentItem.AttachmentType.Photo -> {
|
||||||
val photo = baseAttachment.photo ?: continue
|
val photo = baseAttachment.photo ?: continue
|
||||||
attachments += photo.asVkPhoto()
|
attachments += photo.asVkPhoto()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.VIDEO -> {
|
BaseVkAttachmentItem.AttachmentType.Video -> {
|
||||||
val video = baseAttachment.video ?: continue
|
val video = baseAttachment.video ?: continue
|
||||||
attachments += video.asVkVideo()
|
attachments += video.asVkVideo()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.AUDIO -> {
|
BaseVkAttachmentItem.AttachmentType.Audio -> {
|
||||||
val audio = baseAttachment.audio ?: continue
|
val audio = baseAttachment.audio ?: continue
|
||||||
attachments += audio.asVkAudio()
|
attachments += audio.asVkAudio()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.FILE -> {
|
BaseVkAttachmentItem.AttachmentType.File -> {
|
||||||
val file = baseAttachment.file ?: continue
|
val file = baseAttachment.file ?: continue
|
||||||
attachments += file.asVkFile()
|
attachments += file.asVkFile()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.LINK -> {
|
BaseVkAttachmentItem.AttachmentType.Link -> {
|
||||||
val link = baseAttachment.link ?: continue
|
val link = baseAttachment.link ?: continue
|
||||||
attachments += link.asVkLink()
|
attachments += link.asVkLink()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.MINI_APP -> {
|
BaseVkAttachmentItem.AttachmentType.MiniApp -> {
|
||||||
val miniApp = baseAttachment.miniApp ?: continue
|
val miniApp = baseAttachment.miniApp ?: continue
|
||||||
attachments += VkMiniApp(
|
attachments += miniApp.asVkMiniApp()
|
||||||
link = miniApp.app.shareUrl
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.VOICE -> {
|
BaseVkAttachmentItem.AttachmentType.Voice -> {
|
||||||
val voiceMessage = baseAttachment.voiceMessage ?: continue
|
val voiceMessage = baseAttachment.voiceMessage ?: continue
|
||||||
attachments += voiceMessage.asVkVoiceMessage()
|
attachments += voiceMessage.asVkVoiceMessage()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.STICKER -> {
|
BaseVkAttachmentItem.AttachmentType.Sticker -> {
|
||||||
val sticker = baseAttachment.sticker ?: continue
|
val sticker = baseAttachment.sticker ?: continue
|
||||||
attachments += sticker.asVkSticker()
|
attachments += sticker.asVkSticker()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.GIFT -> {
|
BaseVkAttachmentItem.AttachmentType.Gift -> {
|
||||||
val gift = baseAttachment.gift ?: continue
|
val gift = baseAttachment.gift ?: continue
|
||||||
attachments += gift.asVkGift()
|
attachments += gift.asVkGift()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL -> {
|
BaseVkAttachmentItem.AttachmentType.Wall -> {
|
||||||
val wall = baseAttachment.wall ?: continue
|
val wall = baseAttachment.wall ?: continue
|
||||||
attachments += wall.asVkWall()
|
attachments += wall.asVkWall()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.GRAFFITI -> {
|
BaseVkAttachmentItem.AttachmentType.Graffiti -> {
|
||||||
val graffiti = baseAttachment.graffiti ?: continue
|
val graffiti = baseAttachment.graffiti ?: continue
|
||||||
attachments += graffiti.asVkGraffiti()
|
attachments += graffiti.asVkGraffiti()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.POLL -> {
|
BaseVkAttachmentItem.AttachmentType.Poll -> {
|
||||||
val poll = baseAttachment.poll ?: continue
|
val poll = baseAttachment.poll ?: continue
|
||||||
attachments += VkPoll(
|
attachments += poll.asVkPoll()
|
||||||
id = poll.id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> {
|
BaseVkAttachmentItem.AttachmentType.WallReply -> {
|
||||||
val wallReply = baseAttachment.wallReply ?: continue
|
val wallReply = baseAttachment.wallReply ?: continue
|
||||||
attachments += VkWallReply(
|
attachments += wallReply.asVkWallReply()
|
||||||
id = wallReply.id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.CALL -> {
|
BaseVkAttachmentItem.AttachmentType.Call -> {
|
||||||
val call = baseAttachment.call ?: continue
|
val call = baseAttachment.call ?: continue
|
||||||
attachments += call.asVkCall()
|
attachments += call.asVkCall()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> {
|
BaseVkAttachmentItem.AttachmentType.GroupCallInProgress -> {
|
||||||
val groupCall = baseAttachment.groupCall ?: continue
|
val groupCall = baseAttachment.groupCall ?: continue
|
||||||
attachments += VkGroupCall(
|
attachments += groupCall.asVkGroupCall()
|
||||||
initiatorId = groupCall.initiator_id
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.CURATOR -> {
|
BaseVkAttachmentItem.AttachmentType.Curator -> {
|
||||||
val curator = baseAttachment.curator ?: continue
|
val curator = baseAttachment.curator ?: continue
|
||||||
attachments += curator.asVkCurator()
|
attachments += curator.asVkCurator()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.EVENT -> {
|
BaseVkAttachmentItem.AttachmentType.Event -> {
|
||||||
val event = baseAttachment.event ?: continue
|
val event = baseAttachment.event ?: continue
|
||||||
attachments += event.asVkEvent()
|
attachments += event.asVkEvent()
|
||||||
}
|
}
|
||||||
BaseVkAttachmentItem.AttachmentType.STORY -> {
|
BaseVkAttachmentItem.AttachmentType.Story -> {
|
||||||
val story = baseAttachment.story ?: continue
|
val story = baseAttachment.story ?: continue
|
||||||
attachments += story.asVkStory()
|
attachments += story.asVkStory()
|
||||||
}
|
}
|
||||||
|
BaseVkAttachmentItem.AttachmentType.Widget -> {
|
||||||
|
val widget = baseAttachment.widget ?: continue
|
||||||
|
attachments += widget.asVkWidget()
|
||||||
|
}
|
||||||
else -> continue
|
else -> continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -580,22 +576,22 @@ object VkUtils {
|
|||||||
attachmentType: BaseVkAttachmentItem.AttachmentType
|
attachmentType: BaseVkAttachmentItem.AttachmentType
|
||||||
): Drawable? {
|
): Drawable? {
|
||||||
val resId = when (attachmentType) {
|
val resId = when (attachmentType) {
|
||||||
BaseVkAttachmentItem.AttachmentType.PHOTO -> R.drawable.ic_attachment_photo
|
BaseVkAttachmentItem.AttachmentType.Photo -> R.drawable.ic_attachment_photo
|
||||||
BaseVkAttachmentItem.AttachmentType.VIDEO -> R.drawable.ic_attachment_video
|
BaseVkAttachmentItem.AttachmentType.Video -> R.drawable.ic_attachment_video
|
||||||
BaseVkAttachmentItem.AttachmentType.AUDIO -> R.drawable.ic_attachment_audio
|
BaseVkAttachmentItem.AttachmentType.Audio -> R.drawable.ic_attachment_audio
|
||||||
BaseVkAttachmentItem.AttachmentType.FILE -> R.drawable.ic_attachment_file
|
BaseVkAttachmentItem.AttachmentType.File -> R.drawable.ic_attachment_file
|
||||||
BaseVkAttachmentItem.AttachmentType.LINK -> R.drawable.ic_attachment_link
|
BaseVkAttachmentItem.AttachmentType.Link -> R.drawable.ic_attachment_link
|
||||||
BaseVkAttachmentItem.AttachmentType.VOICE -> R.drawable.ic_attachment_voice
|
BaseVkAttachmentItem.AttachmentType.Voice -> R.drawable.ic_attachment_voice
|
||||||
BaseVkAttachmentItem.AttachmentType.MINI_APP -> R.drawable.ic_attachment_mini_app
|
BaseVkAttachmentItem.AttachmentType.MiniApp -> R.drawable.ic_attachment_mini_app
|
||||||
BaseVkAttachmentItem.AttachmentType.STICKER -> R.drawable.ic_attachment_sticker
|
BaseVkAttachmentItem.AttachmentType.Sticker -> R.drawable.ic_attachment_sticker
|
||||||
BaseVkAttachmentItem.AttachmentType.GIFT -> R.drawable.ic_attachment_gift
|
BaseVkAttachmentItem.AttachmentType.Gift -> R.drawable.ic_attachment_gift
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL -> R.drawable.ic_attachment_wall
|
BaseVkAttachmentItem.AttachmentType.Wall -> R.drawable.ic_attachment_wall
|
||||||
BaseVkAttachmentItem.AttachmentType.GRAFFITI -> R.drawable.ic_attachment_graffiti
|
BaseVkAttachmentItem.AttachmentType.Graffiti -> R.drawable.ic_attachment_graffiti
|
||||||
BaseVkAttachmentItem.AttachmentType.POLL -> R.drawable.ic_attachment_poll
|
BaseVkAttachmentItem.AttachmentType.Poll -> R.drawable.ic_attachment_poll
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL_REPLY -> R.drawable.ic_attachment_wall_reply
|
BaseVkAttachmentItem.AttachmentType.WallReply -> R.drawable.ic_attachment_wall_reply
|
||||||
BaseVkAttachmentItem.AttachmentType.CALL -> R.drawable.ic_attachment_call
|
BaseVkAttachmentItem.AttachmentType.Call -> R.drawable.ic_attachment_call
|
||||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_attachment_group_call
|
BaseVkAttachmentItem.AttachmentType.GroupCallInProgress -> R.drawable.ic_attachment_group_call
|
||||||
BaseVkAttachmentItem.AttachmentType.STORY -> R.drawable.ic_attachment_story
|
BaseVkAttachmentItem.AttachmentType.Story -> R.drawable.ic_attachment_story
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,24 +613,25 @@ object VkUtils {
|
|||||||
|
|
||||||
fun getAttachmentTypeByClass(attachment: VkAttachment): BaseVkAttachmentItem.AttachmentType? {
|
fun getAttachmentTypeByClass(attachment: VkAttachment): BaseVkAttachmentItem.AttachmentType? {
|
||||||
return when (attachment) {
|
return when (attachment) {
|
||||||
is VkPhoto -> BaseVkAttachmentItem.AttachmentType.PHOTO
|
is VkPhoto -> BaseVkAttachmentItem.AttachmentType.Photo
|
||||||
is VkVideo -> BaseVkAttachmentItem.AttachmentType.VIDEO
|
is VkVideo -> BaseVkAttachmentItem.AttachmentType.Video
|
||||||
is VkAudio -> BaseVkAttachmentItem.AttachmentType.AUDIO
|
is VkAudio -> BaseVkAttachmentItem.AttachmentType.Audio
|
||||||
is VkFile -> BaseVkAttachmentItem.AttachmentType.FILE
|
is VkFile -> BaseVkAttachmentItem.AttachmentType.File
|
||||||
is VkLink -> BaseVkAttachmentItem.AttachmentType.LINK
|
is VkLink -> BaseVkAttachmentItem.AttachmentType.Link
|
||||||
is VkMiniApp -> BaseVkAttachmentItem.AttachmentType.MINI_APP
|
is VkMiniApp -> BaseVkAttachmentItem.AttachmentType.MiniApp
|
||||||
is VkVoiceMessage -> BaseVkAttachmentItem.AttachmentType.VOICE
|
is VkVoiceMessage -> BaseVkAttachmentItem.AttachmentType.Voice
|
||||||
is VkSticker -> BaseVkAttachmentItem.AttachmentType.STICKER
|
is VkSticker -> BaseVkAttachmentItem.AttachmentType.Sticker
|
||||||
is VkGift -> BaseVkAttachmentItem.AttachmentType.GIFT
|
is VkGift -> BaseVkAttachmentItem.AttachmentType.Gift
|
||||||
is VkWall -> BaseVkAttachmentItem.AttachmentType.WALL
|
is VkWall -> BaseVkAttachmentItem.AttachmentType.Wall
|
||||||
is VkGraffiti -> BaseVkAttachmentItem.AttachmentType.GRAFFITI
|
is VkGraffiti -> BaseVkAttachmentItem.AttachmentType.Graffiti
|
||||||
is VkPoll -> BaseVkAttachmentItem.AttachmentType.POLL
|
is VkPoll -> BaseVkAttachmentItem.AttachmentType.Poll
|
||||||
is VkWallReply -> BaseVkAttachmentItem.AttachmentType.WALL_REPLY
|
is VkWallReply -> BaseVkAttachmentItem.AttachmentType.WallReply
|
||||||
is VkCall -> BaseVkAttachmentItem.AttachmentType.CALL
|
is VkCall -> BaseVkAttachmentItem.AttachmentType.Call
|
||||||
is VkGroupCall -> BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS
|
is VkGroupCall -> BaseVkAttachmentItem.AttachmentType.GroupCallInProgress
|
||||||
is VkEvent -> BaseVkAttachmentItem.AttachmentType.EVENT
|
is VkEvent -> BaseVkAttachmentItem.AttachmentType.Event
|
||||||
is VkCurator -> BaseVkAttachmentItem.AttachmentType.CURATOR
|
is VkCurator -> BaseVkAttachmentItem.AttachmentType.Curator
|
||||||
is VkStory -> BaseVkAttachmentItem.AttachmentType.STORY
|
is VkStory -> BaseVkAttachmentItem.AttachmentType.Story
|
||||||
|
is VkWidget -> BaseVkAttachmentItem.AttachmentType.Widget
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,42 +642,44 @@ object VkUtils {
|
|||||||
size: Int = 1
|
size: Int = 1
|
||||||
): String {
|
): String {
|
||||||
return when (attachmentType) {
|
return when (attachmentType) {
|
||||||
BaseVkAttachmentItem.AttachmentType.PHOTO ->
|
BaseVkAttachmentItem.AttachmentType.Photo ->
|
||||||
context.resources.getQuantityString(R.plurals.attachment_photos, size, size)
|
context.resources.getQuantityString(R.plurals.attachment_photos, size, size)
|
||||||
BaseVkAttachmentItem.AttachmentType.VIDEO ->
|
BaseVkAttachmentItem.AttachmentType.Video ->
|
||||||
context.resources.getQuantityString(R.plurals.attachment_videos, size, size)
|
context.resources.getQuantityString(R.plurals.attachment_videos, size, size)
|
||||||
BaseVkAttachmentItem.AttachmentType.AUDIO ->
|
BaseVkAttachmentItem.AttachmentType.Audio ->
|
||||||
context.resources.getQuantityString(R.plurals.attachment_audios, size, size)
|
context.resources.getQuantityString(R.plurals.attachment_audios, size, size)
|
||||||
BaseVkAttachmentItem.AttachmentType.FILE ->
|
BaseVkAttachmentItem.AttachmentType.File ->
|
||||||
context.resources.getQuantityString(R.plurals.attachment_files, size, size)
|
context.resources.getQuantityString(R.plurals.attachment_files, size, size)
|
||||||
BaseVkAttachmentItem.AttachmentType.LINK ->
|
BaseVkAttachmentItem.AttachmentType.Link ->
|
||||||
context.resources.getString(R.string.message_attachments_link)
|
context.resources.getString(R.string.message_attachments_link)
|
||||||
BaseVkAttachmentItem.AttachmentType.VOICE ->
|
BaseVkAttachmentItem.AttachmentType.Voice ->
|
||||||
context.resources.getString(R.string.message_attachments_voice)
|
context.resources.getString(R.string.message_attachments_voice)
|
||||||
BaseVkAttachmentItem.AttachmentType.MINI_APP ->
|
BaseVkAttachmentItem.AttachmentType.MiniApp ->
|
||||||
context.resources.getString(R.string.message_attachments_mini_app)
|
context.resources.getString(R.string.message_attachments_mini_app)
|
||||||
BaseVkAttachmentItem.AttachmentType.STICKER ->
|
BaseVkAttachmentItem.AttachmentType.Sticker ->
|
||||||
context.resources.getString(R.string.message_attachments_sticker)
|
context.resources.getString(R.string.message_attachments_sticker)
|
||||||
BaseVkAttachmentItem.AttachmentType.GIFT ->
|
BaseVkAttachmentItem.AttachmentType.Gift ->
|
||||||
context.resources.getString(R.string.message_attachments_gift)
|
context.resources.getString(R.string.message_attachments_gift)
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL ->
|
BaseVkAttachmentItem.AttachmentType.Wall ->
|
||||||
context.resources.getString(R.string.message_attachments_wall)
|
context.resources.getString(R.string.message_attachments_wall)
|
||||||
BaseVkAttachmentItem.AttachmentType.GRAFFITI ->
|
BaseVkAttachmentItem.AttachmentType.Graffiti ->
|
||||||
context.resources.getString(R.string.message_attachments_graffiti)
|
context.resources.getString(R.string.message_attachments_graffiti)
|
||||||
BaseVkAttachmentItem.AttachmentType.POLL ->
|
BaseVkAttachmentItem.AttachmentType.Poll ->
|
||||||
context.resources.getString(R.string.message_attachments_poll)
|
context.resources.getString(R.string.message_attachments_poll)
|
||||||
BaseVkAttachmentItem.AttachmentType.WALL_REPLY ->
|
BaseVkAttachmentItem.AttachmentType.WallReply ->
|
||||||
context.resources.getString(R.string.message_attachments_wall_reply)
|
context.resources.getString(R.string.message_attachments_wall_reply)
|
||||||
BaseVkAttachmentItem.AttachmentType.CALL ->
|
BaseVkAttachmentItem.AttachmentType.Call ->
|
||||||
context.resources.getString(R.string.message_attachments_call)
|
context.resources.getString(R.string.message_attachments_call)
|
||||||
BaseVkAttachmentItem.AttachmentType.GROUP_CALL_IN_PROGRESS ->
|
BaseVkAttachmentItem.AttachmentType.GroupCallInProgress ->
|
||||||
context.resources.getString(R.string.message_attachments_call_in_progress)
|
context.resources.getString(R.string.message_attachments_call_in_progress)
|
||||||
BaseVkAttachmentItem.AttachmentType.EVENT ->
|
BaseVkAttachmentItem.AttachmentType.Event ->
|
||||||
context.resources.getString(R.string.message_attachments_event)
|
context.resources.getString(R.string.message_attachments_event)
|
||||||
BaseVkAttachmentItem.AttachmentType.CURATOR ->
|
BaseVkAttachmentItem.AttachmentType.Curator ->
|
||||||
context.resources.getString(R.string.message_attachments_curator)
|
context.resources.getString(R.string.message_attachments_curator)
|
||||||
BaseVkAttachmentItem.AttachmentType.STORY ->
|
BaseVkAttachmentItem.AttachmentType.Story ->
|
||||||
context.resources.getString(R.string.message_attachments_story)
|
context.resources.getString(R.string.message_attachments_story)
|
||||||
|
BaseVkAttachmentItem.AttachmentType.Widget ->
|
||||||
|
context.resources.getString(R.string.message_attachments_widget)
|
||||||
else -> attachmentType.value
|
else -> attachmentType.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.meloda.fast.api.model
|
package com.meloda.fast.api.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.meloda.fast.model.SelectableItem
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ data class VkConversation(
|
|||||||
|
|
||||||
@Embedded(prefix = "lastMessage_")
|
@Embedded(prefix = "lastMessage_")
|
||||||
var lastMessage: VkMessage? = null,
|
var lastMessage: VkMessage? = null,
|
||||||
) : Parcelable {
|
) : SelectableItem(id) {
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import androidx.room.PrimaryKey
|
|||||||
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.model.attachments.VkAttachment
|
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||||
import com.meloda.fast.base.adapter.SelectableItem
|
import com.meloda.fast.model.SelectableItem
|
||||||
import com.meloda.fast.util.TimeUtils
|
import com.meloda.fast.util.TimeUtils
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -33,10 +33,8 @@ data class VkMessage(
|
|||||||
|
|
||||||
var forwards: List<VkMessage>? = null,
|
var forwards: List<VkMessage>? = null,
|
||||||
var attachments: List<VkAttachment>? = null,
|
var attachments: List<VkAttachment>? = null,
|
||||||
|
|
||||||
// @Embedded(prefix = "replyMessage_")
|
|
||||||
var replyMessage: VkMessage? = null
|
var replyMessage: VkMessage? = null
|
||||||
) : SelectableItem() {
|
) : SelectableItem(id) {
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
@@ -53,8 +51,8 @@ data class VkMessage(
|
|||||||
fun isGroup() = fromId < 0
|
fun isGroup() = fromId < 0
|
||||||
|
|
||||||
fun isRead(conversation: VkConversation) =
|
fun isRead(conversation: VkConversation) =
|
||||||
if (isOut) conversation.outRead < id
|
if (isOut) conversation.outRead - id >= 0
|
||||||
else conversation.inRead < id
|
else conversation.inRead - id >= 0
|
||||||
|
|
||||||
fun getPreparedAction(): Action? {
|
fun getPreparedAction(): Action? {
|
||||||
if (action == null) return null
|
if (action == null) return null
|
||||||
|
|||||||
@@ -20,21 +20,30 @@ data class VkPhoto(
|
|||||||
val userId: Int?
|
val userId: Int?
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SIZE_TYPE_75 = 's'
|
||||||
|
const val SIZE_TYPE_130 = 'm'
|
||||||
|
const val SIZE_TYPE_604 = 'x'
|
||||||
|
const val SIZE_TYPE_807 = 'y'
|
||||||
|
const val SIZE_TYPE_1080_1024 = 'z'
|
||||||
|
const val SIZE_TYPE_2560_2048 = 'w'
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
private val sizesChars = Stack<Char>()
|
private val sizesChars = Stack<Char>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sizesChars.push('s')
|
sizesChars.push(SIZE_TYPE_75)
|
||||||
sizesChars.push('m')
|
sizesChars.push(SIZE_TYPE_130)
|
||||||
sizesChars.push('x')
|
sizesChars.push(SIZE_TYPE_604)
|
||||||
sizesChars.push('o')
|
sizesChars.push('o')
|
||||||
sizesChars.push('p')
|
sizesChars.push('p')
|
||||||
sizesChars.push('q')
|
sizesChars.push('q')
|
||||||
sizesChars.push('r')
|
sizesChars.push('r')
|
||||||
sizesChars.push('y')
|
sizesChars.push(SIZE_TYPE_807)
|
||||||
sizesChars.push('z')
|
sizesChars.push(SIZE_TYPE_1080_1024)
|
||||||
sizesChars.push('w')
|
sizesChars.push(SIZE_TYPE_2560_2048)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
@@ -61,7 +70,7 @@ data class VkPhoto(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getSizeOrSmaller(type: Char): BaseVkPhoto.Size? {
|
fun getSizeOrSmaller(type: Char): BaseVkPhoto.Size? {
|
||||||
val photoStack = sizesChars.clone() as Stack<Char>
|
val photoStack = sizesChars.clone() as Stack<*>
|
||||||
|
|
||||||
val sizeIndex = photoStack.search(type)
|
val sizeIndex = photoStack.search(type)
|
||||||
|
|
||||||
@@ -72,7 +81,7 @@ data class VkPhoto(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until photoStack.size) {
|
for (i in 0 until photoStack.size) {
|
||||||
val size = getSizeOrNull(photoStack.peek())
|
val size = getSizeOrNull(photoStack.peek() as Char)
|
||||||
|
|
||||||
if (size == null) {
|
if (size == null) {
|
||||||
photoStack.pop()
|
photoStack.pop()
|
||||||
|
|||||||
@@ -7,5 +7,11 @@ data class VkStory(
|
|||||||
val id: Int,
|
val id: Int,
|
||||||
val ownerId: Int,
|
val ownerId: Int,
|
||||||
val date: Int,
|
val date: Int,
|
||||||
val photo: VkPhoto
|
val photo: VkPhoto?
|
||||||
) : VkAttachment()
|
) : VkAttachment() {
|
||||||
|
|
||||||
|
fun isFromUser() = ownerId > 0
|
||||||
|
|
||||||
|
fun isFromGroup() = ownerId < 0
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.meloda.fast.api.model.attachments
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import com.meloda.fast.api.VkUtils
|
import com.meloda.fast.api.VkUtils
|
||||||
import com.meloda.fast.api.model.base.attachments.BaseVkVideo
|
import com.meloda.fast.api.model.base.attachments.BaseVkVideo
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
@@ -9,7 +10,7 @@ import kotlinx.parcelize.Parcelize
|
|||||||
data class VkVideo(
|
data class VkVideo(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val ownerId: Int,
|
val ownerId: Int,
|
||||||
val images: List<BaseVkVideo.Image>,
|
val images: List<VideoImage>,
|
||||||
val firstFrames: List<BaseVkVideo.FirstFrame>?,
|
val firstFrames: List<BaseVkVideo.FirstFrame>?,
|
||||||
val accessKey: String?
|
val accessKey: String?
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
@@ -17,10 +18,57 @@ data class VkVideo(
|
|||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
val className: String = this::class.java.name
|
val className: String = this::class.java.name
|
||||||
|
|
||||||
fun imageForWidth(width: Int): BaseVkVideo.Image? {
|
fun imageForWidth(width: Int): VideoImage? {
|
||||||
return images.find { it.width == width }
|
return images.find { it.width == width }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun imageForWidthAtLeast(width: Int): VideoImage? {
|
||||||
|
var certainImages = images.sortedByDescending { it.width }
|
||||||
|
var containsVertical = false
|
||||||
|
for (image in images) {
|
||||||
|
if (image.shapeKind == ShapeKind.Vertical) {
|
||||||
|
containsVertical = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsVertical) {
|
||||||
|
certainImages = certainImages.filter { it.shapeKind == ShapeKind.Vertical }
|
||||||
|
}
|
||||||
|
|
||||||
|
certainImages = certainImages.filter { it.width >= width }
|
||||||
|
|
||||||
|
return certainImages.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class VideoImage(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
val url: String,
|
||||||
|
val withPadding: Boolean
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
var shapeKind: ShapeKind
|
||||||
|
|
||||||
|
init {
|
||||||
|
val ratio = width.toFloat() / height.toFloat()
|
||||||
|
|
||||||
|
shapeKind = when {
|
||||||
|
ratio > 1 -> ShapeKind.Horizontal
|
||||||
|
ratio < 1 -> ShapeKind.Vertical
|
||||||
|
else -> ShapeKind.Square
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ShapeKind {
|
||||||
|
object Vertical : ShapeKind()
|
||||||
|
object Horizontal : ShapeKind()
|
||||||
|
object Square : ShapeKind()
|
||||||
|
}
|
||||||
|
|
||||||
override fun asString(withAccessKey: Boolean) = VkUtils.attachmentToString(
|
override fun asString(withAccessKey: Boolean) = VkUtils.attachmentToString(
|
||||||
attachmentClass = this::class.java,
|
attachmentClass = this::class.java,
|
||||||
id = id,
|
id = id,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ data class VkVoiceMessage(
|
|||||||
val linkOgg: String,
|
val linkOgg: String,
|
||||||
val linkMp3: String,
|
val linkMp3: String,
|
||||||
val accessKey: String,
|
val accessKey: String,
|
||||||
val transcriptState: String,
|
val transcriptState: String?,
|
||||||
val transcript: String
|
val transcript: String?
|
||||||
) : VkAttachment() {
|
) : VkAttachment() {
|
||||||
|
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.meloda.fast.api.model.attachments
|
||||||
|
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class VkWidget(
|
||||||
|
val id: Int
|
||||||
|
) : VkAttachment()
|
||||||
@@ -8,17 +8,17 @@ import kotlinx.parcelize.Parcelize
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class BaseVkMessage(
|
data class BaseVkMessage(
|
||||||
|
val id: Int,
|
||||||
|
val peer_id: Int,
|
||||||
val date: Int,
|
val date: Int,
|
||||||
val from_id: Int,
|
val from_id: Int,
|
||||||
val id: Int,
|
|
||||||
val out: Int,
|
val out: Int,
|
||||||
val peer_id: Int,
|
|
||||||
val text: String,
|
val text: String,
|
||||||
val conversation_message_id: Int,
|
val conversation_message_id: Int,
|
||||||
val fwd_messages: List<BaseVkMessage>? = listOf(),
|
val fwd_messages: List<BaseVkMessage>? = emptyList(),
|
||||||
val important: Boolean,
|
val important: Boolean,
|
||||||
val random_id: Int,
|
val random_id: Int,
|
||||||
val attachments: List<BaseVkAttachmentItem> = listOf(),
|
val attachments: List<BaseVkAttachmentItem> = emptyList(),
|
||||||
val is_hidden: Boolean,
|
val is_hidden: Boolean,
|
||||||
val payload: String,
|
val payload: String,
|
||||||
val geo: Geo?,
|
val geo: Geo?,
|
||||||
@@ -29,7 +29,7 @@ data class BaseVkMessage(
|
|||||||
|
|
||||||
fun asVkMessage() = VkMessage(
|
fun asVkMessage() = VkMessage(
|
||||||
id = id,
|
id = id,
|
||||||
text = if (text.isBlank()) null else text,
|
text = text.ifBlank { null },
|
||||||
isOut = out == 1,
|
isOut = out == 1,
|
||||||
peerId = peer_id,
|
peerId = peer_id,
|
||||||
fromId = from_id,
|
fromId = from_id,
|
||||||
|
|||||||
+25
-23
@@ -30,38 +30,40 @@ data class BaseVkAttachmentItem(
|
|||||||
val groupCall: BaseVkGroupCall?,
|
val groupCall: BaseVkGroupCall?,
|
||||||
val curator: BaseVkCurator?,
|
val curator: BaseVkCurator?,
|
||||||
val event: BaseVkEvent?,
|
val event: BaseVkEvent?,
|
||||||
val story: BaseVkStory?
|
val story: BaseVkStory?,
|
||||||
|
val widget: BaseVkWidget?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
fun getPreparedType() = AttachmentType.parse(type)
|
fun getPreparedType() = AttachmentType.parse(type)
|
||||||
|
|
||||||
enum class AttachmentType(var value: String) {
|
enum class AttachmentType(var value: String) {
|
||||||
UNKNOWN("unknown"),
|
Unknown("unknown"),
|
||||||
PHOTO("photo"),
|
Photo("photo"),
|
||||||
VIDEO("video"),
|
Video("video"),
|
||||||
AUDIO("audio"),
|
Audio("audio"),
|
||||||
FILE("doc"),
|
File("doc"),
|
||||||
LINK("link"),
|
Link("link"),
|
||||||
VOICE("audio_message"),
|
Voice("audio_message"),
|
||||||
MINI_APP("mini_app"),
|
MiniApp("mini_app"),
|
||||||
STICKER("sticker"),
|
Sticker("sticker"),
|
||||||
GIFT("gift"),
|
Gift("gift"),
|
||||||
WALL("wall"),
|
Wall("wall"),
|
||||||
GRAFFITI("graffiti"),
|
Graffiti("graffiti"),
|
||||||
POLL("poll"),
|
Poll("poll"),
|
||||||
WALL_REPLY("wall_reply"),
|
WallReply("wall_reply"),
|
||||||
CALL("call"),
|
Call("call"),
|
||||||
GROUP_CALL_IN_PROGRESS("group_call_in_progress"),
|
GroupCallInProgress("group_call_in_progress"),
|
||||||
CURATOR("curator"),
|
Curator("curator"),
|
||||||
EVENT("event"),
|
Event("event"),
|
||||||
STORY("story")
|
Story("story"),
|
||||||
|
Widget("widget")
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(value: String): AttachmentType? {
|
fun parse(value: String): AttachmentType {
|
||||||
val parsedValue = values().firstOrNull { it.value == value } ?: UNKNOWN
|
val parsedValue = values().firstOrNull { it.value == value } ?: Unknown
|
||||||
|
|
||||||
if (parsedValue == UNKNOWN) {
|
if (parsedValue == Unknown) {
|
||||||
Log.e("AttachmentType", "Unknown attachment type: $value")
|
Log.e("AttachmentType", "Unknown attachment type: $value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,11 @@ data class BaseVkEvent(
|
|||||||
val is_favorite: Boolean,
|
val is_favorite: Boolean,
|
||||||
val text: String,
|
val text: String,
|
||||||
val address: String,
|
val address: String,
|
||||||
val friends: List<Int> = listOf(),
|
val friends: List<Int> = emptyList(),
|
||||||
val member_status: Int,
|
val member_status: Int,
|
||||||
val time: Int
|
val time: Int
|
||||||
) : BaseVkAttachment() {
|
) : BaseVkAttachment() {
|
||||||
|
|
||||||
fun asVkEvent() = VkEvent(
|
fun asVkEvent() = VkEvent(id = id)
|
||||||
id = id
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.api.model.base.attachments
|
package com.meloda.fast.api.model.base.attachments
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.meloda.fast.api.model.attachments.VkGroupCall
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -16,4 +17,6 @@ data class BaseVkGroupCall(
|
|||||||
val count: Int
|
val count: Int
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
fun asVkGroupCall() = VkGroupCall(initiatorId = initiator_id)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.meloda.fast.api.model.base.attachments
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.meloda.fast.api.model.attachments.VkMiniApp
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -63,4 +64,6 @@ data class BaseVkMiniApp(
|
|||||||
val url: String
|
val url: String
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
fun asVkMiniApp() = VkMiniApp(link = app.shareUrl)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.api.model.base.attachments
|
package com.meloda.fast.api.model.base.attachments
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.meloda.fast.api.model.attachments.VkPoll
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -55,7 +56,8 @@ data class BaseVkPoll(
|
|||||||
val color: String,
|
val color: String,
|
||||||
val position: Double
|
val position: Double
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun asVkPoll() = VkPoll(id = id)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BaseVkStory(
|
|||||||
val date: Int,
|
val date: Int,
|
||||||
val expires_at: Int,
|
val expires_at: Int,
|
||||||
val is_ads: Boolean,
|
val is_ads: Boolean,
|
||||||
val photo: BaseVkPhoto,
|
val photo: BaseVkPhoto?,
|
||||||
val replies: Replies,
|
val replies: Replies,
|
||||||
val is_one_time: Boolean,
|
val is_one_time: Boolean,
|
||||||
val track_code: String,
|
val track_code: String,
|
||||||
@@ -40,7 +40,7 @@ data class BaseVkStory(
|
|||||||
id = id,
|
id = id,
|
||||||
ownerId = owner_id,
|
ownerId = owner_id,
|
||||||
date = date,
|
date = date,
|
||||||
photo = photo.asVkPhoto()
|
photo = photo?.asVkPhoto()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|||||||
@@ -41,18 +41,26 @@ data class BaseVkVideo(
|
|||||||
fun asVkVideo() = VkVideo(
|
fun asVkVideo() = VkVideo(
|
||||||
id = id,
|
id = id,
|
||||||
ownerId = owner_id,
|
ownerId = owner_id,
|
||||||
images = image,
|
images = image.map { it.asVideoImage() },
|
||||||
firstFrames = first_frame,
|
firstFrames = first_frame,
|
||||||
accessKey = access_key
|
accessKey = access_key
|
||||||
)
|
)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Image(
|
data class Image(
|
||||||
val height: Int,
|
|
||||||
val width: Int,
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
val url: String,
|
val url: String,
|
||||||
val with_padding: Int
|
val with_padding: Int?
|
||||||
) : Parcelable
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun asVideoImage() = VkVideo.VideoImage(
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
url = url,
|
||||||
|
withPadding = with_padding == 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class FirstFrame(
|
data class FirstFrame(
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@ data class BaseVkVoiceMessage(
|
|||||||
val link_ogg: String,
|
val link_ogg: String,
|
||||||
val link_mp3: String,
|
val link_mp3: String,
|
||||||
val access_key: String,
|
val access_key: String,
|
||||||
val transcript_state: String,
|
val transcript_state: String?,
|
||||||
val transcript: String
|
val transcript: String?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
fun asVkVoiceMessage() = VkVoiceMessage(
|
fun asVkVoiceMessage() = VkVoiceMessage(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.api.model.base.attachments
|
package com.meloda.fast.api.model.base.attachments
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.meloda.fast.api.model.attachments.VkWallReply
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -17,7 +18,6 @@ data class BaseVkWallReply(
|
|||||||
val reply_to_comment: Int?
|
val reply_to_comment: Int?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Likes(
|
data class Likes(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
@@ -26,4 +26,6 @@ data class BaseVkWallReply(
|
|||||||
val can_publish: Int
|
val can_publish: Int
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
fun asVkWallReply() = VkWallReply(id = id)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.meloda.fast.api.model.base.attachments
|
||||||
|
|
||||||
|
import com.meloda.fast.api.model.attachments.VkWidget
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class BaseVkWidget(val id: Int) : BaseVkAttachment() {
|
||||||
|
|
||||||
|
fun asVkWidget() = VkWidget(id)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.meloda.fast.api.network
|
|||||||
|
|
||||||
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.account.AccountUrls
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
@@ -12,10 +13,12 @@ class AuthInterceptor : Interceptor {
|
|||||||
val builder = chain.request().url.newBuilder()
|
val builder = chain.request().url.newBuilder()
|
||||||
.addQueryParameter("v", URLEncoder.encode(VKConstants.API_VERSION, "utf-8"))
|
.addQueryParameter("v", URLEncoder.encode(VKConstants.API_VERSION, "utf-8"))
|
||||||
|
|
||||||
UserConfig.accessToken.let {
|
|
||||||
if (it.isNotBlank())
|
if (!builder.build().toUrl().toString().contains(AccountUrls.SetOnline))
|
||||||
builder.addQueryParameter("access_token", URLEncoder.encode(it, "utf-8"))
|
UserConfig.accessToken.let {
|
||||||
}
|
if (it.isNotBlank())
|
||||||
|
builder.addQueryParameter("access_token", URLEncoder.encode(it, "utf-8"))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: 9/29/2021 crash on timeout
|
// 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())
|
||||||
|
|||||||
@@ -5,6 +5,36 @@ object VkUrls {
|
|||||||
const val OAUTH = "https://oauth.vk.com"
|
const val OAUTH = "https://oauth.vk.com"
|
||||||
const val API = "https://api.vk.com/method"
|
const val API = "https://api.vk.com/method"
|
||||||
|
|
||||||
|
object Auth {
|
||||||
|
const val DirectAuth = "$OAUTH/token"
|
||||||
|
const val SendSms = "$API/auth.validatePhone"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Conversations {
|
||||||
|
const val Get = "$API/messages.getConversations"
|
||||||
|
const val Delete = "$API/messages.deleteConversation"
|
||||||
|
const val Pin = "$API/messages.pinConversation"
|
||||||
|
const val Unpin = "$API/messages.unpinConversation"
|
||||||
|
const val ReorderPinned = "$API/messages.reorderPinnedConversations"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Users {
|
||||||
|
const val GetById = "$API/users.get"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Messages {
|
||||||
|
const val GetHistory = "$API/messages.getHistory"
|
||||||
|
const val Send = "$API/messages.send"
|
||||||
|
const val MarkAsImportant = "$API/messages.markAsImportant"
|
||||||
|
const val GetLongPollServer = "$API/messages.getLongPollServer"
|
||||||
|
const val GetLongPollHistory = "$API/messages.getLongPollHistory"
|
||||||
|
const val Pin = "$API/messages.pin"
|
||||||
|
const val Unpin = "$API/messages.unpin"
|
||||||
|
const val Delete = "$API/messages.delete"
|
||||||
|
const val Edit = "$API/messages.edit"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.meloda.fast.api.network.account
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AccountDataSource @Inject constructor(
|
||||||
|
private val repo: AccountRepo
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun setOnline(params: AccountSetOnlineRequest) = repo.setOnline(params.map)
|
||||||
|
|
||||||
|
suspend fun setOffline(params: AccountSetOfflineRequest) = repo.setOffline(params.map)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.meloda.fast.api.network.account
|
||||||
|
|
||||||
|
import com.meloda.fast.api.base.ApiResponse
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.QueryMap
|
||||||
|
|
||||||
|
interface AccountRepo {
|
||||||
|
|
||||||
|
@GET(AccountUrls.SetOnline)
|
||||||
|
suspend fun setOnline(@QueryMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||||
|
|
||||||
|
@POST(AccountUrls.SetOffline)
|
||||||
|
suspend fun setOffline(@QueryMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.meloda.fast.api.network.account
|
||||||
|
|
||||||
|
import com.meloda.fast.api.ApiExtensions.intString
|
||||||
|
|
||||||
|
data class AccountSetOnlineRequest(
|
||||||
|
val voip: Boolean,
|
||||||
|
val accessToken: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
val map
|
||||||
|
get() = mutableMapOf(
|
||||||
|
"voip" to voip.intString,
|
||||||
|
"access_token" to accessToken
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AccountSetOfflineRequest(val accessToken: String) {
|
||||||
|
val map get() = mutableMapOf("access_token" to accessToken)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.meloda.fast.api.network.account
|
||||||
|
|
||||||
|
import com.meloda.fast.api.network.VkUrls
|
||||||
|
|
||||||
|
object AccountUrls {
|
||||||
|
|
||||||
|
const val SetOnline = "${VkUrls.API}/account.setOnline"
|
||||||
|
const val SetOffline = "${VkUrls.API}/account.setOffline"
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ class AuthDataSource @Inject constructor(
|
|||||||
private val repo: AuthRepo
|
private val repo: AuthRepo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun auth(params: RequestAuthDirect) = repo.auth(params.map)
|
suspend fun auth(params: AuthDirectRequest) = repo.auth(params.map)
|
||||||
|
|
||||||
suspend fun sendSms(validationSid: String) = repo.sendSms(validationSid)
|
suspend fun sendSms(validationSid: String) = repo.sendSms(validationSid)
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import retrofit2.http.QueryMap
|
|||||||
interface AuthRepo {
|
interface AuthRepo {
|
||||||
|
|
||||||
@GET(AuthUrls.DirectAuth)
|
@GET(AuthUrls.DirectAuth)
|
||||||
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<ResponseAuthDirect>
|
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<AuthDirectResponse>
|
||||||
|
|
||||||
@GET(AuthUrls.SendSms)
|
@GET(AuthUrls.SendSms)
|
||||||
suspend fun sendSms(@Query("sid") validationSid: String): Answer<ResponseSendSms>
|
suspend fun sendSms(@Query("sid") validationSid: String): Answer<SendSmsResponse>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package com.meloda.fast.api.network.auth
|
package com.meloda.fast.api.network.auth
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.meloda.fast.BuildConfig
|
||||||
|
import com.meloda.fast.api.VKConstants
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class RequestAuthDirect(
|
data class AuthDirectRequest(
|
||||||
@SerializedName("grant_type") val grantType: String,
|
val grantType: String,
|
||||||
@SerializedName("client_id") val clientId: String,
|
val clientId: String,
|
||||||
@SerializedName("client_secret") val clientSecret: String,
|
val clientSecret: String,
|
||||||
@SerializedName("username") val username: String,
|
val username: String,
|
||||||
@SerializedName("password") val password: String,
|
val password: String,
|
||||||
@SerializedName("scope") val scope: String,
|
val scope: String,
|
||||||
@SerializedName("2fa_supported") val twoFaSupported: Boolean = true,
|
val twoFaSupported: Boolean = true,
|
||||||
@SerializedName("force_sms") val twoFaForceSms: Boolean = false,
|
val twoFaForceSms: Boolean = false,
|
||||||
@SerializedName("code") val twoFaCode: String? = null,
|
val twoFaCode: String? = null,
|
||||||
@SerializedName("captcha_sid") val captchaSid: String? = null,
|
val captchaSid: String? = null,
|
||||||
@SerializedName("captcha_key") val captchaKey: String? = null,
|
val captchaKey: String? = null,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
val map
|
val map
|
||||||
get() = mutableMapOf(
|
get() = mutableMapOf(
|
||||||
"grant_type" to grantType,
|
"grant_type" to grantType,
|
||||||
@@ -28,10 +30,38 @@ data class RequestAuthDirect(
|
|||||||
"scope" to scope,
|
"scope" to scope,
|
||||||
"2fa_supported" to if (twoFaSupported) "1" else "0",
|
"2fa_supported" to if (twoFaSupported) "1" else "0",
|
||||||
"force_sms" to if (twoFaForceSms) "1" else "0"
|
"force_sms" to if (twoFaForceSms) "1" else "0"
|
||||||
)
|
)
|
||||||
.apply {
|
.apply {
|
||||||
twoFaCode?.let { this["code"] = it }
|
twoFaCode?.let { this["code"] = it }
|
||||||
captchaSid?.let { this["captcha_sid"] = it }
|
captchaSid?.let { this["captcha_sid"] = it }
|
||||||
captchaKey?.let { this["captcha_key"] = it }
|
captchaKey?.let { this["captcha_key"] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AuthWithAppRequest(
|
||||||
|
val redirectUrl: String = "https://oauth.vk.com/blank.html",
|
||||||
|
val display: String = "page",
|
||||||
|
val responseType: String = "token",
|
||||||
|
val accessToken: String,
|
||||||
|
val revoke: Int = 1,
|
||||||
|
val scope: Int = 136297695,
|
||||||
|
val clientId: String = VKConstants.FAST_APP_ID,
|
||||||
|
val sdkPackage: String = BuildConfig.sdkPackage,
|
||||||
|
val sdkFingerprint: String = BuildConfig.sdkFingerprint
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
val map
|
||||||
|
get() = mutableMapOf(
|
||||||
|
"redirect_url" to redirectUrl,
|
||||||
|
"display" to display,
|
||||||
|
"response_type" to responseType,
|
||||||
|
"access_token" to accessToken,
|
||||||
|
"revoke" to revoke.toString(),
|
||||||
|
"scope" to scope.toString(),
|
||||||
|
"client_id" to clientId,
|
||||||
|
"sdk_package" to sdkPackage,
|
||||||
|
"sdk_fingerprint" to sdkFingerprint
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ResponseAuthDirect(
|
data class AuthDirectResponse(
|
||||||
@SerializedName("access_token") val accessToken: String? = null,
|
@SerializedName("access_token") val accessToken: String? = null,
|
||||||
@SerializedName("user_id") val userId: Int? = null,
|
@SerializedName("user_id") val userId: Int? = null,
|
||||||
@SerializedName("trusted_hash") val twoFaHash: String? = null,
|
@SerializedName("trusted_hash") val twoFaHash: String? = null,
|
||||||
@@ -13,7 +13,7 @@ data class ResponseAuthDirect(
|
|||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ResponseSendSms(
|
data class SendSmsResponse(
|
||||||
@SerializedName("sid") val validationSid: String?,
|
@SerializedName("sid") val validationSid: String?,
|
||||||
@SerializedName("delay") val delay: Int?,
|
@SerializedName("delay") val delay: Int?,
|
||||||
@SerializedName("validation_type") val validationType: String?,
|
@SerializedName("validation_type") val validationType: String?,
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package com.meloda.fast.api.network.longpoll
|
package com.meloda.fast.api.network.longpoll
|
||||||
|
|
||||||
import com.meloda.fast.api.base.ApiResponse
|
import com.google.gson.JsonObject
|
||||||
import com.meloda.fast.api.network.Answer
|
import com.meloda.fast.api.network.Answer
|
||||||
import org.json.JSONObject
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
|
||||||
import retrofit2.http.QueryMap
|
import retrofit2.http.QueryMap
|
||||||
|
import retrofit2.http.Url
|
||||||
|
|
||||||
interface LongPollRepo {
|
interface LongPollRepo {
|
||||||
|
|
||||||
@GET("https://{serverUrl}")
|
@GET
|
||||||
suspend fun getResponse(
|
suspend fun getResponse(
|
||||||
@Path("serverUrl") serverUrl: String,
|
@Url serverUrl: String,
|
||||||
@QueryMap params: Map<String, String>
|
@QueryMap params: Map<String, String>
|
||||||
): Answer<ApiResponse<JSONObject>>
|
): Answer<JsonObject>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.meloda.fast.api.network.longpoll
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class LongPollGetUpdatesRequest(
|
||||||
|
val act: String = "a_check",
|
||||||
|
val key: String,
|
||||||
|
val ts: Int,
|
||||||
|
val wait: Int,
|
||||||
|
val mode: Int
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
val map
|
||||||
|
get() = mutableMapOf(
|
||||||
|
"act" to act,
|
||||||
|
"key" to key,
|
||||||
|
"ts" to ts.toString(),
|
||||||
|
"wait" to wait.toString(),
|
||||||
|
"mode" to mode.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,40 +1,50 @@
|
|||||||
package com.meloda.fast.api.network.messages
|
package com.meloda.fast.api.network.messages
|
||||||
|
|
||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
|
import com.meloda.fast.api.network.longpoll.LongPollGetUpdatesRequest
|
||||||
|
import com.meloda.fast.api.network.longpoll.LongPollRepo
|
||||||
import com.meloda.fast.database.dao.MessagesDao
|
import com.meloda.fast.database.dao.MessagesDao
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MessagesDataSource @Inject constructor(
|
class MessagesDataSource @Inject constructor(
|
||||||
private val repo: MessagesRepo,
|
private val messagesRepo: MessagesRepo,
|
||||||
private val dao: MessagesDao
|
private val messagesDao: MessagesDao,
|
||||||
|
private val longPollRepo: LongPollRepo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
suspend fun store(messages: List<VkMessage>) = messagesDao.insert(messages)
|
||||||
|
|
||||||
|
suspend fun getCached(peerId: Int) = messagesDao.getByPeerId(peerId)
|
||||||
|
|
||||||
suspend fun getHistory(params: MessagesGetHistoryRequest) =
|
suspend fun getHistory(params: MessagesGetHistoryRequest) =
|
||||||
repo.getHistory(params.map)
|
messagesRepo.getHistory(params.map)
|
||||||
|
|
||||||
suspend fun send(params: MessagesSendRequest) =
|
suspend fun send(params: MessagesSendRequest) =
|
||||||
repo.send(params.map)
|
messagesRepo.send(params.map)
|
||||||
|
|
||||||
suspend fun markAsImportant(params: MessagesMarkAsImportantRequest) =
|
suspend fun markAsImportant(params: MessagesMarkAsImportantRequest) =
|
||||||
repo.markAsImportant(params.map)
|
messagesRepo.markAsImportant(params.map)
|
||||||
|
|
||||||
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
||||||
repo.getLongPollServer(params.map)
|
messagesRepo.getLongPollServer(params.map)
|
||||||
|
|
||||||
suspend fun pin(params: MessagesPinMessageRequest) =
|
suspend fun pin(params: MessagesPinMessageRequest) =
|
||||||
repo.pin(params.map)
|
messagesRepo.pin(params.map)
|
||||||
|
|
||||||
suspend fun unpin(params: MessagesUnPinMessageRequest) =
|
suspend fun unpin(params: MessagesUnPinMessageRequest) =
|
||||||
repo.unpin(params.map)
|
messagesRepo.unpin(params.map)
|
||||||
|
|
||||||
suspend fun delete(params: MessagesDeleteRequest) =
|
suspend fun delete(params: MessagesDeleteRequest) =
|
||||||
repo.delete(params.map)
|
messagesRepo.delete(params.map)
|
||||||
|
|
||||||
suspend fun edit(params: MessagesEditRequest) =
|
suspend fun edit(params: MessagesEditRequest) =
|
||||||
repo.edit(params.map)
|
messagesRepo.edit(params.map)
|
||||||
|
|
||||||
suspend fun store(messages: List<VkMessage>) = dao.insert(messages)
|
suspend fun getLongPollUpdates(
|
||||||
|
serverUrl: String,
|
||||||
suspend fun getCached(peerId: Int) = dao.getByPeerId(peerId)
|
params: LongPollGetUpdatesRequest
|
||||||
|
) = longPollRepo.getResponse(serverUrl, params.map)
|
||||||
|
|
||||||
|
suspend fun getById(params: MessagesGetByIdRequest) =
|
||||||
|
messagesRepo.getById(params.map)
|
||||||
}
|
}
|
||||||
@@ -42,4 +42,8 @@ interface MessagesRepo {
|
|||||||
@POST(MessagesUrls.Edit)
|
@POST(MessagesUrls.Edit)
|
||||||
suspend fun edit(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
suspend fun edit(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MessagesUrls.GetById)
|
||||||
|
suspend fun getById(@FieldMap params: Map<String, String>): Answer<ApiResponse<MessagesGetByIdResponse>>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,8 @@ data class MessagesSendRequest(
|
|||||||
val replyTo: Int? = null,
|
val replyTo: Int? = null,
|
||||||
val stickerId: Int? = null,
|
val stickerId: Int? = null,
|
||||||
val disableMentions: Boolean? = null,
|
val disableMentions: Boolean? = null,
|
||||||
val dontParseLinks: Boolean? = null
|
val dontParseLinks: Boolean? = null,
|
||||||
|
val silent: Boolean? = null
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
val map
|
val map
|
||||||
@@ -55,6 +56,7 @@ data class MessagesSendRequest(
|
|||||||
stickerId?.let { this["sticker_id"] = it.toString() }
|
stickerId?.let { this["sticker_id"] = it.toString() }
|
||||||
disableMentions?.let { this["disable_mentions"] = it.intString }
|
disableMentions?.let { this["disable_mentions"] = it.intString }
|
||||||
dontParseLinks?.let { this["dont_parse_links"] = it.intString }
|
dontParseLinks?.let { this["dont_parse_links"] = it.intString }
|
||||||
|
silent?.let { this["silent"] = it.toString() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,3 +168,20 @@ data class MessagesEditRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class MessagesGetByIdRequest(
|
||||||
|
val messagesIds: List<Int>,
|
||||||
|
val extended: Boolean? = null,
|
||||||
|
val fields: String? = null
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
val map
|
||||||
|
get() = mutableMapOf(
|
||||||
|
"message_ids" to messagesIds.joinToString { it.toString() },
|
||||||
|
).apply {
|
||||||
|
extended?.let { this["extended"] = it.intString }
|
||||||
|
fields?.let { this["fields"] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,8 +10,16 @@ import kotlinx.parcelize.Parcelize
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data class MessagesGetHistoryResponse(
|
data class MessagesGetHistoryResponse(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val items: List<BaseVkMessage> = listOf(),
|
val items: List<BaseVkMessage> = emptyList(),
|
||||||
val conversations: List<BaseVkConversation>?,
|
val conversations: List<BaseVkConversation>?,
|
||||||
val profiles: List<BaseVkUser>?,
|
val profiles: List<BaseVkUser>?,
|
||||||
val groups: List<BaseVkGroup>?
|
val groups: List<BaseVkGroup>?
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class MessagesGetByIdResponse(
|
||||||
|
val count: Int,
|
||||||
|
val items: List<BaseVkMessage> = emptyList(),
|
||||||
|
val profiles: List<BaseVkUser>?,
|
||||||
|
val groups: List<BaseVkGroup>?
|
||||||
|
) : Parcelable
|
||||||
@@ -13,5 +13,6 @@ object MessagesUrls {
|
|||||||
const val Unpin = "${VkUrls.API}/messages.unpin"
|
const val Unpin = "${VkUrls.API}/messages.unpin"
|
||||||
const val Delete = "${VkUrls.API}/messages.delete"
|
const val Delete = "${VkUrls.API}/messages.delete"
|
||||||
const val Edit = "${VkUrls.API}/messages.edit"
|
const val Edit = "${VkUrls.API}/messages.edit"
|
||||||
|
const val GetById = "${VkUrls.API}/messages.getById"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,12 @@
|
|||||||
package com.meloda.fast.base
|
package com.meloda.fast.base
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
|
||||||
|
|
||||||
abstract class BaseActivity : AppCompatActivity, LifecycleOwner {
|
abstract class BaseActivity : AppCompatActivity {
|
||||||
|
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
|
|
||||||
constructor(@LayoutRes resId: Int) : super(resId)
|
constructor(@LayoutRes resId: Int) : super(resId)
|
||||||
|
|
||||||
protected lateinit var lifecycleRegistry: LifecycleRegistry
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
lifecycleRegistry = LifecycleRegistry(this)
|
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.meloda.fast.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
|
abstract class ResourceManager(protected val context: Context) {
|
||||||
|
|
||||||
|
protected fun getString(@StringRes resId: Int): String {
|
||||||
|
return context.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
protected fun getColor(@ColorRes resId: Int): Int {
|
||||||
|
return ContextCompat.getColor(context, resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,115 +1,187 @@
|
|||||||
package com.meloda.fast.base.adapter
|
package com.meloda.fast.base.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import com.meloda.fast.model.DataItem
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
@Suppress("MemberVisibilityCanBePrivate", "unused", "UNCHECKED_CAST")
|
||||||
abstract class BaseAdapter<Item, VH : BaseHolder>(
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
abstract class BaseAdapter<T : DataItem<*>, VH : BaseHolder> constructor(
|
||||||
var context: Context,
|
var context: Context,
|
||||||
values: MutableList<Item>,
|
diffUtil: DiffUtil.ItemCallback<T>,
|
||||||
diffUtil: DiffUtil.ItemCallback<Item>
|
preAddedValues: List<T> = emptyList(),
|
||||||
) : ListAdapter<Item, VH>(diffUtil) {
|
) : ListAdapter<T, VH>(diffUtil) {
|
||||||
|
|
||||||
val cleanValues = mutableListOf<Item>()
|
protected val adapterScope = CoroutineScope(Dispatchers.Default)
|
||||||
val values = mutableListOf<Item>()
|
private val cleanList = mutableListOf<T>()
|
||||||
|
|
||||||
init {
|
|
||||||
addAll(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected var inflater: LayoutInflater = LayoutInflater.from(context)
|
protected var inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
var itemClickListener: ((position: Int) -> Unit) = {}
|
var itemClickListener: ((position: Int) -> Unit)? = null
|
||||||
var itemLongClickListener: ((position: Int) -> Boolean) = { false }
|
var itemLongClickListener: ((position: Int) -> Boolean)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
cleanList.addAll(preAddedValues)
|
||||||
|
addAll(preAddedValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cloneCurrentList(): MutableList<T> {
|
||||||
|
return ArrayList(currentList)
|
||||||
|
}
|
||||||
|
|
||||||
open fun destroy() {}
|
open fun destroy() {}
|
||||||
|
|
||||||
override fun getItem(position: Int): Item {
|
fun getOrNull(position: Int): T? {
|
||||||
return values[position]
|
return if (position >= 0 && position <= currentList.lastIndex) get(position) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrNull(position: Int): Item? {
|
fun getOrElse(position: Int, defaultValue: (Int) -> T): T {
|
||||||
return if (position >= 0 && position <= values.lastIndex) get(position) else null
|
return if (position >= 0 && position <= currentList.lastIndex) get(position)
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrElse(position: Int, defaultValue: (Int) -> Item): Item {
|
|
||||||
return if (position >= 0 && position <= values.lastIndex) get(position)
|
|
||||||
else defaultValue(position)
|
else defaultValue(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(position: Int, item: Item) {
|
fun add(
|
||||||
values.add(position, item)
|
item: T,
|
||||||
cleanValues.add(position, item)
|
position: Int? = null,
|
||||||
|
beforeFooter: Boolean = false,
|
||||||
|
commitCallback: (() -> Unit)? = null
|
||||||
|
) = addAll(listOf(item), position, beforeFooter, commitCallback)
|
||||||
|
|
||||||
|
fun addAll(
|
||||||
|
items: List<T>,
|
||||||
|
position: Int? = null,
|
||||||
|
beforeFooter: Boolean = false,
|
||||||
|
commitCallback: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
adapterScope.launch {
|
||||||
|
val newList = cloneCurrentList()
|
||||||
|
if (position == null) {
|
||||||
|
val mutableItems = items.toMutableList()
|
||||||
|
if (beforeFooter && newList.lastOrNull() is DataItem.Footer) {
|
||||||
|
newList.removeLastOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beforeFooter) {
|
||||||
|
mutableItems += DataItem.Footer as T
|
||||||
|
}
|
||||||
|
|
||||||
|
newList.addAll(mutableItems)
|
||||||
|
cleanList.addAll(mutableItems)
|
||||||
|
} else {
|
||||||
|
newList.addAll(position, items)
|
||||||
|
cleanList.addAll(position, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
submitList(newList, commitCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(item: Item) {
|
fun remove(item: T, commitCallback: (() -> Unit)? = null) =
|
||||||
values += item
|
removeAll(listOf(item), commitCallback)
|
||||||
cleanValues.add(item)
|
|
||||||
|
fun removeAll(items: List<T>, commitCallback: (() -> Unit)? = null) {
|
||||||
|
val newList = cloneCurrentList()
|
||||||
|
newList.removeAll(items)
|
||||||
|
submitList(newList, commitCallback)
|
||||||
|
|
||||||
|
cleanList.removeAll(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAll(items: List<Item>) {
|
fun removeAt(index: Int, commitCallback: (() -> Unit)? = null) {
|
||||||
values += items
|
val newList = cloneCurrentList()
|
||||||
cleanValues.addAll(items)
|
newList.removeAt(index)
|
||||||
|
submitList(newList, commitCallback)
|
||||||
|
|
||||||
|
cleanList.removeAt(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAll(position: Int, items: List<Item>) {
|
fun clear(commitCallback: (() -> Unit)? = null) = removeAll(currentList, commitCallback)
|
||||||
values.addAll(position, items)
|
|
||||||
cleanValues.addAll(position, items)
|
fun setItem(
|
||||||
|
item: T,
|
||||||
|
withHeader: Boolean = false,
|
||||||
|
withFooter: Boolean = false,
|
||||||
|
commitCallback: (() -> Unit)? = null
|
||||||
|
) = setItems(listOf(item), withHeader, withFooter, commitCallback)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun setItems(
|
||||||
|
list: List<T>?,
|
||||||
|
withHeader: Boolean = false,
|
||||||
|
withFooter: Boolean = false,
|
||||||
|
commitCallback: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
adapterScope.launch {
|
||||||
|
val items = mutableListOf<T>()
|
||||||
|
if (withHeader) items.add(DataItem.Header as T)
|
||||||
|
if (!list.isNullOrEmpty()) items.addAll(list)
|
||||||
|
if (withFooter) items.add(DataItem.Footer as T)
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (items == currentList) {
|
||||||
|
refreshList()
|
||||||
|
} else {
|
||||||
|
submitList(items, commitCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAll(items: List<Item>) {
|
fun indexOf(item: T): Int {
|
||||||
values.removeAll(items)
|
return currentList.indexOf(item)
|
||||||
cleanValues.removeAll(items)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAt(index: Int) {
|
val indices get() = currentList.indices
|
||||||
values.removeAt(index)
|
|
||||||
cleanValues.removeAt(index)
|
operator fun get(position: Int): T {
|
||||||
|
return currentList[position]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: Item) {
|
operator fun set(position: Int, item: T) = setItem(position, item)
|
||||||
values.remove(item)
|
|
||||||
cleanValues.remove(item)
|
fun setItem(position: Int, item: T, commitCallback: (() -> Unit)? = null) {
|
||||||
|
val newList = cloneCurrentList()
|
||||||
|
newList[position] = item
|
||||||
|
submitList(newList, commitCallback)
|
||||||
|
|
||||||
|
cleanList[position] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun isEmpty() = currentList.isEmpty()
|
||||||
values.clear()
|
fun isNotEmpty() = currentList.isNotEmpty()
|
||||||
cleanValues.clear()
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun refreshList() {
|
||||||
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(position: Int): Item {
|
fun updateCleanList(list: List<T>?) {
|
||||||
return values[position]
|
cleanList.clear()
|
||||||
|
list?.run { cleanList.addAll(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(position: Int, item: Item) {
|
override fun submitList(list: List<T>?) {
|
||||||
values[position] = item
|
super.submitList(list)
|
||||||
cleanValues[position] = item
|
updateCleanList(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun notifyChanges(oldList: List<Item>, newList: List<Item>) {}
|
override fun submitList(list: List<T>?, commitCallback: Runnable?) {
|
||||||
|
super.submitList(list, commitCallback)
|
||||||
fun isEmpty() = values.isEmpty()
|
updateCleanList(list)
|
||||||
fun isNotEmpty() = values.isNotEmpty()
|
|
||||||
|
|
||||||
fun view(resId: Int, viewGroup: ViewGroup, attachToRoot: Boolean = false): View {
|
|
||||||
return inflater.inflate(resId, viewGroup, attachToRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateValues(list: MutableList<Item>) {
|
|
||||||
values.clear()
|
|
||||||
values += list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||||
onBindItemViewHolder(holder, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onBindItemViewHolder(holder: VH, position: Int) {
|
|
||||||
initListeners(holder.itemView, position)
|
initListeners(holder.itemView, position)
|
||||||
holder.bind(position)
|
holder.bind(position)
|
||||||
}
|
}
|
||||||
@@ -117,15 +189,16 @@ abstract class BaseAdapter<Item, VH : BaseHolder>(
|
|||||||
protected open 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) }
|
||||||
itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
|
itemView.setOnLongClickListener {
|
||||||
|
itemLongClickListener?.invoke(position)
|
||||||
|
return@setOnLongClickListener itemClickListener != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return values.size
|
return currentList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastPosition
|
val lastPosition get() = currentList.lastIndex
|
||||||
get() = itemCount - 1
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.meloda.fast.extensions.dpToPx
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ class EmptyHeaderAdapter(
|
|||||||
private fun generateHeaderView() = View(context).apply {
|
private fun generateHeaderView() = View(context).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
AndroidUtils.px(56).roundToInt()
|
56.dpToPx()
|
||||||
)
|
)
|
||||||
isClickable = false
|
isClickable = false
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -17,3 +17,7 @@ object StartProgressEvent : VkEvent()
|
|||||||
object StopProgressEvent : VkEvent()
|
object StopProgressEvent : VkEvent()
|
||||||
|
|
||||||
abstract class VkEvent
|
abstract class VkEvent
|
||||||
|
|
||||||
|
interface VkEventCallback<in T : Any> {
|
||||||
|
fun onEvent(event: T)
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ class AppGlobal : Application() {
|
|||||||
lateinit var preferences: SharedPreferences
|
lateinit var preferences: SharedPreferences
|
||||||
lateinit var resources: Resources
|
lateinit var resources: Resources
|
||||||
lateinit var packageName: String
|
lateinit var packageName: String
|
||||||
lateinit var instance: AppGlobal
|
private lateinit var instance: AppGlobal
|
||||||
|
|
||||||
lateinit var appDatabase: AppDatabase
|
lateinit var appDatabase: AppDatabase
|
||||||
|
|
||||||
@@ -41,6 +41,8 @@ class AppGlobal : Application() {
|
|||||||
|
|
||||||
var screenWidth = 0
|
var screenWidth = 0
|
||||||
var screenHeight = 0
|
var screenHeight = 0
|
||||||
|
|
||||||
|
val Instance get() = instance
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -51,9 +53,7 @@ class AppGlobal : Application() {
|
|||||||
ACRA.init(this)
|
ACRA.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
appDatabase = Room.databaseBuilder(
|
appDatabase = Room.databaseBuilder(this, AppDatabase::class.java, "cache")
|
||||||
this, AppDatabase::class.java, "cache"
|
|
||||||
)
|
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -85,10 +85,8 @@ class AppGlobal : Application() {
|
|||||||
"width: $screenWidth; height: $screenHeight; density: $density; diagonal: $diagonal; dpiDensity: $densityDpi; scaledDensity: $densityScaled; xDpi: $xDpi; yDpi: $yDpi"
|
"width: $screenWidth; height: $screenHeight; density: $density; diagonal: $diagonal; dpiDensity: $densityDpi; scaledDensity: $densityScaled; xDpi: $xDpi; yDpi: $yDpi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.meloda.fast.common
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.github.terrakok.cicerone.androidx.FragmentScreen
|
||||||
|
import com.meloda.fast.screens.conversations.ConversationsFragment
|
||||||
|
import com.meloda.fast.screens.login.LoginFragment
|
||||||
|
import com.meloda.fast.screens.main.MainFragment
|
||||||
|
import com.meloda.fast.screens.messages.MessagesHistoryFragment
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
object Screens {
|
||||||
|
fun Main() = FragmentScreen { MainFragment() }
|
||||||
|
fun Login() = FragmentScreen { LoginFragment() }
|
||||||
|
fun Conversations() = FragmentScreen { ConversationsFragment() }
|
||||||
|
fun MessagesHistory(bundle: Bundle) =
|
||||||
|
FragmentScreen { MessagesHistoryFragment.newInstance(bundle) }
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import com.meloda.fast.database.dao.UsersDao
|
|||||||
VkUser::class,
|
VkUser::class,
|
||||||
VkGroup::class
|
VkGroup::class
|
||||||
],
|
],
|
||||||
version = 26,
|
version = 28,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import com.google.gson.Gson
|
|||||||
import com.meloda.fast.api.model.VkMessage
|
import com.meloda.fast.api.model.VkMessage
|
||||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
|
@Suppress("UnnecessaryVariable")
|
||||||
class Converters {
|
class Converters {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val CACHE_SEPARATOR = "fastkruta228355"
|
||||||
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun fromListVkMessageToString(messages: List<VkMessage>?): String? {
|
fun fromListVkMessageToString(messages: List<VkMessage>?): String? {
|
||||||
if (messages == null) return null
|
if (messages == null) return null
|
||||||
|
|
||||||
val string =
|
val string = messages.map { fromVkMessageToString(it)!! }.joinToString { CACHE_SEPARATOR }
|
||||||
messages.map { fromVkMessageToString(it)!! }.stream()
|
|
||||||
.collect(Collectors.joining("fastkruta228355"))
|
|
||||||
|
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
@@ -24,9 +26,9 @@ class Converters {
|
|||||||
fun fromStringToListVkMessage(string: String?): List<VkMessage>? {
|
fun fromStringToListVkMessage(string: String?): List<VkMessage>? {
|
||||||
if (string == null) return null
|
if (string == null) return null
|
||||||
|
|
||||||
if (string.contains("fastkruta228355")) {
|
if (string.contains(CACHE_SEPARATOR)) {
|
||||||
val messages =
|
val messages =
|
||||||
string.split("fastkruta228355").map { fromStringToVkMessage(it)!! }
|
string.split(CACHE_SEPARATOR).map { fromStringToVkMessage(it)!! }
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +57,7 @@ class Converters {
|
|||||||
if (attachments == null) return null
|
if (attachments == null) return null
|
||||||
|
|
||||||
val string =
|
val string =
|
||||||
attachments.map { fromVkAttachmentToString(it)!! }.stream()
|
attachments.map { fromVkAttachmentToString(it)!! }.joinToString { CACHE_SEPARATOR }
|
||||||
.collect(Collectors.joining("fastkruta228355"))
|
|
||||||
|
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
@@ -65,9 +66,9 @@ class Converters {
|
|||||||
fun fromStringToListVkAttachment(string: String?): List<VkAttachment>? {
|
fun fromStringToListVkAttachment(string: String?): List<VkAttachment>? {
|
||||||
if (string == null) return null
|
if (string == null) return null
|
||||||
|
|
||||||
if (string.contains("fastkruta228355")) {
|
if (string.contains(CACHE_SEPARATOR)) {
|
||||||
val attachments =
|
val attachments =
|
||||||
string.split("fastkruta228355").map { fromStringToVkAttachment(it)!! }
|
string.split(CACHE_SEPARATOR).map { fromStringToVkAttachment(it)!! }
|
||||||
return attachments
|
return attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.meloda.fast.di
|
||||||
|
|
||||||
|
import com.github.terrakok.cicerone.Cicerone
|
||||||
|
import com.github.terrakok.cicerone.Router
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
object NavigationModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getCicerone(): Cicerone<Router> = Cicerone.create()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getRouter(cicerone: Cicerone<Router>) = cicerone.router
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getNavigationHolder(cicerone: Cicerone<Router>) = cicerone.getNavigatorHolder()
|
||||||
|
}
|
||||||
@@ -2,16 +2,19 @@ package com.meloda.fast.di
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.meloda.fast.api.LongPollUpdatesParser
|
||||||
import com.meloda.fast.api.network.AuthInterceptor
|
import com.meloda.fast.api.network.AuthInterceptor
|
||||||
import com.meloda.fast.api.network.ResultCallFactory
|
import com.meloda.fast.api.network.ResultCallFactory
|
||||||
import com.meloda.fast.api.network.auth.AuthRepo
|
import com.meloda.fast.api.network.account.AccountDataSource
|
||||||
|
import com.meloda.fast.api.network.account.AccountRepo
|
||||||
import com.meloda.fast.api.network.auth.AuthDataSource
|
import com.meloda.fast.api.network.auth.AuthDataSource
|
||||||
|
import com.meloda.fast.api.network.auth.AuthRepo
|
||||||
import com.meloda.fast.api.network.conversations.ConversationsDataSource
|
import com.meloda.fast.api.network.conversations.ConversationsDataSource
|
||||||
import com.meloda.fast.api.network.conversations.ConversationsRepo
|
import com.meloda.fast.api.network.conversations.ConversationsRepo
|
||||||
import com.meloda.fast.api.network.longpoll.LongPollRepo
|
import com.meloda.fast.api.network.longpoll.LongPollRepo
|
||||||
import com.meloda.fast.api.network.messages.MessagesDataSource
|
import com.meloda.fast.api.network.messages.MessagesDataSource
|
||||||
import com.meloda.fast.api.network.users.UsersDataSource
|
|
||||||
import com.meloda.fast.api.network.messages.MessagesRepo
|
import com.meloda.fast.api.network.messages.MessagesRepo
|
||||||
|
import com.meloda.fast.api.network.users.UsersDataSource
|
||||||
import com.meloda.fast.api.network.users.UsersRepo
|
import com.meloda.fast.api.network.users.UsersRepo
|
||||||
import com.meloda.fast.database.dao.ConversationsDao
|
import com.meloda.fast.database.dao.ConversationsDao
|
||||||
import com.meloda.fast.database.dao.MessagesDao
|
import com.meloda.fast.database.dao.MessagesDao
|
||||||
@@ -67,22 +70,27 @@ object NetworkModule {
|
|||||||
fun provideAuthInterceptor(): AuthInterceptor = AuthInterceptor()
|
fun provideAuthInterceptor(): AuthInterceptor = AuthInterceptor()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
fun provideAuthRepo(retrofit: Retrofit): AuthRepo =
|
fun provideAuthRepo(retrofit: Retrofit): AuthRepo =
|
||||||
retrofit.create(AuthRepo::class.java)
|
retrofit.create(AuthRepo::class.java)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
fun provideConversationsRepo(retrofit: Retrofit): ConversationsRepo =
|
fun provideConversationsRepo(retrofit: Retrofit): ConversationsRepo =
|
||||||
retrofit.create(ConversationsRepo::class.java)
|
retrofit.create(ConversationsRepo::class.java)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
fun provideUsersRepo(retrofit: Retrofit): UsersRepo =
|
fun provideUsersRepo(retrofit: Retrofit): UsersRepo =
|
||||||
retrofit.create(UsersRepo::class.java)
|
retrofit.create(UsersRepo::class.java)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
fun provideMessagesRepo(retrofit: Retrofit): MessagesRepo =
|
fun provideMessagesRepo(retrofit: Retrofit): MessagesRepo =
|
||||||
retrofit.create(MessagesRepo::class.java)
|
retrofit.create(MessagesRepo::class.java)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
fun provideLongPollRepo(retrofit: Retrofit): LongPollRepo =
|
fun provideLongPollRepo(retrofit: Retrofit): LongPollRepo =
|
||||||
retrofit.create(LongPollRepo::class.java)
|
retrofit.create(LongPollRepo::class.java)
|
||||||
|
|
||||||
@@ -109,7 +117,27 @@ object NetworkModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideMessagesDataSource(
|
fun provideMessagesDataSource(
|
||||||
repo: MessagesRepo,
|
messagesRepo: MessagesRepo,
|
||||||
dao: MessagesDao
|
messagesDao: MessagesDao,
|
||||||
): MessagesDataSource = MessagesDataSource(repo, dao)
|
longPollRepo: LongPollRepo
|
||||||
|
): MessagesDataSource = MessagesDataSource(
|
||||||
|
messagesRepo = messagesRepo,
|
||||||
|
messagesDao = messagesDao,
|
||||||
|
longPollRepo = longPollRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideLongPollUpdatesParser(messagesDataSource: MessagesDataSource): LongPollUpdatesParser =
|
||||||
|
LongPollUpdatesParser(messagesDataSource)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAccountRepo(retrofit: Retrofit): AccountRepo =
|
||||||
|
retrofit.create(AccountRepo::class.java)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAccountDataSource(repo: AccountRepo): AccountDataSource =
|
||||||
|
AccountDataSource(repo)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.meloda.fast.extensions
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.util.SparseArray
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.Px
|
||||||
|
import androidx.annotation.StyleRes
|
||||||
|
import androidx.core.view.children
|
||||||
|
|
||||||
|
fun Int.dpToPx(): Int {
|
||||||
|
val metrics = Resources.getSystem().displayMetrics
|
||||||
|
return (this * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Float.dpToPx(): Int {
|
||||||
|
val metrics = Resources.getSystem().displayMetrics
|
||||||
|
return (this * (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextView.clear() {
|
||||||
|
text = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ViewGroup.saveChildViewStates(): SparseArray<Parcelable> {
|
||||||
|
val childViewStates = SparseArray<Parcelable>()
|
||||||
|
children.forEach { child -> child.saveHierarchyState(childViewStates) }
|
||||||
|
return childViewStates
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ViewGroup.restoreChildViewStates(childViewStates: SparseArray<Parcelable>) {
|
||||||
|
children.forEach { child -> child.restoreHierarchyState(childViewStates) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.invisible() = run { visibility = View.INVISIBLE }
|
||||||
|
|
||||||
|
fun View.visible() = run { visibility = View.VISIBLE }
|
||||||
|
fun View.gone() = run { visibility = View.GONE }
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun View.toggleVisibility(visible: Boolean?, visibilityWhenFalse: Int = View.GONE) =
|
||||||
|
run { visibility = if (visible == true) View.VISIBLE else visibilityWhenFalse }
|
||||||
|
|
||||||
|
fun ValueAnimator.startWithIntValues(from: Int, to: Int) {
|
||||||
|
setIntValues(from, to)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.setMarginsPx(
|
||||||
|
@Px leftMargin: Int? = null,
|
||||||
|
@Px topMargin: Int? = null,
|
||||||
|
@Px rightMargin: Int? = null,
|
||||||
|
@Px bottomMargin: Int? = null
|
||||||
|
) {
|
||||||
|
if (layoutParams is ViewGroup.MarginLayoutParams) {
|
||||||
|
val params = layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
leftMargin?.run { params.leftMargin = this }
|
||||||
|
topMargin?.run { params.topMargin = this }
|
||||||
|
rightMargin?.run { params.rightMargin = this }
|
||||||
|
bottomMargin?.run { params.bottomMargin = this }
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, K> Pair<T?, K?>.runIfElementsNotNull(block: (T, K) -> Unit) {
|
||||||
|
val firstCopy = first
|
||||||
|
val secondCopy = second
|
||||||
|
if (firstCopy != null && secondCopy != null) {
|
||||||
|
block(firstCopy, secondCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun ImageView.toggleVisibilityIfHasContent(visibilityWhenFalse: Int = View.GONE) {
|
||||||
|
visibility = if (drawable != null) View.VISIBLE else visibilityWhenFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun TextView.toggleVisibilityIfHasContent(visibilityWhenFalse: Int = View.GONE) {
|
||||||
|
visibility = if (!text.isNullOrEmpty()) View.VISIBLE else visibilityWhenFalse
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package com.meloda.fast.extensions
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.Priority
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.Transformation
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.*
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
|
||||||
|
object ImageLoader {
|
||||||
|
|
||||||
|
val userAvatarTransformations = listOf(
|
||||||
|
TypeTransformations.CircleCrop
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ImageView.clear() {
|
||||||
|
this.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ImageView.loadWithGlide(
|
||||||
|
url: String? = null,
|
||||||
|
uri: Uri? = null,
|
||||||
|
drawableRes: Int? = null,
|
||||||
|
drawable: Drawable? = null,
|
||||||
|
placeholderDrawable: Drawable = ColorDrawable(Color.TRANSPARENT),
|
||||||
|
errorDrawable: Drawable = placeholderDrawable,
|
||||||
|
crossFade: Boolean = false,
|
||||||
|
crossFadeDuration: Int = 200,
|
||||||
|
transformations: List<TypeTransformations> = emptyList(),
|
||||||
|
onLoadedAction: (() -> Unit)? = null,
|
||||||
|
onFailedAction: (() -> Unit)? = null,
|
||||||
|
priority: Priority = Priority.NORMAL,
|
||||||
|
cacheStrategy: DiskCacheStrategy = DiskCacheStrategy.ALL
|
||||||
|
) {
|
||||||
|
val request = Glide.with(this)
|
||||||
|
|
||||||
|
var builder = when {
|
||||||
|
url != null -> request.load(url)
|
||||||
|
uri != null -> request.load(uri)
|
||||||
|
drawableRes != null -> request.load(drawableRes)
|
||||||
|
drawable != null -> request.load(drawable)
|
||||||
|
else -> request.load(null as Drawable?)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder = builder
|
||||||
|
.apply(TypeTransformations.createRequestOptions(transformations))
|
||||||
|
.error(errorDrawable)
|
||||||
|
.placeholder(placeholderDrawable)
|
||||||
|
.addListener(ImageLoadRequestListener(onLoadedAction, onFailedAction))
|
||||||
|
.diskCacheStrategy(cacheStrategy)
|
||||||
|
.priority(priority)
|
||||||
|
|
||||||
|
if (crossFade) {
|
||||||
|
builder = builder.transition(withCrossFade(crossFadeDuration))
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.into(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageLoadRequestListener(
|
||||||
|
private val onLoadedAction: (() -> Unit)?,
|
||||||
|
private val onFailedAction: (() -> Unit)?
|
||||||
|
) : RequestListener<Drawable> {
|
||||||
|
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
onFailedAction?.invoke()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Drawable?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
onLoadedAction?.invoke()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TypeTransformations {
|
||||||
|
|
||||||
|
object CenterCrop : TypeTransformations()
|
||||||
|
|
||||||
|
object CenterInside : TypeTransformations()
|
||||||
|
|
||||||
|
object CircleCrop : TypeTransformations()
|
||||||
|
|
||||||
|
class RoundedCornerCrop(val radius: Int) : TypeTransformations()
|
||||||
|
|
||||||
|
class GranularRoundedCornerCrop(
|
||||||
|
val topLeft: Float,
|
||||||
|
val topRight: Float,
|
||||||
|
val bottomRight: Float,
|
||||||
|
val bottomLeft: Float
|
||||||
|
) : TypeTransformations()
|
||||||
|
|
||||||
|
fun toGlideTransform(): Transformation<Bitmap> = when (this) {
|
||||||
|
CenterCrop -> CenterCrop()
|
||||||
|
CenterInside -> CenterInside()
|
||||||
|
is RoundedCornerCrop -> RoundedCorners(radius)
|
||||||
|
is GranularRoundedCornerCrop -> GranularRoundedCorners(
|
||||||
|
topLeft,
|
||||||
|
topRight,
|
||||||
|
bottomRight,
|
||||||
|
bottomLeft
|
||||||
|
)
|
||||||
|
CircleCrop -> CircleCrop()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun createRequestOptions(transformations: List<TypeTransformations>): RequestOptions {
|
||||||
|
val mappedTransformations = transformations
|
||||||
|
.map(TypeTransformations::toGlideTransform)
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
return RequestOptions().transform(* mappedTransformations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
package com.meloda.fast.extensions
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.collection.SparseArrayCompat
|
|
||||||
import androidx.collection.forEach
|
|
||||||
import androidx.collection.set
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
import com.meloda.fast.R
|
|
||||||
import com.meloda.fast.api.UserConfig
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages the various graphs needed for a [BottomNavigationView].
|
|
||||||
*
|
|
||||||
* This sample is a workaround until the Navigation Component supports multiple back stacks.
|
|
||||||
*/
|
|
||||||
object NavigationExtensions {
|
|
||||||
|
|
||||||
fun BottomNavigationView.setupWithNavController(
|
|
||||||
navGraphIds: List<Int>,
|
|
||||||
fragmentManager: FragmentManager,
|
|
||||||
containerId: Int,
|
|
||||||
intent: Intent
|
|
||||||
): LiveData<NavController> {
|
|
||||||
|
|
||||||
// Map of tags
|
|
||||||
val graphIdToTagMap = SparseArrayCompat<String>()
|
|
||||||
// Result. Mutable live data with the selected controlled
|
|
||||||
val selectedNavController = MutableLiveData<NavController>()
|
|
||||||
|
|
||||||
var firstFragmentGraphId = 0
|
|
||||||
|
|
||||||
// First create a NavHostFragment for each NavGraph ID
|
|
||||||
navGraphIds.forEachIndexed { index, navGraphId ->
|
|
||||||
val fragmentTag = getFragmentTag(index)
|
|
||||||
|
|
||||||
// Find or create the Navigation host fragment
|
|
||||||
val navHostFragment = obtainNavHostFragment(
|
|
||||||
fragmentManager,
|
|
||||||
fragmentTag,
|
|
||||||
navGraphId,
|
|
||||||
containerId
|
|
||||||
)
|
|
||||||
|
|
||||||
// Obtain its id
|
|
||||||
val graphId = navHostFragment.navController.graph.id
|
|
||||||
|
|
||||||
if (index == 0) {
|
|
||||||
firstFragmentGraphId = graphId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to the map
|
|
||||||
graphIdToTagMap[graphId] = fragmentTag
|
|
||||||
|
|
||||||
// Attach or detach nav host fragment depending on whether it's the selected item.
|
|
||||||
if (this.selectedItemId == graphId) {
|
|
||||||
// Update livedata with the selected graph
|
|
||||||
selectedNavController.value = navHostFragment.navController
|
|
||||||
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
|
|
||||||
} else {
|
|
||||||
detachNavHostFragment(fragmentManager, navHostFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now connect selecting an item with swapping Fragments
|
|
||||||
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
|
|
||||||
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId] ?: ""
|
|
||||||
var isOnFirstFragment = selectedItemTag == firstFragmentTag
|
|
||||||
|
|
||||||
setOnItemSelectedListener { item ->
|
|
||||||
// Don't do anything if the state is state has already been saved.
|
|
||||||
if (fragmentManager.isStateSaved) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
val navController =
|
|
||||||
(fragmentManager.findFragmentByTag(selectedItemTag) as NavHostFragment).navController
|
|
||||||
navController.popBackStack(navController.graph.startDestination, false)
|
|
||||||
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
|
|
||||||
val newlySelectedItemTag = //graphIdToTagMap[item.itemId]
|
|
||||||
if (!UserConfig.isLoggedIn()) graphIdToTagMap[R.id.login] else graphIdToTagMap[item.itemId]
|
|
||||||
|
|
||||||
fragmentManager.popBackStack(
|
|
||||||
firstFragmentTag,
|
|
||||||
FragmentManager.POP_BACK_STACK_INCLUSIVE
|
|
||||||
)
|
|
||||||
val selectedFragment =
|
|
||||||
fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
|
|
||||||
|
|
||||||
// Exclude the first fragment tag because it's always in the back stack.
|
|
||||||
if (firstFragmentTag != newlySelectedItemTag) {
|
|
||||||
// Commit a transaction that cleans the back stack and adds the first fragment
|
|
||||||
// to it, creating the fixed started destination.
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.setCustomAnimations(
|
|
||||||
R.anim.nav_default_enter_anim,
|
|
||||||
R.anim.nav_default_exit_anim,
|
|
||||||
R.anim.nav_default_pop_enter_anim,
|
|
||||||
R.anim.nav_default_pop_exit_anim
|
|
||||||
)
|
|
||||||
.attach(selectedFragment)
|
|
||||||
.setPrimaryNavigationFragment(selectedFragment)
|
|
||||||
.apply {
|
|
||||||
// Detach all other Fragments
|
|
||||||
graphIdToTagMap.forEach { _, fragmentTagIter ->
|
|
||||||
if (fragmentTagIter != newlySelectedItemTag) {
|
|
||||||
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.addToBackStack(firstFragmentTag)
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
selectedItemTag = newlySelectedItemTag
|
|
||||||
isOnFirstFragment = selectedItemTag == firstFragmentTag
|
|
||||||
selectedNavController.value = selectedFragment.navController
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnItemReselectedListener { item ->
|
|
||||||
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
|
|
||||||
val selectedFragment =
|
|
||||||
fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
|
|
||||||
val navController = selectedFragment.navController
|
|
||||||
// Pop the back stack to the start destination of the current navController graph
|
|
||||||
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.setCustomAnimations(
|
|
||||||
R.anim.nav_default_enter_anim,
|
|
||||||
R.anim.nav_default_exit_anim,
|
|
||||||
R.anim.nav_default_pop_enter_anim,
|
|
||||||
R.anim.nav_default_pop_exit_anim
|
|
||||||
)
|
|
||||||
.attach(selectedFragment)
|
|
||||||
.setPrimaryNavigationFragment(selectedFragment)
|
|
||||||
.apply {
|
|
||||||
// Detach all other Fragments
|
|
||||||
graphIdToTagMap.forEach { _, fragmentTagIter ->
|
|
||||||
if (fragmentTagIter != newlySelectedItemTag) {
|
|
||||||
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.addToBackStack(firstFragmentTag)
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.commit()
|
|
||||||
selectedItemTag = newlySelectedItemTag
|
|
||||||
isOnFirstFragment = selectedItemTag == firstFragmentTag
|
|
||||||
selectedNavController.value = selectedFragment.navController
|
|
||||||
} else navController.popBackStack(navController.graph.startDestination, false)
|
|
||||||
}
|
|
||||||
// Optional: on item reselected, pop back stack to the destination of the graph
|
|
||||||
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
|
|
||||||
|
|
||||||
// Finally, ensure that we update our BottomNavigationView when the back stack changes
|
|
||||||
fragmentManager.addOnBackStackChangedListener {
|
|
||||||
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
|
|
||||||
this.selectedItemId = firstFragmentGraphId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the graph if the currentDestination is not valid (happens when the back
|
|
||||||
// stack is popped after using the back button).
|
|
||||||
selectedNavController.value?.let { controller ->
|
|
||||||
if (controller.currentDestination == null) {
|
|
||||||
controller.navigate(controller.graph.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selectedNavController
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BottomNavigationView.setupDeepLinks(
|
|
||||||
navGraphIds: List<Int>,
|
|
||||||
fragmentManager: FragmentManager,
|
|
||||||
containerId: Int,
|
|
||||||
intent: Intent
|
|
||||||
) {
|
|
||||||
navGraphIds.forEachIndexed { index, navGraphId ->
|
|
||||||
val fragmentTag = getFragmentTag(index)
|
|
||||||
|
|
||||||
// Find or create the Navigation host fragment
|
|
||||||
val navHostFragment = obtainNavHostFragment(
|
|
||||||
fragmentManager,
|
|
||||||
fragmentTag,
|
|
||||||
navGraphId,
|
|
||||||
containerId
|
|
||||||
)
|
|
||||||
// Handle Intent
|
|
||||||
if (navHostFragment.navController.handleDeepLink(intent) &&
|
|
||||||
selectedItemId != navHostFragment.navController.graph.id
|
|
||||||
) {
|
|
||||||
this.selectedItemId = navHostFragment.navController.graph.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun detachNavHostFragment(
|
|
||||||
fragmentManager: FragmentManager,
|
|
||||||
navHostFragment: NavHostFragment
|
|
||||||
) {
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.detach(navHostFragment)
|
|
||||||
.commitNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attachNavHostFragment(
|
|
||||||
fragmentManager: FragmentManager,
|
|
||||||
navHostFragment: NavHostFragment,
|
|
||||||
isPrimaryNavFragment: Boolean
|
|
||||||
) {
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.attach(navHostFragment)
|
|
||||||
.apply {
|
|
||||||
if (isPrimaryNavFragment) {
|
|
||||||
setPrimaryNavigationFragment(navHostFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.commitNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun obtainNavHostFragment(
|
|
||||||
fragmentManager: FragmentManager,
|
|
||||||
fragmentTag: String,
|
|
||||||
navGraphId: Int,
|
|
||||||
containerId: Int,
|
|
||||||
): NavHostFragment {
|
|
||||||
// If the Nav Host fragment exists, return it
|
|
||||||
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
|
|
||||||
existingFragment?.let { return it }
|
|
||||||
|
|
||||||
// Otherwise, create it and return it.
|
|
||||||
val navHostFragment = NavHostFragment.create(navGraphId)
|
|
||||||
fragmentManager.beginTransaction()
|
|
||||||
.add(containerId, navHostFragment, fragmentTag)
|
|
||||||
.commitNow()
|
|
||||||
return navHostFragment
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
|
|
||||||
val backStackCount = backStackEntryCount
|
|
||||||
for (index in 0 until backStackCount) {
|
|
||||||
if (getBackStackEntryAt(index).name == backStackName) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val FragmentManager.visibleFragments
|
|
||||||
get(): List<Fragment> {
|
|
||||||
val visibleFragments = arrayListOf<Fragment>()
|
|
||||||
fragments.forEach { if (it.isVisible) visibleFragments.add(it) }
|
|
||||||
return visibleFragments
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.meloda.fast.extensions
|
|
||||||
|
|
||||||
import android.widget.TextView
|
|
||||||
|
|
||||||
object TextViewExtensions {
|
|
||||||
|
|
||||||
fun TextView.clear() {
|
|
||||||
text = null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.meloda.fast.model
|
||||||
|
|
||||||
|
sealed class DataItem<IdType> {
|
||||||
|
abstract val dataItemId: IdType
|
||||||
|
|
||||||
|
object Header : DataItem<Int>() {
|
||||||
|
override val dataItemId = Int.MIN_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
object Footer : DataItem<Int>() {
|
||||||
|
override val dataItemId = Int.MIN_VALUE + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.meloda.fast.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
open class SelectableItem constructor(
|
||||||
|
@Ignore
|
||||||
|
val selectableItemId: Int = 0
|
||||||
|
) : DataItem<Int>(), Parcelable {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
var isSelected: Boolean = false
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val dataItemId = selectableItemId
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,10 +8,10 @@ import android.text.TextUtils
|
|||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.util.ObjectsCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import coil.load
|
|
||||||
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.VKConstants
|
||||||
@@ -22,26 +22,35 @@ import com.meloda.fast.api.model.VkUser
|
|||||||
import com.meloda.fast.base.adapter.BaseAdapter
|
import com.meloda.fast.base.adapter.BaseAdapter
|
||||||
import com.meloda.fast.base.adapter.BindingHolder
|
import com.meloda.fast.base.adapter.BindingHolder
|
||||||
import com.meloda.fast.databinding.ItemConversationBinding
|
import com.meloda.fast.databinding.ItemConversationBinding
|
||||||
|
import com.meloda.fast.extensions.ImageLoader
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.clear
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.loadWithGlide
|
||||||
|
import com.meloda.fast.extensions.gone
|
||||||
|
import com.meloda.fast.extensions.toggleVisibility
|
||||||
|
import com.meloda.fast.extensions.visible
|
||||||
import com.meloda.fast.util.TimeUtils
|
import com.meloda.fast.util.TimeUtils
|
||||||
|
|
||||||
class ConversationsAdapter constructor(
|
class ConversationsAdapter constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
values: MutableList<VkConversation>,
|
private val resourceManager: ConversationsResourceManager,
|
||||||
|
var isMultilineEnabled: Boolean = true,
|
||||||
val profiles: HashMap<Int, VkUser> = hashMapOf(),
|
val profiles: HashMap<Int, VkUser> = hashMapOf(),
|
||||||
val groups: HashMap<Int, VkGroup> = hashMapOf(),
|
val groups: HashMap<Int, VkGroup> = hashMapOf(),
|
||||||
var isMultilineEnabled: Boolean = true
|
) : BaseAdapter<VkConversation, ConversationsAdapter.ItemHolder>(context, Comparator) {
|
||||||
) : BaseAdapter<VkConversation, ConversationsAdapter.ItemHolder>(
|
|
||||||
context, values, COMPARATOR
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
var pinnedCount = 0
|
||||||
ItemHolder(ItemConversationBinding.inflate(inflater, parent, false))
|
|
||||||
|
|
||||||
inner class ItemHolder(binding: ItemConversationBinding) :
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
|
||||||
BindingHolder<ItemConversationBinding>(binding) {
|
return ItemHolder(
|
||||||
|
ItemConversationBinding.inflate(inflater, parent, false),
|
||||||
|
resourceManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val dateColor = ContextCompat.getColor(context, R.color.n2_500)
|
inner class ItemHolder(
|
||||||
private val youPrefix = context.getString(R.string.you_message_prefix)
|
binding: ItemConversationBinding,
|
||||||
|
private val resourceManager: ConversationsResourceManager
|
||||||
|
) : BindingHolder<ItemConversationBinding>(binding) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.title.ellipsize = TextUtils.TruncateAt.END
|
binding.title.ellipsize = TextUtils.TruncateAt.END
|
||||||
@@ -69,7 +78,7 @@ class ConversationsAdapter constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val span = SpannableString(text)
|
val span = SpannableString(text)
|
||||||
span.setSpan(ForegroundColorSpan(dateColor), 0, text.length, 0)
|
span.setSpan(ForegroundColorSpan(resourceManager.colorOutline), 0, text.length, 0)
|
||||||
|
|
||||||
binding.message.text = span
|
binding.message.text = span
|
||||||
return
|
return
|
||||||
@@ -87,51 +96,46 @@ class ConversationsAdapter constructor(
|
|||||||
conversationGroup = conversationGroup
|
conversationGroup = conversationGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.avatar.isVisible = avatar != null
|
binding.avatar.toggleVisibility(avatar != null)
|
||||||
|
|
||||||
if (avatar == null) {
|
if (avatar == null) {
|
||||||
binding.avatarPlaceholder.isVisible = true
|
binding.avatarPlaceholder.visible()
|
||||||
|
|
||||||
if (conversation.ownerId == VKConstants.FAST_GROUP_ID) {
|
if (conversation.ownerId == VKConstants.FAST_GROUP_ID) {
|
||||||
binding.placeholderBack.setImageDrawable(
|
binding.placeholderBack.loadWithGlide(
|
||||||
ColorDrawable(
|
drawable = ColorDrawable(resourceManager.icLauncherColor),
|
||||||
ContextCompat.getColor(context, R.color.a1_400)
|
transformations = ImageLoader.userAvatarTransformations
|
||||||
)
|
|
||||||
)
|
)
|
||||||
binding.placeholder.imageTintList =
|
binding.placeholder.imageTintList =
|
||||||
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.a1_0))
|
ColorStateList.valueOf(resourceManager.colorOnPrimary)
|
||||||
binding.placeholder.setImageResource(R.drawable.ic_fast_logo)
|
binding.placeholder.setImageResource(R.drawable.ic_fast_logo)
|
||||||
binding.placeholder.setPadding(18)
|
binding.placeholder.setPadding(18)
|
||||||
} else {
|
} else {
|
||||||
binding.placeholderBack.setImageDrawable(
|
binding.placeholderBack.loadWithGlide(
|
||||||
ColorDrawable(
|
drawable = ColorDrawable(resourceManager.colorOnUserAvatarAction),
|
||||||
ContextCompat.getColor(context, R.color.n1_50)
|
transformations = ImageLoader.userAvatarTransformations
|
||||||
)
|
|
||||||
)
|
)
|
||||||
binding.placeholder.imageTintList =
|
binding.placeholder.imageTintList =
|
||||||
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.n2_500))
|
ColorStateList.valueOf(resourceManager.colorUserAvatarAction)
|
||||||
binding.placeholder.setImageResource(R.drawable.ic_account_circle_cut)
|
binding.placeholder.setImageResource(R.drawable.ic_account_circle_cut)
|
||||||
binding.placeholder.setPadding(0)
|
binding.placeholder.setPadding(0)
|
||||||
binding.avatar.setImageDrawable(null)
|
binding.avatar.clear()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.avatar.load(avatar) {
|
binding.avatar.loadWithGlide(
|
||||||
crossfade(200)
|
url = avatar,
|
||||||
target {
|
crossFade = true,
|
||||||
binding.avatarPlaceholder.isVisible = false
|
onLoadedAction = { binding.avatarPlaceholder.gone() }
|
||||||
binding.avatar.setImageDrawable(it)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.online.isVisible = conversationUser?.online == true
|
binding.online.toggleVisibility(conversationUser?.online == true)
|
||||||
|
binding.pin.toggleVisibility(conversation.isPinned)
|
||||||
binding.pin.isVisible = conversation.isPinned
|
|
||||||
|
|
||||||
val actionMessage = VkUtils.getActionConversationText(
|
val actionMessage = VkUtils.getActionConversationText(
|
||||||
context = context,
|
context = context,
|
||||||
message = message,
|
message = message,
|
||||||
youPrefix = youPrefix,
|
youPrefix = resourceManager.youPrefix,
|
||||||
profiles = profiles,
|
profiles = profiles,
|
||||||
groups = groups,
|
groups = groups,
|
||||||
messageUser = messageUser,
|
messageUser = messageUser,
|
||||||
@@ -150,7 +154,7 @@ class ConversationsAdapter constructor(
|
|||||||
message = message
|
message = message
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.textAttachment.isVisible = attachmentIcon != null
|
binding.textAttachment.toggleVisibility(attachmentIcon != null)
|
||||||
binding.textAttachment.setImageDrawable(attachmentIcon)
|
binding.textAttachment.setImageDrawable(attachmentIcon)
|
||||||
|
|
||||||
val attachmentText = if (attachmentIcon == null) VkUtils.getAttachmentText(
|
val attachmentText = if (attachmentIcon == null) VkUtils.getAttachmentText(
|
||||||
@@ -174,7 +178,7 @@ class ConversationsAdapter constructor(
|
|||||||
|
|
||||||
var prefix = when {
|
var prefix = when {
|
||||||
actionMessage != null -> ""
|
actionMessage != null -> ""
|
||||||
message.isOut -> "$youPrefix: "
|
message.isOut -> "${resourceManager.youPrefix}: "
|
||||||
else -> {
|
else -> {
|
||||||
if (message.isUser() && messageUser != null && messageUser.firstName.isNotBlank()) "${messageUser.firstName}: "
|
if (message.isUser() && messageUser != null && messageUser.firstName.isNotBlank()) "${messageUser.firstName}: "
|
||||||
else if (message.isGroup() && messageGroup != null && messageGroup.name.isNotBlank()) "${messageGroup.name}: "
|
else if (message.isGroup() && messageGroup != null && messageGroup.name.isNotBlank()) "${messageGroup.name}: "
|
||||||
@@ -190,7 +194,7 @@ class ConversationsAdapter constructor(
|
|||||||
|
|
||||||
val spanMessage = SpannableString(spanText)
|
val spanMessage = SpannableString(spanText)
|
||||||
spanMessage.setSpan(
|
spanMessage.setSpan(
|
||||||
ForegroundColorSpan(dateColor), 0,
|
ForegroundColorSpan(resourceManager.colorOutline), 0,
|
||||||
prefix.length + coloredMessage.length,
|
prefix.length + coloredMessage.length,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
@@ -208,6 +212,15 @@ class ConversationsAdapter constructor(
|
|||||||
R.drawable.ic_message_unread
|
R.drawable.ic_message_unread
|
||||||
) else null
|
) else null
|
||||||
|
|
||||||
|
binding.onlineBorder.setImageDrawable(
|
||||||
|
ColorDrawable(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
if (conversation.isUnread()) R.color.colorBackgroundVariant
|
||||||
|
else R.color.colorBackground
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
binding.counter.isVisible = conversation.isInUnread()
|
binding.counter.isVisible = conversation.isInUnread()
|
||||||
if (conversation.isInUnread()) {
|
if (conversation.isInUnread()) {
|
||||||
@@ -222,10 +235,10 @@ class ConversationsAdapter constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeConversation(conversationId: Int): Int? {
|
fun removeConversation(conversationId: Int): Int? {
|
||||||
for (i in values.indices) {
|
for (i in indices) {
|
||||||
val conversation = values[i]
|
val conversation = getItem(i)
|
||||||
if (conversation.id == conversationId) {
|
if (conversation.id == conversationId) {
|
||||||
values.removeAt(i)
|
removeAt(i)
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,17 +246,29 @@ class ConversationsAdapter constructor(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun searchConversationIndex(conversationId: Int): Int? {
|
||||||
|
for (i in indices) {
|
||||||
|
val conversation = getItem(i)
|
||||||
|
|
||||||
|
if (conversation.id == conversationId) return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val COMPARATOR = object : DiffUtil.ItemCallback<VkConversation>() {
|
private val Comparator = object : DiffUtil.ItemCallback<VkConversation>() {
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
oldItem: VkConversation,
|
oldItem: VkConversation,
|
||||||
newItem: VkConversation
|
newItem: VkConversation
|
||||||
) = false
|
): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: VkConversation,
|
oldItem: VkConversation,
|
||||||
newItem: VkConversation
|
newItem: VkConversation
|
||||||
) = false
|
) = ObjectsCompat.equals(oldItem, newItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-77
@@ -1,23 +1,16 @@
|
|||||||
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.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.appcompat.widget.PopupMenu
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
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 coil.load
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
|
||||||
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.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
|
||||||
@@ -28,14 +21,16 @@ 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
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.loadWithGlide
|
||||||
|
import com.meloda.fast.extensions.gone
|
||||||
|
import com.meloda.fast.extensions.toggleVisibility
|
||||||
|
import com.meloda.fast.screens.messages.MessagesHistoryFragment
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collect
|
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.roundToInt
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ConversationsFragment :
|
class ConversationsFragment :
|
||||||
@@ -47,9 +42,7 @@ class ConversationsFragment :
|
|||||||
private val adapter: ConversationsAdapter by lazy {
|
private val adapter: ConversationsAdapter by lazy {
|
||||||
ConversationsAdapter(
|
ConversationsAdapter(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
mutableListOf(),
|
ConversationsResourceManager(requireContext())
|
||||||
hashMapOf(),
|
|
||||||
hashMapOf()
|
|
||||||
).also {
|
).also {
|
||||||
it.itemClickListener = this::onItemClick
|
it.itemClickListener = this::onItemClick
|
||||||
it.itemLongClickListener = this::onItemLongClick
|
it.itemLongClickListener = this::onItemLongClick
|
||||||
@@ -74,13 +67,6 @@ class ConversationsFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isPaused = false
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
isPaused = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
prepareViews()
|
prepareViews()
|
||||||
@@ -90,41 +76,16 @@ class ConversationsFragment :
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
requireContext().dataStore.data.map {
|
requireContext().dataStore.data.map {
|
||||||
adapter.isMultilineEnabled = it[AppSettings.keyIsMultilineEnabled] ?: true
|
adapter.isMultilineEnabled = it[AppSettings.keyIsMultilineEnabled] ?: true
|
||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
adapter.refreshList()
|
||||||
}.collect()
|
}.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.createChat.setOnClickListener {}
|
binding.createChat.setOnClickListener {}
|
||||||
|
|
||||||
UserConfig.vkUser.observe(viewLifecycleOwner) {
|
UserConfig.vkUser.observe(viewLifecycleOwner) { user ->
|
||||||
it?.let { user -> binding.avatar.load(user.photo200) { crossfade(100) } }
|
user?.run { binding.avatar.loadWithGlide(url = this.photo200, crossFade = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
|
||||||
if (isPaused) return@OnOffsetChangedListener
|
|
||||||
|
|
||||||
binding.appBar.animate().translationZ(
|
|
||||||
if (verticalOffset < 0) AndroidUtils.px(3).roundToInt().toFloat()
|
|
||||||
else 0f
|
|
||||||
).setDuration(50).start()
|
|
||||||
|
|
||||||
val padding = AndroidUtils.px(if (verticalOffset <= -100) 10 else 30).roundToInt()
|
|
||||||
|
|
||||||
binding.avatarContainer.updatePadding(
|
|
||||||
bottom = padding,
|
|
||||||
right = padding
|
|
||||||
)
|
|
||||||
|
|
||||||
val minusAlpha = (1 - (abs(verticalOffset) * 0.01)).toFloat()
|
|
||||||
val plusAlpha = (abs(1 + verticalOffset * 0.01) * 1.01).toFloat()
|
|
||||||
|
|
||||||
println("Fast::ConversationsFragment::onOffset offset: $verticalOffset; minusAlpha: $minusAlpha; plusAlpha: $plusAlpha")
|
|
||||||
|
|
||||||
val alpha: Float = if (verticalOffset <= -100) plusAlpha else minusAlpha
|
|
||||||
|
|
||||||
binding.avatarContainer.alpha = alpha
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.avatar.setOnClickListener { avatarPopupMenu.show() }
|
binding.avatar.setOnClickListener { avatarPopupMenu.show() }
|
||||||
|
|
||||||
binding.avatar.setOnLongClickListener {
|
binding.avatar.setOnLongClickListener {
|
||||||
@@ -134,23 +95,18 @@ class ConversationsFragment :
|
|||||||
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
settings[AppSettings.keyIsMultilineEnabled] = !isMultilineEnabled
|
||||||
|
|
||||||
adapter.isMultilineEnabled = !isMultilineEnabled
|
adapter.isMultilineEnabled = !isMultilineEnabled
|
||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
adapter.refreshList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPaused) {
|
|
||||||
isPaused = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.loadProfileUser()
|
viewModel.loadProfileUser()
|
||||||
viewModel.loadConversations()
|
viewModel.loadConversations()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLogOutDialog() {
|
private fun showLogOutDialog() {
|
||||||
val isEasterEgg = UserConfig.userId == UserConfig.userId
|
val isEasterEgg = UserConfig.userId == 37610580
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(
|
.setTitle(
|
||||||
@@ -166,13 +122,7 @@ class ConversationsFragment :
|
|||||||
UserConfig.clear()
|
UserConfig.clear()
|
||||||
AppGlobal.appDatabase.clearAllTables()
|
AppGlobal.appDatabase.clearAllTables()
|
||||||
|
|
||||||
requireActivity().finishAffinity()
|
viewModel.openRootScreen()
|
||||||
requireActivity().startActivity(
|
|
||||||
Intent(
|
|
||||||
requireContext(),
|
|
||||||
MainActivity::class.java
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.no, null)
|
.setNegativeButton(R.string.no, null)
|
||||||
@@ -185,21 +135,31 @@ class ConversationsFragment :
|
|||||||
is StartProgressEvent -> onProgressStarted()
|
is StartProgressEvent -> onProgressStarted()
|
||||||
is StopProgressEvent -> onProgressStopped()
|
is StopProgressEvent -> onProgressStopped()
|
||||||
|
|
||||||
is ConversationsLoaded -> refreshConversations(event)
|
is ConversationsLoadedEvent -> refreshConversations(event)
|
||||||
is ConversationsDelete -> deleteConversation(event.peerId)
|
is ConversationsDeleteEvent -> deleteConversation(event.peerId)
|
||||||
|
|
||||||
// TODO: 10-Oct-21 remove this and sort conversations list
|
// TODO: 10-Oct-21 remove this and sort conversations list
|
||||||
is ConversationsPin, is ConversationsUnpin -> viewModel.loadConversations()
|
is ConversationsPinEvent -> {
|
||||||
|
adapter.pinnedCount++
|
||||||
|
viewModel.loadConversations()
|
||||||
|
}
|
||||||
|
is ConversationsUnpinEvent -> {
|
||||||
|
adapter.pinnedCount--
|
||||||
|
viewModel.loadConversations()
|
||||||
|
}
|
||||||
|
|
||||||
|
is MessagesNewEvent -> onMessageNew(event)
|
||||||
|
is MessagesEditEvent -> onMessageEdit(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onProgressStarted() {
|
private fun onProgressStarted() {
|
||||||
binding.progressBar.isVisible = adapter.isEmpty()
|
binding.progressBar.toggleVisibility(adapter.isEmpty())
|
||||||
binding.refreshLayout.isRefreshing = adapter.isNotEmpty()
|
binding.refreshLayout.isRefreshing = adapter.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onProgressStopped() {
|
private fun onProgressStopped() {
|
||||||
binding.progressBar.isVisible = false
|
binding.progressBar.gone()
|
||||||
binding.refreshLayout.isRefreshing = false
|
binding.refreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,16 +193,17 @@ class ConversationsFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshConversations(event: ConversationsLoaded) {
|
private fun refreshConversations(event: ConversationsLoadedEvent) {
|
||||||
adapter.profiles += event.profiles
|
adapter.profiles += event.profiles
|
||||||
adapter.groups += event.groups
|
adapter.groups += event.groups
|
||||||
|
|
||||||
|
val pinnedConversations = event.conversations.filter { it.isPinned }
|
||||||
|
adapter.pinnedCount = pinnedConversations.count()
|
||||||
|
|
||||||
fillRecyclerView(event.conversations)
|
fillRecyclerView(event.conversations)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillRecyclerView(values: List<VkConversation>) {
|
private fun fillRecyclerView(values: List<VkConversation>) {
|
||||||
adapter.values.clear()
|
|
||||||
adapter.values += values
|
|
||||||
adapter.submitList(values)
|
adapter.submitList(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,12 +218,11 @@ class ConversationsFragment :
|
|||||||
if (conversation.isGroup()) adapter.groups[conversation.id]
|
if (conversation.isGroup()) adapter.groups[conversation.id]
|
||||||
else null
|
else null
|
||||||
|
|
||||||
findNavController().navigate(
|
viewModel.openMessagesHistoryScreen(
|
||||||
R.id.toMessagesHistory,
|
|
||||||
bundleOf(
|
bundleOf(
|
||||||
"conversation" to adapter[position],
|
MessagesHistoryFragment.ARG_USER to user,
|
||||||
"user" to user,
|
MessagesHistoryFragment.ARG_GROUP to group,
|
||||||
"group" to group
|
MessagesHistoryFragment.ARG_CONVERSATION to conversation
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -277,7 +237,7 @@ class ConversationsFragment :
|
|||||||
|
|
||||||
var canPinOneMoreDialog = true
|
var canPinOneMoreDialog = true
|
||||||
if (adapter.itemCount > 4) {
|
if (adapter.itemCount > 4) {
|
||||||
val firstFiveDialogs = adapter.values.subList(0, 5)
|
val firstFiveDialogs = adapter.currentList.subList(0, 5)
|
||||||
var pinnedCount = 0
|
var pinnedCount = 0
|
||||||
|
|
||||||
firstFiveDialogs.forEach { if (it.isPinned) pinnedCount++ }
|
firstFiveDialogs.forEach { if (it.isPinned) pinnedCount++ }
|
||||||
@@ -321,8 +281,7 @@ class ConversationsFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteConversation(conversationId: Int) {
|
private fun deleteConversation(conversationId: Int) {
|
||||||
val index = adapter.removeConversation(conversationId) ?: return
|
adapter.removeConversation(conversationId)
|
||||||
adapter.notifyItemRemoved(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showPinConversationDialog(conversation: VkConversation) {
|
private fun showPinConversationDialog(conversation: VkConversation) {
|
||||||
@@ -345,4 +304,45 @@ class ConversationsFragment :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onMessageNew(event: MessagesNewEvent) {
|
||||||
|
adapter.profiles += event.profiles
|
||||||
|
adapter.groups += event.groups
|
||||||
|
|
||||||
|
val message = event.message
|
||||||
|
|
||||||
|
val conversationIndex = adapter.searchConversationIndex(message.peerId)
|
||||||
|
if (conversationIndex == null) { // диалога нет в списке
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val conversation = adapter[conversationIndex]
|
||||||
|
conversation.run {
|
||||||
|
lastMessage = message
|
||||||
|
lastMessageId = message.id
|
||||||
|
lastConversationMessageId = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation.isPinned) {
|
||||||
|
adapter[conversationIndex] = conversation
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.removeConversation(message.peerId) ?: return
|
||||||
|
val toPosition = adapter.pinnedCount
|
||||||
|
|
||||||
|
adapter.add(conversation, toPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMessageEdit(event: MessagesEditEvent) {
|
||||||
|
val message = event.message
|
||||||
|
|
||||||
|
val conversationIndex = adapter.searchConversationIndex(message.peerId)
|
||||||
|
if (conversationIndex == null) { // диалога нет в списке
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val conversation = adapter[conversationIndex]
|
||||||
|
conversation.lastMessage = message
|
||||||
|
adapter[conversationIndex] = conversation
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.meloda.fast.screens.conversations
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.base.ResourceManager
|
||||||
|
import com.meloda.fast.extensions.TypeTransformations
|
||||||
|
|
||||||
|
class ConversationsResourceManager(context: Context) : ResourceManager(context) {
|
||||||
|
|
||||||
|
val colorOutline = getColor(R.color.colorOutline)
|
||||||
|
val colorOnPrimary = getColor(R.color.colorOnPrimary)
|
||||||
|
val colorUserAvatarAction = getColor(R.color.colorUserAvatarAction)
|
||||||
|
val colorOnUserAvatarAction = getColor(R.color.colorOnUserAvatarAction)
|
||||||
|
|
||||||
|
val icLauncherColor = getColor(R.color.a1_500)
|
||||||
|
|
||||||
|
val youPrefix = getString(R.string.you_message_prefix)
|
||||||
|
|
||||||
|
}
|
||||||
+62
-10
@@ -1,28 +1,49 @@
|
|||||||
package com.meloda.fast.screens.conversations
|
package com.meloda.fast.screens.conversations
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.terrakok.cicerone.Router
|
||||||
|
import com.meloda.fast.api.LongPollEvent
|
||||||
|
import com.meloda.fast.api.LongPollUpdatesParser
|
||||||
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.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.VkUser
|
import com.meloda.fast.api.model.VkUser
|
||||||
import com.meloda.fast.api.network.conversations.*
|
import com.meloda.fast.api.network.conversations.*
|
||||||
import com.meloda.fast.api.network.users.UsersDataSource
|
import com.meloda.fast.api.network.users.UsersDataSource
|
||||||
import com.meloda.fast.api.network.users.UsersGetRequest
|
import com.meloda.fast.api.network.users.UsersGetRequest
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
import com.meloda.fast.base.viewmodel.VkEvent
|
import com.meloda.fast.base.viewmodel.VkEvent
|
||||||
|
import com.meloda.fast.common.Screens
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ConversationsViewModel @Inject constructor(
|
class ConversationsViewModel @Inject constructor(
|
||||||
private val conversations: ConversationsDataSource,
|
private val conversations: ConversationsDataSource,
|
||||||
private val users: UsersDataSource
|
private val users: UsersDataSource,
|
||||||
|
updatesParser: LongPollUpdatesParser,
|
||||||
|
private val router: Router
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ConversationsViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
updatesParser.onNewMessage {
|
||||||
|
viewModelScope.launch { handleNewMessage(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
updatesParser.onMessageEdited {
|
||||||
|
viewModelScope.launch { handleEditedMessage(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadConversations(
|
fun loadConversations(
|
||||||
offset: Int? = null
|
offset: Int? = null
|
||||||
) = viewModelScope.launch(Dispatchers.Default) {
|
) = viewModelScope.launch(Dispatchers.Default) {
|
||||||
@@ -49,7 +70,7 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(
|
sendEvent(
|
||||||
ConversationsLoaded(
|
ConversationsLoadedEvent(
|
||||||
count = response.count,
|
count = response.count,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
unreadCount = response.unreadCount ?: 0,
|
unreadCount = response.unreadCount ?: 0,
|
||||||
@@ -84,7 +105,7 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
conversations.delete(
|
conversations.delete(
|
||||||
ConversationsDeleteRequest(peerId)
|
ConversationsDeleteRequest(peerId)
|
||||||
)
|
)
|
||||||
}, onAnswer = { sendEvent(ConversationsDelete(peerId)) })
|
}, onAnswer = { sendEvent(ConversationsDeleteEvent(peerId)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pinConversation(
|
fun pinConversation(
|
||||||
@@ -94,18 +115,41 @@ class ConversationsViewModel @Inject constructor(
|
|||||||
if (pin) {
|
if (pin) {
|
||||||
makeJob(
|
makeJob(
|
||||||
{ conversations.pin(ConversationsPinRequest(peerId)) },
|
{ conversations.pin(ConversationsPinRequest(peerId)) },
|
||||||
onAnswer = { sendEvent(ConversationsPin(peerId)) }
|
onAnswer = { sendEvent(ConversationsPinEvent(peerId)) }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
makeJob(
|
makeJob(
|
||||||
{ conversations.unpin(ConversationsUnpinRequest(peerId)) },
|
{ conversations.unpin(ConversationsUnpinRequest(peerId)) },
|
||||||
onAnswer = { sendEvent(ConversationsUnpin(peerId)) }
|
onAnswer = { sendEvent(ConversationsUnpinEvent(peerId)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
|
||||||
|
sendEvent(
|
||||||
|
MessagesNewEvent(
|
||||||
|
message = event.message,
|
||||||
|
profiles = event.profiles,
|
||||||
|
groups = event.groups
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleEditedMessage(event: LongPollEvent.VkMessageEditEvent) {
|
||||||
|
sendEvent(MessagesEditEvent(event.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openRootScreen() {
|
||||||
|
router.exit()
|
||||||
|
router.newRootScreen(Screens.Main())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openMessagesHistoryScreen(bundle: Bundle) {
|
||||||
|
router.navigateTo(Screens.MessagesHistory(bundle))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ConversationsLoaded(
|
data class ConversationsLoadedEvent(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val offset: Int?,
|
val offset: Int?,
|
||||||
val unreadCount: Int?,
|
val unreadCount: Int?,
|
||||||
@@ -114,8 +158,16 @@ data class ConversationsLoaded(
|
|||||||
val groups: HashMap<Int, VkGroup>
|
val groups: HashMap<Int, VkGroup>
|
||||||
) : VkEvent()
|
) : VkEvent()
|
||||||
|
|
||||||
data class ConversationsDelete(val peerId: Int) : VkEvent()
|
data class ConversationsDeleteEvent(val peerId: Int) : VkEvent()
|
||||||
|
|
||||||
data class ConversationsPin(val peerId: Int) : VkEvent()
|
data class ConversationsPinEvent(val peerId: Int) : VkEvent()
|
||||||
|
|
||||||
data class ConversationsUnpin(val peerId: Int) : VkEvent()
|
data class ConversationsUnpinEvent(val peerId: Int) : VkEvent()
|
||||||
|
|
||||||
|
data class MessagesNewEvent(
|
||||||
|
val message: VkMessage,
|
||||||
|
val profiles: HashMap<Int, VkUser>,
|
||||||
|
val groups: HashMap<Int, VkGroup>
|
||||||
|
) : VkEvent()
|
||||||
|
|
||||||
|
data class MessagesEditEvent(val message: VkMessage) : VkEvent()
|
||||||
@@ -18,7 +18,6 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import coil.load
|
import coil.load
|
||||||
import coil.transform.RoundedCornersTransformation
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
@@ -35,9 +34,7 @@ import com.meloda.fast.databinding.FragmentLoginBinding
|
|||||||
import com.meloda.fast.util.KeyboardUtils
|
import com.meloda.fast.util.KeyboardUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
@@ -77,7 +74,7 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
is ErrorEvent -> showErrorSnackbar(event.errorText)
|
is ErrorEvent -> showErrorSnackbar(event.errorText)
|
||||||
is CaptchaEvent -> showCaptchaDialog(event.sid, event.image)
|
is CaptchaEvent -> showCaptchaDialog(event.sid, event.image)
|
||||||
is ValidationEvent -> showValidationRequired(event.sid)
|
is ValidationEvent -> showValidationRequired(event.sid)
|
||||||
is SuccessAuth -> goToMain(event)
|
is SuccessAuth -> launchWebView()
|
||||||
|
|
||||||
is CodeSent -> showValidationDialog()
|
is CodeSent -> showValidationDialog()
|
||||||
is StartProgressEvent -> onProgressStarted()
|
is StartProgressEvent -> onProgressStarted()
|
||||||
@@ -119,12 +116,8 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
parseAuthUrl(url)
|
parseAuthUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView, url: String) {
|
override fun onPageFinished(view: WebView, url: String?) {
|
||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
|
|
||||||
val a = Jsoup.parse(url)
|
|
||||||
|
|
||||||
val b = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,15 +130,23 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun launchWebView() {
|
private fun launchWebView() {
|
||||||
|
binding.webView.isVisible = true
|
||||||
binding.webView.loadUrl(
|
binding.webView.loadUrl(
|
||||||
"https://oauth.vk.com/authorize?client_id=${UserConfig.FAST_APP_ID}&" +
|
"https://oauth.vk.com/authorize?client_id=${UserConfig.FAST_APP_ID}&" +
|
||||||
"display=mobile&scope=136297695&" +
|
"access_token=${UserConfig.accessToken}&" +
|
||||||
|
"sdk_package=com.meloda.fast.activity&" +
|
||||||
|
"sdk_fingerprint=AA88DSADAS8DG8FSA8&" +
|
||||||
|
"display=page&" +
|
||||||
|
"revoke=1&" +
|
||||||
|
"scope=136297695&" +
|
||||||
"redirect_uri=${
|
"redirect_uri=${
|
||||||
URLEncoder.encode(
|
URLEncoder.encode(
|
||||||
"https://oauth.vk.com/blank.html",
|
"https://oauth.vk.com/blank.html",
|
||||||
Charsets.UTF_8.toString()
|
Charsets.UTF_8.toString()
|
||||||
)
|
)
|
||||||
}&response_type=token&v=${VKConstants.API_VERSION}"
|
}&" +
|
||||||
|
"response_type=token&" +
|
||||||
|
"v=${VKConstants.API_VERSION}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +168,8 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
val token = authData.first
|
val token = authData.first
|
||||||
|
|
||||||
UserConfig.fastToken = token
|
UserConfig.fastToken = token
|
||||||
|
|
||||||
|
viewModel.openPrimaryScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,9 +208,9 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
else TextInputLayout.END_ICON_NONE
|
else TextInputLayout.END_ICON_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.passwordInput.setOnEditorActionListener { _, _, event ->
|
binding.passwordInput.setOnEditorActionListener edit@{ _, _, event ->
|
||||||
if (event == null) return@setOnEditorActionListener false
|
if (event == null) return@edit false
|
||||||
return@setOnEditorActionListener if (event.action == EditorInfo.IME_ACTION_GO ||
|
return@edit if (event.action == EditorInfo.IME_ACTION_GO ||
|
||||||
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
|
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
|
||||||
) {
|
) {
|
||||||
KeyboardUtils.hideKeyboardFrom(binding.passwordInput)
|
KeyboardUtils.hideKeyboardFrom(binding.passwordInput)
|
||||||
@@ -237,7 +240,6 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
|
|
||||||
KeyboardUtils.hideKeyboardFrom(requireView().findFocus())
|
KeyboardUtils.hideKeyboardFrom(requireView().findFocus())
|
||||||
|
|
||||||
|
|
||||||
viewModel.login(
|
viewModel.login(
|
||||||
login = loginString,
|
login = loginString,
|
||||||
password = passwordString
|
password = passwordString
|
||||||
@@ -383,16 +385,4 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
|||||||
snackbar.animationMode = Snackbar.ANIMATION_MODE_FADE
|
snackbar.animationMode = Snackbar.ANIMATION_MODE_FADE
|
||||||
snackbar.show()
|
snackbar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goToMain(event: SuccessAuth) = lifecycleScope.launch {
|
|
||||||
UserConfig.userId = event.userId
|
|
||||||
UserConfig.accessToken = event.vkToken
|
|
||||||
|
|
||||||
if (event.haveAuthorized) delay(500)
|
|
||||||
|
|
||||||
launchWebView()
|
|
||||||
|
|
||||||
findNavController().navigate(R.id.toMain)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,30 @@
|
|||||||
package com.meloda.fast.screens.login
|
package com.meloda.fast.screens.login
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.terrakok.cicerone.Router
|
||||||
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.api.VKConstants
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.api.VKException
|
import com.meloda.fast.api.VKException
|
||||||
import com.meloda.fast.api.network.auth.AuthDataSource
|
import com.meloda.fast.api.network.auth.AuthDataSource
|
||||||
import com.meloda.fast.api.network.auth.RequestAuthDirect
|
import com.meloda.fast.api.network.auth.AuthDirectRequest
|
||||||
import com.meloda.fast.base.viewmodel.*
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
|
import com.meloda.fast.base.viewmodel.ErrorEvent
|
||||||
|
import com.meloda.fast.base.viewmodel.VkEvent
|
||||||
|
import com.meloda.fast.common.Screens
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LoginViewModel @Inject constructor(
|
class LoginViewModel @Inject constructor(
|
||||||
private val dataSource: AuthDataSource
|
private val dataSource: AuthDataSource,
|
||||||
|
private val router: Router
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "LoginViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
fun login(
|
fun login(
|
||||||
login: String,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -24,7 +34,7 @@ class LoginViewModel @Inject constructor(
|
|||||||
makeJob(
|
makeJob(
|
||||||
{
|
{
|
||||||
dataSource.auth(
|
dataSource.auth(
|
||||||
RequestAuthDirect(
|
AuthDirectRequest(
|
||||||
grantType = VKConstants.Auth.GrantType.PASSWORD,
|
grantType = VKConstants.Auth.GrantType.PASSWORD,
|
||||||
clientId = VKConstants.VK_APP_ID,
|
clientId = VKConstants.VK_APP_ID,
|
||||||
clientSecret = VKConstants.VK_SECRET,
|
clientSecret = VKConstants.VK_SECRET,
|
||||||
@@ -44,12 +54,24 @@ class LoginViewModel @Inject constructor(
|
|||||||
return@makeJob
|
return@makeJob
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(
|
UserConfig.userId = it.userId
|
||||||
SuccessAuth(
|
UserConfig.accessToken = it.accessToken
|
||||||
userId = it.userId,
|
|
||||||
vkToken = it.accessToken
|
sendEvent(SuccessAuth())
|
||||||
)
|
|
||||||
)
|
// TODO: 19-Oct-21 do somewhen
|
||||||
|
// makeJob({
|
||||||
|
// dataSource.authWithApp(
|
||||||
|
// AuthWithAppRequest(
|
||||||
|
// accessToken = it.accessToken
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }, onAnswer = { kindaAnswer ->
|
||||||
|
// println("$TAG: AppAuthResponse: $kindaAnswer")
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
if (it !is VKException) {
|
if (it !is VKException) {
|
||||||
@@ -69,12 +91,14 @@ class LoginViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openPrimaryScreen() {
|
||||||
|
router.navigateTo(Screens.Conversations())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object CodeSent : VkEvent()
|
object CodeSent : VkEvent()
|
||||||
|
|
||||||
data class SuccessAuth(
|
data class SuccessAuth(
|
||||||
val haveAuthorized: Boolean = true,
|
val haveAuthorized: Boolean = true
|
||||||
val userId: Int,
|
|
||||||
val vkToken: String
|
|
||||||
) : VkEvent()
|
) : VkEvent()
|
||||||
@@ -1,45 +1,29 @@
|
|||||||
package com.meloda.fast.screens.main
|
package com.meloda.fast.screens.main
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.meloda.fast.R
|
|
||||||
import com.meloda.fast.api.UserConfig
|
|
||||||
import com.meloda.fast.base.BaseViewModelFragment
|
import com.meloda.fast.base.BaseViewModelFragment
|
||||||
import com.meloda.fast.databinding.FragmentMainBinding
|
|
||||||
import com.meloda.fast.extensions.NavigationExtensions.setupWithNavController
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainFragment : BaseViewModelFragment<MainViewModel>(R.layout.fragment_main) {
|
class MainFragment : BaseViewModelFragment<MainViewModel>() {
|
||||||
|
|
||||||
override val viewModel: MainViewModel by viewModels()
|
override val viewModel: MainViewModel by viewModels()
|
||||||
private val binding: FragmentMainBinding by viewBinding()
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
return View(context)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
if (!UserConfig.isLoggedIn()) findNavController().navigate(R.id.toLogin)
|
viewModel.checkSession(requireContext())
|
||||||
else if (savedInstanceState == null) setupBottomBar()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupBottomBar() {
|
|
||||||
val navGraphIds = listOf(
|
|
||||||
R.navigation.messages,
|
|
||||||
R.navigation.login
|
|
||||||
)
|
|
||||||
|
|
||||||
with(binding.bottomBar) {
|
|
||||||
selectedItemId = R.id.messages
|
|
||||||
setupWithNavController(
|
|
||||||
navGraphIds = navGraphIds,
|
|
||||||
fragmentManager = childFragmentManager,
|
|
||||||
containerId = R.id.fragmentContainer,
|
|
||||||
intent = requireActivity().intent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,30 @@
|
|||||||
package com.meloda.fast.screens.main
|
package com.meloda.fast.screens.main
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.github.terrakok.cicerone.Router
|
||||||
|
import com.meloda.fast.api.UserConfig
|
||||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||||
|
import com.meloda.fast.common.Screens
|
||||||
|
import com.meloda.fast.service.MessagesUpdateService
|
||||||
|
import com.meloda.fast.service.OnlineService
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MainViewModel : BaseViewModel()
|
@HiltViewModel
|
||||||
|
class MainViewModel @Inject constructor(private val router: Router) : BaseViewModel() {
|
||||||
|
|
||||||
|
fun checkSession(context: Context) {
|
||||||
|
if (UserConfig.isLoggedIn()) {
|
||||||
|
router.navigateTo(Screens.Conversations())
|
||||||
|
|
||||||
|
context.run {
|
||||||
|
startService(Intent(this, MessagesUpdateService::class.java))
|
||||||
|
startService(Intent(this, OnlineService::class.java))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.navigateTo(Screens.Login())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,24 +1,19 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.Space
|
import android.widget.Space
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat
|
import androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isNotEmpty
|
import androidx.core.view.*
|
||||||
import androidx.core.view.isVisible
|
import com.bumptech.glide.Priority
|
||||||
import androidx.core.view.setPadding
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import coil.load
|
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
|
||||||
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.VkUtils
|
import com.meloda.fast.api.VkUtils
|
||||||
@@ -27,8 +22,10 @@ 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.*
|
import com.meloda.fast.api.model.attachments.*
|
||||||
import com.meloda.fast.databinding.*
|
import com.meloda.fast.databinding.*
|
||||||
|
import com.meloda.fast.extensions.*
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.clear
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.loadWithGlide
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import com.meloda.fast.widget.RoundedFrameLayout
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -46,10 +43,18 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
private val inflater = LayoutInflater.from(context)
|
private val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
private val playColor = ContextCompat.getColor(context, R.color.a3_700)
|
private val colorBackground = ContextCompat.getColor(
|
||||||
private val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
context,
|
||||||
|
R.color.colorBackground
|
||||||
|
)
|
||||||
|
private val colorSecondary = ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
R.color.colorSecondary
|
||||||
|
)
|
||||||
|
|
||||||
var photoClickListener: ((url: String) -> Unit)? = null
|
private var photoClickListener: ((url: String) -> Unit)? = null
|
||||||
|
|
||||||
|
private val displayMetrics get() = Resources.getSystem().displayMetrics
|
||||||
|
|
||||||
fun setPhotoClickListener(unit: ((url: String) -> Unit)?): AttachmentInflater {
|
fun setPhotoClickListener(unit: ((url: String) -> Unit)?): AttachmentInflater {
|
||||||
this.photoClickListener = unit
|
this.photoClickListener = unit
|
||||||
@@ -57,15 +62,15 @@ class AttachmentInflater constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun inflate() {
|
fun inflate() {
|
||||||
if (message.attachments.isNullOrEmpty()) return
|
|
||||||
attachments = message.attachments!!
|
|
||||||
|
|
||||||
container.removeAllViews()
|
container.removeAllViews()
|
||||||
|
|
||||||
if (textContainer.childCount > 1) {
|
if (textContainer.childCount > 1) {
|
||||||
textContainer.removeViews(1, textContainer.childCount - 1)
|
textContainer.removeViews(1, textContainer.childCount - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.attachments.isNullOrEmpty()) return
|
||||||
|
attachments = message.attachments!!
|
||||||
|
|
||||||
if (attachments.size == 1) {
|
if (attachments.size == 1) {
|
||||||
when (val attachment = attachments[0]) {
|
when (val attachment = attachments[0]) {
|
||||||
is VkSticker -> return sticker(attachment)
|
is VkSticker -> return sticker(attachment)
|
||||||
@@ -74,6 +79,7 @@ class AttachmentInflater constructor(
|
|||||||
is VkCall -> return call(attachment)
|
is VkCall -> return call(attachment)
|
||||||
is VkGraffiti -> return graffiti(attachment)
|
is VkGraffiti -> return graffiti(attachment)
|
||||||
is VkGift -> return gift(attachment)
|
is VkGift -> return gift(attachment)
|
||||||
|
is VkStory -> return story(attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,112 +119,107 @@ class AttachmentInflater constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun photo(photo: VkPhoto) {
|
private fun photo(photo: VkPhoto) {
|
||||||
val size = photo.getSizeOrSmaller('y') ?: return
|
val size = photo.getSizeOrSmaller(VkPhoto.SIZE_TYPE_807) ?: return
|
||||||
|
|
||||||
val newPhoto = ShapeableImageView(context).apply {
|
val specRatio = size.width.toFloat() / size.height.toFloat()
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
val widthMultiplier: Float = when {
|
||||||
// ViewGroup.LayoutParams.MATCH_PARENT,
|
specRatio > 1 -> 0.7F
|
||||||
size.width,
|
specRatio < 1 -> 0.45F
|
||||||
size.height
|
else -> 0.35F
|
||||||
// AndroidUtils.px(size.width).roundToInt(),
|
}
|
||||||
// AndroidUtils.px(size.height).roundToInt()
|
val ratio = "${size.width}:${size.height}"
|
||||||
)
|
|
||||||
|
|
||||||
shapeAppearanceModel =
|
val spacer = Space(context).apply {
|
||||||
shapeAppearanceModel.withCornerSize {
|
layoutParams =
|
||||||
AndroidUtils.px(5)
|
LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5.dpToPx())
|
||||||
}
|
|
||||||
|
|
||||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (photoClickListener != null) {
|
if (container.isNotEmpty()) {
|
||||||
newPhoto.setOnClickListener { photoClickListener?.invoke(size.url) }
|
|
||||||
} else {
|
|
||||||
newPhoto.setOnClickListener(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val spacer = Space(context).also {
|
|
||||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
AndroidUtils.px(5).roundToInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container.isNotEmpty())
|
|
||||||
container.addView(spacer)
|
container.addView(spacer)
|
||||||
|
}
|
||||||
|
|
||||||
if (attachments.size == 1) {
|
val binding = ItemMessageAttachmentPhotoBinding.inflate(inflater, container, true)
|
||||||
val roundedLayout = RoundedFrameLayout(context).apply {
|
|
||||||
setTopRightCornerRadius((if (message.isOut) 30 else 40).toFloat())
|
val cornersRadius = 8.dpToPx().toFloat()
|
||||||
setTopLeftCornerRadius((if (message.isOut) 40 else 30).toFloat())
|
|
||||||
setBottomRightCornerRadius((if (message.isOut) 5 else 40).toFloat())
|
binding.border.run {
|
||||||
setBottomLeftCornerRadius((if (message.isOut) 40 else 5).toFloat())
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(cornersRadius)
|
||||||
|
|
||||||
|
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
width = (displayMetrics.widthPixels * widthMultiplier).roundToInt()
|
||||||
|
dimensionRatio = ratio
|
||||||
|
}
|
||||||
|
loadWithGlide(
|
||||||
|
drawable = ColorDrawable(colorSecondary),
|
||||||
|
priority = Priority.IMMEDIATE,
|
||||||
|
cacheStrategy = DiskCacheStrategy.NONE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.image.run {
|
||||||
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(cornersRadius * 0.8F)
|
||||||
|
|
||||||
|
if (photoClickListener != null) {
|
||||||
|
setOnClickListener { photoClickListener?.invoke(size.url) }
|
||||||
|
} else {
|
||||||
|
setOnClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
roundedLayout.addView(newPhoto)
|
loadWithGlide(
|
||||||
container.addView(roundedLayout)
|
url = size.url,
|
||||||
} else {
|
crossFade = true,
|
||||||
container.addView(newPhoto)
|
placeholderDrawable = ColorDrawable(colorBackground),
|
||||||
|
priority = Priority.LOW
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
newPhoto.load(size.url) { crossfade(100) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun video(video: VkVideo) {
|
private fun video(video: VkVideo) {
|
||||||
val size = video.images[1]
|
|
||||||
|
|
||||||
val layout = FrameLayout(context).apply {
|
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val newPhoto = ShapeableImageView(context).apply {
|
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
AndroidUtils.px(size.width).roundToInt(),
|
|
||||||
AndroidUtils.px(size.height).roundToInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
shapeAppearanceModel =
|
|
||||||
shapeAppearanceModel.withCornerSize { AndroidUtils.px(5) }
|
|
||||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
||||||
}
|
|
||||||
|
|
||||||
val play = AppCompatImageView(context).apply {
|
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
AndroidUtils.px(50).roundToInt(),
|
|
||||||
AndroidUtils.px(50).roundToInt()
|
|
||||||
).also {
|
|
||||||
it.gravity = Gravity.CENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
backgroundTintList = ColorStateList.valueOf(playBackgroundColor)
|
|
||||||
imageTintList = ColorStateList.valueOf(playColor)
|
|
||||||
|
|
||||||
setBackgroundResource(R.drawable.ic_play_button_circle_background)
|
|
||||||
setImageResource(R.drawable.ic_round_play_arrow_24)
|
|
||||||
|
|
||||||
setPadding(12)
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.addView(newPhoto)
|
|
||||||
layout.addView(play)
|
|
||||||
|
|
||||||
val spacer = Space(context).apply {
|
val spacer = Space(context).apply {
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
layoutParams =
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5.dpToPx())
|
||||||
AndroidUtils.px(5).roundToInt()
|
}
|
||||||
|
if (container.isNotEmpty()) {
|
||||||
|
container.addView(spacer)
|
||||||
|
}
|
||||||
|
|
||||||
|
val size = video.imageForWidthAtLeast(300) ?: return
|
||||||
|
val binding = ItemMessageAttachmentVideoBinding.inflate(inflater, container, true)
|
||||||
|
|
||||||
|
val specRatio = size.width.toFloat() / size.height.toFloat()
|
||||||
|
val widthMultiplier: Float = when {
|
||||||
|
specRatio > 1 -> 0.7F
|
||||||
|
specRatio < 1 -> 0.45F
|
||||||
|
else -> 0.35F
|
||||||
|
}
|
||||||
|
val ratio = "${size.width}:${size.height}"
|
||||||
|
|
||||||
|
val cornersRadius = 8.dpToPx().toFloat()
|
||||||
|
|
||||||
|
binding.border.run {
|
||||||
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(cornersRadius)
|
||||||
|
|
||||||
|
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
width = (displayMetrics.widthPixels * widthMultiplier).roundToInt()
|
||||||
|
dimensionRatio = ratio
|
||||||
|
}
|
||||||
|
loadWithGlide(
|
||||||
|
drawable = ColorDrawable(colorSecondary),
|
||||||
|
priority = Priority.IMMEDIATE,
|
||||||
|
cacheStrategy = DiskCacheStrategy.NONE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.isNotEmpty())
|
binding.image.run {
|
||||||
container.addView(spacer)
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(cornersRadius * 0.8F)
|
||||||
|
|
||||||
container.addView(layout)
|
loadWithGlide(
|
||||||
|
url = size.url,
|
||||||
newPhoto.load(size.url) { crossfade(100) }
|
crossFade = true,
|
||||||
|
placeholderDrawable = ColorDrawable(colorBackground),
|
||||||
|
priority = Priority.LOW
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun audio(audio: VkAudio) {
|
private fun audio(audio: VkAudio) {
|
||||||
@@ -245,14 +246,14 @@ class AttachmentInflater constructor(
|
|||||||
val binding = ItemMessageAttachmentLinkBinding.inflate(inflater, textContainer, true)
|
val binding = ItemMessageAttachmentLinkBinding.inflate(inflater, textContainer, true)
|
||||||
|
|
||||||
binding.title.text = link.title
|
binding.title.text = link.title
|
||||||
binding.title.isVisible = !link.title.isNullOrBlank()
|
binding.title.toggleVisibility(!link.title.isNullOrBlank())
|
||||||
|
|
||||||
binding.caption.text = link.caption
|
binding.caption.text = link.caption
|
||||||
binding.caption.isVisible = !link.caption.isNullOrBlank()
|
binding.caption.toggleVisibility(!link.caption.isNullOrBlank())
|
||||||
|
|
||||||
link.photo?.getSizeOrSmaller('y')?.let {
|
link.photo?.getSizeOrSmaller('y')?.let { size ->
|
||||||
binding.preview.load(it.url) { crossfade(150) }
|
binding.preview.loadWithGlide(url = size.url, crossFade = true)
|
||||||
binding.linkIcon.isVisible = false
|
binding.linkIcon.gone()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ class AttachmentInflater constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.linkIcon.isVisible = true
|
binding.linkIcon.visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sticker(sticker: VkSticker) {
|
private fun sticker(sticker: VkSticker) {
|
||||||
@@ -272,13 +273,12 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
val url = sticker.urlForSize(352)
|
val url = sticker.urlForSize(352)
|
||||||
|
|
||||||
with(binding.image) {
|
binding.image.run {
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
val size = 140.dpToPx()
|
||||||
AndroidUtils.px(140).roundToInt(),
|
|
||||||
AndroidUtils.px(140).roundToInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
load(url) { crossfade(150) }
|
layoutParams = LinearLayoutCompat.LayoutParams(size, size)
|
||||||
|
|
||||||
|
loadWithGlide(url = url, crossFade = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,14 +307,14 @@ class AttachmentInflater constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.postTitle.text = context.getString(postTitleRes)
|
binding.postTitle.text = context.getString(postTitleRes)
|
||||||
binding.postTitle.isVisible = false
|
binding.postTitle.gone()
|
||||||
|
|
||||||
binding.avatar.isVisible = group != null || user != null
|
binding.avatar.toggleVisibility(group != null || user != null)
|
||||||
|
|
||||||
if (binding.avatar.isVisible) {
|
if (binding.avatar.isVisible) {
|
||||||
binding.avatar.load(avatar) { crossfade(150) }
|
binding.avatar.loadWithGlide(url = avatar, crossFade = true)
|
||||||
} else {
|
} else {
|
||||||
binding.avatar.setImageDrawable(null)
|
binding.avatar.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.title.text = title
|
binding.title.text = title
|
||||||
@@ -328,12 +328,13 @@ class AttachmentInflater constructor(
|
|||||||
private fun voice(voiceMessage: VkVoiceMessage) {
|
private fun voice(voiceMessage: VkVoiceMessage) {
|
||||||
val binding = ItemMessageAttachmentVoiceBinding.inflate(inflater, textContainer, true)
|
val binding = ItemMessageAttachmentVoiceBinding.inflate(inflater, textContainer, true)
|
||||||
|
|
||||||
if (message.isOut)
|
if (message.isOut) {
|
||||||
|
val padding = 6.dpToPx()
|
||||||
binding.root.updatePadding(
|
binding.root.updatePadding(
|
||||||
bottom = AndroidUtils.px(6).roundToInt(),
|
bottom = padding,
|
||||||
left = AndroidUtils.px(6).roundToInt()
|
left = padding
|
||||||
)
|
)
|
||||||
|
}
|
||||||
val waveform = IntArray(voiceMessage.waveform.size)
|
val waveform = IntArray(voiceMessage.waveform.size)
|
||||||
voiceMessage.waveform.forEachIndexed { index, i -> waveform[index] = i }
|
voiceMessage.waveform.forEachIndexed { index, i -> waveform[index] = i }
|
||||||
|
|
||||||
@@ -352,8 +353,8 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
if (message.isOut)
|
if (message.isOut)
|
||||||
binding.root.updatePadding(
|
binding.root.updatePadding(
|
||||||
bottom = AndroidUtils.px(5).roundToInt(),
|
bottom = 5.dpToPx(),
|
||||||
left = AndroidUtils.px(6).roundToInt()
|
left = 6.dpToPx()
|
||||||
)
|
)
|
||||||
|
|
||||||
val callType =
|
val callType =
|
||||||
@@ -383,15 +384,17 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
val url = graffiti.url
|
val url = graffiti.url
|
||||||
|
|
||||||
val heightCoefficient = graffiti.height / AndroidUtils.px(140)
|
val size = 140.dpToPx()
|
||||||
|
|
||||||
with(binding.image) {
|
val heightCoefficient = graffiti.height / size.toFloat()
|
||||||
|
|
||||||
|
binding.image.run {
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||||
AndroidUtils.px(140).roundToInt(),
|
size,
|
||||||
(graffiti.height / heightCoefficient).roundToInt()
|
(graffiti.height / heightCoefficient).roundToInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
load(url) { crossfade(150) }
|
loadWithGlide(url = url, crossFade = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,16 +403,72 @@ class AttachmentInflater constructor(
|
|||||||
|
|
||||||
val url = gift.thumb256 ?: gift.thumb96 ?: gift.thumb48
|
val url = gift.thumb256 ?: gift.thumb96 ?: gift.thumb48
|
||||||
|
|
||||||
with(binding.image) {
|
binding.image.run {
|
||||||
shapeAppearanceModel = shapeAppearanceModel.withCornerSize { AndroidUtils.px(12) }
|
val size = 140.dpToPx()
|
||||||
|
|
||||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(12.dpToPx().toFloat())
|
||||||
AndroidUtils.px(140).roundToInt(),
|
|
||||||
AndroidUtils.px(140).roundToInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
load(url) { crossfade(150) }
|
layoutParams = LinearLayoutCompat.LayoutParams(size, size)
|
||||||
|
|
||||||
|
loadWithGlide(url = url, crossFade = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun story(story: VkStory) {
|
||||||
|
val binding = ItemMessageAttachmentStoryBinding.inflate(inflater, container, true)
|
||||||
|
|
||||||
|
val photoUrl = story.photo?.getSizeOrSmaller(VkPhoto.SIZE_TYPE_807)?.url
|
||||||
|
|
||||||
|
val dimmerDrawable =
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_message_attachment_story_image_dimmer)
|
||||||
|
|
||||||
|
val cornersRadius = 24.dpToPx()
|
||||||
|
|
||||||
|
binding.caption.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
val margin = cornersRadius / 2
|
||||||
|
updateMarginsRelative(
|
||||||
|
top = margin,
|
||||||
|
start = margin,
|
||||||
|
end = margin,
|
||||||
|
bottom = margin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.dimmer.loadWithGlide(
|
||||||
|
drawable = dimmerDrawable,
|
||||||
|
transformations = listOf(TypeTransformations.RoundedCornerCrop(cornersRadius)),
|
||||||
|
priority = Priority.IMMEDIATE,
|
||||||
|
cacheStrategy = DiskCacheStrategy.NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.image.run {
|
||||||
|
shapeAppearanceModel = shapeAppearanceModel.withCornerSize(cornersRadius.toFloat())
|
||||||
|
|
||||||
|
loadWithGlide(
|
||||||
|
url = photoUrl,
|
||||||
|
crossFade = true,
|
||||||
|
placeholderDrawable = ColorDrawable(Color.GRAY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (story.ownerId == UserConfig.userId) {
|
||||||
|
binding.caption.text = context.getString(R.string.message_attachment_story_your_story)
|
||||||
|
} else {
|
||||||
|
val storyOwnerUser = if (story.isFromUser()) profiles[story.ownerId] else null
|
||||||
|
val storyOwnerGroup = if (story.isFromGroup()) groups[story.ownerId] else null
|
||||||
|
|
||||||
|
val ownerName = when {
|
||||||
|
storyOwnerUser != null -> storyOwnerUser.fullName
|
||||||
|
storyOwnerGroup != null -> storyOwnerGroup.name
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.caption.text = context.getString(
|
||||||
|
R.string.message_attachment_story_story_from,
|
||||||
|
ownerName
|
||||||
|
)
|
||||||
|
binding.caption.toggleVisibility(ownerName != null)
|
||||||
|
binding.dimmer.toggleVisibility(binding.caption.isVisible)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -19,76 +21,89 @@ import com.meloda.fast.api.model.VkUser
|
|||||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||||
import com.meloda.fast.base.adapter.BaseAdapter
|
import com.meloda.fast.base.adapter.BaseAdapter
|
||||||
import com.meloda.fast.base.adapter.BaseHolder
|
import com.meloda.fast.base.adapter.BaseHolder
|
||||||
import com.meloda.fast.databinding.*
|
import com.meloda.fast.databinding.ItemMessageInBinding
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.databinding.ItemMessageOutBinding
|
||||||
import java.util.*
|
import com.meloda.fast.databinding.ItemMessageServiceBinding
|
||||||
import kotlin.math.roundToInt
|
import com.meloda.fast.extensions.dpToPx
|
||||||
|
import com.meloda.fast.model.DataItem
|
||||||
|
|
||||||
class MessagesHistoryAdapter constructor(
|
class MessagesHistoryAdapter constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
values: MutableList<VkMessage>,
|
|
||||||
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.BasicHolder>(context, values, COMPARATOR) {
|
) : BaseAdapter<DataItem<Int>, MessagesHistoryAdapter.BasicHolder>(context, Comparator) {
|
||||||
|
|
||||||
var avatarLongClickListener: ((position: Int) -> Unit)? = null
|
var avatarLongClickListener: ((position: Int) -> Unit)? = null
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
when {
|
return when (val item = getItem(position)) {
|
||||||
isPositionHeader(position) -> return HEADER
|
is VkMessage -> {
|
||||||
isPositionFooter(position) -> return FOOTER
|
return when {
|
||||||
|
item.action != null -> TypeService
|
||||||
|
item.isOut -> TypeOutgoing
|
||||||
|
!item.isOut -> TypeIncoming
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is DataItem.Header -> {
|
||||||
|
return TypeHeader
|
||||||
|
}
|
||||||
|
is DataItem.Footer -> {
|
||||||
|
return TypeFooter
|
||||||
|
}
|
||||||
|
else -> -1
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(position).let { message ->
|
|
||||||
if (message.action != null) return SERVICE
|
|
||||||
if (message.isOut) return OUTGOING
|
|
||||||
if (!message.isOut) return INCOMING
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isPositionHeader(position: Int) = position == 0
|
|
||||||
private fun isPositionFooter(position: Int) = position >= actualSize
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BasicHolder {
|
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))
|
TypeHeader -> {
|
||||||
FOOTER -> Footer(createEmptyView(36))
|
Header(createEmptyView(60))
|
||||||
SERVICE -> ServiceMessage(
|
}
|
||||||
|
TypeFooter -> {
|
||||||
|
Footer(
|
||||||
|
createEmptyView(
|
||||||
|
context.resources.getDimensionPixelSize(R.dimen.messages_history_input_panel_height_with_margins)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TypeService -> ServiceMessage(
|
||||||
ItemMessageServiceBinding.inflate(inflater, parent, false)
|
ItemMessageServiceBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
OUTGOING -> OutgoingMessage(
|
TypeOutgoing -> OutgoingMessage(
|
||||||
ItemMessageOutBinding.inflate(inflater, parent, false)
|
ItemMessageOutBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
INCOMING -> IncomingMessage(
|
TypeIncoming -> IncomingMessage(
|
||||||
ItemMessageInBinding.inflate(inflater, parent, false)
|
ItemMessageInBinding.inflate(inflater, parent, false)
|
||||||
)
|
)
|
||||||
else -> throw IllegalStateException("Wrong viewType: $viewType")
|
else -> throw IllegalStateException("Wrong viewType: $viewType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun initListeners(itemView: View, position: Int) {
|
override fun onBindViewHolder(holder: BasicHolder, position: Int) {
|
||||||
// if (itemView is AdapterView<*>) return
|
if (holder is Header || holder is Footer) {
|
||||||
//
|
Log.d(
|
||||||
// itemView.setOnClickListener { onItemClickListener?.invoke(position, itemView) }
|
"MessagesHistoryAdapter",
|
||||||
// itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
|
"onBindViewHolder: index $position, holder is ${holder.javaClass.simpleName}. Skip"
|
||||||
// }
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(
|
||||||
|
"MessagesHistoryAdapter",
|
||||||
|
"onBindViewHolder: index $position, holder is ${holder.javaClass.simpleName}. Bind"
|
||||||
|
)
|
||||||
|
|
||||||
val actualSize get() = values.size
|
initListeners(holder.itemView, position)
|
||||||
|
holder.bind(position)
|
||||||
override fun getItemCount(): Int {
|
|
||||||
if (actualSize == 0) return 2
|
|
||||||
return super.getItemCount() + 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
AndroidUtils.px(size).roundToInt()
|
size
|
||||||
)
|
)
|
||||||
|
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
@@ -96,13 +111,6 @@ class MessagesHistoryAdapter constructor(
|
|||||||
isFocusable = false
|
isFocusable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
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 BasicHolder(v: View = View(context)) : BaseHolder(v)
|
open inner class BasicHolder(v: View = View(context)) : BaseHolder(v)
|
||||||
|
|
||||||
inner class Header(v: View) : BasicHolder(v)
|
inner class Header(v: View) : BasicHolder(v)
|
||||||
@@ -114,10 +122,10 @@ class MessagesHistoryAdapter constructor(
|
|||||||
) : BasicHolder(binding.root) {
|
) : BasicHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
val message = getItem(position) as VkMessage
|
||||||
|
|
||||||
val prevMessage = getOrNull(position - 1)
|
val prevMessage = getVkMessage(getOrNull(position - 1))
|
||||||
val nextMessage = getOrNull(position + 1)
|
val nextMessage = getVkMessage(getOrNull(position + 1))
|
||||||
|
|
||||||
MessagesPreparator(
|
MessagesPreparator(
|
||||||
context = context,
|
context = context,
|
||||||
@@ -159,9 +167,8 @@ class MessagesHistoryAdapter constructor(
|
|||||||
) : BasicHolder(binding.root) {
|
) : BasicHolder(binding.root) {
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
val message = getItem(position) as VkMessage
|
||||||
|
val prevMessage = getVkMessage(getOrNull(position - 1))
|
||||||
val prevMessage = getOrNull(position - 1)
|
|
||||||
|
|
||||||
MessagesPreparator(
|
MessagesPreparator(
|
||||||
context = context,
|
context = context,
|
||||||
@@ -192,13 +199,12 @@ class MessagesHistoryAdapter constructor(
|
|||||||
private val youPrefix = context.getString(R.string.you_message_prefix)
|
private val youPrefix = context.getString(R.string.you_message_prefix)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.photo.shapeAppearanceModel.run {
|
binding.photo.shapeAppearanceModel =
|
||||||
withCornerSize { AndroidUtils.px(4) }
|
binding.photo.shapeAppearanceModel.withCornerSize(4.dpToPx().toFloat())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(position: Int) {
|
override fun bind(position: Int) {
|
||||||
val message = getItem(position)
|
val message = getItem(position) as VkMessage
|
||||||
|
|
||||||
val messageUser =
|
val messageUser =
|
||||||
if (message.isUser()) profiles[message.fromId]
|
if (message.isUser()) profiles[message.fromId]
|
||||||
@@ -241,59 +247,56 @@ class MessagesHistoryAdapter constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeMessageById(id: Int): Int? {
|
fun getVkMessage(item: DataItem<*>?): VkMessage? {
|
||||||
for (i in values.indices) {
|
if (item == null) return null
|
||||||
val message = values[i]
|
if (item is VkMessage) return item
|
||||||
if (message.id == id) {
|
|
||||||
values.removeAt(i)
|
return null
|
||||||
return i
|
}
|
||||||
}
|
|
||||||
|
fun searchMessageIndex(messageId: Int): Int? {
|
||||||
|
for (i in indices) {
|
||||||
|
val message = getItem(i)
|
||||||
|
if (message is VkMessage && message.id == messageId) return i
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeMessagesByIds(ids: List<Int>): List<Int> {
|
fun searchMessageById(messageId: Int): VkMessage? {
|
||||||
val positions = mutableListOf<Int>()
|
for (i in indices) {
|
||||||
|
val message = getItem(i)
|
||||||
for (i in values.indices) {
|
if (message is VkMessage && message.id == messageId) return message
|
||||||
val message = values[i]
|
|
||||||
if (ids.contains(message.id)) {
|
|
||||||
values.removeAt(i)
|
|
||||||
positions += i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return positions
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchMessageIndex(messageId: Int): Int? {
|
|
||||||
for (i in values.indices) {
|
|
||||||
val message = values[i]
|
|
||||||
if (message.id == messageId) return i
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SERVICE = 1
|
private const val TypeService = 1
|
||||||
private const val HEADER = 0
|
private const val TypeHeader = 0
|
||||||
private const val FOOTER = 2
|
private const val TypeFooter = 2
|
||||||
private const val INCOMING = 3
|
private const val TypeIncoming = 3
|
||||||
private const val OUTGOING = 4
|
private const val TypeOutgoing = 4
|
||||||
|
|
||||||
|
private val Comparator = object : DiffUtil.ItemCallback<DataItem<Int>>() {
|
||||||
private val COMPARATOR = object : DiffUtil.ItemCallback<VkMessage>() {
|
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
oldItem: VkMessage,
|
oldItem: DataItem<Int>,
|
||||||
newItem: VkMessage
|
newItem: DataItem<Int>
|
||||||
) = false
|
): Boolean {
|
||||||
|
return if (oldItem is VkMessage && newItem is VkMessage) {
|
||||||
|
oldItem.id == newItem.id
|
||||||
|
} else {
|
||||||
|
oldItem is DataItem.Footer && newItem is DataItem.Footer
|
||||||
|
|| oldItem is DataItem.Header && newItem is DataItem.Header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DiffUtilEquals")
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: VkMessage,
|
oldItem: DataItem<Int>,
|
||||||
newItem: VkMessage
|
newItem: DataItem<Int>
|
||||||
) = false
|
): Boolean = oldItem == newItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.content.res.ColorStateList
|
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.view.animation.LinearInterpolator
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
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
|
||||||
@@ -32,7 +37,9 @@ 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.DialogMessageDeleteBinding
|
import com.meloda.fast.databinding.DialogMessageDeleteBinding
|
||||||
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
||||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
import com.meloda.fast.extensions.*
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.clear
|
||||||
|
import com.meloda.fast.extensions.ImageLoader.loadWithGlide
|
||||||
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
|
||||||
@@ -41,10 +48,26 @@ import java.util.*
|
|||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MessagesHistoryFragment :
|
class MessagesHistoryFragment :
|
||||||
BaseViewModelFragment<MessagesHistoryViewModel>(R.layout.fragment_messages_history) {
|
BaseViewModelFragment<MessagesHistoryViewModel>(R.layout.fragment_messages_history) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ARG_USER: String = "user"
|
||||||
|
const val ARG_GROUP: String = "group"
|
||||||
|
const val ARG_CONVERSATION: String = "conversation"
|
||||||
|
|
||||||
|
private const val ATTACHMENT_PANEL_ANIMATION_DURATION = 150L
|
||||||
|
|
||||||
|
fun newInstance(bundle: Bundle): MessagesHistoryFragment {
|
||||||
|
val fragment = MessagesHistoryFragment()
|
||||||
|
fragment.arguments = bundle
|
||||||
|
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val viewModel: MessagesHistoryViewModel by viewModels()
|
override val viewModel: MessagesHistoryViewModel by viewModels()
|
||||||
private val binding: FragmentMessagesHistoryBinding by viewBinding()
|
private val binding: FragmentMessagesHistoryBinding by viewBinding()
|
||||||
|
|
||||||
@@ -55,21 +78,20 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val user: VkUser? by lazy {
|
private val user: VkUser? by lazy {
|
||||||
requireArguments().getParcelable("user")
|
requireArguments().getParcelable(ARG_USER)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val group: VkGroup? by lazy {
|
private val group: VkGroup? by lazy {
|
||||||
requireArguments().getParcelable("group")
|
requireArguments().getParcelable(ARG_GROUP)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val conversation: VkConversation by lazy {
|
private val conversation: VkConversation by lazy {
|
||||||
requireNotNull(requireArguments().getParcelable("conversation"))
|
requireNotNull(requireArguments().getParcelable(ARG_CONVERSATION))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val adapter: MessagesHistoryAdapter by lazy {
|
private val adapter: MessagesHistoryAdapter by lazy {
|
||||||
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
|
MessagesHistoryAdapter(requireContext(), conversation).also {
|
||||||
it.itemClickListener = this::onItemClick
|
it.itemClickListener = this::onItemClick
|
||||||
it.itemLongClickListener = this::onItemLongClick
|
|
||||||
it.avatarLongClickListener = this::onAvatarLongClickListener
|
it.avatarLongClickListener = this::onAvatarLongClickListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +112,8 @@ class MessagesHistoryFragment :
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.back.setOnClickListener { requireActivity().onBackPressed() }
|
||||||
|
|
||||||
binding.title.ellipsize = TextUtils.TruncateAt.END
|
binding.title.ellipsize = TextUtils.TruncateAt.END
|
||||||
binding.status.ellipsize = TextUtils.TruncateAt.END
|
binding.status.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
@@ -121,7 +145,7 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
binding.action.setOnClickListener { performAction() }
|
binding.action.setOnClickListener { performAction() }
|
||||||
|
|
||||||
binding.recyclerView.addOnLayoutChangeListener { _, i, i2, i3, bottom, i5, i6, i7, oldBottom ->
|
binding.recyclerView.addOnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
|
||||||
if (bottom >= oldBottom) return@addOnLayoutChangeListener
|
if (bottom >= oldBottom) return@addOnLayoutChangeListener
|
||||||
val lastVisiblePosition =
|
val lastVisiblePosition =
|
||||||
(binding.recyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
(binding.recyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
||||||
@@ -138,8 +162,8 @@ class MessagesHistoryFragment :
|
|||||||
val firstPosition =
|
val firstPosition =
|
||||||
(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||||
|
|
||||||
val message = adapter.getOrNull(firstPosition)
|
adapter.getOrNull(firstPosition)?.let {
|
||||||
message?.let {
|
if (it !is VkMessage) return
|
||||||
binding.timestamp.isVisible = true
|
binding.timestamp.isVisible = true
|
||||||
|
|
||||||
val time = "${
|
val time = "${
|
||||||
@@ -158,7 +182,7 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
timestampTimer = Timer()
|
timestampTimer = Timer()
|
||||||
timestampTimer?.schedule(2500) {
|
timestampTimer?.schedule(2500) {
|
||||||
recyclerView.post { binding.timestamp.isVisible = false }
|
recyclerView.post { binding.timestamp.gone() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +209,8 @@ class MessagesHistoryFragment :
|
|||||||
.scaleY(1.25f)
|
.scaleY(1.25f)
|
||||||
.setDuration(100)
|
.setDuration(100)
|
||||||
.withEndAction {
|
.withEndAction {
|
||||||
|
if (getView() == null) return@withEndAction
|
||||||
|
|
||||||
binding.action.animate()
|
binding.action.animate()
|
||||||
.scaleX(1f)
|
.scaleX(1f)
|
||||||
.scaleY(1f)
|
.scaleY(1f)
|
||||||
@@ -209,21 +235,37 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentController.isPanelVisible.observe(viewLifecycleOwner) {
|
attachmentController.isPanelVisible.observe(viewLifecycleOwner) { isVisible ->
|
||||||
if (it) binding.message.setSelection(binding.message.text.toString().length)
|
if (isVisible) binding.message.setSelection(binding.message.text.toString().length)
|
||||||
|
|
||||||
val layoutParams = binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams
|
val currentMargin =
|
||||||
layoutParams.bottomMargin =
|
(binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin
|
||||||
if (it) (binding.attachmentPanel.height / 1.5).roundToInt() else 0
|
|
||||||
|
val newMargin =
|
||||||
|
if (isVisible) (binding.attachmentPanel.measuredHeight / 1.5).roundToInt()
|
||||||
|
else 0
|
||||||
|
|
||||||
|
ValueAnimator.ofInt(currentMargin, newMargin).apply {
|
||||||
|
duration = ATTACHMENT_PANEL_ANIMATION_DURATION
|
||||||
|
interpolator = LinearInterpolator()
|
||||||
|
|
||||||
|
addUpdateListener { animator ->
|
||||||
|
if (getView() == null) return@addUpdateListener
|
||||||
|
val value = animator.animatedValue as Int
|
||||||
|
binding.refreshLayout.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||||
|
bottomMargin = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.attachmentPanel.setOnClickListener c@{
|
binding.attachmentPanel.setOnClickListener c@{
|
||||||
val message = attachmentController.message.value ?: return@c
|
val message = attachmentController.message.value ?: return@c
|
||||||
|
|
||||||
val index = adapter.values.indexOf(message)
|
val index = adapter.indexOf(message)
|
||||||
if (index == -1) return@c
|
if (index == -1) return@c
|
||||||
|
|
||||||
binding.recyclerView.smoothScrollToPosition(index)
|
binding.recyclerView.scrollToPosition(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.dismissReply.setOnClickListener {
|
binding.dismissReply.setOnClickListener {
|
||||||
@@ -232,6 +274,11 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private fun getColor(@ColorRes resId: Int): Int {
|
||||||
|
return ContextCompat.getColor(requireContext(), resId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun prepareAvatar() {
|
private fun prepareAvatar() {
|
||||||
val avatar = when {
|
val avatar = when {
|
||||||
conversation.ownerId == VKConstants.FAST_GROUP_ID -> null
|
conversation.ownerId == VKConstants.FAST_GROUP_ID -> null
|
||||||
@@ -241,46 +288,49 @@ class MessagesHistoryFragment :
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.avatar.isVisible = avatar != null
|
val colorOnPrimary = getColor(R.color.colorOnPrimary)
|
||||||
|
val colorUserAvatarAction = getColor(R.color.colorUserAvatarAction)
|
||||||
|
val colorOnUserAvatarAction = getColor(R.color.colorOnUserAvatarAction)
|
||||||
|
|
||||||
|
val icLauncherColor = getColor(R.color.a1_500)
|
||||||
|
|
||||||
|
binding.avatar.toggleVisibility(avatar != null)
|
||||||
|
|
||||||
if (avatar == null) {
|
if (avatar == null) {
|
||||||
binding.avatarPlaceholder.isVisible = true
|
binding.avatarPlaceholder.visible()
|
||||||
|
|
||||||
if (conversation.ownerId == VKConstants.FAST_GROUP_ID) {
|
if (conversation.ownerId == VKConstants.FAST_GROUP_ID) {
|
||||||
binding.placeholderBack.setImageDrawable(
|
binding.placeholderBack.loadWithGlide(
|
||||||
ColorDrawable(
|
drawable = ColorDrawable(icLauncherColor),
|
||||||
ContextCompat.getColor(requireContext(), R.color.a1_400)
|
transformations = ImageLoader.userAvatarTransformations
|
||||||
)
|
|
||||||
)
|
)
|
||||||
binding.placeholder.imageTintList =
|
binding.placeholder.imageTintList =
|
||||||
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.a1_0))
|
ColorStateList.valueOf(colorOnPrimary)
|
||||||
binding.placeholder.setImageResource(R.drawable.ic_fast_logo)
|
binding.placeholder.setImageResource(R.drawable.ic_fast_logo)
|
||||||
binding.placeholder.setPadding(18)
|
binding.placeholder.setPadding(18)
|
||||||
} else {
|
} else {
|
||||||
binding.placeholderBack.setImageDrawable(
|
binding.placeholderBack.loadWithGlide(
|
||||||
ColorDrawable(
|
drawable = ColorDrawable(colorOnUserAvatarAction),
|
||||||
ContextCompat.getColor(requireContext(), R.color.n1_50)
|
transformations = ImageLoader.userAvatarTransformations
|
||||||
)
|
|
||||||
)
|
)
|
||||||
binding.placeholder.imageTintList =
|
binding.placeholder.imageTintList =
|
||||||
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.n2_500))
|
ColorStateList.valueOf(colorUserAvatarAction)
|
||||||
binding.placeholder.setImageResource(R.drawable.ic_account_circle_cut)
|
binding.placeholder.setImageResource(R.drawable.ic_account_circle_cut)
|
||||||
binding.placeholder.setPadding(0)
|
binding.placeholder.setPadding(0)
|
||||||
binding.avatar.setImageDrawable(null)
|
binding.avatar.clear()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.avatar.load(avatar) {
|
binding.avatar.load(avatar) {
|
||||||
crossfade(200)
|
crossfade(200)
|
||||||
target {
|
target {
|
||||||
binding.avatarPlaceholder.isVisible = false
|
binding.avatarPlaceholder.gone()
|
||||||
binding.avatar.setImageDrawable(it)
|
binding.avatar.setImageDrawable(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.phantomIcon.isVisible = conversation.isPhantom
|
binding.phantomIcon.toggleVisibility(conversation.isPhantom)
|
||||||
binding.online.isVisible = user?.online == true
|
binding.online.toggleVisibility(user?.online)
|
||||||
binding.pin.isVisible = conversation.isPinned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performAction() {
|
private fun performAction() {
|
||||||
@@ -293,8 +343,10 @@ class MessagesHistoryFragment :
|
|||||||
|
|
||||||
val date = System.currentTimeMillis()
|
val date = System.currentTimeMillis()
|
||||||
|
|
||||||
|
val messageIndex = adapter.lastPosition
|
||||||
|
|
||||||
val message = VkMessage(
|
val message = VkMessage(
|
||||||
id = -1,
|
id = Int.MAX_VALUE,
|
||||||
text = messageText,
|
text = messageText,
|
||||||
isOut = true,
|
isOut = true,
|
||||||
peerId = conversation.id,
|
peerId = conversation.id,
|
||||||
@@ -304,10 +356,10 @@ class MessagesHistoryFragment :
|
|||||||
replyMessage = attachmentController.message.value
|
replyMessage = attachmentController.message.value
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter.add(message)
|
adapter.add(message, beforeFooter = true, commitCallback = {
|
||||||
adapter.notifyItemInserted(adapter.actualSize - 1)
|
binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
||||||
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
binding.message.clear()
|
||||||
binding.message.clear()
|
})
|
||||||
|
|
||||||
val replyMessage = attachmentController.message.value
|
val replyMessage = attachmentController.message.value
|
||||||
attachmentController.message.value = null
|
attachmentController.message.value = null
|
||||||
@@ -316,8 +368,13 @@ class MessagesHistoryFragment :
|
|||||||
peerId = conversation.id,
|
peerId = conversation.id,
|
||||||
message = messageText,
|
message = messageText,
|
||||||
randomId = 0,
|
randomId = 0,
|
||||||
replyTo = replyMessage?.id
|
replyTo = replyMessage?.id,
|
||||||
) { message.id = it }
|
setId = { messageId ->
|
||||||
|
val messageToUpdate = adapter[messageIndex] as VkMessage
|
||||||
|
messageToUpdate.id = messageId
|
||||||
|
adapter[messageIndex] = messageToUpdate
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Action.EDIT -> {
|
Action.EDIT -> {
|
||||||
val message = attachmentController.message.value ?: return
|
val message = attachmentController.message.value ?: return
|
||||||
@@ -336,6 +393,7 @@ class MessagesHistoryFragment :
|
|||||||
Action.DELETE -> attachmentController.message.value?.let {
|
Action.DELETE -> attachmentController.message.value?.let {
|
||||||
showDeleteMessageDialog(it)
|
showDeleteMessageDialog(it)
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,12 +404,12 @@ class MessagesHistoryFragment :
|
|||||||
is StartProgressEvent -> onProgressStarted()
|
is StartProgressEvent -> onProgressStarted()
|
||||||
is StopProgressEvent -> onProgressStopped()
|
is StopProgressEvent -> onProgressStopped()
|
||||||
|
|
||||||
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
|
is MessagesMarkAsImportantEvent -> markMessagesAsImportant(event)
|
||||||
is MessagesLoaded -> refreshMessages(event)
|
is MessagesLoadedEvent -> refreshMessages(event)
|
||||||
is MessagesPin -> conversation.pinnedMessage = event.message
|
is MessagesPinEvent -> conversation.pinnedMessage = event.message
|
||||||
is MessagesUnpin -> conversation.pinnedMessage = null
|
is MessagesUnpinEvent -> conversation.pinnedMessage = null
|
||||||
is MessagesDelete -> deleteMessages(event)
|
is MessagesDeleteEvent -> deleteMessages(event)
|
||||||
is MessagesEdit -> editMessage(event)
|
is MessagesEditEvent -> editMessage(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,26 +453,24 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markMessagesAsImportant(event: MessagesMarkAsImportant) {
|
private fun markMessagesAsImportant(event: MessagesMarkAsImportantEvent) {
|
||||||
var changed = false
|
var changed = false
|
||||||
val positions = mutableListOf<Int>()
|
val positions = mutableListOf<Int>()
|
||||||
|
|
||||||
for (i in adapter.values.indices) {
|
for (i in adapter.indices) {
|
||||||
val message = adapter.values[i]
|
val message = adapter[i] as VkMessage
|
||||||
message.important = event.important
|
message.important = event.important
|
||||||
if (event.messagesIds.contains(message.id)) {
|
if (event.messagesIds.contains(message.id)) {
|
||||||
if (!changed) changed = true
|
if (!changed) changed = true
|
||||||
|
|
||||||
positions.add(i)
|
positions.add(i)
|
||||||
|
|
||||||
adapter.values[i] = message
|
adapter[i] = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) positions.forEach { adapter.notifyItemChanged(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshMessages(event: MessagesLoaded) {
|
private fun refreshMessages(event: MessagesLoadedEvent) {
|
||||||
adapter.profiles += event.profiles
|
adapter.profiles += event.profiles
|
||||||
adapter.groups += event.groups
|
adapter.groups += event.groups
|
||||||
|
|
||||||
@@ -424,22 +480,23 @@ class MessagesHistoryFragment :
|
|||||||
private fun fillRecyclerView(values: List<VkMessage>) {
|
private fun fillRecyclerView(values: List<VkMessage>) {
|
||||||
val smoothScroll = adapter.isNotEmpty()
|
val smoothScroll = adapter.isNotEmpty()
|
||||||
|
|
||||||
adapter.values.clear()
|
adapter.setItems(
|
||||||
adapter.values += values.sortedBy { it.date }
|
values.sortedBy { it.date },
|
||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
withHeader = true,
|
||||||
|
withFooter = true,
|
||||||
if (smoothScroll) binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
commitCallback = {
|
||||||
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
if (smoothScroll) binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
||||||
|
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onItemClick(position: Int) {
|
private fun onItemClick(position: Int) {
|
||||||
showOptionsDialog(position)
|
showOptionsDialog(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onItemLongClick(position: Int) = true
|
|
||||||
|
|
||||||
private fun onAvatarLongClickListener(position: Int) {
|
private fun onAvatarLongClickListener(position: Int) {
|
||||||
val message = adapter.values[position]
|
val message = adapter[position] as VkMessage
|
||||||
|
|
||||||
val messageUser = VkUtils.getMessageUser(message, adapter.profiles)
|
val messageUser = VkUtils.getMessageUser(message, adapter.profiles)
|
||||||
val messageGroup = VkUtils.getMessageGroup(message, adapter.groups)
|
val messageGroup = VkUtils.getMessageGroup(message, adapter.groups)
|
||||||
@@ -449,7 +506,7 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showOptionsDialog(position: Int) {
|
private fun showOptionsDialog(position: Int) {
|
||||||
val message = adapter.values[position]
|
val message = adapter[position] as VkMessage
|
||||||
if (message.action != null) return
|
if (message.action != null) return
|
||||||
|
|
||||||
val time = getString(
|
val time = getString(
|
||||||
@@ -577,16 +634,14 @@ class MessagesHistoryFragment :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteMessages(event: MessagesDelete) {
|
private fun deleteMessages(event: MessagesDeleteEvent) {
|
||||||
adapter.removeMessagesByIds(event.messagesIds).let {
|
val messagesToDelete = event.messagesIds.mapNotNull { id -> adapter.searchMessageById(id) }
|
||||||
it.forEach { index -> adapter.notifyItemRemoved(index) }
|
adapter.removeAll(messagesToDelete)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun editMessage(event: MessagesEdit) {
|
private fun editMessage(event: MessagesEditEvent) {
|
||||||
adapter.searchMessageIndex(event.message.id)?.let { index ->
|
adapter.searchMessageIndex(event.message.id)?.let { index ->
|
||||||
adapter.values[index] = event.message
|
adapter[index] = event.message
|
||||||
adapter.notifyItemChanged(index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,8 +665,6 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun applyMessage(message: VkMessage) {
|
private fun applyMessage(message: VkMessage) {
|
||||||
showPanel()
|
|
||||||
|
|
||||||
val title = when {
|
val title = when {
|
||||||
message.isGroup() && message.group.value != null -> message.group.value?.name
|
message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||||
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||||
@@ -637,6 +690,8 @@ class MessagesHistoryFragment :
|
|||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
binding.message.setText(message.text)
|
binding.message.setText(message.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearMessage() {
|
private fun clearMessage() {
|
||||||
@@ -651,28 +706,64 @@ class MessagesHistoryFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showPanel(duration: Long = 250) {
|
private fun showPanel() {
|
||||||
|
binding.attachmentPanel.visible()
|
||||||
|
binding.attachmentPanel.measure(
|
||||||
|
View.MeasureSpec.AT_MOST, View.MeasureSpec.UNSPECIFIED
|
||||||
|
)
|
||||||
|
|
||||||
if (attachmentController.isPanelVisible.value == false)
|
if (attachmentController.isPanelVisible.value == false)
|
||||||
attachmentController.isPanelVisible.value = true
|
attachmentController.isPanelVisible.value = true
|
||||||
|
|
||||||
|
val measuredHeight = binding.attachmentPanel.measuredHeight
|
||||||
|
|
||||||
|
binding.attachmentPanel.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||||
|
height = 0
|
||||||
|
}
|
||||||
|
|
||||||
binding.attachmentPanel.animate()
|
binding.attachmentPanel.animate()
|
||||||
.translationY(0f)
|
.translationY(0f)
|
||||||
.alpha(1f)
|
.setDuration(ATTACHMENT_PANEL_ANIMATION_DURATION)
|
||||||
.setDuration(duration)
|
|
||||||
.withStartAction { binding.attachmentPanel.isVisible = true }
|
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
|
ValueAnimator.ofInt(0, measuredHeight).apply {
|
||||||
|
duration = ATTACHMENT_PANEL_ANIMATION_DURATION
|
||||||
|
interpolator = LinearInterpolator()
|
||||||
|
|
||||||
|
addUpdateListener { animator ->
|
||||||
|
if (view == null) return@addUpdateListener
|
||||||
|
val value = animator.animatedValue as Int
|
||||||
|
binding.attachmentPanel.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||||
|
height = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hidePanel(duration: Long = 250) {
|
private fun hidePanel() {
|
||||||
if (attachmentController.isPanelVisible.value == true)
|
if (attachmentController.isPanelVisible.value == true)
|
||||||
attachmentController.isPanelVisible.value = false
|
attachmentController.isPanelVisible.value = false
|
||||||
|
|
||||||
|
val currentHeight = binding.attachmentPanel.height
|
||||||
|
|
||||||
binding.attachmentPanel.animate()
|
binding.attachmentPanel.animate()
|
||||||
.alpha(0f)
|
.translationY(75F)
|
||||||
.translationY(50f)
|
.setDuration(ATTACHMENT_PANEL_ANIMATION_DURATION)
|
||||||
.setDuration(duration)
|
|
||||||
.withEndAction { binding.attachmentPanel.isVisible = false }
|
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
|
ValueAnimator.ofInt(currentHeight, 0).apply {
|
||||||
|
duration = ATTACHMENT_PANEL_ANIMATION_DURATION
|
||||||
|
interpolator = LinearInterpolator()
|
||||||
|
|
||||||
|
addUpdateListener { animator ->
|
||||||
|
if (view == null) return@addUpdateListener
|
||||||
|
val value = animator.animatedValue as Int
|
||||||
|
|
||||||
|
binding.attachmentPanel.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||||
|
height = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.meloda.fast.screens.messages
|
package com.meloda.fast.screens.messages
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.meloda.fast.api.LongPollEvent
|
||||||
|
import com.meloda.fast.api.LongPollUpdatesParser
|
||||||
import com.meloda.fast.api.VKConstants
|
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
|
||||||
@@ -16,12 +18,25 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MessagesHistoryViewModel @Inject constructor(
|
class MessagesHistoryViewModel @Inject constructor(
|
||||||
private val messages: MessagesDataSource
|
private val messages: MessagesDataSource,
|
||||||
|
updatesParser: LongPollUpdatesParser
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
fun loadHistory(
|
init {
|
||||||
peerId: Int
|
updatesParser.onNewMessage {
|
||||||
) = viewModelScope.launch {
|
// viewModelScope.launch { handleNewMessage(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
updatesParser.onMessageEdited {
|
||||||
|
viewModelScope.launch { handleEditedMessage(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleEditedMessage(event: LongPollEvent.VkMessageEditEvent) {
|
||||||
|
sendEvent(com.meloda.fast.screens.messages.MessagesEditEvent(event.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadHistory(peerId: Int) = viewModelScope.launch {
|
||||||
makeJob({
|
makeJob({
|
||||||
messages.getHistory(
|
messages.getHistory(
|
||||||
MessagesGetHistoryRequest(
|
MessagesGetHistoryRequest(
|
||||||
@@ -66,7 +81,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(
|
sendEvent(
|
||||||
MessagesLoaded(
|
MessagesLoadedEvent(
|
||||||
count = response.count,
|
count = response.count,
|
||||||
profiles = profiles,
|
profiles = profiles,
|
||||||
groups = groups,
|
groups = groups,
|
||||||
@@ -116,7 +131,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
onAnswer = {
|
onAnswer = {
|
||||||
val response = it.response ?: return@makeJob
|
val response = it.response ?: return@makeJob
|
||||||
sendEvent(
|
sendEvent(
|
||||||
MessagesMarkAsImportant(
|
MessagesMarkAsImportantEvent(
|
||||||
messagesIds = response,
|
messagesIds = response,
|
||||||
important = important
|
important = important
|
||||||
)
|
)
|
||||||
@@ -142,14 +157,14 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
},
|
},
|
||||||
onAnswer = {
|
onAnswer = {
|
||||||
val response = it.response ?: return@makeJob
|
val response = it.response ?: return@makeJob
|
||||||
sendEvent(MessagesPin(response.asVkMessage()))
|
sendEvent(MessagesPinEvent(response.asVkMessage()))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
makeJob({ messages.unpin(MessagesUnPinMessageRequest(peerId = peerId)) },
|
makeJob({ messages.unpin(MessagesUnPinMessageRequest(peerId = peerId)) },
|
||||||
onAnswer = {
|
onAnswer = {
|
||||||
println("Fast::MessagesHistoryViewModel::unPin::Response::${it.response}")
|
println("Fast::MessagesHistoryViewModel::unPin::Response::${it.response}")
|
||||||
sendEvent(MessagesUnpin)
|
sendEvent(MessagesUnpinEvent)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -172,7 +187,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
deleteForAll = deleteForAll
|
deleteForAll = deleteForAll
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}, onAnswer = { sendEvent(MessagesDelete(messagesIds = messagesIds ?: listOf())) })
|
}, onAnswer = { sendEvent(MessagesDeleteEvent(messagesIds = messagesIds ?: emptyList())) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editMessage(
|
fun editMessage(
|
||||||
@@ -195,13 +210,13 @@ class MessagesHistoryViewModel @Inject constructor(
|
|||||||
},
|
},
|
||||||
onAnswer = {
|
onAnswer = {
|
||||||
originalMessage.text = message
|
originalMessage.text = message
|
||||||
sendEvent(MessagesEdit(originalMessage))
|
sendEvent(com.meloda.fast.screens.messages.MessagesEditEvent(originalMessage))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MessagesLoaded(
|
data class MessagesLoadedEvent(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val conversations: HashMap<Int, VkConversation>,
|
val conversations: HashMap<Int, VkConversation>,
|
||||||
val messages: List<VkMessage>,
|
val messages: List<VkMessage>,
|
||||||
@@ -209,21 +224,12 @@ data class MessagesLoaded(
|
|||||||
val groups: HashMap<Int, VkGroup>
|
val groups: HashMap<Int, VkGroup>
|
||||||
) : VkEvent()
|
) : VkEvent()
|
||||||
|
|
||||||
data class MessagesMarkAsImportant(
|
data class MessagesMarkAsImportantEvent(val messagesIds: List<Int>, val important: Boolean) : VkEvent()
|
||||||
val messagesIds: List<Int>,
|
|
||||||
val important: Boolean
|
|
||||||
) : VkEvent()
|
|
||||||
|
|
||||||
data class MessagesPin(
|
data class MessagesPinEvent(val message: VkMessage) : VkEvent()
|
||||||
val message: VkMessage
|
|
||||||
) : VkEvent()
|
|
||||||
|
|
||||||
object MessagesUnpin : VkEvent()
|
object MessagesUnpinEvent : VkEvent()
|
||||||
|
|
||||||
data class MessagesDelete(
|
data class MessagesDeleteEvent(val messagesIds: List<Int>) : VkEvent()
|
||||||
val messagesIds: List<Int>
|
|
||||||
) : VkEvent()
|
|
||||||
|
|
||||||
data class MessagesEdit(
|
data class MessagesEditEvent(val message: VkMessage) : VkEvent()
|
||||||
val message: VkMessage
|
|
||||||
) : VkEvent()
|
|
||||||
@@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.api.VKConstants
|
||||||
import com.meloda.fast.api.VkUtils
|
import com.meloda.fast.api.VkUtils
|
||||||
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
|
||||||
@@ -19,6 +20,9 @@ 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.extensions.gone
|
||||||
|
import com.meloda.fast.extensions.toggleVisibility
|
||||||
|
import com.meloda.fast.extensions.visible
|
||||||
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.*
|
||||||
@@ -150,9 +154,7 @@ class MessagesPreparator constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareUnreadIndicator() {
|
private fun prepareUnreadIndicator() {
|
||||||
if (unread != null) {
|
unread?.toggleVisibility(!message.isRead(conversation))
|
||||||
unread.isVisible = message.isRead(conversation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareSpacer() {
|
private fun prepareSpacer() {
|
||||||
@@ -160,12 +162,20 @@ class MessagesPreparator constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareAttachments() {
|
private fun prepareAttachments() {
|
||||||
|
attachmentContainer?.removeAllViews()
|
||||||
|
|
||||||
|
textContainer?.let { textContainer ->
|
||||||
|
if (textContainer.childCount > 1) {
|
||||||
|
textContainer.removeViews(1, textContainer.childCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (attachmentContainer != null && textContainer != null) {
|
if (attachmentContainer != null && textContainer != null) {
|
||||||
|
|
||||||
if (message.attachments.isNullOrEmpty()) {
|
if (message.attachments.isNullOrEmpty()) {
|
||||||
attachmentContainer.isVisible = false
|
attachmentContainer.gone()
|
||||||
attachmentContainer.removeAllViews()
|
|
||||||
} else {
|
} else {
|
||||||
attachmentContainer.isVisible = true
|
attachmentContainer.visible()
|
||||||
|
|
||||||
AttachmentInflater(
|
AttachmentInflater(
|
||||||
context = context,
|
context = context,
|
||||||
@@ -208,11 +218,23 @@ class MessagesPreparator constructor(
|
|||||||
private fun prepareText() {
|
private fun prepareText() {
|
||||||
if (bubble != null && text != null) {
|
if (bubble != null && text != null) {
|
||||||
if (message.text == null) {
|
if (message.text == null) {
|
||||||
text.isVisible = false
|
text.gone()
|
||||||
bubble.isVisible = !message.attachments.isNullOrEmpty()
|
|
||||||
|
val hasAttachments = !message.attachments.isNullOrEmpty()
|
||||||
|
var shouldBeVisible = hasAttachments
|
||||||
|
if (hasAttachments) {
|
||||||
|
for (attachment in message.attachments ?: emptyList()) {
|
||||||
|
if (VKConstants.separatedFromTextAttachments.contains(attachment.javaClass)) {
|
||||||
|
shouldBeVisible = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bubble.toggleVisibility(shouldBeVisible)
|
||||||
} else {
|
} else {
|
||||||
text.isVisible = true
|
text.visible()
|
||||||
bubble.isVisible = true
|
bubble.visible()
|
||||||
text.text = VkUtils.prepareMessageText(message.text ?: "")
|
text.text = VkUtils.prepareMessageText(message.text ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.meloda.fast.service
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.meloda.fast.api.network.messages.MessagesGetLongPollServerRequest
|
|
||||||
import com.meloda.fast.api.network.messages.MessagesDataSource
|
|
||||||
import com.meloda.fast.api.network.longpoll.LongPollRepo
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
class LongPollService {
|
|
||||||
}
|
|
||||||
|
|
||||||
class LongPollTask @Inject constructor(
|
|
||||||
private val dataSource: MessagesDataSource,
|
|
||||||
private val longPollRepo: LongPollRepo
|
|
||||||
) : CoroutineScope {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "LongPollTask"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val job = SupervisorJob()
|
|
||||||
|
|
||||||
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
|
||||||
Log.d(TAG, "error: $throwable")
|
|
||||||
throwable.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext
|
|
||||||
get() = Dispatchers.Default + job + exceptionHandler
|
|
||||||
|
|
||||||
fun startPolling(): Job {
|
|
||||||
if (job.isCompleted || job.isCancelled) throw Exception("Job is over")
|
|
||||||
|
|
||||||
return launch {
|
|
||||||
val serverInfo = dataSource.getLongPollServer(
|
|
||||||
MessagesGetLongPollServerRequest(
|
|
||||||
needPts = true,
|
|
||||||
version = 10
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
println("TESTJOPAAAAAA: $serverInfo")
|
|
||||||
// val response = serverInfo.response ?: return@launch
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package com.meloda.fast.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.meloda.fast.api.LongPollUpdatesParser
|
||||||
|
import com.meloda.fast.api.VKConstants
|
||||||
|
import com.meloda.fast.api.VKException
|
||||||
|
import com.meloda.fast.api.model.base.BaseVkLongPoll
|
||||||
|
import com.meloda.fast.api.network.Answer
|
||||||
|
import com.meloda.fast.api.network.longpoll.LongPollGetUpdatesRequest
|
||||||
|
import com.meloda.fast.api.network.longpoll.LongPollRepo
|
||||||
|
import com.meloda.fast.api.network.messages.MessagesDataSource
|
||||||
|
import com.meloda.fast.api.network.messages.MessagesGetLongPollServerRequest
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MessagesUpdateService : Service(), CoroutineScope {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "LongPollTask"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val job = SupervisorJob()
|
||||||
|
|
||||||
|
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
Log.d(TAG, "error: $throwable")
|
||||||
|
throwable.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Default + job + exceptionHandler
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var dataSource: MessagesDataSource
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var longPollRepo: LongPollRepo
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var updatesParser: LongPollUpdatesParser
|
||||||
|
|
||||||
|
override fun onBind(p0: Intent?): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
launch { startPolling().join() }
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPolling(): Job {
|
||||||
|
if (job.isCompleted || job.isCancelled) throw Exception("Job is over")
|
||||||
|
|
||||||
|
return launch {
|
||||||
|
var serverInfo = getServerInfo()
|
||||||
|
?: throw VKException(error = "bad VK response (server info)")
|
||||||
|
|
||||||
|
var lastUpdatesResponse: JsonObject? = getUpdatesResponse(serverInfo)
|
||||||
|
?: throw VKException(error = "initiation error: bad VK response (last updates)")
|
||||||
|
|
||||||
|
var failCount = 0
|
||||||
|
|
||||||
|
while (job.isActive) {
|
||||||
|
if (lastUpdatesResponse == null) {
|
||||||
|
failCount++
|
||||||
|
serverInfo = getServerInfo()
|
||||||
|
?: throw VKException(error = "failed retrieving server info after error: bad VK response (server info #2)")
|
||||||
|
lastUpdatesResponse = getUpdatesResponse(serverInfo)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
when (lastUpdatesResponse["failed"]?.asInt) {
|
||||||
|
1 -> {
|
||||||
|
var newTs = lastUpdatesResponse["ts"]?.asInt
|
||||||
|
if (newTs == null) {
|
||||||
|
newTs = serverInfo.ts
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs))
|
||||||
|
}
|
||||||
|
2, 3 -> {
|
||||||
|
serverInfo = getServerInfo()
|
||||||
|
?: throw VKException(
|
||||||
|
error = "failed retrieving server info after error: bad VK response (server info #3)"
|
||||||
|
)
|
||||||
|
lastUpdatesResponse = getUpdatesResponse(serverInfo)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val newTs = lastUpdatesResponse["ts"]?.asInt
|
||||||
|
|
||||||
|
if (newTs == null) {
|
||||||
|
failCount++
|
||||||
|
} else {
|
||||||
|
val updates = lastUpdatesResponse["updates"]?.asJsonArray
|
||||||
|
|
||||||
|
if (updates == null) {
|
||||||
|
failCount++
|
||||||
|
} else {
|
||||||
|
updates.forEach { item ->
|
||||||
|
item.asJsonArray?.also {
|
||||||
|
launch {
|
||||||
|
handleUpdateEvent(it)
|
||||||
|
}
|
||||||
|
} ?: failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getServerInfo(): BaseVkLongPoll? {
|
||||||
|
val response = dataSource.getLongPollServer(
|
||||||
|
MessagesGetLongPollServerRequest(
|
||||||
|
needPts = true,
|
||||||
|
version = VKConstants.LP_VERSION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
println("$TAG: serverInfoResponse: $response")
|
||||||
|
|
||||||
|
if (response is Answer.Error) return null
|
||||||
|
if (response is Answer.Success) {
|
||||||
|
return response.data.response
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getUpdatesResponse(server: BaseVkLongPoll): JsonObject? {
|
||||||
|
val response = dataSource.getLongPollUpdates(
|
||||||
|
serverUrl = "https://${server.server}",
|
||||||
|
params = LongPollGetUpdatesRequest(
|
||||||
|
key = server.key,
|
||||||
|
ts = server.ts,
|
||||||
|
wait = 25,
|
||||||
|
mode = 2 or 8 or 32 or 64 or 128
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
println("$TAG: lastUpdateResponse: $response")
|
||||||
|
|
||||||
|
if (response is Answer.Error) return null
|
||||||
|
|
||||||
|
if (response is Answer.Success) {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleUpdateEvent(eventJson: JsonArray) {
|
||||||
|
// println("$TAG: handleUpdateEvent: $eventJson")
|
||||||
|
|
||||||
|
updatesParser.parseNextUpdate(eventJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun <T : Any> registerListener(eventType: Int, listener: VkEventCallback<T>) =
|
||||||
|
// updatesParser.registerListener(eventType, listener)
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
try {
|
||||||
|
job.cancel()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
updatesParser.clearListeners()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.meloda.fast.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import com.meloda.fast.api.UserConfig
|
||||||
|
import com.meloda.fast.api.network.account.AccountDataSource
|
||||||
|
import com.meloda.fast.api.network.account.AccountSetOfflineRequest
|
||||||
|
import com.meloda.fast.api.network.account.AccountSetOnlineRequest
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class OnlineService : Service(), CoroutineScope {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val TAG = "OnlineService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val job = SupervisorJob()
|
||||||
|
|
||||||
|
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
Log.d(MessagesUpdateService.TAG, "error: $throwable")
|
||||||
|
throwable.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Default + job + exceptionHandler
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var dataSource: AccountDataSource
|
||||||
|
|
||||||
|
private var timer: Timer? = null
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
timer = Timer().apply {
|
||||||
|
schedule(delay = 0, period = 60_000) {
|
||||||
|
launch {
|
||||||
|
setOffline()
|
||||||
|
delay(5000)
|
||||||
|
setOnline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return START_STICKY_COMPATIBILITY
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun setOnline() {
|
||||||
|
println("$TAG: setOnline()")
|
||||||
|
val response = dataSource.setOnline(
|
||||||
|
AccountSetOnlineRequest(
|
||||||
|
voip = false,
|
||||||
|
accessToken = UserConfig.fastToken
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun setOffline() {
|
||||||
|
println("$TAG: setOffline()")
|
||||||
|
val response = dataSource.setOffline(
|
||||||
|
AccountSetOfflineRequest(
|
||||||
|
accessToken = UserConfig.accessToken
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import android.content.ClipData
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import com.meloda.fast.common.AppGlobal
|
import com.meloda.fast.common.AppGlobal
|
||||||
@@ -12,22 +11,8 @@ import com.meloda.fast.common.AppGlobal
|
|||||||
|
|
||||||
object AndroidUtils {
|
object AndroidUtils {
|
||||||
|
|
||||||
fun px(dp: Float): Float {
|
|
||||||
return dp * (AppGlobal.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun px(dp: Int) = px(dp.toFloat())
|
|
||||||
|
|
||||||
fun dp(px: Float): Float {
|
|
||||||
return px / (AppGlobal.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dp(px: Int) = dp(px.toFloat())
|
|
||||||
|
|
||||||
fun isDarkTheme(): Boolean {
|
fun isDarkTheme(): Boolean {
|
||||||
val currentNightMode =
|
return when (AppGlobal.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||||
AppGlobal.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
||||||
return when (currentNightMode) {
|
|
||||||
Configuration.UI_MODE_NIGHT_YES -> true
|
Configuration.UI_MODE_NIGHT_YES -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.extensions.dpToPx
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class NoItemsView @JvmOverloads constructor(
|
class NoItemsView @JvmOverloads constructor(
|
||||||
@@ -43,7 +42,7 @@ class NoItemsView @JvmOverloads constructor(
|
|||||||
private fun create() {
|
private fun create() {
|
||||||
val a = context.obtainStyledAttributes(attrs, R.styleable.NoItemsView)
|
val a = context.obtainStyledAttributes(attrs, R.styleable.NoItemsView)
|
||||||
|
|
||||||
minimumWidth = AndroidUtils.px(256).roundToInt()
|
minimumWidth = 256.dpToPx()
|
||||||
minimumHeight = minimumWidth
|
minimumHeight = minimumWidth
|
||||||
|
|
||||||
orientation = VERTICAL
|
orientation = VERTICAL
|
||||||
@@ -51,9 +50,12 @@ class NoItemsView @JvmOverloads constructor(
|
|||||||
|
|
||||||
noItemsPicture = ImageView(context)
|
noItemsPicture = ImageView(context)
|
||||||
|
|
||||||
val params = imageViewParams
|
val imageViewSize = 64.dpToPx()
|
||||||
params.height = AndroidUtils.px(64).roundToInt()
|
|
||||||
params.width = AndroidUtils.px(64).roundToInt()
|
val params = imageViewParams.apply {
|
||||||
|
height = imageViewSize
|
||||||
|
width = imageViewSize
|
||||||
|
}
|
||||||
|
|
||||||
noItemsPicture.layoutParams = params
|
noItemsPicture.layoutParams = params
|
||||||
|
|
||||||
@@ -72,10 +74,10 @@ class NoItemsView @JvmOverloads constructor(
|
|||||||
noItemsTextView = TextView(context)
|
noItemsTextView = TextView(context)
|
||||||
|
|
||||||
val textParams = textViewParams
|
val textParams = textViewParams
|
||||||
textParams.width = AndroidUtils.px(256).roundToInt()
|
textParams.width = 256.dpToPx()
|
||||||
|
|
||||||
if (noItemsDrawable != null) {
|
if (noItemsDrawable != null) {
|
||||||
textParams.topMargin = AndroidUtils.px(8).roundToInt()
|
textParams.topMargin = 8.dpToPx()
|
||||||
}
|
}
|
||||||
|
|
||||||
noItemsTextView.layoutParams = textParams
|
noItemsTextView.layoutParams = textParams
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<gradient
|
||||||
|
android:angle="-90"
|
||||||
|
android:endColor="@android:color/transparent"
|
||||||
|
android:startColor="#BF000000" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="2dp"
|
||||||
android:color="@color/messageOutStrokeColor" />
|
android:color="?colorSurfaceVariant" />
|
||||||
|
|
||||||
<solid android:color="@color/messageOutColor" />
|
<solid android:color="?colorSurface" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="40dp"
|
android:bottomLeftRadius="40dp"
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="2dp"
|
||||||
android:color="@color/messageOutStrokeColor" />
|
android:color="?colorSurfaceVariant" />
|
||||||
|
|
||||||
<solid android:color="@color/messageOutColor" />
|
<solid android:color="?colorSurface" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="40dp"
|
android:bottomLeftRadius="40dp"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<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">
|
||||||
|
|
||||||
<solid android:color="@color/messageOutStrokeColor" />
|
<solid android:color="?colorSurfaceVariant" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="40dp"
|
android:bottomLeftRadius="40dp"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<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">
|
||||||
|
|
||||||
<solid android:color="@color/messageOutStrokeColor" />
|
<solid android:color="?colorSurfaceVariant" />
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="40dp"
|
android:bottomLeftRadius="40dp"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<corners android:radius="50dp" />
|
<corners android:radius="50dp" />
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="2dp"
|
||||||
android:color="?android:windowBackground" />
|
android:color="?colorBackground" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M19,11H7.83l4.88,-4.88c0.39,-0.39 0.39,-1.03 0,-1.42 -0.39,-0.39 -1.02,-0.39 -1.41,0l-6.59,6.59c-0.39,0.39 -0.39,1.02 0,1.41l6.59,6.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L7.83,13H19c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z" />
|
||||||
|
</vector>
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:id="@+id/root_fragment_container"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/fragmentContainer"
|
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:defaultNavHost="true"
|
|
||||||
app:navGraph="@navigation/main" />
|
|
||||||
</LinearLayout>
|
|
||||||
</layout>
|
|
||||||
@@ -1,84 +1,78 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/activity_horizontal_margin">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/captchaContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/captchaImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/ic_security"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/captchaLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/captchaInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/captcha_hint"
|
||||||
|
android:imeOptions="actionGo"
|
||||||
|
android:lines="1"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:gravity="center"
|
||||||
android:padding="@dimen/activity_horizontal_margin">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/image"
|
android:id="@+id/cancel"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_height="100dp"
|
android:layout_width="wrap_content"
|
||||||
android:scaleType="fitCenter"
|
|
||||||
tools:src="@tools:sample/backgrounds/scenic" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/captchaContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
android:orientation="horizontal">
|
android:layout_weight="1"
|
||||||
|
android:backgroundTint="@color/a1_600"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
app:elevation="0dp" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/captchaImage"
|
android:id="@+id/ok"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:src="@drawable/ic_security"
|
|
||||||
app:tint="?colorAccent" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/captchaLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/captchaInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/captcha_hint"
|
|
||||||
android:imeOptions="actionGo"
|
|
||||||
android:lines="1"
|
|
||||||
android:maxLines="1" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:layout_weight="1"
|
||||||
android:orientation="horizontal">
|
android:backgroundTint="@color/a3_200"
|
||||||
|
android:text="@android:string/ok"
|
||||||
<com.google.android.material.button.MaterialButton
|
app:elevation="0dp" />
|
||||||
android:id="@+id/cancel"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="@color/a1_600"
|
|
||||||
android:text="@android:string/cancel"
|
|
||||||
app:elevation="0dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/ok"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="@color/a3_200"
|
|
||||||
android:text="@android:string/ok"
|
|
||||||
app:elevation="0dp" />
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</LinearLayout>
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/check"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="16dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingVertical="12dp">
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/message_delete_for_all"
|
||||||
<com.google.android.material.checkbox.MaterialCheckBox
|
app:useMaterialThemeColors="true" />
|
||||||
android:id="@+id/check"
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/message_delete_for_all"
|
|
||||||
app:useMaterialThemeColors="true" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
</layout>
|
|
||||||
@@ -1,77 +1,71 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/activity_horizontal_margin">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/codeContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/codeImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/ic_security"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/codeLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/codeInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/code_hint"
|
||||||
|
android:imeOptions="actionGo"
|
||||||
|
android:inputType="number"
|
||||||
|
android:lines="1"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:gravity="center"
|
||||||
android:padding="@dimen/activity_horizontal_margin">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/codeContainer"
|
android:id="@+id/cancel"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
android:orientation="horizontal">
|
android:layout_weight="1"
|
||||||
|
android:backgroundTint="@color/n1_900"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
app:elevation="0dp" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/codeImage"
|
android:id="@+id/ok"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:src="@drawable/ic_security"
|
|
||||||
app:tint="?colorAccent" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/codeLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/codeInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/code_hint"
|
|
||||||
android:imeOptions="actionGo"
|
|
||||||
android:inputType="number"
|
|
||||||
android:lines="1"
|
|
||||||
android:maxLines="1" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:layout_weight="1"
|
||||||
android:orientation="horizontal">
|
android:backgroundTint="@color/a3_200"
|
||||||
|
android:text="@android:string/ok"
|
||||||
<com.google.android.material.button.MaterialButton
|
app:elevation="0dp" />
|
||||||
android:id="@+id/cancel"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="@color/n1_900"
|
|
||||||
android:text="@android:string/cancel"
|
|
||||||
app:elevation="0dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/ok"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="@color/a3_200"
|
|
||||||
android:text="@android:string/ok"
|
|
||||||
app:elevation="0dp" />
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</LinearLayout>
|
||||||
@@ -1,142 +1,92 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/appBar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="?actionBarSize"
|
||||||
app:elevation="0dp">
|
android:background="?colorBackground"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||||
|
app:menu="@menu/fragment_conversations"
|
||||||
|
app:title="@string/title_messages"
|
||||||
|
app:titleTextColor="?colorOnBackground">
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/collapsingToolbarLayout"
|
android:id="@+id/avatarContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:elevation="0dp"
|
android:layout_gravity="end|center_vertical"
|
||||||
app:collapsedTitleTextAppearance="@style/CollapsingToolbarCollapsedTitle"
|
android:orientation="horizontal"
|
||||||
app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle"
|
android:paddingStart="0dp"
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
android:paddingEnd="16dp"
|
||||||
app:title="Messages">
|
app:layout_collapseMode="none">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
android:id="@+id/expandedImage"
|
android:id="@+id/search"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="30dp"
|
||||||
android:layout_height="140dp"
|
android:layout_height="30dp"
|
||||||
android:elevation="0dp" />
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_search"
|
||||||
|
android:tint="?colorPrimary" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/avatarContainer"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="30dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="30dp"
|
||||||
android:layout_gravity="bottom|end"
|
tools:src="@tools:sample/avatars" />
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:paddingEnd="30dp"
|
|
||||||
android:paddingBottom="30dp"
|
|
||||||
app:layout_collapseMode="none">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
android:id="@+id/search"
|
</androidx.appcompat.widget.Toolbar>
|
||||||
android:layout_width="30dp"
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
android:layout_height="30dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_search"
|
|
||||||
android:tint="?colorSecondary3Variant" />
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_width="30dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="30dp"
|
android:layout_height="match_parent"
|
||||||
tools:src="@tools:sample/avatars" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
<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="none"
|
|
||||||
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"-->
|
|
||||||
<!-- android:visibility="gone"-->
|
|
||||||
<!-- 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.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
android:id="@+id/refreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
android:orientation="vertical"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_conversation" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/item_conversation" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ProgressBar
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/createChat"
|
||||||
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:layout_gravity="end|bottom"
|
||||||
android:visibility="gone"
|
android:layout_margin="16dp"
|
||||||
tools:visibility="visible" />
|
android:src="@drawable/ic_baseline_create_24"
|
||||||
|
app:elevation="3dp"
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
app:fabSize="normal"
|
||||||
android:id="@+id/createChat"
|
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
|
||||||
android:layout_width="56dp"
|
app:pressedTranslationZ="1dp"
|
||||||
android:layout_height="56dp"
|
app:shapeAppearanceOverlay="@style/RoundedView.56"
|
||||||
android:layout_gravity="end|bottom"
|
tools:ignore="ContentDescription" />
|
||||||
android:layout_margin="16dp"
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
android:src="@drawable/ic_baseline_create_24"
|
|
||||||
app:elevation="3dp"
|
|
||||||
app:fabSize="normal"
|
|
||||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
|
|
||||||
app:pressedTranslationZ="1dp"
|
|
||||||
app:shapeAppearanceOverlay="@style/RoundedView.56"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
</layout>
|
|
||||||
@@ -1,148 +1,141 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/loginRoot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<WebView
|
||||||
android:id="@+id/loginRoot"
|
android:id="@+id/webView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:visibility="gone" />
|
||||||
|
|
||||||
<WebView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/webView"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="0dp"
|
android:fillViewport="true">
|
||||||
android:clickable="false"
|
|
||||||
android:focusable="false"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:fillViewport="true">
|
android:animateLayoutChanges="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/logoContainer"
|
||||||
|
android:layout_width="140dp"
|
||||||
|
android:layout_height="140dp"
|
||||||
|
android:layout_gravity="center_horizontal">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/logoImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="42dp"
|
||||||
|
android:src="@drawable/ic_fast_lightning"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/loginContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:animateLayoutChanges="true"
|
android:layout_marginTop="48dp"
|
||||||
android:gravity="center"
|
android:orientation="horizontal">
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/logoContainer"
|
android:id="@+id/loginImage"
|
||||||
android:layout_width="140dp"
|
|
||||||
android:layout_height="140dp"
|
|
||||||
android:layout_gravity="center_horizontal">
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:id="@+id/logoImage"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:padding="42dp"
|
|
||||||
android:src="@drawable/ic_fast_lightning"
|
|
||||||
app:tint="?colorAccent" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="48dp"
|
android:layout_marginTop="16dp"
|
||||||
android:visibility="gone"
|
android:src="@drawable/ic_baseline_account_circle_24"
|
||||||
tools:visibility="visible" />
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/loginContainer"
|
android:id="@+id/loginLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="48dp"
|
app:boxStrokeErrorColor="@android:color/transparent">
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/loginImage"
|
android:id="@+id/loginInput"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:src="@drawable/ic_baseline_account_circle_24"
|
|
||||||
app:tint="?colorAccent" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/loginLayout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="48dp"
|
||||||
app:boxStrokeErrorColor="@android:color/transparent">
|
android:hint="@string/login_hint"
|
||||||
|
android:imeOptions="actionGo"
|
||||||
|
android:inputType="textEmailAddress" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
android:id="@+id/loginInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:hint="@string/login_hint"
|
|
||||||
android:imeOptions="actionGo"
|
|
||||||
android:inputType="textEmailAddress" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/passwordContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/passwordImage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:src="@drawable/ic_key"
|
|
||||||
app:tint="?colorAccent" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/passwordLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:boxStrokeErrorColor="@android:color/transparent"
|
|
||||||
app:passwordToggleEnabled="true"
|
|
||||||
app:passwordToggleTint="?colorAccent">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/passwordInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:fontFamily="@font/roboto_regular"
|
|
||||||
android:hint="@string/password_login_hint"
|
|
||||||
android:imeOptions="actionGo"
|
|
||||||
android:typeface="normal" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/auth"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:backgroundTint="@color/a1_600"
|
|
||||||
android:fontFamily="@font/google_sans_medium"
|
|
||||||
android:letterSpacing="0"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:text="@string/log_in"
|
|
||||||
android:textSize="14sp"
|
|
||||||
app:cornerRadius="50dp"
|
|
||||||
app:elevation="16dp"
|
|
||||||
app:icon="@drawable/ic_arrow_end"
|
|
||||||
app:iconGravity="end" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
<LinearLayout
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
android:id="@+id/passwordContainer"
|
||||||
</layout>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/passwordImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/ic_key"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/passwordLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:boxStrokeErrorColor="@android:color/transparent"
|
||||||
|
app:passwordToggleEnabled="true"
|
||||||
|
app:passwordToggleTint="?colorAccent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/passwordInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:fontFamily="@font/roboto_regular"
|
||||||
|
android:hint="@string/password_login_hint"
|
||||||
|
android:imeOptions="actionGo"
|
||||||
|
android:typeface="normal" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/auth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:backgroundTint="@color/a1_600"
|
||||||
|
android:fontFamily="@font/google_sans_medium"
|
||||||
|
android:letterSpacing="0"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/log_in"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:cornerRadius="50dp"
|
||||||
|
app:elevation="16dp"
|
||||||
|
app:icon="@drawable/ic_arrow_end"
|
||||||
|
app:iconGravity="end" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?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">
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/fragmentContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
android:id="@+id/bottomBar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="?colorSurface"
|
|
||||||
app:elevation="0.5dp"
|
|
||||||
app:labelVisibilityMode="unlabeled"
|
|
||||||
app:menu="@menu/activity_main_bottom" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
||||||
</layout>
|
|
||||||
@@ -1,339 +1,328 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="86dp">
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/refreshLayout"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="86dp">
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="100"
|
||||||
|
tools:listitem="@layout/item_message_out" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:itemCount="100"
|
|
||||||
tools:listitem="@layout/item_message_out" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
<FrameLayout
|
||||||
|
android:id="@+id/toolbarContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:background="@drawable/ic_messages_history_toolbar_gradient_background"
|
||||||
|
android:backgroundTint="?colorBackground"
|
||||||
|
android:minHeight="140dp">
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/toolbarContainer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:animateLayoutChanges="true"
|
android:orientation="horizontal"
|
||||||
android:background="@drawable/ic_messages_history_toolbar_gradient_background"
|
android:paddingStart="12dp"
|
||||||
android:backgroundTint="@color/n1_50"
|
android:paddingTop="18dp"
|
||||||
android:minHeight="140dp">
|
android:paddingEnd="30dp"
|
||||||
|
android:paddingBottom="24dp">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/back"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="36dp"
|
||||||
android:orientation="horizontal"
|
android:layout_height="36dp"
|
||||||
android:paddingHorizontal="30dp"
|
android:layout_gravity="center_vertical"
|
||||||
android:paddingTop="18dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:paddingBottom="24dp">
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_round_arrow_back_24"
|
||||||
|
android:tint="?colorOnBackground" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="56dp"
|
android:id="@+id/avatarPlaceholder"
|
||||||
android:layout_height="56dp">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/placeholderBack"
|
||||||
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" />
|
android:layout_margin="1dp"
|
||||||
|
tools:src="@color/colorOnUserAvatarAction" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/avatarPlaceholder"
|
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"
|
||||||
<com.meloda.fast.widget.CircleImageView
|
app:tint="@color/colorUserAvatarAction" />
|
||||||
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"
|
|
||||||
app:tint="@color/n2_500" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/online"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="end|bottom"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@color/n1_50" />
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_online_pc"
|
|
||||||
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
|
|
||||||
android:id="@+id/service"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="end|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/phantomIcon"
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:elevation="1dp"
|
|
||||||
android:src="@drawable/ic_phantom"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="@color/n2_10"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:id="@+id/callIcon"
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:elevation="1dp"
|
|
||||||
android:src="@drawable/ic_attachment_group_call"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="@color/n2_0" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/online"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="14dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_height="14dp"
|
||||||
android:orientation="vertical">
|
android:layout_gravity="end|bottom"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/title"
|
android:id="@+id/online_border"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:maxLines="1"
|
android:layout_gravity="center"
|
||||||
android:textColor="@color/n1_900"
|
android:src="?colorBackground" />
|
||||||
android:textSize="24sp"
|
|
||||||
tools:text="@tools:sample/full_names" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/status"
|
android:layout_width="10dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="10dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_gravity="center"
|
||||||
android:alpha="0.7"
|
android:src="@drawable/ic_online_pc"
|
||||||
android:maxLines="1"
|
android:tint="?colorPrimaryVariant" />
|
||||||
android:textColor="@color/n1_900"
|
|
||||||
tools:text="Online" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
<FrameLayout
|
||||||
|
android:id="@+id/service"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/timestamp"
|
android:layout_width="14dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="14dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_gravity="center"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:background="@drawable/ic_back"
|
||||||
android:layout_marginBottom="30dp"
|
android:backgroundTint="@color/colorUserAvatarAction"
|
||||||
android:elevation="2dp"
|
android:elevation="0.5dp" />
|
||||||
android:enabled="false"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="4dp"
|
|
||||||
android:textColor="@color/n1_900"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:chipBackgroundColor="@color/n1_100"
|
|
||||||
app:textEndPadding="12dp"
|
|
||||||
app:textStartPadding="12dp"
|
|
||||||
tools:text="today"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/phantomIcon"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:src="@drawable/ic_phantom"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="@color/colorOnUserAvatarAction"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/attachmentPanel"
|
android:id="@+id/callIcon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="10dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="10dp"
|
||||||
android:layout_marginHorizontal="12dp"
|
android:layout_gravity="center"
|
||||||
android:layout_marginBottom="35dp"
|
android:elevation="1dp"
|
||||||
android:background="@drawable/ic_chat_attachment_panel_background"
|
android:src="@drawable/ic_attachment_group_call"
|
||||||
android:backgroundTint="@color/n2_100"
|
android:visibility="gone"
|
||||||
android:minHeight="105dp"
|
app:tint="@color/colorOnUserAvatarAction"
|
||||||
android:orientation="vertical"
|
tools:visibility="visible" />
|
||||||
android:padding="16dp"
|
|
||||||
android:visibility="gone"
|
</FrameLayout>
|
||||||
app:layout_anchor="@+id/messagePanel"
|
</FrameLayout>
|
||||||
app:layout_anchorGravity="center_vertical|top"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/replyMessage"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
android:orientation="vertical">
|
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="?selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_round_close_20"
|
|
||||||
android:tint="@color/n1_800" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/replyMessageText"
|
android:id="@+id/title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:fontFamily="@font/google_sans_regular"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="?textColorPrimary"
|
android:textColor="?colorOnBackground"
|
||||||
android:textSize="16sp"
|
android:textSize="20sp"
|
||||||
app:fontFamily="@font/roboto_regular"
|
tools:text="@tools:sample/full_names" />
|
||||||
tools:text="Short Message." />
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.7"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
tools:text="Online" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<FrameLayout
|
<com.google.android.material.chip.Chip
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/timestamp"
|
||||||
android:layout_height="40dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/ic_message_panel_gradient"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:backgroundTint="@color/n1_50" />
|
android:layout_marginBottom="24dp"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="4dp"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:chipBackgroundColor="?colorBackgroundVariant"
|
||||||
|
app:chipCornerRadius="16dp"
|
||||||
|
app:chipStrokeWidth="0dp"
|
||||||
|
app:textEndPadding="12dp"
|
||||||
|
app:textStartPadding="12dp"
|
||||||
|
tools:text="today"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</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="?colorSurfaceVariant"
|
||||||
|
android:minHeight="105dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:translationY="50dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_anchor="@+id/messagePanel"
|
||||||
|
app:layout_anchorGravity="center_vertical|top"
|
||||||
|
tools:translationY="0dp"
|
||||||
|
tools:visibility="gone">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/messagePanel"
|
android:id="@+id/replyMessage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:orientation="vertical">
|
||||||
android:layout_margin="12dp"
|
|
||||||
android:background="@drawable/ic_message_panel_background"
|
|
||||||
android:backgroundTint="?colorSurface"
|
|
||||||
android:clickable="true"
|
|
||||||
android:elevation="3dp"
|
|
||||||
android:focusable="true"
|
|
||||||
android:minHeight="60dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatEditText
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/message"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:gravity="center_vertical"
|
||||||
android:layout_gravity="center_vertical"
|
android:orientation="horizontal">
|
||||||
android:layout_marginHorizontal="20dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:hint="@string/message_input_hint"
|
|
||||||
android:singleLine="true" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/attach"
|
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="?colorOnBackground"
|
||||||
|
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="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_round_close_20"
|
||||||
|
android:tint="?colorOnBackground" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/replyMessageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="18dp"
|
android:ellipsize="end"
|
||||||
android:layout_marginEnd="18dp"
|
android:maxLines="1"
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
android:textColor="?colorOnBackground"
|
||||||
android:src="@drawable/ic_baseline_attach_file_24"
|
android:textSize="16sp"
|
||||||
android:tint="?colorSecondary3" />
|
app:fontFamily="@font/roboto_regular"
|
||||||
|
tools:text="Short Message." />
|
||||||
<androidx.appcompat.widget.AppCompatImageButton
|
|
||||||
android:id="@+id/action"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:src="@drawable/ic_round_mic_24"
|
|
||||||
android:tint="?colorSecondary3" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<ProgressBar
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
android:id="@+id/progressBar"
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="@drawable/ic_message_panel_gradient"
|
||||||
|
android:backgroundTint="?colorBackground" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/messagePanel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:background="@drawable/ic_message_panel_background"
|
||||||
|
android:backgroundTint="@color/colorSurface"
|
||||||
|
android:clickable="true"
|
||||||
|
android:elevation="3dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:minHeight="60dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/message_input_hint"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
android:textColorHint="@color/colorOnBackground50" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/attach"
|
||||||
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:layout_marginTop="18dp"
|
||||||
android:visibility="gone"
|
android:layout_marginEnd="18dp"
|
||||||
tools:visibility="visible" />
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_attach_file_24"
|
||||||
|
android:tint="?colorPrimary" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="18dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_round_mic_24"
|
||||||
|
android:tint="?colorPrimary" />
|
||||||
|
|
||||||
</layout>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -1,255 +1,253 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="4dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="4dp"
|
android:layout_marginStart="20dp"
|
||||||
android:orientation="horizontal">
|
android:backgroundTint="?colorBackgroundVariant"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
tools:background="@drawable/ic_message_unread">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<FrameLayout
|
||||||
android:id="@+id/container"
|
android:layout_width="56dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="56dp">
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:backgroundTint="@color/n1_100"
|
android:id="@+id/avatar"
|
||||||
android:orientation="horizontal"
|
android:layout_width="match_parent"
|
||||||
android:paddingVertical="8dp"
|
android:layout_height="match_parent"
|
||||||
android:paddingStart="8dp"
|
tools:src="@tools:sample/avatars" />
|
||||||
android:paddingEnd="32dp"
|
|
||||||
tools:background="@drawable/ic_message_unread">
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="56dp"
|
android:id="@+id/avatarPlaceholder"
|
||||||
android:layout_height="56dp">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/placeholderBack"
|
||||||
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" />
|
android:layout_margin="1dp"
|
||||||
|
tools:src="@color/colorOnUserAvatarAction" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/avatarPlaceholder"
|
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"
|
||||||
<com.meloda.fast.widget.CircleImageView
|
app:tint="@color/colorUserAvatarAction" />
|
||||||
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"
|
|
||||||
app:tint="@color/n2_500" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/online"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="end|bottom"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@color/n1_50" />
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_online_pc"
|
|
||||||
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
|
|
||||||
android:id="@+id/service"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_gravity="end|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/phantomIcon"
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:elevation="1dp"
|
|
||||||
android:src="@drawable/ic_phantom"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="@color/n2_10"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.meloda.fast.widget.CircleImageView
|
|
||||||
android:id="@+id/callIcon"
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:elevation="1dp"
|
|
||||||
android:src="@drawable/ic_attachment_group_call"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="@color/n2_0" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<FrameLayout
|
||||||
|
android:id="@+id/online"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/online_border"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="?colorBackground" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_online_pc"
|
||||||
|
android:tint="?colorPrimaryVariant" />
|
||||||
|
|
||||||
|
</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/colorUserAvatarAction"
|
||||||
|
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/colorOnUserAvatarAction" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/service"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="end|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/colorUserAvatarAction"
|
||||||
|
android:elevation="0.5dp" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/phantomIcon"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:src="@drawable/ic_phantom"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="@color/colorOnUserAvatarAction"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<com.meloda.fast.widget.CircleImageView
|
||||||
|
android:id="@+id/callIcon"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:elevation="1dp"
|
||||||
|
android:src="@drawable/ic_attachment_group_call"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="@color/colorOnUserAvatarAction"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_toStartOf="@+id/date"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:fontFamily="@font/google_sans_regular"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:textColor="?textColorPrimary"
|
|
||||||
android:textSize="22sp"
|
|
||||||
tools:text="Title" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/counter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="-4dp"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:background="@drawable/ic_back"
|
|
||||||
android:backgroundTint="?colorSecondary3"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minWidth="18dp"
|
|
||||||
android:textColor="?colorOnSecondary3"
|
|
||||||
android:textSize="10sp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:text="12"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/date"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:alpha="0.5"
|
|
||||||
android:fontFamily="@font/roboto_regular"
|
|
||||||
android:gravity="end|center_vertical"
|
|
||||||
android:textColor="?textColorSecondaryVariant"
|
|
||||||
tools:text="20:00" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_toStartOf="@+id/date"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/textAttachment"
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:src="@drawable/ic_baseline_attach_file_24"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="?textColorSecondaryVariant"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/message"
|
android:id="@+id/title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:alpha="0.7"
|
android:layout_weight="1"
|
||||||
android:fontFamily="@font/roboto_regular"
|
android:fontFamily="@font/google_sans_regular"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:textColor="?textColorPrimary"
|
android:textColor="?colorOnBackground"
|
||||||
android:textSize="16sp"
|
android:textSize="20sp"
|
||||||
tools:text="Message" />
|
tools:text="Title" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/counter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="-4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:background="@drawable/ic_back"
|
||||||
|
android:backgroundTint="?colorOnBackgroundVariantContainer"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="18dp"
|
||||||
|
android:paddingHorizontal="2dp"
|
||||||
|
android:textColor="?colorOnBackgroundVariantOnContainer"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="12"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:fontFamily="@font/roboto_regular"
|
||||||
|
android:gravity="end|center_vertical"
|
||||||
|
android:textColor="?colorOutline"
|
||||||
|
tools:text="20:00" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/textAttachment"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:src="@drawable/ic_baseline_attach_file_24"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="?colorOutline"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.7"
|
||||||
|
android:fontFamily="@font/roboto_regular"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Message" />
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?selectableItemBackground" />
|
android:background="?selectableItemBackground"
|
||||||
</FrameLayout>
|
tools:visibility="gone" />
|
||||||
|
</FrameLayout>
|
||||||
</layout>
|
|
||||||
@@ -1,59 +1,53 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:background="@drawable/ic_play_button_circle_background"
|
||||||
|
android:backgroundTint="?colorPrimaryVariant">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_round_play_arrow_24"
|
||||||
|
app:tint="@color/a3_700" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginStart="8dp"
|
||||||
android:padding="4dp">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:layout_width="42dp"
|
android:id="@+id/title"
|
||||||
android:layout_height="42dp"
|
|
||||||
android:layout_gravity="top"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:background="@drawable/ic_play_button_circle_background"
|
|
||||||
android:backgroundTint="@color/a3_200">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_round_play_arrow_24"
|
|
||||||
app:tint="@color/a3_700" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:ellipsize="end"
|
||||||
android:orientation="vertical">
|
android:fontFamily="@font/google_sans_regular"
|
||||||
|
android:maxLines="1"
|
||||||
<com.google.android.material.textview.MaterialTextView
|
android:textColor="?colorOnBackground"
|
||||||
android:id="@+id/title"
|
android:textSize="18sp"
|
||||||
android:layout_width="wrap_content"
|
tools:text="Даня, дай Фаст" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="@font/google_sans_regular"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="Даня, дай Фаст" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/artist"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:alpha="0.8"
|
|
||||||
android:fontFamily="@font/roboto_regular"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
tools:text="Эльчин Оруджев | 0:36" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/artist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.8"
|
||||||
|
android:fontFamily="@font/roboto_regular"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
tools:text="Эльчин Оруджев | 0:36" />
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
|
||||||
|
|||||||
@@ -1,59 +1,54 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:background="@drawable/ic_play_button_circle_background"
|
||||||
|
android:backgroundTint="?colorPrimaryVariant">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_attachment_call"
|
||||||
|
app:tint="@color/a3_700" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:padding="4dp">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:layout_width="42dp"
|
android:id="@+id/type"
|
||||||
android:layout_height="42dp"
|
|
||||||
android:layout_gravity="top"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:background="@drawable/ic_play_button_circle_background"
|
|
||||||
android:backgroundTint="@color/a3_200">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_attachment_call"
|
|
||||||
app:tint="@color/a3_700" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:ellipsize="end"
|
||||||
android:orientation="vertical">
|
android:fontFamily="@font/google_sans_regular"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="Исходящий звонок" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/type"
|
android:id="@+id/state"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:alpha="0.8"
|
||||||
android:fontFamily="@font/google_sans_regular"
|
android:fontFamily="@font/roboto_regular"
|
||||||
android:maxLines="1"
|
android:textColor="?colorOnBackground"
|
||||||
android:textColor="@color/n1_800"
|
tools:text="Отменён" />
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="Исходящий звонок" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/state"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:alpha="0.8"
|
|
||||||
android:fontFamily="@font/roboto_regular"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
tools:text="Отменён" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
|
||||||
@@ -1,59 +1,53 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:background="@drawable/ic_play_button_circle_background"
|
||||||
|
android:backgroundTint="?colorPrimaryVariant">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_attachment_file"
|
||||||
|
app:tint="@color/a3_700" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginStart="8dp"
|
||||||
android:padding="4dp">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:layout_width="42dp"
|
android:id="@+id/title"
|
||||||
android:layout_height="42dp"
|
|
||||||
android:layout_gravity="top"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:background="@drawable/ic_play_button_circle_background"
|
|
||||||
android:backgroundTint="@color/a3_200">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_attachment_file"
|
|
||||||
app:tint="@color/a3_700" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:ellipsize="end"
|
||||||
android:orientation="vertical">
|
android:fontFamily="@font/google_sans_regular"
|
||||||
|
android:maxLines="1"
|
||||||
<com.google.android.material.textview.MaterialTextView
|
android:textColor="?colorOnBackground"
|
||||||
android:id="@+id/title"
|
android:textSize="18sp"
|
||||||
android:layout_width="wrap_content"
|
tools:text="Kids" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="@font/google_sans_regular"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="Kids" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/size"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:alpha="0.8"
|
|
||||||
android:fontFamily="@font/roboto_regular"
|
|
||||||
android:textColor="@color/n1_800"
|
|
||||||
tools:text="3.28 TB" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/size"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.8"
|
||||||
|
android:fontFamily="@font/roboto_regular"
|
||||||
|
android:textColor="?colorOnBackground"
|
||||||
|
tools:text="3.28 TB" />
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
</layout>
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/image"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
android:orientation="vertical">
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
</layout>
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/image"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
android:orientation="vertical">
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
</layout>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user