lil update
This commit is contained in:
@@ -4,6 +4,7 @@ plugins {
|
|||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
id("dagger.hilt.android.plugin")
|
id("dagger.hilt.android.plugin")
|
||||||
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -64,7 +65,7 @@ kapt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.20")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.21")
|
||||||
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation("com.github.yogacp:android-viewbinding:1.0.2")
|
implementation("com.github.yogacp:android-viewbinding:1.0.2")
|
||||||
|
|
||||||
implementation("io.coil-kt:coil:1.2.2")
|
implementation("io.coil-kt:coil:1.3.0")
|
||||||
|
|
||||||
implementation("com.google.code.gson:gson:2.8.7")
|
implementation("com.google.code.gson:gson:2.8.7")
|
||||||
implementation("org.jsoup:jsoup:1.14.1")
|
implementation("org.jsoup:jsoup:1.14.1")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
android:testOnly="false"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.MainActivity"
|
android:name=".activity.MainActivity"
|
||||||
|
|||||||
@@ -27,14 +27,19 @@ object VKAuth {
|
|||||||
|
|
||||||
const val redirectUrl = "https://oauth.vk.com/blank.html"
|
const val redirectUrl = "https://oauth.vk.com/blank.html"
|
||||||
|
|
||||||
fun getDirectAuthUrl(login: String, password: String, captcha: String = ""): String {
|
fun getDirectAuthUrl(
|
||||||
|
login: String,
|
||||||
|
password: String,
|
||||||
|
captchaSid: String? = null,
|
||||||
|
captchaKey: String? = null
|
||||||
|
): String {
|
||||||
return "https://oauth.vk.com/token?grant_type=password&" +
|
return "https://oauth.vk.com/token?grant_type=password&" +
|
||||||
"client_id=${VKConstants.VK_APP_ID}&" +
|
"client_id=${VKConstants.VK_APP_ID}&" +
|
||||||
"scope=$settings&" +
|
"scope=$settings&" +
|
||||||
"client_secret=${VKConstants.VK_APP_SECRET}&" +
|
"client_secret=${VKConstants.VK_APP_SECRET}&" +
|
||||||
"username=$login&" +
|
"username=$login&" +
|
||||||
"password=$password" +
|
"password=$password" +
|
||||||
(if (captcha.isBlank()) "" else "&$captcha") +
|
(if (captchaSid == null || captchaKey == null) "" else "&captcha_sid=$captchaSid&captcha_key=$captchaKey") +
|
||||||
"&v=${VKApi.API_VERSION}"
|
"&v=${VKApi.API_VERSION}"
|
||||||
// return "https://oauth.vk.com/token?grant_type=password&" +
|
// return "https://oauth.vk.com/token?grant_type=password&" +
|
||||||
// "client_id=2274003&" +
|
// "client_id=2274003&" +
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
package com.meloda.fast.api
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
import com.meloda.fast.api.model.response.GetConversationsResponse
|
||||||
|
import retrofit2.http.Field
|
||||||
|
import retrofit2.http.FormUrlEncoded
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
interface VKRepo {
|
interface VKRepo {
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(VKUrls.getConversations)
|
||||||
|
suspend fun getAllChats(
|
||||||
|
@Field("user_id") chatId: Int,
|
||||||
|
@Field("token") token: String
|
||||||
|
): Answer<GetConversationsResponse>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
object VKUrls {
|
||||||
|
|
||||||
|
const val getConversations = "messages.getConversations"
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ class VKConversation() : VKModel(), Cloneable {
|
|||||||
var isNoSound: Boolean = false
|
var isNoSound: Boolean = false
|
||||||
|
|
||||||
var membersCount: Int = 0
|
var membersCount: Int = 0
|
||||||
var title: String = ""
|
var title: String? = null
|
||||||
|
|
||||||
var pinnedMessage: VKMessage? = null
|
var pinnedMessage: VKMessage? = null
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ class VKConversation() : VKModel(), Cloneable {
|
|||||||
o.optJSONObject("chat_settings")?.let {
|
o.optJSONObject("chat_settings")?.let {
|
||||||
membersCount = it.optInt("members_count")
|
membersCount = it.optInt("members_count")
|
||||||
title = it.optString("title")
|
title = it.optString("title")
|
||||||
|
if (title?.isBlank() == true) title = null
|
||||||
|
|
||||||
it.optJSONObject("pinned_message")?.let { pinned ->
|
it.optJSONObject("pinned_message")?.let { pinned ->
|
||||||
pinnedMessage = VKMessage(pinned)
|
pinnedMessage = VKMessage(pinned)
|
||||||
@@ -109,13 +110,9 @@ class VKConversation() : VKModel(), Cloneable {
|
|||||||
|
|
||||||
fun isGroup() = type == Type.GROUP
|
fun isGroup() = type == Type.GROUP
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString() = title ?: ""
|
||||||
return title
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun clone(): VKConversation {
|
public override fun clone() = super.clone() as VKConversation
|
||||||
return super.clone() as VKConversation
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Type(val value: String) {
|
enum class Type(val value: String) {
|
||||||
NULL("null"),
|
NULL("null"),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.meloda.fast.api.model
|
package com.meloda.fast.api.model
|
||||||
|
|
||||||
|
import com.meloda.fast.base.adapter.BaseItem
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
abstract class VKModel : Serializable {
|
abstract class VKModel : BaseItem(), Serializable {
|
||||||
|
|
||||||
abstract val attachmentType: VKAttachments.Type
|
abstract val attachmentType: VKAttachments.Type
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.meloda.fast.api.model.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
class MessagesResponse(
|
||||||
|
val count: Int
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class GetConversationsResponse(val a: String) : Parcelable
|
||||||
|
// TODO: 7/12/2021 use hilt for this like in LIR and make simple conversations' screen
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
package com.meloda.fast.api.model.response
|
|
||||||
|
|
||||||
class ResponseMessagesGetConversations(
|
|
||||||
val count: Int
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.api.util
|
package com.meloda.fast.api.util
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import com.meloda.fast.api.model.*
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -27,9 +28,9 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sortMessagesByDate(
|
fun sortMessagesByDate(
|
||||||
values: ArrayList<com.meloda.fast.api.model.VKMessage>,
|
values: ArrayList<VKMessage>,
|
||||||
firstOnTop: Boolean
|
firstOnTop: Boolean
|
||||||
): ArrayList<com.meloda.fast.api.model.VKMessage> {
|
): ArrayList<VKMessage> {
|
||||||
values.sortWith { m1, m2 ->
|
values.sortWith { m1, m2 ->
|
||||||
val d1 = m1.date
|
val d1 = m1.date
|
||||||
val d2 = m2.date
|
val d2 = m2.date
|
||||||
@@ -45,9 +46,9 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sortConversationsByDate(
|
fun sortConversationsByDate(
|
||||||
values: ArrayList<com.meloda.fast.api.model.VKConversation>,
|
values: ArrayList<VKConversation>,
|
||||||
firstOnTop: Boolean
|
firstOnTop: Boolean
|
||||||
): ArrayList<com.meloda.fast.api.model.VKConversation> {
|
): ArrayList<VKConversation> {
|
||||||
values.sortWith { c1, c2 ->
|
values.sortWith { c1, c2 ->
|
||||||
val d1 = c1.lastMessage.date
|
val d1 = c1.lastMessage.date
|
||||||
val d2 = c2.lastMessage.date
|
val d2 = c2.lastMessage.date
|
||||||
@@ -121,25 +122,29 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getTitle(conversation: com.meloda.fast.api.model.VKConversation, peerUser: com.meloda.fast.api.model.VKUser?, peerGroup: com.meloda.fast.api.model.VKGroup?): String {
|
fun getTitle(
|
||||||
|
conversation: VKConversation,
|
||||||
|
peerUser: VKUser?,
|
||||||
|
peerGroup: VKGroup?
|
||||||
|
): String {
|
||||||
return when {
|
return when {
|
||||||
conversation.isUser() -> {
|
conversation.isUser() -> peerUser?.let { return it.toString() } ?: ""
|
||||||
peerUser?.let { return it.toString() } ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.isGroup() -> {
|
|
||||||
peerGroup?.let { return it.name } ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.isChat() -> {
|
conversation.isGroup() -> peerGroup?.let { return it.name } ?: ""
|
||||||
conversation.title
|
|
||||||
}
|
|
||||||
|
conversation.isChat() -> conversation.title ?: ""
|
||||||
|
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMessageTitle(message: com.meloda.fast.api.model.VKMessage, fromUser: com.meloda.fast.api.model.VKUser?, fromGroup: com.meloda.fast.api.model.VKGroup?): String {
|
fun getMessageTitle(
|
||||||
|
message: VKMessage,
|
||||||
|
fromUser: VKUser?,
|
||||||
|
fromGroup: VKGroup?
|
||||||
|
): String {
|
||||||
return when {
|
return when {
|
||||||
message.isFromUser() -> {
|
message.isFromUser() -> {
|
||||||
fromUser?.let { return it.toString() } ?: ""
|
fromUser?.let { return it.toString() } ?: ""
|
||||||
@@ -153,7 +158,11 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAvatar(conversation: com.meloda.fast.api.model.VKConversation, peerUser: com.meloda.fast.api.model.VKUser?, peerGroup: com.meloda.fast.api.model.VKGroup?): String {
|
fun getAvatar(
|
||||||
|
conversation: VKConversation,
|
||||||
|
peerUser: VKUser?,
|
||||||
|
peerGroup: VKGroup?
|
||||||
|
): String {
|
||||||
return when {
|
return when {
|
||||||
conversation.isUser() -> {
|
conversation.isUser() -> {
|
||||||
peerUser?.let { return it.photo200 } ?: ""
|
peerUser?.let { return it.photo200 } ?: ""
|
||||||
@@ -171,7 +180,11 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserAvatar(message: com.meloda.fast.api.model.VKMessage, fromUser: com.meloda.fast.api.model.VKUser?, fromGroup: com.meloda.fast.api.model.VKGroup?): String {
|
fun getUserAvatar(
|
||||||
|
message: VKMessage,
|
||||||
|
fromUser: VKUser?,
|
||||||
|
fromGroup: VKGroup?
|
||||||
|
): String {
|
||||||
return when {
|
return when {
|
||||||
message.isFromUser() -> {
|
message.isFromUser() -> {
|
||||||
fromUser?.let { return it.photo100 } ?: ""
|
fromUser?.let { return it.photo100 } ?: ""
|
||||||
@@ -185,7 +198,7 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserPhoto(user: com.meloda.fast.api.model.VKUser): String {
|
fun getUserPhoto(user: VKUser): String {
|
||||||
if (user.photo200.isEmpty()) {
|
if (user.photo200.isEmpty()) {
|
||||||
if (user.photo100.isEmpty()) {
|
if (user.photo100.isEmpty()) {
|
||||||
if (user.photo50.isEmpty()) {
|
if (user.photo50.isEmpty()) {
|
||||||
@@ -201,7 +214,7 @@ object VKUtil {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGroupPhoto(group: com.meloda.fast.api.model.VKGroup): String {
|
fun getGroupPhoto(group: VKGroup): String {
|
||||||
if (group.photo200.isEmpty()) {
|
if (group.photo200.isEmpty()) {
|
||||||
if (group.photo100.isEmpty()) {
|
if (group.photo100.isEmpty()) {
|
||||||
if (group.photo50.isEmpty()) {
|
if (group.photo50.isEmpty()) {
|
||||||
@@ -218,26 +231,26 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun parseConversations(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKConversation> {
|
fun parseConversations(array: JSONArray): ArrayList<VKConversation> {
|
||||||
val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
|
val conversations = arrayListOf<VKConversation>()
|
||||||
for (i in 0 until array.length()) {
|
for (i in 0 until array.length()) {
|
||||||
conversations.add(com.meloda.fast.api.model.VKConversation(array.optJSONObject(i)))
|
conversations.add(VKConversation(array.optJSONObject(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversations
|
return conversations
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseMessages(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKMessage> {
|
fun parseMessages(array: JSONArray): ArrayList<VKMessage> {
|
||||||
val messages = arrayListOf<com.meloda.fast.api.model.VKMessage>()
|
val messages = arrayListOf<VKMessage>()
|
||||||
for (i in 0 until array.length()) {
|
for (i in 0 until array.length()) {
|
||||||
messages.add(com.meloda.fast.api.model.VKMessage(array.optJSONObject(i)))
|
messages.add(VKMessage(array.optJSONObject(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isMessageHasFlag(mask: Int, flagName: String): Boolean {
|
fun isMessageHasFlag(mask: Int, flagName: String): Boolean {
|
||||||
val o: Any? = com.meloda.fast.api.model.VKMessage.flags[flagName]
|
val o: Any? = VKMessage.flags[flagName]
|
||||||
return if (o != null) { //has flag
|
return if (o != null) { //has flag
|
||||||
val flag = o as Int
|
val flag = o as Int
|
||||||
flag and mask > 0
|
flag and mask > 0
|
||||||
@@ -248,8 +261,8 @@ object VKUtil {
|
|||||||
//fromUser and fromGroup are null
|
//fromUser and fromGroup are null
|
||||||
@Deprecated("need to rewrite")
|
@Deprecated("need to rewrite")
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun parseLongPollMessage(array: JSONArray): com.meloda.fast.api.model.VKMessage {
|
fun parseLongPollMessage(array: JSONArray): VKMessage {
|
||||||
val message = com.meloda.fast.api.model.VKMessage()
|
val message = VKMessage()
|
||||||
|
|
||||||
val id = array.optInt(1)
|
val id = array.optInt(1)
|
||||||
val flags = array.optInt(2)
|
val flags = array.optInt(2)
|
||||||
@@ -276,33 +289,34 @@ object VKUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (it.has("source_act")) {
|
if (it.has("source_act")) {
|
||||||
message.action = com.meloda.fast.api.model.VKMessageAction().also { action ->
|
message.action = VKMessageAction().also { action ->
|
||||||
action.type = com.meloda.fast.api.model.VKMessageAction.Type.fromString(it.optString("source_act"))
|
action.type =
|
||||||
|
VKMessageAction.Type.fromString(it.optString("source_act"))
|
||||||
|
|
||||||
when (action.type) {
|
when (action.type) {
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.CHAT_CREATE -> {
|
VKMessageAction.Type.CHAT_CREATE -> {
|
||||||
action.text = it.optString("source_text")
|
action.text = it.optString("source_text")
|
||||||
}
|
}
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.TITLE_UPDATE -> {
|
VKMessageAction.Type.TITLE_UPDATE -> {
|
||||||
action.oldText = it.optString("source_old_text")
|
action.oldText = it.optString("source_old_text")
|
||||||
action.text = it.optString("source_text")
|
action.text = it.optString("source_text")
|
||||||
}
|
}
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.PIN_MESSAGE -> {
|
VKMessageAction.Type.PIN_MESSAGE -> {
|
||||||
action.memberId = it.optInt("source_mid")
|
action.memberId = it.optInt("source_mid")
|
||||||
action.conversationMessageId = it.optInt("source_chat_local_id")
|
action.conversationMessageId = it.optInt("source_chat_local_id")
|
||||||
|
|
||||||
it.optJSONObject("source_message")?.let { message ->
|
it.optJSONObject("source_message")?.let { message ->
|
||||||
action.message = com.meloda.fast.api.model.VKMessage(message)
|
action.message = VKMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.UNPIN_MESSAGE -> {
|
VKMessageAction.Type.UNPIN_MESSAGE -> {
|
||||||
action.memberId = it.optInt("source_mid")
|
action.memberId = it.optInt("source_mid")
|
||||||
action.conversationMessageId = it.optInt("source_chat_local_id")
|
action.conversationMessageId = it.optInt("source_chat_local_id")
|
||||||
}
|
}
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER,
|
VKMessageAction.Type.INVITE_USER,
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.KICK_USER,
|
VKMessageAction.Type.KICK_USER,
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.SCREENSHOT,
|
VKMessageAction.Type.SCREENSHOT,
|
||||||
com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER_BY_CALL -> {
|
VKMessageAction.Type.INVITE_USER_BY_CALL -> {
|
||||||
action.memberId = it.optInt("source_mid")
|
action.memberId = it.optInt("source_mid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import androidx.annotation.LayoutRes
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.meloda.fast.base.viewmodel.BaseVM
|
import com.meloda.fast.base.viewmodel.BaseVM
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
abstract class BaseVMFragment<VM : BaseVM> : BaseFragment {
|
abstract class BaseVMFragment<VM : BaseVM> : BaseFragment {
|
||||||
|
|
||||||
@@ -24,10 +24,6 @@ abstract class BaseVMFragment<VM : BaseVM> : BaseFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onEvent(event: VKEvent) {
|
protected open fun onEvent(event: VKEvent) {}
|
||||||
when (event) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,37 +5,25 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.meloda.fast.base.BaseHolder
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.add
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.addAll
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.clear
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.get
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.isEmpty
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.isNotEmpty
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.plusAssign
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.remove
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.removeAll
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.removeAt
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.set
|
|
||||||
import com.meloda.fast.extensions.LiveDataExtensions.size
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate", "CanBeParameter")
|
@Suppress("UNCHECKED_CAST", "unused", "MemberVisibilityCanBePrivate", "CanBeParameter")
|
||||||
abstract class BaseAdapter<Item, VH : BaseHolder>(
|
abstract class BaseAdapter<Item : BaseItem, VH : BaseHolder>(
|
||||||
var context: Context,
|
var context: Context,
|
||||||
values: ArrayList<Item>
|
values: ArrayList<Item>,
|
||||||
) : RecyclerView.Adapter<VH>() {
|
diffUtil: DiffUtil.ItemCallback<Item>
|
||||||
|
) : ListAdapter<Item, VH>(diffUtil) {
|
||||||
|
|
||||||
val cleanValues = MutableLiveData<MutableList<Item>>(arrayListOf())
|
val cleanValues = arrayListOf<Item>()
|
||||||
val values = MutableLiveData<MutableList<Item>>(arrayListOf())
|
val values = arrayListOf<Item>()
|
||||||
|
|
||||||
protected var inflater: LayoutInflater = LayoutInflater.from(context)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.values.value = values
|
addAll(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected var inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
var itemClickListener: OnItemClickListener? = null
|
var itemClickListener: OnItemClickListener? = null
|
||||||
var itemLongClickListener: OnItemLongClickListener? = null
|
var itemLongClickListener: OnItemLongClickListener? = null
|
||||||
|
|
||||||
@@ -44,13 +32,13 @@ abstract class BaseAdapter<Item, VH : BaseHolder>(
|
|||||||
itemLongClickListener = null
|
itemLongClickListener = null
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getItem(position: Int): Item {
|
override fun getItem(position: Int): Item {
|
||||||
return values[position]
|
return values[position]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(position: Int, item: Item) {
|
fun add(position: Int, item: Item) {
|
||||||
values.add(item, position)
|
values.add(position, item)
|
||||||
cleanValues.add(item, position)
|
cleanValues.add(position, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(item: Item) {
|
fun add(item: Item) {
|
||||||
@@ -64,8 +52,8 @@ abstract class BaseAdapter<Item, VH : BaseHolder>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addAll(position: Int, items: List<Item>) {
|
fun addAll(position: Int, items: List<Item>) {
|
||||||
values.addAll(items, position)
|
values.addAll(position, items)
|
||||||
cleanValues.addAll(items, position)
|
cleanValues.addAll(position, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAll(items: List<Item>) {
|
fun removeAll(items: List<Item>) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.meloda.fast.base.adapter
|
||||||
|
|
||||||
|
abstract class BaseItem
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.meloda.fast.base.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.meloda.fast.util.AndroidUtils
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class EmptyHeaderAdapter(
|
||||||
|
var context: Context
|
||||||
|
) : RecyclerView.Adapter<EmptyHeaderAdapter.Holder>() {
|
||||||
|
|
||||||
|
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(generateHeaderView())
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = 1
|
||||||
|
|
||||||
|
private fun generateHeaderView() = View(context).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
AndroidUtils.px(56).roundToInt()
|
||||||
|
)
|
||||||
|
isClickable = false
|
||||||
|
isEnabled = false
|
||||||
|
isFocusable = false
|
||||||
|
isInvisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package com.meloda.fast.base
|
package com.meloda.fast.base.adapter
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
|
||||||
abstract class BaseHolder(v: View) : RecyclerView.ViewHolder(v) {
|
abstract class BaseHolder(v: View) : RecyclerView.ViewHolder(v) {
|
||||||
|
|
||||||
open fun bind(position: Int) {
|
open fun bind(position: Int) {}
|
||||||
bind(position, mutableListOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun bind(position: Int, payloads: MutableList<Any>?) {}
|
open fun bind(position: Int, payloads: MutableList<Any>?) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class BindingHolder<B : ViewBinding>(protected val binding: B) : BaseHolder(binding.root)
|
||||||
@@ -2,11 +2,13 @@ package com.meloda.fast.common
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.meloda.fast.BuildConfig
|
import com.meloda.fast.BuildConfig
|
||||||
@@ -34,6 +36,8 @@ class AppGlobal : Application() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
lateinit var inputMethodManager: InputMethodManager
|
||||||
|
|
||||||
lateinit var preferences: SharedPreferences
|
lateinit var preferences: SharedPreferences
|
||||||
lateinit var locale: Locale
|
lateinit var locale: Locale
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
@@ -82,6 +86,8 @@ class AppGlobal : Application() {
|
|||||||
|
|
||||||
screenWidth = AndroidUtils.getDisplayWidth()
|
screenWidth = AndroidUtils.getDisplayWidth()
|
||||||
screenHeight = AndroidUtils.getDisplayHeight()
|
screenHeight = AndroidUtils.getDisplayHeight()
|
||||||
|
|
||||||
|
inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,11 @@ package com.meloda.fast.fragment.login
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@@ -19,14 +16,15 @@ import androidx.fragment.app.viewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseVMFragment
|
import com.meloda.fast.base.BaseVMFragment
|
||||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||||
import com.meloda.fast.base.viewmodel.VKEvent
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
|
import com.meloda.fast.databinding.DialogCaptchaBinding
|
||||||
import com.meloda.fast.databinding.FragmentLoginBinding
|
import com.meloda.fast.databinding.FragmentLoginBinding
|
||||||
import com.meloda.fast.fragment.main.MainFragment
|
import com.meloda.fast.fragment.main.MainFragment
|
||||||
import com.meloda.fast.util.KeyboardUtils
|
import com.meloda.fast.util.KeyboardUtils
|
||||||
@@ -36,7 +34,6 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
||||||
@@ -48,6 +45,7 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
private var lastPassword: String = ""
|
private var lastPassword: String = ""
|
||||||
|
|
||||||
private var errorTimer: Timer? = null
|
private var errorTimer: Timer? = null
|
||||||
|
private var captchaInputLayout: TextInputLayout? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@@ -56,20 +54,28 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
|
|
||||||
prepareViews()
|
prepareViews()
|
||||||
|
|
||||||
|
binding.loginInput.clearFocus()
|
||||||
|
|
||||||
setFragmentResultListener("validation") { _, bundle ->
|
setFragmentResultListener("validation") { _, bundle ->
|
||||||
lifecycleScope.launch { viewModel.getValidatedData(bundle) }
|
lifecycleScope.launch { viewModel.getValidatedData(bundle) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showCaptchaDialog(
|
||||||
|
// "https://www.vets4pets.com/syssiteassets/species/cat/kitten/tiny-kitten-in-field.jpg?width=1040",
|
||||||
|
// ""
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEvent(event: VKEvent) {
|
override fun onEvent(event: VKEvent) {
|
||||||
super.onEvent(event)
|
super.onEvent(event)
|
||||||
|
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is ShowError -> showErrorSnackbar(event.errorDescription)
|
||||||
is ShowCaptchaDialog -> showCaptchaDialog(event.captchaImage, event.captchaSid)
|
is ShowCaptchaDialog -> showCaptchaDialog(event.captchaImage, event.captchaSid)
|
||||||
is GoToValidationEvent -> goToValidation(event.redirectUrl)
|
is GoToValidationEvent -> goToValidation(event.redirectUrl)
|
||||||
is GoToMainEvent -> goToMain(event.haveAuthorized)
|
is GoToMainEvent -> goToMain(event.haveAuthorized)
|
||||||
StartProgressEvent -> onProgressStarted()
|
StartProgressEvent -> onProgressStarted()
|
||||||
StopProgressEvent -> onProgressEnded()
|
StopProgressEvent -> onProgressStopped()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +86,7 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
binding.progress.isVisible = true
|
binding.progress.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onProgressEnded() {
|
private fun onProgressStopped() {
|
||||||
binding.loginContainer.isVisible = true
|
binding.loginContainer.isVisible = true
|
||||||
binding.passwordContainer.isVisible = true
|
binding.passwordContainer.isVisible = true
|
||||||
binding.auth.isVisible = true
|
binding.auth.isVisible = true
|
||||||
@@ -150,7 +156,7 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
|
|
||||||
if (!validateInputData(loginString, passwordString)) return@setOnClickListener
|
if (!validateInputData(loginString, passwordString)) return@setOnClickListener
|
||||||
|
|
||||||
KeyboardUtils.hideKeyboardFrom(it)
|
KeyboardUtils.hideKeyboardFrom(requireView().findFocus())
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.login(
|
viewModel.login(
|
||||||
@@ -162,19 +168,29 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateInputData(loginString: String, passwordString: String): Boolean {
|
// TODO: 7/27/2021 extract strings to resources
|
||||||
|
private fun validateInputData(
|
||||||
|
loginString: String?,
|
||||||
|
passwordString: String?,
|
||||||
|
captchaCode: String? = null
|
||||||
|
): Boolean {
|
||||||
var isValidated = true
|
var isValidated = true
|
||||||
|
|
||||||
if (loginString.isEmpty()) {
|
if (loginString?.isEmpty() == true) {
|
||||||
isValidated = false
|
isValidated = false
|
||||||
setError("Input login", binding.loginLayout)
|
setError("Input login", binding.loginLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordString.isEmpty()) {
|
if (passwordString?.isEmpty() == true) {
|
||||||
isValidated = false
|
isValidated = false
|
||||||
setError("Input password", binding.passwordLayout)
|
setError("Input password", binding.passwordLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (captchaCode?.isEmpty() == true && captchaInputLayout != null) {
|
||||||
|
isValidated = false
|
||||||
|
setError("Input code", captchaInputLayout!!)
|
||||||
|
}
|
||||||
|
|
||||||
return isValidated
|
return isValidated
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,59 +214,60 @@ class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
|||||||
private fun clearErrors() {
|
private fun clearErrors() {
|
||||||
binding.loginLayout.error = ""
|
binding.loginLayout.error = ""
|
||||||
binding.passwordLayout.error = ""
|
binding.passwordLayout.error = ""
|
||||||
|
|
||||||
|
captchaInputLayout?.error = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 7/10/2021 extract layout to resources
|
|
||||||
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
|
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
|
||||||
val metrics = resources.displayMetrics
|
val captchaBinding = DialogCaptchaBinding.inflate(layoutInflater, null, false)
|
||||||
|
captchaInputLayout = captchaBinding.captchaLayout
|
||||||
|
|
||||||
val width = (metrics.widthPixels / 3.5).roundToInt()
|
captchaBinding.image.load(captchaImage) {
|
||||||
val height = metrics.heightPixels / 7
|
crossfade(100)
|
||||||
|
transformations(RoundedCornersTransformation(4f))
|
||||||
val image = ShapeableImageView(requireContext()).also {
|
|
||||||
it.layoutParams = ViewGroup.LayoutParams(width, height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val shapeModel = image.shapeAppearanceModel
|
|
||||||
image.shapeAppearanceModel = shapeModel.withCornerSize { 12f }
|
|
||||||
|
|
||||||
image.load(captchaImage) { crossfade(100) }
|
|
||||||
|
|
||||||
val captchaCodeEditText = TextInputEditText(requireContext())
|
|
||||||
captchaCodeEditText.setHint(R.string.captcha_hint)
|
|
||||||
|
|
||||||
captchaCodeEditText.layoutParams =
|
|
||||||
LinearLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(requireContext())
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
|
.setView(captchaBinding.root)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setTitle(R.string.input_captcha)
|
||||||
|
|
||||||
val layout = LinearLayout(requireContext())
|
val dialog = builder.show()
|
||||||
|
|
||||||
layout.orientation = LinearLayout.VERTICAL
|
captchaBinding.ok.setOnClickListener {
|
||||||
layout.gravity = Gravity.CENTER
|
val captchaCode = captchaBinding.captchaInput.text.toString().trim()
|
||||||
layout.addView(image)
|
|
||||||
layout.addView(captchaCodeEditText)
|
|
||||||
|
|
||||||
builder.setView(layout)
|
if (!validateInputData(
|
||||||
builder.setNegativeButton(android.R.string.cancel, null)
|
loginString = null,
|
||||||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
passwordString = null,
|
||||||
val captchaCode = captchaCodeEditText.text.toString().trim()
|
captchaCode = captchaCode
|
||||||
|
)
|
||||||
|
) return@setOnClickListener
|
||||||
|
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.login(
|
viewModel.login(
|
||||||
binding.webView,
|
webView = binding.webView,
|
||||||
lastEmail,
|
email = lastEmail,
|
||||||
lastPassword,
|
password = lastPassword,
|
||||||
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
|
captchaSid = captchaSid,
|
||||||
|
captchaKey = captchaCode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
captchaBinding.cancel.setOnClickListener { dialog.dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
builder.setTitle(R.string.input_captcha)
|
private fun showErrorSnackbar(errorDescription: String) {
|
||||||
builder.show()
|
val snackbar = Snackbar.make(
|
||||||
|
requireView(),
|
||||||
|
getString(R.string.error, errorDescription),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
)
|
||||||
|
|
||||||
|
snackbar.animationMode = Snackbar.ANIMATION_MODE_FADE
|
||||||
|
snackbar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goToValidation(redirectUrl: String) {
|
private fun goToValidation(redirectUrl: String) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.meloda.fast.fragment.login
|
package com.meloda.fast.fragment.login
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
@@ -19,20 +20,21 @@ import org.jsoup.Jsoup
|
|||||||
|
|
||||||
class LoginVM : BaseVM() {
|
class LoginVM : BaseVM() {
|
||||||
|
|
||||||
private var isWebViewPrepared = true
|
private var isWebViewPrepared = false
|
||||||
|
|
||||||
suspend fun login(
|
suspend fun login(
|
||||||
webView: WebView,
|
webView: WebView,
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
captcha: String = ""
|
captchaSid: String? = null,
|
||||||
|
captchaKey: String? = null
|
||||||
) {
|
) {
|
||||||
sendEvent(StartProgressEvent)
|
sendEvent(StartProgressEvent)
|
||||||
|
|
||||||
val urlToGo = VKAuth.getDirectAuthUrl(email, password, captcha)
|
val urlToGo = VKAuth.getDirectAuthUrl(email, password, captchaSid, captchaKey)
|
||||||
|
|
||||||
if (isWebViewPrepared) {
|
if (!isWebViewPrepared) {
|
||||||
isWebViewPrepared = false
|
isWebViewPrepared = true
|
||||||
|
|
||||||
webView.addJavascriptInterface(WebViewHandlerInterface(), "HtmlHandler")
|
webView.addJavascriptInterface(WebViewHandlerInterface(), "HtmlHandler")
|
||||||
|
|
||||||
@@ -52,11 +54,14 @@ class LoginVM : BaseVM() {
|
|||||||
@Suppress("MoveVariableDeclarationIntoWhen")
|
@Suppress("MoveVariableDeclarationIntoWhen")
|
||||||
private fun checkResponse(response: JSONObject) {
|
private fun checkResponse(response: JSONObject) {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
delay(1500)
|
|
||||||
sendEvent(StopProgressEvent)
|
|
||||||
|
|
||||||
if (response.has("error")) {
|
if (response.has("error")) {
|
||||||
|
sendEvent(StopProgressEvent)
|
||||||
|
|
||||||
val errorString = response.optString("error")
|
val errorString = response.optString("error")
|
||||||
|
val errorDescription = response.optString("error_description")
|
||||||
|
|
||||||
|
// TODO: 7/27/2021 use this with localized resources
|
||||||
|
// val errorType = response.optString("error_type")
|
||||||
|
|
||||||
when (errorString) {
|
when (errorString) {
|
||||||
"need_validation" -> {
|
"need_validation" -> {
|
||||||
@@ -68,10 +73,18 @@ class LoginVM : BaseVM() {
|
|||||||
val captchaImage = response.optString("captcha_img")
|
val captchaImage = response.optString("captcha_img")
|
||||||
val captchaSid = response.optString("captcha_sid")
|
val captchaSid = response.optString("captcha_sid")
|
||||||
|
|
||||||
|
Log.d("CAPTCHA", "captchaImage: $captchaImage")
|
||||||
|
|
||||||
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
|
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
tasksEventChannel.send(ShowError(errorDescription))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
delay(1500)
|
||||||
|
sendEvent(StopProgressEvent)
|
||||||
|
|
||||||
val userId = response.optInt("user_id", -1)
|
val userId = response.optInt("user_id", -1)
|
||||||
val accessToken = response.optString("access_token")
|
val accessToken = response.optString("access_token")
|
||||||
|
|
||||||
@@ -108,6 +121,7 @@ class LoginVM : BaseVM() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ShowError(val errorDescription: String) : VKEvent()
|
||||||
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
|
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
|
||||||
data class GoToValidationEvent(val redirectUrl: String) : VKEvent()
|
data class GoToValidationEvent(val redirectUrl: String) : VKEvent()
|
||||||
data class GoToMainEvent(val haveAuthorized: Boolean = true) : VKEvent()
|
data class GoToMainEvent(val haveAuthorized: Boolean = true) : VKEvent()
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.meloda.fast.fragment.messages
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import com.meloda.fast.api.model.VKConversation
|
||||||
|
import com.meloda.fast.base.adapter.BaseAdapter
|
||||||
|
import com.meloda.fast.base.adapter.BindingHolder
|
||||||
|
import com.meloda.fast.databinding.ItemConversationBinding
|
||||||
|
|
||||||
|
class ConversationsAdapter(context: Context, values: ArrayList<VKConversation>) :
|
||||||
|
BaseAdapter<VKConversation, ConversationsAdapter.ItemHolder>(
|
||||||
|
context, values, COMPARATOR
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val COMPARATOR = object : DiffUtil.ItemCallback<VKConversation>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: VKConversation,
|
||||||
|
newItem: VKConversation
|
||||||
|
) = false
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: VKConversation,
|
||||||
|
newItem: VKConversation
|
||||||
|
) = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ItemHolder(binding: ItemConversationBinding) :
|
||||||
|
BindingHolder<ItemConversationBinding>(binding) {
|
||||||
|
|
||||||
|
override fun bind(position: Int) {
|
||||||
|
binding.title.text = getItem(position).title ?: "HUI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
ItemHolder(ItemConversationBinding.inflate(inflater, parent, false))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,19 +3,81 @@ package com.meloda.fast.fragment.messages
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseFragment
|
import com.meloda.fast.base.BaseVMFragment
|
||||||
|
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||||
|
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||||
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
import com.meloda.fast.databinding.FragmentConversationsBinding
|
import com.meloda.fast.databinding.FragmentConversationsBinding
|
||||||
|
import com.meloda.fast.util.AndroidUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ConversationsFragment : BaseFragment(R.layout.fragment_conversations) {
|
class ConversationsFragment : BaseVMFragment<ConversationsVM>(R.layout.fragment_conversations) {
|
||||||
|
|
||||||
|
override val viewModel: ConversationsVM by viewModels()
|
||||||
private val binding: FragmentConversationsBinding by viewBinding()
|
private val binding: FragmentConversationsBinding by viewBinding()
|
||||||
|
|
||||||
|
private lateinit var adapter: ConversationsAdapter
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
prepareViews()
|
||||||
|
|
||||||
|
viewModel.loadConversations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: VKEvent) {
|
||||||
|
super.onEvent(event)
|
||||||
|
when (event) {
|
||||||
|
StartProgressEvent -> onProgressStarted()
|
||||||
|
StopProgressEvent -> onProgressStopped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onProgressStarted() {
|
||||||
|
if (adapter.isEmpty())
|
||||||
|
binding.progressBar.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onProgressStopped() {
|
||||||
|
binding.progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareViews() {
|
||||||
|
prepareRecyclerView()
|
||||||
|
prepareRefreshLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareRecyclerView() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareRefreshLayout() {
|
||||||
|
with(binding.refreshLayout) {
|
||||||
|
setProgressViewOffset(
|
||||||
|
true,
|
||||||
|
AndroidUtils.px(40).roundToInt(),
|
||||||
|
AndroidUtils.px(96).roundToInt()
|
||||||
|
)
|
||||||
|
setProgressBackgroundColorSchemeColor(
|
||||||
|
AndroidUtils.getThemeAttrColor(
|
||||||
|
requireContext(),
|
||||||
|
R.attr.colorSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setColorSchemeColors(
|
||||||
|
AndroidUtils.getThemeAttrColor(
|
||||||
|
requireContext(),
|
||||||
|
R.attr.colorAccent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setOnRefreshListener { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.meloda.fast.fragment.messages
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseVM
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ConversationsVM : BaseVM() {
|
||||||
|
|
||||||
|
fun loadConversations() = viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package com.meloda.fast.util
|
package com.meloda.fast.util
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import com.meloda.fast.common.AppGlobal
|
||||||
|
|
||||||
//TODO
|
|
||||||
object KeyboardUtils {
|
object KeyboardUtils {
|
||||||
|
|
||||||
fun hideKeyboardFrom(view: View) {
|
fun hideKeyboardFrom(focusedView: View?) {
|
||||||
// AppGlobal.inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
|
AppGlobal.inputMethodManager.hideSoftInputFromWindow(focusedView?.windowToken, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showKeyboard(focusedView: View) {
|
fun showKeyboard(viewToFocus: View) {
|
||||||
// AppGlobal.inputMethodManager.showSoftInput(focusedView, 0)
|
AppGlobal.inputMethodManager.showSoftInput(viewToFocus, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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="#ffffff"
|
||||||
|
android:pathData="M12,12H19C18.47,16.11 15.72,19.78 12,20.92V12H5V6.3L12,3.19M12,1L3,5V11C3,16.55 6.84,21.73 12,23C17.16,21.73 21,16.55 21,11V5L12,1Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
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="50dp"
|
||||||
|
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"
|
||||||
|
style="@style/AppTheme.Login.EditText.Icon"
|
||||||
|
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"
|
||||||
|
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||||
|
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:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
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: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:text="@android:string/ok"
|
||||||
|
app:elevation="0dp" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
||||||
@@ -1,45 +1,47 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/refreshLayout"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="conversations" />
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
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>
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="52dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:background="@android:color/transparent"
|
android:background="@drawable/toolbar_background"
|
||||||
app:elevation="0dp">
|
android:elevation="3dp"
|
||||||
|
app:title="@string/conversations"
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
app:titleCentered="true" />
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?actionBarSize"
|
|
||||||
app:titleCentered="true" />
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<include layout="@layout/recycler_view" />
|
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
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="center"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/no_items_view"
|
layout="@layout/no_items_view"
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
android:id="@+id/loginLayout"
|
android:id="@+id/loginLayout"
|
||||||
style="@style/Widget.TextInputLayout.NoError.Dense"
|
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
app:boxStrokeErrorColor="@android:color/transparent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/loginInput"
|
android:id="@+id/loginInput"
|
||||||
@@ -81,12 +82,10 @@
|
|||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:hint="@string/login_hint"
|
android:hint="@string/login_hint"
|
||||||
android:imeOptions="actionGo"
|
android:imeOptions="actionGo"
|
||||||
android:inputType="textEmailAddress"
|
android:inputType="textEmailAddress" />
|
||||||
android:textCursorDrawable="@null" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -110,6 +109,7 @@
|
|||||||
style="@style/Widget.TextInputLayout.NoError.Dense"
|
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:boxStrokeErrorColor="@android:color/transparent"
|
||||||
app:passwordToggleEnabled="true"
|
app:passwordToggleEnabled="true"
|
||||||
app:passwordToggleTint="?colorAccent">
|
app:passwordToggleTint="?colorAccent">
|
||||||
|
|
||||||
@@ -117,9 +117,9 @@
|
|||||||
android:id="@+id/passwordInput"
|
android:id="@+id/passwordInput"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
|
android:imeOptions="actionGo"
|
||||||
android:hint="@string/password_login_hint"
|
android:hint="@string/password_login_hint"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword" />
|
||||||
android:textCursorDrawable="@null" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<FrameLayout 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:id="@+id/conversationRoot"
|
android:id="@+id/root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
android:paddingBottom="2dp">
|
android:paddingBottom="2dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/conversationContainer"
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="6dp"
|
android:layout_margin="6dp"
|
||||||
@@ -27,15 +27,15 @@
|
|||||||
android:layout_weight="0">
|
android:layout_weight="0">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/conversationAvatar"
|
android:id="@+id/peerAvatar"
|
||||||
android:layout_width="56dp"
|
android:layout_width="56dp"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
tools:actualImageResource="@color/accent"
|
app:shapeAppearanceOverlay="@style/CircleImageView.56"
|
||||||
tools:src="?colorAccent" />
|
tools:src="?colorAccent" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/conversationUserOnline"
|
android:id="@+id/peerOnline"
|
||||||
android:layout_width="14dp"
|
android:layout_width="14dp"
|
||||||
android:layout_height="14dp"
|
android:layout_height="14dp"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/conversationType"
|
android:id="@+id/type"
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
tools:src="@drawable/ic_dialog_type_conversation" />
|
tools:src="@drawable/ic_dialog_type_conversation" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/conversationTitle"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
@@ -93,17 +93,17 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/conversationUserAvatar"
|
android:id="@+id/fromAvatar"
|
||||||
android:layout_width="20dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginStart="6dp"
|
android:layout_marginStart="6dp"
|
||||||
tools:actualImageResource="@color/accent"
|
app:shapeAppearanceOverlay="@style/CircleImageView.24"
|
||||||
tools:src="?colorAccent"
|
tools:src="?colorAccent"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/conversationTextAttachment"
|
android:id="@+id/textAttachment"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/conversationText"
|
android:id="@+id/text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
android:paddingEnd="6dp">
|
android:paddingEnd="6dp">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/conversationOut"
|
android:id="@+id/out"
|
||||||
android:layout_width="10dp"
|
android:layout_width="10dp"
|
||||||
android:layout_height="10dp"
|
android:layout_height="10dp"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/conversationDate"
|
android:id="@+id/date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end|top"
|
android:layout_gravity="end|top"
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
tools:text="now" />
|
tools:text="now" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/conversationCounter"
|
android:id="@+id/counter"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="primary">#ffffff</color>
|
<color name="primary">@color/accent</color>
|
||||||
<color name="primaryDark">#ffffff</color>
|
<color name="primaryDark">#ffffff</color>
|
||||||
<color name="accent">#4284F4</color>
|
<color name="accent">#4284F4</color>
|
||||||
<color name="navigationBar">#ffffff</color>
|
<color name="navigationBar">#ffffff</color>
|
||||||
|
|||||||
@@ -150,9 +150,11 @@
|
|||||||
<string name="password_login_hint">Password</string>
|
<string name="password_login_hint">Password</string>
|
||||||
<string name="email_login_hint">E-mail or phone number</string>
|
<string name="email_login_hint">E-mail or phone number</string>
|
||||||
<string name="log_in">Log in</string>
|
<string name="log_in">Log in</string>
|
||||||
<string name="captcha_hint">Captcha</string>
|
<string name="captcha_hint">Captcha code</string>
|
||||||
<string name="input_captcha">Input code from picture</string>
|
<string name="input_captcha">Input code from picture</string>
|
||||||
|
|
||||||
<string name="login_hint">Login</string>
|
<string name="login_hint">Login</string>
|
||||||
|
|
||||||
|
<string name="conversations">Conversations</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="AppTheme.ActivityAnimation" parent="@android:style/Animation.Activity">
|
<style name="AppTheme.ActivityAnimation" parent="@android:style/Animation.Activity">
|
||||||
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
|
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
|
||||||
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
|
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
|
||||||
@@ -11,4 +12,17 @@
|
|||||||
<item name="android:windowEnterAnimation">@anim/slide_up</item>
|
<item name="android:windowEnterAnimation">@anim/slide_up</item>
|
||||||
<item name="android:windowExitAnimation">@anim/slide_down</item>
|
<item name="android:windowExitAnimation">@anim/slide_down</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="CircleImageView">
|
||||||
|
<item name="cornerFamily">rounded</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="CircleImageView.56">
|
||||||
|
<item name="cornerRadius">56dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="CircleImageView.24">
|
||||||
|
<item name="cornerRadius">24dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
|
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
|
||||||
@android:color/transparent
|
@android:color/transparent
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
|
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
|
||||||
@@ -37,21 +38,15 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Toolbar.Title" parent="TextAppearance.MaterialComponents.Body1">
|
<style name="Toolbar.Title" parent="TextAppearance.MaterialComponents.Body1">
|
||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">20sp</item>
|
||||||
<item name="android:fontFamily">@font/tt_commons_medium</item>
|
<item name="android:textColor">@color/accent</item>
|
||||||
|
<item name="android:fontFamily">@font/google_sans_medium</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert" />
|
<style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert" />
|
||||||
|
|
||||||
<style name="AppTheme.Login.EditText" parent="">
|
<style name="AppTheme.Login.EditText" parent="Widget.Design.TextInputEditText">
|
||||||
<item name="android:layout_height">52dp</item>
|
<item name="android:layout_height">48dp</item>
|
||||||
<item name="android:background">@drawable/edittext_filled_background</item>
|
|
||||||
<item name="android:paddingStart">16dp</item>
|
|
||||||
<item name="android:paddingEnd">16dp</item>
|
|
||||||
<item name="android:layout_marginEnd">16dp</item>
|
|
||||||
<item name="android:textSize">14sp</item>
|
|
||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
|
||||||
<item name="android:textColorHint">?textColorSecondary</item>
|
|
||||||
<item name="fontFamily">@font/google_sans_regular</item>
|
<item name="fontFamily">@font/google_sans_regular</item>
|
||||||
<item name="android:singleLine">true</item>
|
<item name="android:singleLine">true</item>
|
||||||
<item name="android:maxLines">1</item>
|
<item name="android:maxLines">1</item>
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
|
||||||
classpath("com.android.tools.build:gradle:4.2.2")
|
classpath("com.android.tools.build:gradle:7.0.0")
|
||||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5")
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5")
|
||||||
|
|
||||||
classpath("com.google.dagger:hilt-android-gradle-plugin:2.37")
|
classpath("com.google.dagger:hilt-android-gradle-plugin:2.37")
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||||
|
|||||||
Reference in New Issue
Block a user