code saving

This commit is contained in:
2021-06-26 21:58:57 +03:00
parent 49d56d7a91
commit 98bbc6a791
273 changed files with 1076 additions and 6921 deletions
-81
View File
@@ -1,81 +0,0 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.meloda.fast"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation project(":mvp")
implementation project(":vksdk")
implementation project(":arrayutils")
implementation project(":netservices")
implementation project(":concurrent")
implementation project(":extensions")
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
implementation 'androidx.core:core-ktx:1.5.0-beta02'
implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.recyclerview:recyclerview:1.2.0-beta02'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.fragment:fragment-ktx:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.room:room-runtime:2.3.0-beta02'
kapt 'androidx.room:room-compiler:2.3.0-beta02'
implementation 'com.facebook.fresco:fresco:2.3.0'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.github.rahatarmanahmed:circularprogressview:2.5.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'ch.acra:acra:4.11.1'
def appCenterSdkVersion = '4.1.0'
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
}
+95
View File
@@ -0,0 +1,95 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}
android {
compileSdkVersion(ConfigData.compileSdkVersion)
buildToolsVersion(ConfigData.buildToolsVersion)
defaultConfig {
applicationId = "com.meloda.fast"
minSdkVersion(ConfigData.minSdkVersion)
targetSdkVersion(ConfigData.targetSdkVersion)
versionCode = ConfigData.versionCode
versionName = ConfigData.versionName
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
buildFeatures {
dataBinding = true
viewBinding = true
}
}
java {
val kotlinSrcDir = "src/main/kotlin"
println(sourceSets.names)
// val mainJavaSourceSet: SourceDirectorySet = sourceSets.getByName("main").java
// mainJavaSourceSet.srcDir(kotlinSrcDir)
// println(mainJavaSourceSet.srcDirs)
}
//java.sourceSets.create("src/main/kotlin")
//sourceSets {
// main.java.srcDirs += "src/main/kotlin"
//}
dependencies {
implementation(Deps.kotlin)
coreLibraryDesugaring(Deps.desugaring)
implementation(Deps.appCompat)
implementation(Deps.material)
implementation(Deps.core)
implementation(Deps.preferences)
implementation(Deps.swipeRefreshLayout)
implementation(Deps.recyclerView)
implementation(Deps.cardView)
implementation(Deps.fragment)
implementation(Deps.coroutineCore)
implementation(Deps.coroutineAndroid)
implementation(Deps.roomRuntime)
kapt(Deps.roomCompiler)
implementation(Deps.gson)
implementation(Deps.jsoup)
implementation(Deps.acra)
implementation("com.github.yogacp:android-viewbinding:1.0.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}")
implementation("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}")
implementation("com.squareup.retrofit2:retrofit:${Versions.retrofit}")
implementation("com.squareup.retrofit2:converter-gson:${Versions.retrofit}")
}
+1 -1
View File
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here. # Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the # You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle. # proguardFiles setting in build.gradle.kts.
# #
# For more details, see # For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html # http://developer.android.com/guide/developing/tools/proguard.html
-14
View File
@@ -26,20 +26,6 @@
android:name=".service.LongPollService" android:name=".service.LongPollService"
android:enabled="true" /> android:enabled="true" />
<activity android:name=".activity.MessagesActivityDeprecated" />
<activity
android:name=".activity.LoginActivityDeprecated"
android:label="@string/activity_login" />
<activity android:name=".activity.DropUserDataActivity" />
<activity
android:name=".activity.SettingsActivityDeprecated"
android:label="@string/navigation_settings" />
<activity android:name=".activity.UpdateActivityDeprecated" />
<receiver <receiver
android:name=".receiver.MinuteReceiver" android:name=".receiver.MinuteReceiver"
android:enabled="true"> android:enabled="true">
@@ -2,11 +2,11 @@ package com.meloda.fast
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.meloda.concurrent.EventInfo import com.meloda.fast.concurrent.EventInfo
import com.meloda.concurrent.TaskManager import com.meloda.fast.concurrent.TaskManager
import com.meloda.vksdk.VKApiKeys import com.meloda.fast.api.VKApiKeys
import com.meloda.vksdk.model.VKMessage import com.meloda.fast.api.model.VKMessage
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.api.util.VKUtil
import org.json.JSONArray import org.json.JSONArray
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -1,21 +0,0 @@
package com.meloda.fast.activity
import android.content.Intent
import android.os.Bundle
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseActivity
class DropUserDataActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
UserConfig.clear()
// TaskManager.execute { AppGlobal.database.clearAllTables() }
startActivity(Intent(this, MainActivity::class.java))
finishAffinity()
}
}
@@ -1,134 +0,0 @@
package com.meloda.fast.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.webkit.*
import android.widget.ProgressBar
import androidx.core.view.isVisible
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.extensions.ContextExtensions.color
import com.meloda.extensions.ContextExtensions.drawable
import com.meloda.extensions.DrawableExtensions.tint
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.widget.Toolbar
import com.meloda.vksdk.VKAuth
class LoginActivityDeprecated : BaseActivity() {
private lateinit var toolbar: Toolbar
private lateinit var progressBar: ProgressBar
private lateinit var webView: WebView
private lateinit var refreshLayout: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
initViews()
prepareToolbar()
prepareRefreshLayout()
prepareSettings()
val url = VKAuth.getUrl(UserConfig.API_ID, VKAuth.settings)
webView.loadUrl(url)
}
private fun initViews() {
toolbar = findViewById(R.id.toolbar)
progressBar = findViewById(R.id.progressBar)
webView = findViewById(R.id.webView)
refreshLayout = findViewById(R.id.refreshLayout)
}
@SuppressLint("SetJavaScriptEnabled")
private fun prepareSettings() {
webView.settings.javaScriptEnabled = true
webView.clearCache(true)
webView.webViewClient = VKWebClient()
val cookieManager = CookieManager.getInstance()
cookieManager.setAcceptCookie(true)
}
private fun prepareToolbar() {
setSupportActionBar(toolbar)
toolbar.navigationIcon = drawable(R.drawable.ic_close).tint(color(R.color.accent))
toolbar.setNavigationClickListener { onBackPressed() }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) onBackPressed()
return super.onOptionsItemSelected(item)
}
private fun prepareRefreshLayout() {
refreshLayout.apply {
setColorSchemeColors(color(R.color.accent))
setOnRefreshListener {
webView.reload()
isRefreshing = false
}
}
}
private inner class VKWebClient : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return true
}
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
progressBar.isVisible = true
view.isVisible = false
parseUrl(url)
}
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
progressBar.isVisible = false
view.isVisible = true
}
override fun onReceivedError(
view: WebView,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
Log.e("VKM WebView", error.toString())
}
}
private fun parseUrl(url: String) {
try {
if (url.startsWith(VKAuth.redirectUrl) && !url.contains("error=")) {
val auth = VKAuth.parseRedirectUrl(url)
val token = auth[0]
val id = auth[1].toInt()
UserConfig.token = token
UserConfig.userId = id
UserConfig.save()
finishAffinity()
startActivity(Intent(this, MainActivity::class.java))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -1,254 +1,23 @@
package com.meloda.fast.activity package com.meloda.fast.activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.viewbinding.library.activity.viewBinding
import android.view.MenuItem
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.navigation.NavigationView
import com.meloda.extensions.ContextExtensions.color
import com.meloda.extensions.ContextExtensions.drawable
import com.meloda.extensions.DrawableExtensions.tint
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseActivity import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.AppGlobal import com.meloda.fast.databinding.ActivityMainBinding
import com.meloda.fast.common.FragmentSwitcher import com.meloda.fast.fragment.LoginFragment
import com.meloda.fast.common.TimeManager
import com.meloda.fast.dialog.AccountDialog
import com.meloda.fast.fragment.*
import com.meloda.fast.service.LongPollService
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.Toolbar
import com.meloda.vksdk.VKApi
import com.meloda.vksdk.model.VKUser
import java.util.*
class MainActivity : BaseActivity(), class MainActivity : BaseActivity(R.layout.activity_main) {
NavigationView.OnNavigationItemSelectedListener,
BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var fragmentConversationsDeprecated: FragmentConversationsDeprecated private val binding: ActivityMainBinding by viewBinding()
private lateinit var fragmentFriendsDeprecated: FragmentFriendsDeprecated
private lateinit var settingsFragment: SettingsFragment
private var selectedId = 0
private lateinit var drawerLayout: DrawerLayout
lateinit var bottomBar: BottomNavigationView
private lateinit var navigationView: NavigationView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews() supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, LoginFragment())
// checkLogin() .commit()
if (UserConfig.isLoggedIn()) {
VKApi.init(Locale.getDefault().language, UserConfig.token, AppGlobal.handler)
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, ChatsFragment())
.commit()
} else {
bottomBar.isVisible = false
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, LoginFragment())
.commit()
}
// TimeManager.init(this)
// prepareFragments()
// prepareNavigationView()
// prepareBottomBar()
// checkLogin()
} }
private fun initViews() {
drawerLayout = findViewById(R.id.drawerLayout)
bottomBar = findViewById(R.id.bottomBar)
navigationView = findViewById(R.id.navigationView)
}
override fun onDestroy() {
TimeManager.destroy()
super.onDestroy()
}
private fun prepareFragments() {
fragmentConversationsDeprecated = FragmentConversationsDeprecated()
fragmentFriendsDeprecated = FragmentFriendsDeprecated(UserConfig.userId)
settingsFragment = SettingsFragment()
val containerId = R.id.fragmentContainer
FragmentSwitcher.addFragments(
supportFragmentManager,
containerId,
listOf(fragmentConversationsDeprecated)
)
}
fun initToolbar(toolbar: Toolbar) {
toolbar.navigationIcon =
drawable(R.drawable.ic_search).tint(color(R.color.text_secondary_60_alpha))
toolbar.setTitleMode(Toolbar.TitleMode.HINT)
toolbar.setTitle(R.string.action_search)
toolbar.setAvatarClickListener { openAccountDialog() }
}
private fun openAccountDialog() {
AccountDialog().show(supportFragmentManager, AccountDialog.TAG)
}
private fun prepareNavigationView() {
navigationView.layoutParams?.width = AppGlobal.screenWidth - AppGlobal.screenWidth / 6
navigationView.setNavigationItemSelectedListener(this)
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
private fun prepareBottomBar() {
// val menu = bottomBar.menu
//
// val navigationFriends = menu.add(R.string.navigation_friends)
// navigationFriends.icon = drawable(R.drawable.ic_people_outline)
//
// val navigationConversations = menu.add(R.string.navigation_conversations)
// navigationConversations.icon = drawable(R.drawable.ic_message_outline)
//
// val navigationImportant = menu.add(R.string.navigation_important)
// navigationImportant.icon = drawable(R.drawable.ic_star_border)
bottomBar.setOnNavigationItemSelectedListener(this)
}
private fun createMenuItem(menu: Menu, tag: String): MenuItem {
return when (tag) {
"friends" ->
menu.add("Friends").apply { icon = drawable(R.drawable.ic_people_outline) }
"conversations" ->
menu.add("Conversations").apply { icon = drawable(R.drawable.ic_message_outline) }
"important" ->
menu.add("Important").apply { icon = drawable(R.drawable.ic_star_border) }
else -> menu.add("")
}
}
private fun checkLogin() {
if (UserConfig.isLoggedIn()) {
startLongPoll()
loadProfileInfo()
} else {
}
}
private fun openMainScreen() {
selectedId = R.id.navigationConversations
bottomBar.selectedItemId = selectedId
openConversationsScreen()
}
private fun startLongPoll() {
startService(Intent(this, LongPollService::class.java))
}
private fun openConversationsScreen() {
FragmentSwitcher.showFragment(
supportFragmentManager,
fragmentConversationsDeprecated.javaClass.simpleName,
true
)
}
private fun openFriendsScreen() {
FragmentSwitcher.showFragment(
supportFragmentManager,
fragmentFriendsDeprecated.javaClass.simpleName,
true
)
}
private fun openSettingsScreen() {
startActivity(Intent(this, SettingsActivityDeprecated::class.java))
}
private fun loadProfileInfo() {
if (AndroidUtils.hasConnection()) {
// TaskManager.loadUser(
// VKApiKeys.UPDATE_USER, UserConfig.userId,
// object : OnResponseListener<VKUser> {
// override fun onResponse(response: VKUser) {
// prepareNavigationHeader(response)
// openMainScreen()
// }
//
// override fun onError(t: Throwable) {
// openMainScreen()
// }
// })
}
}
private fun prepareNavigationHeader(user: VKUser) {
ViewUtils.prepareNavigationHeader(navigationView.getHeaderView(0), user)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
switchFragment(item.itemId)
return true
}
private fun switchFragment(itemId: Int) {
var valid = true
when (itemId) {
R.id.navigationConversations -> {
openConversationsScreen()
}
R.id.navigationFriends -> {
openFriendsScreen()
}
R.id.navigationSettings -> {
openSettingsScreen()
}
else -> {
valid = false
}
}
if (!valid) return
if (selectedId != itemId) {
selectedId = itemId
navigationView.setCheckedItem(selectedId)
}
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
}
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(navigationView)) {
drawerLayout.closeDrawer(navigationView)
} else {
super.onBackPressed()
}
}
} }
@@ -1,402 +0,0 @@
package com.meloda.fast.activity
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.amulyakhare.textdrawable.TextDrawable
import com.meloda.extensions.ContextExtensions.color
import com.meloda.extensions.DrawableExtensions.tint
import com.meloda.fast.R
import com.meloda.fast.activity.ui.presenter.MessagesPresenterDeprecated
import com.meloda.fast.activity.ui.view.MessagesViewDeprecated
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.dialog.ProfileDialog
import com.meloda.fast.fragment.SettingsFragment
import com.meloda.fast.util.KeyboardUtils
import com.meloda.fast.util.TextUtils
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.CircleImageView
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKGroup
import com.meloda.vksdk.model.VKModel
import com.meloda.vksdk.model.VKUser
class MessagesActivityDeprecated : BaseActivity(), MessagesViewDeprecated {
companion object {
const val TAG = "MessagesActivity"
const val MESSAGES_COUNT = 30
const val TAG_EXTRA_TITLE = "title"
const val TAG_EXTRA_AVATAR = "avatar"
const val TAG_EXTRA_ID = "id"
const val TAG_EXTRA_USER = "user"
const val TAG_EXTRA_GROUP = "group"
}
private var isEdit = false
private var fabState = FabState.VOICE
private enum class FabState {
VOICE, SEND, EDIT, DELETE, BLOCKED
}
private var title = ""
private var avatar = ""
private var lastMessageText = ""
private var attachments = arrayListOf<VKModel>()
private var peerId = 0
private var dialogUser: VKUser? = null
private var dialogGroup: VKGroup? = null
private lateinit var presenterDeprecated: MessagesPresenterDeprecated
lateinit var recyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var toolbar: Toolbar
private lateinit var chatAvatar: CircleImageView
private lateinit var chatTitle: TextView
private lateinit var chatInfo: TextView
private lateinit var chatPanel: LinearLayout
private lateinit var chatMessage: EditText
private lateinit var chatSend: ImageButton
private lateinit var progressBar: ProgressBar
private lateinit var noItemsView: LinearLayout
private lateinit var noInternetView: LinearLayout
private lateinit var errorView: LinearLayout
override fun onDestroy() {
super.onDestroy()
presenterDeprecated.destroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_messages)
initViews()
initExtraData()
prepareToolbar()
prepareRefreshLayout()
prepareRecyclerView()
prepareEditText()
presenterDeprecated = MessagesPresenterDeprecated(this)
presenterDeprecated.setup(peerId, recyclerView)
}
private fun initViews() {
toolbar = findViewById(R.id.toolbar)
recyclerView = findViewById(R.id.recyclerView)
refreshLayout = findViewById(R.id.refreshLayout)
chatAvatar = findViewById(R.id.chatAvatar)
chatTitle = findViewById(R.id.chatTitle)
chatInfo = findViewById(R.id.chatInfo)
chatPanel = findViewById(R.id.chatPanel)
chatMessage = findViewById(R.id.chatMessage)
chatSend = findViewById(R.id.chatSend)
progressBar = findViewById(R.id.progressBar)
noItemsView = findViewById(R.id.noItemsView)
noInternetView = findViewById(R.id.noInternetView)
errorView = findViewById(R.id.errorView)
}
private fun initExtraData() {
peerId = intent.getIntExtra(TAG_EXTRA_ID, -1)
title = intent.getStringExtra(TAG_EXTRA_TITLE) ?: ""
avatar = intent.getStringExtra(TAG_EXTRA_AVATAR) ?: ""
dialogUser = intent.getSerializableExtra(TAG_EXTRA_USER) as VKUser?
dialogGroup = intent.getSerializableExtra(TAG_EXTRA_GROUP) as VKGroup?
}
private fun prepareToolbar() {
setSupportActionBar(toolbar)
val placeholder = TextDrawable
.builder()
.buildRound(TextUtils.getFirstLetterFromString(title), color(R.color.accent))
chatAvatar.setImageDrawable(placeholder)
// chatAvatar.loadImage(avatar, placeholder)
toolbar.setOnClickListener { presenterDeprecated.openProfile() }
chatAvatar.setOnClickListener { presenterDeprecated.openProfile() }
chatTitle.text = title
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar.navigationIcon.tint(color(R.color.accent))
}
private fun prepareRefreshLayout() {
refreshLayout.isEnabled = false
}
private fun prepareRecyclerView() {
recyclerView.layoutManager =
LinearLayoutManager(this, RecyclerView.VERTICAL, false).also {
it.stackFromEnd = true
}
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy < 0 && AppGlobal.inputMethodManager.isAcceptingText && AppGlobal.preferences.getBoolean(
SettingsFragment.KEY_HIDE_KEYBOARD_ON_SCROLL_UP,
true
)
) {
KeyboardUtils.hideKeyboardFrom(chatMessage)
}
}
})
}
private fun prepareEditText() {
chatMessage.addTextChangedListener {
fabState = if (it.toString().trim().isEmpty()) {
if (isEdit) {
FabState.DELETE
} else {
FabState.VOICE
}
} else {
if (isEdit) {
FabState.EDIT
} else {
FabState.SEND
}
}
refreshFabStyle()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.activity_messages, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()
R.id.messagesRefresh -> {
presenterDeprecated.updateData()
}
}
return super.onOptionsItemSelected(item)
}
private fun refreshFabStyle() {
chatSend.isClickable = true
when (fabState) {
FabState.VOICE -> {
chatSend.apply {
setImageResource(R.drawable.ic_mic)
setOnClickListener {
showVoiceRecordingTip()
}
setOnLongClickListener {
true
}
}
}
FabState.SEND -> {
chatSend.apply {
setImageResource(R.drawable.ic_send)
setOnClickListener {
presenterDeprecated.sendMessage(chatMessage.text.toString(), attachments)
}
setOnLongClickListener {
presenterDeprecated.sendMessage(chatMessage.text.toString(), attachments, false)
true
}
}
}
FabState.EDIT -> {
chatSend.apply {
setImageResource(R.drawable.ic_done)
setOnClickListener {
//editMessage()
}
setOnLongClickListener {
performClick()
true
}
}
}
FabState.DELETE -> {
chatSend.apply {
setImageResource(R.drawable.ic_trash_outline)
chatSend.setOnClickListener {
//deleteMessage
}
chatSend.setOnLongClickListener {
performClick()
true
}
}
}
FabState.BLOCKED -> {
chatSend.apply {
isClickable = false
setImageResource(R.drawable.ic_lock)
}
}
}
}
override fun showChatPanel() {
chatPanel.isVisible = true
}
override fun hideChatPanel() {
chatPanel.isVisible = false
}
override fun setWritingAllowed(allowed: Boolean) {
if (allowed) {
fabState = FabState.VOICE
chatSend.imageTintList = ColorStateList.valueOf(color(R.color.accent))
chatMessage.isEnabled = true
chatPanel.setBackgroundResource(R.drawable.chat_panel_background)
} else {
fabState = FabState.BLOCKED
chatSend.imageTintList = ColorStateList.valueOf(Color.WHITE)
chatMessage.isEnabled = false
chatMessage.setHintTextColor(Color.WHITE)
chatMessage.setHint(R.string.no_access)
chatPanel.setBackgroundResource(R.drawable.chat_panel_background_blocked)
}
}
override fun setChatInfo(info: String) {
chatInfo.text = info
chatInfo.isVisible = info.isNotEmpty()
}
override fun openProfile(conversation: VKConversation) {
conversation.let {
val profileDialog = ProfileDialog(it, title)
profileDialog.show(supportFragmentManager, ProfileDialog.TAG)
}
}
override fun showErrorLoadConversationAlert() {
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.error_occurred)
builder.setMessage(R.string.error_loading_message)
builder.setPositiveButton(R.string.retry) { _, _ ->
presenterDeprecated.loadConversation(peerId)
}
builder.setNegativeButton(R.string.no) { _, _ -> onBackPressed() }
builder.setCancelable(false)
builder.show()
}
override fun showVoiceRecordingTip() {
Toast.makeText(this, R.string.voice_record_tip, Toast.LENGTH_LONG).show()
}
override fun setMessageText(text: String) {
chatMessage.setText(text)
}
override fun showErrorSnackbar(t: Throwable) {
ViewUtils.showErrorSnackbar(getRootView(), t)
}
override fun prepareNoItemsView() {
}
override fun showNoItemsView() {
noItemsView.isVisible = true
}
override fun hideNoItemsView() {
noItemsView.isVisible = false
}
override fun prepareNoInternetView() {
}
override fun showNoInternetView() {
noInternetView.isVisible = true
}
override fun hideNoInternetView() {
noInternetView.isVisible = false
}
override fun prepareErrorView() {
}
override fun showErrorView() {
errorView.isVisible = true
}
override fun hideErrorView() {
errorView.isVisible = false
}
override fun showProgressBar() {
progressBar.isVisible = true
}
override fun hideProgressBar() {
progressBar.isVisible = false
}
override fun showRefreshLayout() {
refreshLayout.isRefreshing = true
}
override fun hideRefreshLayout() {
refreshLayout.isRefreshing = false
}
}
@@ -1,45 +0,0 @@
package com.meloda.fast.activity
import android.os.Bundle
import com.meloda.extensions.ContextExtensions.drawable
import com.meloda.fast.R
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.FragmentSwitcher
import com.meloda.fast.fragment.SettingsFragment
import com.meloda.fast.util.ColorUtils
import com.meloda.fast.widget.Toolbar
class SettingsActivityDeprecated : BaseActivity() {
private lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
initViews()
setSupportActionBar(toolbar)
toolbar.setNavigationClickListener { onBackPressed() }
toolbar.navigationIcon = drawable(R.drawable.ic_arrow_back)
toolbar.tintNavigationIcon(ColorUtils.getColorAccent(this))
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, SettingsFragment())
.commit()
}
private fun initViews() {
toolbar = findViewById(R.id.toolbar)
}
override fun onBackPressed() {
val currentFragment = FragmentSwitcher.getCurrentFragment(supportFragmentManager) ?: return
if (currentFragment.javaClass == SettingsFragment::class.java && (currentFragment as SettingsFragment).onBackPressed()) {
super.onBackPressed()
}
}
}
@@ -1,350 +0,0 @@
package com.meloda.fast.activity
import android.app.Activity
import android.app.DownloadManager
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import com.github.rahatarmanahmed.cpv.CircularProgressView
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.meloda.concurrent.TaskManager
import com.meloda.extensions.ContextExtensions.drawable
import com.meloda.extensions.FloatExtensions.int
import com.meloda.fast.BuildConfig
import com.meloda.fast.R
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.common.UpdateManager
import com.meloda.fast.model.NewUpdateInfo
import com.meloda.fast.receiver.DownloadUpdateReceiver
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.TimeUtils
import com.meloda.vksdk.OnResponseListener
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class UpdateActivityDeprecated : BaseActivity() {
companion object {
private const val FILE_BASE_PATH = "file://"
private const val MIME_TYPE = "application/vnd.android.package-archive"
private const val PROVIDER_PATH = ".provider"
}
private var isChecking = false
private var isNewUpdate = false
private var isDownloading = false
private var downloadId = 0L
private var lastCheckTime = 0L
private var newUpdate = NewUpdateInfo()
private lateinit var updateCheckUpdates: ExtendedFloatingActionButton
private lateinit var updateState: TextView
private lateinit var updateVersion: TextView
private lateinit var updateInfo: TextView
private lateinit var updateInfoLayout: LinearLayout
private lateinit var updateProgress: LinearLayout
private lateinit var updateProgressBar: CircularProgressView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_update)
initViews()
updateProgressBar.maxProgress = 100F
lastCheckTime = AppGlobal.preferences.getLong("updateCheckTime", 0)
refreshState()
checkUpdates()
updateCheckUpdates.setOnClickListener {
lastCheckTime = System.currentTimeMillis()
AppGlobal.preferences.edit().putLong("updateCheckTime", lastCheckTime).apply()
checkUpdates()
}
}
private fun initViews() {
updateCheckUpdates = findViewById(R.id.updateCheckUpdates)
updateInfo = findViewById(R.id.updateInfo)
updateVersion = findViewById(R.id.updateVersion)
updateState = findViewById(R.id.updateState)
updateInfoLayout = findViewById(R.id.updateInfoLayout)
updateProgress = findViewById(R.id.updateProgress)
updateProgressBar = updateProgress.getChildAt(0) as CircularProgressView
}
private fun installUpdate(context: Activity, file: File) {
val install = Intent(Intent.ACTION_VIEW)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
install.data = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + PROVIDER_PATH,
file
)
} else {
install.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
install.setDataAndType(Uri.fromFile(file), MIME_TYPE)
}
context.startActivity(install)
// context.finishAffinity()
}
private fun downloadUpdate() {
checkIsInstallingAllowed()
val timer = Timer()
updateCheckUpdates.shrink()
updateCheckUpdates.isClickable = false
isDownloading = true
refreshState()
TaskManager.execute {
val apkName = newUpdate.version
val destination =
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/$apkName.apk"
val uri = Uri.parse("$FILE_BASE_PATH$destination")
val file = File(destination)
if (file.exists()) file.delete()
val request = DownloadManager.Request(Uri.parse(newUpdate.downloadLink))
request.setTitle("${getString(R.string.app_name)} ${apkName}.apk")
request.setMimeType(MIME_TYPE)
request.setDestinationUri(uri)
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
val receiver = DownloadUpdateReceiver()
receiver.listener = object : OnResponseListener<Any?> {
override fun onResponse(response: Any?) {
timer.cancel()
installUpdate(this@UpdateActivityDeprecated, file)
unregisterReceiver(receiver)
runOnUiThread {
updateProgressBar.isIndeterminate = true
updateCheckUpdates.extend()
updateCheckUpdates.isClickable = true
isDownloading = false
refreshState()
}
}
override fun onError(t: Throwable) {
}
}
registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
downloadId = AppGlobal.downloadManager.enqueue(request)
timer.schedule(object : TimerTask() {
override fun run() {
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val cursor = AppGlobal.downloadManager.query(query)
if (cursor.moveToFirst()) {
val sizeIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val downloadedIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val size = cursor.getInt(sizeIndex)
val downloaded = cursor.getInt(downloadedIndex)
val progress = if (size != -1) (downloaded * 100.0F / size) else 0.0F
Log.d("Downloading update", "progress $progress%")
if (progress.int() > 0) {
runOnUiThread {
if (updateProgressBar.isIndeterminate) {
updateProgressBar.isIndeterminate = false
updateProgressBar.stopAnimation()
}
updateProgressBar.progress = progress
}
}
}
}
}, 0, 1000)
}
}
private fun checkUpdates() {
if (isChecking) return
isChecking = true
refreshState()
UpdateManager.checkUpdates(object : UpdateManager.OnUpdateListener {
override fun onNewUpdate(updateInfo: NewUpdateInfo) {
isChecking = false
isNewUpdate = true
this@UpdateActivityDeprecated.newUpdate = updateInfo
refreshState()
}
override fun onNoUpdates() {
isNewUpdate = false
isChecking = false
this@UpdateActivityDeprecated.newUpdate = NewUpdateInfo()
refreshState()
}
})
}
private fun checkIsInstallingAllowed() {
if (!AndroidUtils.isCanInstallUnknownApps(this)) {
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.warning)
builder.setMessage(R.string.update_unknown_sources_disabled_message)
builder.setPositiveButton(R.string.yes) { _, _ ->
AndroidUtils.openInstallUnknownAppsScreen(this)
}
builder.setNegativeButton(R.string.no, null)
builder.show()
}
}
private fun refreshState() {
when {
isChecking -> {
updateState.text = getString(R.string.update_state_checking)
setAlpha(updateInfoLayout, true)
setAlpha(updateProgress, false)
setAlpha(updateCheckUpdates, true)
}
isDownloading -> {
updateState.text = getString(R.string.update_state_downloading)
setAlpha(updateInfoLayout, true)
setAlpha(updateProgress, false)
setAlpha(updateCheckUpdates, true)
}
else -> {
if (isNewUpdate) {
updateCheckUpdates.text = getString(R.string.update_download)
updateCheckUpdates.icon = drawable(R.drawable.ic_file_download)
} else {
updateCheckUpdates.text = getString(R.string.update_check_updates)
updateCheckUpdates.icon = drawable(R.drawable.ic_refresh)
}
updateCheckUpdates.setOnClickListener {
if (isNewUpdate) {
downloadUpdate()
} else {
checkUpdates()
}
}
updateState.text =
getString(if (isNewUpdate) R.string.update_state_update_available else R.string.update_state_no_updates)
updateVersion.text =
if (isNewUpdate)
getString(
R.string.update_new_version,
newUpdate.version,
newUpdate.code
)
else getString(
R.string.update_current_version,
AppGlobal.versionName,
AppGlobal.versionCode
)
updateInfo.text =
when {
isNewUpdate -> if (newUpdate.changelog.isEmpty()) "" else getString(
R.string.update_changelog,
HtmlCompat.fromHtml(
newUpdate.changelog,
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
lastCheckTime.toString().isEmpty() || lastCheckTime == 0L -> ""
else -> getString(R.string.update_last_check_time, getCheckTime())
}
setAlpha(updateInfoLayout, false)
setAlpha(updateProgress, true)
setAlpha(updateCheckUpdates, false)
}
}
}
private fun getCheckTime(): String {
val time = lastCheckTime
val lastTime = TimeUtils.removeTime(Date(time))
val currentTime = TimeUtils.removeTime(Date(System.currentTimeMillis()))
val format = if (currentTime > lastTime) {
"dd.MM.yyyy HH:mm"
} else {
"HH:mm"
}
return SimpleDateFormat(format, Locale.getDefault()).format(time)
}
private fun setAlpha(view: View, toZero: Boolean) {
if (toZero) {
view.animate()
.alpha(0F)
.setDuration(250)
.withEndAction { view.isVisible = false }
.start()
} else {
view.animate()
.alpha(1F)
.setDuration(250)
.withStartAction { view.isVisible = true }
.start()
}
}
}
@@ -1,254 +0,0 @@
package com.meloda.fast.activity.ui.presenter
import androidx.recyclerview.widget.RecyclerView
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.activity.ui.repository.MessagesRepositoryDeprecated
import com.meloda.fast.activity.ui.view.MessagesViewDeprecated
import com.meloda.fast.adapter.MessagesAdapterDeprecated
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.listener.ItemLongClickListener
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpPresenter
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKModel
import kotlin.random.Random
class MessagesPresenterDeprecated(viewState: MessagesViewDeprecated) :
MvpPresenter<VKMessage, MessagesRepositoryDeprecated, MessagesViewDeprecated>(
viewState,
MessagesRepositoryDeprecated::class.java.name
),
ItemClickListener,
ItemLongClickListener,
TaskManager.OnEventListener {
companion object {
const val DEFAULT_MESSAGES_COUNT = 30
}
private lateinit var adapter: MessagesAdapterDeprecated
private lateinit var conversation: VKConversation
private var peerId: Int = -1
private var lastMessageText: String = ""
private lateinit var recyclerView: RecyclerView
override fun destroy() {
adapter.destroy()
}
fun setup(peerId: Int, recyclerView: RecyclerView) {
this.peerId = peerId
this.recyclerView = recyclerView
this.context = recyclerView.context
viewState.showProgressBar()
getCachedConversation(peerId)
}
fun updateData() {
adapter.clear()
loadMessages(peerId)
}
fun openProfile() {
viewState.openProfile(conversation)
}
private fun createAdapter() {
adapter = MessagesAdapterDeprecated(context!!, arrayListOf(), conversation).also {
it.itemClickListener = this
it.itemLongClickListener = this
}
recyclerView.adapter = adapter
}
private fun getCachedConversation(peerId: Int) {
repository.getCachedConversation(peerId, object : MvpOnResponseListener<VKConversation> {
override fun onResponse(response: VKConversation) {
conversation = response
createAdapter()
refreshConversation(response)
getCachedMessages(peerId, 0, DEFAULT_MESSAGES_COUNT,
object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
loadConversation(peerId)
loadMessages(peerId)
}
override fun onError(t: Throwable) {
loadConversation(peerId)
loadMessages(peerId)
}
})
}
override fun onError(t: Throwable) {
loadConversation(peerId)
loadMessages(peerId)
}
})
}
fun loadConversation(peerId: Int) {
if (adapter.isNotEmpty()) {
viewState.hideProgressBar()
}
repository.loadConversation(peerId, object : MvpOnResponseListener<VKConversation> {
override fun onResponse(response: VKConversation) {
conversation = response
createAdapter()
refreshConversation(response)
}
override fun onError(t: Throwable) {
viewState.hideProgressBar()
viewState.showErrorLoadConversationAlert()
}
})
}
private fun refreshConversation(conversation: VKConversation) {
checkIsWritingAllowed(conversation)
repository.getChatInfo(
conversation,
object : MvpOnResponseListener<String> {
override fun onResponse(response: String) {
viewState.setChatInfo(response)
}
override fun onError(t: Throwable) {
viewState.setChatInfo(AppGlobal.resources.getString(R.string.error_obtain_chat_info))
}
})
}
private fun checkIsWritingAllowed(conversation: VKConversation) {
if (conversation.isGroupChannel) {
viewState.hideChatPanel()
return
}
viewState.showChatPanel()
viewState.setWritingAllowed(conversation.isAllowed)
}
private fun getCachedMessages(
peerId: Int,
offset: Int = 0,
count: Int = DEFAULT_MESSAGES_COUNT,
listener: MvpOnResponseListener<Any?>? = null
) {
repository.getCachedMessages(peerId, offset, count,
object : MvpOnResponseListener<ArrayList<VKMessage>> {
override fun onResponse(response: ArrayList<VKMessage>) {
viewState.hideProgressBar()
fillAdapter(response, offset)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
if (adapter.isEmpty()) {
viewState.showProgressBar()
}
listener?.onError(t)
}
})
}
private fun loadMessages(peerId: Int, offset: Int = 0, count: Int = DEFAULT_MESSAGES_COUNT) {
repository.loadMessages(peerId, offset, count,
object : MvpOnResponseListener<ArrayList<VKMessage>> {
override fun onResponse(response: ArrayList<VKMessage>) {
fillAdapter(response, offset)
}
override fun onError(t: Throwable) {
}
})
}
private fun fillAdapter(
messages: ArrayList<VKMessage>,
offset: Int
) {
if (adapter.isEmpty()) adapter.isNotCachedValues = true
if (offset == 0) {
adapter.updateValues(messages)
} else {
adapter.addAll(messages)
}
adapter.notifyDataSetChanged()
if (offset == 0) recyclerView.scrollToPosition(adapter.itemCount - 1)
}
override fun onItemClick(position: Int) {
}
override fun onItemLongClick(position: Int) {
}
override fun onNewEvent(info: EventInfo<*>) {
}
fun sendMessage(
text: String = "",
attachments: ArrayList<VKModel> = arrayListOf(),
scrollToBottom: Boolean = true
) {
lastMessageText = text
val message = VKMessage().also {
it.date = (System.currentTimeMillis() / 1000).toInt()
it.text = text
it.isOut = true
it.peerId = peerId
it.fromId = UserConfig.userId
it.randomId = Random.nextInt()
}
viewState.setMessageText("")
adapter.addMessage(message, true, scrollToBottom)
repository.sendMessage(peerId, text, message.randomId, object : MvpOnResponseListener<Int> {
override fun onResponse(response: Int) {
message.id = response
// TaskManager.execute { MemoryCache.put(message) }
// TaskManager.loadMessage(VKApiKeys.UPDATE_MESSAGE, response)
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
viewState.setMessageText(lastMessageText)
}
})
}
}
@@ -1,170 +0,0 @@
package com.meloda.fast.activity.ui.repository
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.common.AppGlobal
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpRepository
import com.meloda.vksdk.OnResponseListener
import com.meloda.vksdk.VKApi
import com.meloda.vksdk.VKConstants
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.util.VKUtil
import java.util.*
class MessagesRepositoryDeprecated : MvpRepository<VKMessage>() {
fun loadMessages(
peerId: Int,
offset: Int,
count: Int,
listener: MvpOnResponseListener<ArrayList<VKMessage>>
) {
TaskManager.execute {
VKApi.messages()
.getHistory()
.peerId(peerId)
.reversed(false)
.extended(true)
.fields(VKConstants.USER_FIELDS + "," + VKConstants.GROUP_FIELDS)
.offset(offset)
.count(count)
.executeArray(
VKMessage::class.java,
object : OnResponseListener<ArrayList<VKMessage>> {
override fun onResponse(response: ArrayList<VKMessage>) {
TaskManager.execute {
cacheLoadedMessages(response)
// MemoryCache.putUsers(VKMessage.profiles)
// MemoryCache.putGroups(VKMessage.groups)
// MemoryCache.putConversations(VKMessage.conversations)
VKUtil.sortMessagesByDate(response, false)
sendResponse(listener, response)
}
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
fun getCachedMessages(
peerId: Int, offset: Int, count: Int,
listener: MvpOnResponseListener<ArrayList<VKMessage>>
) {
TaskManager.execute {
// val messages = MemoryCache.getMessagesByPeerId(peerId).asArrayList()
//
// if (messages.isEmpty()) {
// sendError(listener, NullPointerException("Messages is empty"))
// return@execute
// }
//
// VKUtil.sortMessagesByDate(messages, false)
//
// val preparedMessages = ArrayUtils.cut(messages, offset, count)
//
// sendResponseArray(listener, preparedMessages)
}
}
fun getCachedConversation(peerId: Int, listener: MvpOnResponseListener<VKConversation>) {
TaskManager.execute {
// val conversation = MemoryCache.getConversationById(peerId)
//
// if (conversation == null) {
// sendError(
// listener,
// NullPointerException("Conversation is not cached at the moment")
// )
// } else {
// sendResponse(listener, conversation)
// }
}
}
fun loadConversation(peerId: Int, listener: MvpOnResponseListener<VKConversation>) {
// TaskManager.loadConversation(
// VKApiKeys.UPDATE_CONVERSATION,
// peerId,
// object : OnResponseListener<VKConversation> {
// override fun onResponse(response: VKConversation) {
// sendResponse(listener, response)
// }
//
// override fun onError(t: Throwable) {
// sendError(listener, t)
// }
// })
}
fun getChatInfo(conversation: VKConversation, listener: MvpOnResponseListener<String>) {
when (conversation.type) {
VKConversation.Type.CHAT -> {
sendResponse(
listener,
AppGlobal.resources.getString(
if (conversation.isGroupChannel)
R.string.group_channel_members
else R.string.chat_members,
conversation.membersCount
)
)
}
VKConversation.Type.USER -> {
// val user = VKUtil.searchUser(conversation.conversationId,
// object : OnResponseListener<VKUser> {
// override fun onResponse(response: VKUser) {
// sendResponse(listener, VKUtil.getUserOnline(response))
// }
//
// override fun onError(t: Throwable) {
// sendError(listener, t)
// }
// })
//
// user?.let {
// sendResponse(listener, VKUtil.getUserOnline(it))
// }
}
else -> {
sendResponse(listener, "")
}
}
}
fun sendMessage(
peerId: Int,
message: String,
randomId: Int,
listener: MvpOnResponseListener<Int>
) {
TaskManager.execute {
VKApi.messages()
.send()
.peerId(peerId)
.message(message)
.randomId(randomId)
.executeArray(Int::class.java, object : OnResponseListener<ArrayList<Int>> {
override fun onResponse(response: ArrayList<Int>) {
val messageId = response[0]
sendResponse(listener, messageId)
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
private fun cacheLoadedMessages(messages: ArrayList<VKMessage>) {
// MemoryCache.putMessages(messages)
}
}
@@ -1,24 +0,0 @@
package com.meloda.fast.activity.ui.view
import com.meloda.mvp.MvpView
import com.meloda.vksdk.model.VKConversation
interface MessagesViewDeprecated : MvpView {
fun showChatPanel()
fun hideChatPanel()
fun setWritingAllowed(allowed: Boolean)
fun setChatInfo(info: String)
fun openProfile(conversation: VKConversation)
fun showErrorLoadConversationAlert()
fun showVoiceRecordingTip()
fun setMessageText(text: String)
}
@@ -1,71 +0,0 @@
package com.meloda.fast.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.VKLongPollParser
import com.meloda.fast.adapter.diffutil.ConversationsCallback
import com.meloda.fast.base.BaseAdapter
import com.meloda.fast.base.BaseHolder
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
class ChatsAdapter(context: Context, values: ArrayList<VKConversation>) :
BaseAdapter<VKConversation, ChatsAdapter.ViewHolder>(
context, values
),
TaskManager.OnEventListener,
VKLongPollParser.OnMessagesListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(view(R.layout.item_conversation, parent))
}
override fun notifyChanges(oldList: List<VKConversation>, newList: List<VKConversation>) {
val callback = ConversationsCallback(oldList, newList)
val diff = DiffUtil.calculateDiff(callback)
diff.dispatchUpdatesTo(this)
}
override fun onNewEvent(info: EventInfo<*>) {
}
inner class ViewHolder(v: View) : BaseHolder(v) {
override fun bind(position: Int, payloads: MutableList<Any>?) {
val conversation = getItem(position)
val lastMessage = conversation.lastMessage
TaskManager.execute {
}
}
}
override fun onNewMessage(message: VKMessage) {
TODO("Not yet implemented")
}
override fun onEditMessage(message: VKMessage) {
TODO("Not yet implemented")
}
override fun onRestoredMessage(message: VKMessage) {
TODO("Not yet implemented")
}
override fun onDeleteMessage(peerId: Int, messageId: Int) {
TODO("Not yet implemented")
}
override fun onReadMessage(peerId: Int, messageId: Int) {
TODO("Not yet implemented")
}
}
@@ -1,696 +0,0 @@
package com.meloda.fast.adapter
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.extensions.ContextExtensions.color
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.adapter.diffutil.ConversationsCallbackDeprecated
import com.meloda.fast.base.BaseAdapter
import com.meloda.fast.base.BaseHolder
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.util.VKUtils
import com.meloda.fast.widget.CircleImageView
import com.meloda.vksdk.OnResponseListener
import com.meloda.vksdk.VKApiKeys
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKGroup
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKUser
import com.meloda.vksdk.util.VKUtil
@Suppress("UNCHECKED_CAST")
class ConversationsAdapterDeprecated(
val recyclerView: RecyclerView,
values: ArrayList<VKConversation>
) : BaseAdapter<VKConversation, ConversationsAdapterDeprecated.ConversationHolder>(
recyclerView.context,
values
), TaskManager.OnEventListener {
companion object {
private const val TAG = "ConversationsAdapter"
}
var isLoading: Boolean = false
private var currentPosition: Int = -1
init {
TaskManager.addOnEventListener(this)
}
override fun destroy() {
TaskManager.removeOnEventListener(this)
}
override fun onNewEvent(info: EventInfo<*>) {
when (info.key) {
VKApiKeys.NEW_MESSAGE.name -> addMessage(info.data as VKMessage)
VKApiKeys.EDIT_MESSAGE.name -> editMessage(info.data as VKMessage)
VKApiKeys.RESTORE_MESSAGE.name -> restoreMessage(info.data as VKMessage)
VKApiKeys.READ_MESSAGE.name -> readMessage(
(info.data as Array<Int>)[0],
(info.data as Array<Int>)[1]
)
VKApiKeys.DELETE_MESSAGE.name -> deleteMessage(
(info.data as Array<Int>)[0],
(info.data as Array<Int>)[1]
)
VKApiKeys.UPDATE_CONVERSATION.name -> updateConversation(info.data as Int)
VKApiKeys.UPDATE_MESSAGE.name -> updateMessage(info.data as Int)
VKApiKeys.UPDATE_USER.name -> updateUsers(info.data as ArrayList<Int>)
VKApiKeys.UPDATE_GROUP.name -> updateGroups(info.data as ArrayList<Int>)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationHolder {
return ConversationHolder(view(R.layout.item_conversation, parent))
}
override fun onBindViewHolder(
holder: ConversationHolder,
position: Int,
payloads: MutableList<Any>
) {
currentPosition = position
initListeners(holder.itemView, position)
holder.bind(position, payloads)
}
fun isLastItem() = currentPosition >= itemCount - 1
inner class ConversationHolder(v: View) : BaseHolder(v) {
private var attachments: ImageView = v.findViewById(R.id.conversationTextAttachment)
private var text: TextView = v.findViewById(R.id.conversationText)
private var title: TextView = v.findViewById(R.id.conversationTitle)
private var avatar: ImageView = v.findViewById(R.id.conversationAvatar)
private var online: ImageView = v.findViewById(R.id.conversationUserOnline)
private var out: CircleImageView = v.findViewById(R.id.conversationOut)
private var counter: TextView = v.findViewById(R.id.conversationCounter)
private var date: TextView = v.findViewById(R.id.conversationDate)
private var type: ImageView = v.findViewById(R.id.conversationType)
private var userAvatar: ImageView = v.findViewById(R.id.conversationUserAvatar)
private var root: FrameLayout = v.findViewById(R.id.conversationRoot)
private val colorHighlight = context.color(R.color.accent)
override fun bind(position: Int) {
bind(position, mutableListOf())
}
override fun bind(position: Int, payloads: MutableList<Any>?) {
Log.d(TAG, "bind position: $position")
val conversation = getItem(position)
val lastMessage = conversation.lastMessage
TaskManager.execute {
val peerUser: VKUser? =
if (conversation.isUser()) CacheStorage.usersStorage.getUser(conversation.id) else null
val peerGroup: VKGroup? =
if (conversation.isGroup()) CacheStorage.groupsStorage.getGroup(conversation.id) else null
val fromUser: VKUser? =
if (lastMessage.isFromUser()) CacheStorage.usersStorage.getUser(lastMessage.fromId) else null
val fromGroup: VKGroup? =
if (lastMessage.isFromGroup()) CacheStorage.groupsStorage.getGroup(lastMessage.fromId) else null
conversation.peerUser = peerUser
conversation.peerGroup = peerGroup
lastMessage.fromUser = fromUser
lastMessage.fromGroup = fromGroup
post {
val dialogTitle = setTitle(conversation, peerUser, peerGroup)
if (payloads != null && payloads.isNotEmpty()) {
for (payload in payloads) {
when (payload) {
ConversationsCallbackDeprecated.CONVERSATION -> {
setUserOnline(conversation, peerUser)
prepareUserAvatar(
conversation,
lastMessage,
fromUser,
fromGroup
)
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
setDialogType(conversation)
setIsRead(lastMessage, conversation)
setCounterBackground(conversation)
}
ConversationsCallbackDeprecated.MESSAGE -> {
prepareUserAvatar(
conversation,
lastMessage,
fromUser,
fromGroup
)
prepareAttachments(lastMessage)
setIsRead(lastMessage, conversation)
setDate(lastMessage)
}
ConversationsCallbackDeprecated.GROUP -> {
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
}
ConversationsCallbackDeprecated.USER -> {
setUserOnline(conversation, peerUser)
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
}
ConversationsCallbackDeprecated.EDIT_MESSAGE -> {
prepareUserAvatar(
conversation,
lastMessage,
fromUser,
fromGroup
)
prepareAttachments(lastMessage)
setIsRead(lastMessage, conversation)
setDate(lastMessage)
}
ConversationsCallbackDeprecated.DATE -> {
setDate(lastMessage)
}
ConversationsCallbackDeprecated.ONLINE -> {
setUserOnline(conversation, peerUser)
}
ConversationsCallbackDeprecated.ATTACHMENTS -> {
prepareAttachments(lastMessage)
}
ConversationsCallbackDeprecated.AVATAR -> {
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
}
ConversationsCallbackDeprecated.USER_AVATAR -> {
prepareUserAvatar(
conversation,
lastMessage,
fromUser,
fromGroup
)
}
ConversationsCallbackDeprecated.READ -> {
setIsRead(lastMessage, conversation)
}
ConversationsCallbackDeprecated.NOTIFICATIONS -> {
setCounterBackground(conversation)
}
}
}
return@post
}
setUserOnline(conversation, peerUser)
prepareUserAvatar(conversation, lastMessage, fromUser, fromGroup)
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
setDialogType(conversation)
prepareAttachments(lastMessage)
setIsRead(lastMessage, conversation)
setDate(lastMessage)
setCounterBackground(conversation)
root.isVisible = true
}
}
}
private fun setTitle(
conversation: VKConversation,
peerUser: VKUser?,
peerGroup: VKGroup?
): String {
val dialogTitle = VKUtil.getTitle(conversation, peerUser, peerGroup)
title.text = dialogTitle
return dialogTitle
}
private fun setUserOnline(conversation: VKConversation, peerUser: VKUser?) {
val onlineIcon = VKUtils.getUserOnlineIcon(context, conversation, peerUser)
online.setImageDrawable(onlineIcon)
online.isVisible = onlineIcon != null
}
private fun prepareUserAvatar(
conversation: VKConversation,
lastMessage: VKMessage,
fromUser: VKUser?,
fromGroup: VKGroup?
) {
if ((conversation.isChat() || lastMessage.isOut) && !conversation.isGroupChannel) {
userAvatar.isVisible = true
val avatar = VKUtil.getUserAvatar(lastMessage, fromUser, fromGroup)
if (avatar.isEmpty()) {
userAvatar.setImageDrawable(ColorDrawable(Color.TRANSPARENT))
} else {
userAvatar.setImageURI(Uri.parse(avatar))
}
} else {
userAvatar.isVisible = false
userAvatar.setImageDrawable(null)
}
}
private fun prepareAvatar(
dialogTitle: String,
conversation: VKConversation,
peerUser: VKUser?,
peerGroup: VKGroup?
) {
val dialogAvatarPlaceholder = VKUtils.getAvatarPlaceholder(context, dialogTitle)
avatar.setImageDrawable(dialogAvatarPlaceholder)
val avatarLink = VKUtil.getAvatar(conversation, peerUser, peerGroup)
if (avatarLink.isNotEmpty()) {
avatar.setImageURI(Uri.parse(avatarLink))
}
}
private fun setDialogType(conversation: VKConversation) {
// val dDialogType = VKUtil.getDialogType(context, conversation)
//
// type.setImageDrawable(dDialogType)
// type.isVisible = dDialogType != null
// type.isVisible = false
}
private fun prepareAttachments(lastMessage: VKMessage) {
// text.apply {
// compoundDrawablePadding = 0
// setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
// }
attachments.isVisible = false
if (lastMessage.action == null) {
when {
lastMessage.attachments.isNotEmpty() -> {
val attachmentString =
VKUtils.getAttachmentText(context, lastMessage.attachments)
val attachmentText =
if (lastMessage.text.isEmpty()) attachmentString else lastMessage.text
val startIndex =
if (lastMessage.text.isEmpty()) 0 else lastMessage.text.length
val span = SpannableString(attachmentText).apply {
setSpan(
ForegroundColorSpan(colorHighlight),
startIndex,
attachmentText.length,
0
)
}
val attachmentDrawable =
VKUtils.getAttachmentDrawable(context, lastMessage.attachments)
text.text = span
attachments.isVisible = true
attachments.setImageDrawable(attachmentDrawable)
// text.apply {
// text = span
// setCompoundDrawablesRelativeWithIntrinsicBounds(
// attachmentDrawable,
// null,
// null,
// null
// )
// compoundDrawablePadding = 8
// }
}
lastMessage.fwdMessages.isNotEmpty() -> {
val fwdText =
VKUtils.getFwdText(context, lastMessage.getForwardedMessages())
val span = SpannableString(fwdText).apply {
setSpan(ForegroundColorSpan(colorHighlight), 0, fwdText.length, 0)
}
text.text = span
}
else -> {
text.text = if (text.maxLines == 1) lastMessage.text.replace(
"\n",
" "
) else lastMessage.text
}
}
} else {
VKUtils.getActionText(context, lastMessage,
object : OnResponseListener<String> {
override fun onResponse(response: String) {
val span = SpannableString(response).apply {
setSpan(
ForegroundColorSpan(colorHighlight),
0,
response.length,
0
)
}
text.text = span
}
override fun onError(t: Throwable) {
TODO("Not yet implemented")
}
})
}
if (lastMessage.attachments.isEmpty() && lastMessage.fwdMessages.isEmpty() && lastMessage.action == null && TextUtils.isEmpty(
lastMessage.text
)
) {
val unknown = "..."
val span = SpannableString(unknown).apply {
setSpan(ForegroundColorSpan(colorHighlight), 0, unknown.length, 0)
}
text.text = span
}
}
private fun setIsRead(lastMessage: VKMessage, conversation: VKConversation) {
val isRead =
((lastMessage.isOut && conversation.outReadMessageId == conversation.lastMessageId ||
!lastMessage.isOut && conversation.inReadMessageId == conversation.lastMessageId) && conversation.lastMessageId == lastMessage.id) && conversation.unreadCount == 0
if (isRead) {
counter.visibility = View.GONE
out.visibility = View.GONE
} else {
if (lastMessage.isOut) {
out.visibility = View.VISIBLE
counter.visibility = View.GONE
counter.text = ""
} else {
out.visibility = View.GONE
counter.visibility = View.VISIBLE
counter.text = conversation.unreadCount.toString()
}
}
}
private fun setDate(lastMessage: VKMessage) {
val dateText = VKUtils.getTime(context, lastMessage)
date.text = dateText
}
private fun setCounterBackground(conversation: VKConversation) {
counter.background.setTint(if (conversation.isNotificationsDisabled()) Color.GRAY else colorHighlight)
}
}
@Deprecated("Message is bad")
private fun addMessage(message: VKMessage) {
val index = searchConversationIndex(message.peerId)
val oldList = ArrayList(values)
if (index >= 0) {
val currentConversation = this[index]
val conversation = prepareConversation(currentConversation, message)
removeAt(index)
add(0, conversation)
notifyChanges(oldList)
} else {
// TaskManager.loadConversation(
// VKApiKeys.UPDATE_CONVERSATION,
// message.peerId,
// null
// )
TaskManager.execute {
// val cachedConversation = MemoryCache.getConversationById(message.peerId)
// if (cachedConversation != null) {
// add(0, prepareConversation(cachedConversation, message))
// post { notifyChanges(oldList) }
// return@execute
// }
val tempConversations = VKConversation().apply {
id = message.peerId
localId =
if (VKUtil.isChatId(id)) id - 2000000000 else id
type =
if (id < 0) VKConversation.Type.GROUP else if (id > 2000000000) VKConversation.Type.CHAT else VKConversation.Type.USER
lastMessage = message
lastMessageId = message.id
}
add(0, tempConversations)
post { notifyChanges(oldList) }
}
}
val firstVisiblePosition =
(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
if (firstVisiblePosition <= 1) recyclerView.scrollToPosition(0)
}
private fun editMessage(message: VKMessage) {
val index = searchConversationIndex(message.peerId)
if (index == -1) return
val conversation = getItem(index)
if (conversation.lastMessageId != message.id) return
conversation.lastMessage = message
notifyItemChanged(index, ConversationsCallbackDeprecated.EDIT_MESSAGE)
}
private fun readMessage(peerId: Int, messageId: Int) {
val index = searchConversationIndex(peerId)
if (index == -1) return
val conversation = getItem(index)
val message = conversation.lastMessage
if (message.isInbox()) {
conversation.inReadMessageId = messageId
} else {
conversation.outReadMessageId = messageId
}
conversation.unreadCount = if (conversation.lastMessageId == messageId) {
0
} else {
conversation.lastMessageId - messageId
}
notifyItemChanged(index, ConversationsCallbackDeprecated.READ)
}
@Deprecated("Need to rewrite")
private fun deleteMessage(peerId: Int, messageId: Int) {
return
val index = searchConversationIndex(peerId)
if (index == -1) return
val oldList = ArrayList(values)
val oldDialog = values[index]
val dialog = oldDialog.clone()
// TaskManager.execute {
// val cachedMessages = MemoryCache.getMessagesByPeerId(dialog.conversationId)
// val messages = VKUtil.sortMessagesByDate(ArrayList(cachedMessages), true)
//
// if (messages.isEmpty()) {
// MemoryCache.deleteConversation(dialog.conversationId)
//
// AppGlobal.post {
// removeAt(index)
// notifyChanges(oldList)
// }
// } else {
// val lastMessage = messages[0]
//
// dialog.lastMessageId = lastMessage.messageId
// dialog.lastMessage = lastMessage
//
// set(index, dialog)
//
// VKUtil.sortConversationsByDate(values, true)
//
// AppGlobal.post {
// notifyChanges(oldList)
// }
// }
// }
}
@Deprecated("Message is bad")
private fun restoreMessage(message: VKMessage) {
val index = searchConversationIndex(message.peerId)
if (index == -1) return
val oldList = ArrayList<VKConversation>().apply { addAll(values) }
val oldDialog = values[index]
val dialog = oldDialog.clone()
// TaskManager.execute {
// val messages =
// MemoryCache.getMessagesByPeerId(dialog.conversationId).apply { addMessage(message) }
//
// VKUtil.sortMessagesByDate(ArrayList(messages), true)
//
// val lastMessage = messages[0]
//
// dialog.lastMessageId = lastMessage.messageId
// dialog.lastMessage = lastMessage
//
// set(index, dialog)
//
// VKUtil.sortConversationsByDate(values, true)
//
// AppGlobal.handler.post {
// notifyChanges(oldList)
//
// fragmentConversations.presenter.checkListIsEmpty(values)
// }
// }
}
private fun prepareConversation(
conversation: VKConversation,
newMessage: VKMessage
): VKConversation {
conversation.lastMessage = newMessage
conversation.lastMessageId = newMessage.id
if (newMessage.isOut) {
conversation.unreadCount = 0
newMessage.isRead = false
} else {
conversation.unreadCount++
}
if (newMessage.peerId == newMessage.fromId && newMessage.fromId == UserConfig.userId) { //для лс
conversation.outReadMessageId = newMessage.id
}
return conversation
}
private fun searchConversationIndex(peerId: Int): Int {
for (i in values.indices) {
if (getItem(i).id == peerId) return i
}
return -1
}
private fun searchMessageIndex(messageId: Int): Int {
for (i in values.indices) {
if (getItem(i).lastMessageId == messageId) return i
}
return -1
}
private fun updateConversation(peerId: Int) {
val index = searchConversationIndex(peerId)
if (index == -1) return
// TaskManager.execute {
// val conversation = MemoryCache.getConversationById(peerId) ?: return@execute
//
// set(index, conversation)
//
// AppGlobal.post {
// notifyItemChanged(
// index,
// ConversationsCallbackDeprecated.CONVERSATION
// )
// }
// }
}
private fun updateGroups(groupIds: ArrayList<Int>) {
for (groupId in groupIds) {
val index = searchConversationIndex(groupId)
if (index == -1) return
notifyItemChanged(index)
}
}
private fun updateUsers(userIds: ArrayList<Int>) {
for (userId in userIds) {
val index = searchConversationIndex(userId)
if (index == -1) return
notifyItemChanged(index)
}
}
private fun updateMessage(messageId: Int) {
val index = searchMessageIndex(messageId)
if (index == -1) return
TaskManager.execute {
val conversation = getItem(index).clone()
// conversation.apply {
// lastMessageId = messageId
// lastMessage = MemoryCache.getMessageById(messageId) ?: return@execute
// }
AppGlobal.handler.post {
notifyItemChanged(
index,
ConversationsCallbackDeprecated.MESSAGE
)
}
}
}
}
@@ -1,584 +0,0 @@
package com.meloda.fast.adapter
import android.content.Context
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.extensions.FloatExtensions.int
import com.meloda.fast.BuildConfig
import com.meloda.fast.R
import com.meloda.fast.activity.MessagesActivityDeprecated
import com.meloda.fast.base.BaseAdapter
import com.meloda.fast.base.BaseHolder
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.widget.BoundedLinearLayout
import com.meloda.fast.widget.CircleImageView
import com.meloda.vksdk.VKApiKeys
import com.meloda.vksdk.model.*
import com.meloda.vksdk.util.VKUtil
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
@Suppress("UNCHECKED_CAST")
class MessagesAdapterDeprecated(
context: Context,
values: ArrayList<VKMessage>,
var conversation: VKConversation
) : BaseAdapter<VKMessage, MessagesAdapterDeprecated.Holder>(context, values),
TaskManager.OnEventListener {
companion object {
private const val TYPE_FOOTER = 10101
private const val TYPE_NORMAL_IN = 7910
private const val TYPE_NORMAL_OUT = 7911
private const val TYPE_ATTACHMENT_IN = 7920
private const val TYPE_ATTACHMENT_OUT = 7921
private const val TYPE_ACTION = 7930
private const val TYPE_NORMAL_CHANNEL = 7940
const val TAG = "MessagesAdapter"
}
private var recyclerView = (context as MessagesActivityDeprecated).recyclerView
private var layoutManager = recyclerView.layoutManager as LinearLayoutManager
var isNotCachedValues = false
init {
TaskManager.addOnEventListener(this)
}
override fun destroy() {
TaskManager.removeOnEventListener(this)
}
override fun onNewEvent(info: EventInfo<*>) {
when (info.key) {
VKApiKeys.NEW_MESSAGE.name -> addMessage(info.data as VKMessage)
VKApiKeys.READ_MESSAGE.name -> readMessage(
(info.data as Array<Int>)[0],
(info.data as Array<Int>)[1]
)
VKApiKeys.RESTORE_MESSAGE.name -> restoreMessage(info.data as VKMessage)
VKApiKeys.EDIT_MESSAGE.name -> editMessage(info.data as VKMessage)
VKApiKeys.DELETE_MESSAGE.name -> deleteMessage(
(info.data as Array<Int>)[0],
(info.data as Array<Int>)[1]
)
VKApiKeys.UPDATE_MESSAGE.name -> updateMessage(info.data as Int)
VKApiKeys.UPDATE_USER.name -> updateUser(info.data as ArrayList<Int>)
VKApiKeys.UPDATE_GROUP.name -> updateGroup(info.data as ArrayList<Int>)
else -> return
}
}
override fun getItemCount(): Int {
return values.size + 1
}
override fun getItemViewType(position: Int): Int {
if (position == values.size) return TYPE_FOOTER
val message = getItem(position)
return when {
message.action != null -> TYPE_ACTION
conversation.isGroupChannel -> TYPE_NORMAL_CHANNEL
message.isOut && message.attachments.isEmpty() && message.fwdMessages.isEmpty() -> TYPE_NORMAL_OUT
!message.isOut && message.attachments.isEmpty() && message.fwdMessages.isEmpty() -> TYPE_NORMAL_IN
message.isOut && (message.attachments.isNotEmpty() || message.fwdMessages.isNotEmpty()) -> TYPE_ATTACHMENT_OUT
!message.isOut && (message.attachments.isNotEmpty() || message.fwdMessages.isNotEmpty()) -> TYPE_ATTACHMENT_IN
else -> 0
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int): Holder {
return when (type) {
TYPE_FOOTER -> FooterHolder(generateEmptyView())
TYPE_NORMAL_IN -> ItemNormalIn(view(R.layout.item_message_normal_in, viewGroup))
TYPE_NORMAL_OUT -> ItemNormalOut(view(R.layout.item_message_normal_out, viewGroup))
TYPE_ATTACHMENT_IN -> ItemAttachmentIn(
view(
R.layout.item_message_attachment_in,
viewGroup
)
)
TYPE_ATTACHMENT_OUT -> ItemAttachmentOut(
view(
R.layout.item_message_attachment_out,
viewGroup
)
)
TYPE_ACTION -> ItemAction(view(R.layout.item_message_action, viewGroup))
TYPE_NORMAL_CHANNEL -> ItemChannel(view(R.layout.item_message_channel, viewGroup))
else -> PlaceHolder(view(R.layout.item_message, viewGroup))
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "bind position: $position")
}
if (holder is FooterHolder) return
super.onBindViewHolder(holder, position)
if (!isNotCachedValues) return
val message = this[position]
if (message.isRead.not()) {
// TaskManager.readMessage(
// VKApiKeys.READ_MESSAGE,
// conversation.conversationId,
// message.messageId
// )
}
}
private fun generateEmptyView(): View {
return View(context).also {
it.isFocusable = false
it.isClickable = false
it.isEnabled = false
it.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
if (conversation.isGroupChannel) 0 else AndroidUtils.px(74f).int()
)
}
}
inner class FooterHolder(v: View) : Holder(v) {
override fun bind(position: Int) {}
}
inner class ItemChannel(v: View) : ItemNormalIn(v) {
private val title: TextView = v.findViewById(R.id.channelTitle)
override fun bind(position: Int) {
val message = getItem(position)
ViewController().prepareDate(message, date)
val avatarString = conversation.photo100
// val placeHolder = VKUtil.getAvatarPlaceholder(context, conversation.title)
// avatar.setImageDrawable(placeHolder)
// ImageUtils.loadImage(avatarString, avatar, placeHolder)
title.text = conversation.title
text.text = message.text
root.visibility = View.VISIBLE
}
}
inner class ItemAction(v: View) : Holder(v) {
private val text: TextView = v.findViewById(R.id.messageAction)
override fun bind(position: Int) {
val message = getItem(position)
TaskManager.execute {
val user = searchUser(message)
val group = searchGroup(message)
val name =
(if (group == null && !VKUtil.isGroupId(message.fromId)) user?.firstName else group?.name)
?: "null"
// VKUtil.getActionText(context, message, object : OnResponseListener<String> {
//
// override fun onResponse(response: String) {
// val actionText = "$name $response"
//
// val spannable = SpannableString(actionText)
// spannable.setSpan(StyleSpan(Typeface.BOLD), 0, name.length, 0)
//
// text.text = spannable
// }
//
// override fun onError(t: Throwable) {
// }
// })
post { text.isVisible = true }
}
}
}
open inner class ItemNormalIn(v: View) : NormalViewHolder(v) {
override fun bind(position: Int) {
val message = getItem(position)
TaskManager.execute {
val user = searchUser(message)
val group = searchGroup(message)
post {
ViewController().apply {
prepareText(message, bubble, text)
prepareDate(message, date)
prepareAvatar(message, avatar)
loadAvatarImage(message, user, group, avatar)
}
root.isVisible = true
}
}
}
}
inner class ItemAttachmentIn(v: View) : ItemNormalIn(v) {
val attachments: LinearLayout = v.findViewById(R.id.messageAttachments)
override fun bind(position: Int) {
super.bind(position)
val message = getItem(position)
AttachmentInflater.showAttachments(message, this)
}
}
open inner class ItemNormalOut(v: View) : NormalViewHolder(v) {
override fun bind(position: Int) {
val message = getItem(position)
TaskManager.execute {
val user = searchUser(message)
val group = searchGroup(message)
post {
ViewController().apply {
prepareText(message, bubble, text)
prepareDate(message, date)
prepareAvatar(message, avatar)
loadAvatarImage(message, user, group, avatar)
}
root.isVisible = true
}
}
}
}
inner class ItemAttachmentOut(v: View) : ItemNormalOut(v) {
val attachments: LinearLayout = v.findViewById(R.id.messageAttachments)
override fun bind(position: Int) {
super.bind(position)
val message = getItem(position)
AttachmentInflater.showAttachments(message, this)
}
}
abstract inner class NormalViewHolder(v: View) : Holder(v) {
protected val date: TextView = v.findViewById(R.id.messageDate)
protected val text: TextView = v.findViewById(R.id.messageText)
protected val root: LinearLayout = v.findViewById(R.id.messageRoot)
protected val bubble: BoundedLinearLayout = v.findViewById(R.id.messageBubble)
protected val avatar: CircleImageView = v.findViewById(R.id.messageAvatar)
}
object AttachmentInflater {
fun showAttachments(message: VKMessage, holder: NormalViewHolder) {
val attachments =
(if (holder is ItemAttachmentOut) holder.attachments else if (holder is ItemAttachmentIn) holder.attachments else null)
?: return
if (message.fwdMessages.isNotEmpty() || message.attachments.isNotEmpty()) {
attachments.visibility = View.VISIBLE
attachments.removeAllViews()
} else {
attachments.visibility = View.GONE
}
if (message.attachments.isNotEmpty()) {
prepareAttachments(message, attachments)
}
if (message.fwdMessages.isNotEmpty()) {
prepareForwardedMessages(message, attachments)
}
}
private fun prepareAttachments(message: VKMessage, attachments: LinearLayout) {
for (attachment in message.attachments) {
when (attachment) {
is VKPhoto -> photo(message, attachments)
is VKVideo -> video(message, attachments)
is VKLink -> link(message, attachments)
is VKAudio -> audio(message, attachments)
is VKDocument -> doc(message, attachments)
}
}
}
private fun prepareForwardedMessages(message: VKMessage, attachments: LinearLayout) {
}
fun link(message: VKMessage, attachments: LinearLayout) {
}
fun video(message: VKMessage, attachments: LinearLayout) {
}
fun photo(message: VKMessage, attachments: LinearLayout) {
}
fun audio(message: VKMessage, attachments: LinearLayout) {
}
fun doc(message: VKMessage, attachments: LinearLayout) {
}
}
inner class ViewController {
fun prepareText(message: VKMessage, bubble: BoundedLinearLayout, text: TextView) {
val screenWidth = context.resources.displayMetrics.widthPixels
val boundedWidth = screenWidth - screenWidth / 5
bubble.maxWidth = boundedWidth
text.setTextColor(
AndroidUtils.getThemeAttrColor(
context,
if (message.isOutbox()) R.attr.messageOutTextColor else R.attr.messageInTextColor
)
)
text.text = message.text
}
fun prepareDate(message: VKMessage, date: TextView) {
var dateText =
SimpleDateFormat("HH:mm", Locale.getDefault()).format(message.date * 1000L)
if (message.editTime > 0) {
dateText += ", ${
context.getString(R.string.edited)
.toLowerCase(Locale.getDefault())
}"
}
date.text = dateText
}
fun prepareAvatar(message: VKMessage, avatar: ImageView) {
avatar.isVisible = !message.isOut
}
fun loadAvatarImage(message: VKMessage, user: VKUser?, group: VKGroup?, avatar: ImageView) {
// val dialogTitle = VKUtil.getMessageTitle(message, user, group)
// val avatarPlaceholder = VKUtil.getAvatarPlaceholder(context, dialogTitle)
//
// avatar.setImageDrawable(avatarPlaceholder)
//
// val avatarString = VKUtil.getUserAvatar(message, user, group)
//
// ImageUtils.loadImage(avatarString, avatar, avatarPlaceholder)
}
}
open inner class Holder(v: View) : BaseHolder(v) {
override fun bind(position: Int) {
}
}
inner class PlaceHolder(v: View) : NormalViewHolder(v)
private fun searchUser(message: VKMessage): VKUser? {
if (!message.isFromUser()) return null
// return VKUtil.searchUser(message.fromId)
return null
}
private fun searchGroup(message: VKMessage): VKGroup? {
if (!message.isFromGroup()) return null
// return VKUtil.searchGroup(message.fromId)
return null
}
private fun updateGroup(groupIds: ArrayList<Int>) {
for (groupId in groupIds) {
var index = -1
for (i in values.indices) {
val item = getItem(i)
if (abs(item.fromId) == groupId) {
index = i
break
}
}
if (index == -1) return
notifyItemChanged(index)
}
}
private fun updateUser(userIds: ArrayList<Int>) {
for (userId in userIds) {
var index = -1
for (i in values.indices) {
val item = getItem(i)
if (item.fromId == userId) {
index = i
break
}
}
if (index == -1) return
notifyItemChanged(index)
}
}
private fun updateMessage(messageId: Int) {
var index = -1
for (i in values.indices) {
val item = getItem(i)
if (item.id == messageId) {
index = i
break
}
}
if (index == -1) return
// TaskManager.execute {
// AppGlobal.database.messages.getById(messageId)?.let {
// values[index] = it
//
// post { notifyItemChanged(index) }
// }
// }
}
private fun searchMessagePosition(messageId: Int): Int {
for (i in values.indices) {
if (getItem(i).id == messageId) return i
}
return -1
}
private fun containsRandomId(randomId: Int): Boolean {
for (message in values) {
if (message.randomId == randomId) return true
}
return false
}
fun addMessage(message: VKMessage, fromApp: Boolean = false, withScroll: Boolean = false) {
val randomId = message.randomId
if (randomId > 0 && containsRandomId(message.randomId) || message.peerId != conversation.id) return
add(message)
notifyDataSetChanged()
val lastVisiblePosition = layoutManager.findLastVisibleItemPosition()
if ((message.isInbox() && lastVisiblePosition >= itemCount - 2) || !fromApp || withScroll) {
recyclerView.scrollToPosition(itemCount - 1)
}
}
private fun readMessage(peerId: Int, messageId: Int) {
if (peerId != conversation.id) return
val index = searchMessagePosition(messageId)
if (index == -1) return
val message = this[index]
message.isRead = true
notifyDataSetChanged()
if (message.isInbox()) {
conversation.inReadMessageId = messageId
} else {
conversation.outReadMessageId = messageId
}
conversation.unreadCount--
// TaskManager.execute {
// MemoryCache.put(message)
// MemoryCache.put(conversation)
// }
}
fun editMessage(message: VKMessage) {
val index = searchMessagePosition(message.id)
if (index == -1) return
set(index, message)
notifyDataSetChanged()
}
fun deleteMessage(messageId: Int, peerId: Int) {
if (peerId != conversation.id) return
val index = searchMessagePosition(messageId)
if (index == -1) return
removeAt(index)
notifyDataSetChanged()
}
//TODO: кривое сообщение
fun restoreMessage(message: VKMessage) {
if (message.peerId != conversation.id) return
updateValues(VKUtil.sortMessagesByDate(values.apply { add(message) }, false))
notifyDataSetChanged()
}
}
@@ -1,35 +0,0 @@
package com.meloda.fast.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.meloda.fast.R
import com.meloda.fast.base.BaseAdapter
import com.meloda.fast.base.BaseHolder
import com.meloda.fast.item.SimpleMenuItem
import java.util.*
class SimpleItemAdapter(context: Context, values: ArrayList<SimpleMenuItem>) :
BaseAdapter<SimpleMenuItem, SimpleItemAdapter.ViewHolder>(context, values) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(view(R.layout.item_simple_menu, parent))
}
inner class ViewHolder(v: View) : BaseHolder(v) {
private val title: TextView = v.findViewById(R.id.profileItemTitle)
private val icon: ImageView = v.findViewById(R.id.profileItemIcon)
override fun bind(position: Int) {
val item = getItem(position)
title.text = item.title
icon.setImageDrawable(item.icon)
}
}
}
@@ -1,68 +0,0 @@
package com.meloda.fast.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import com.meloda.fast.R
import com.meloda.fast.adapter.diffutil.UsersCallbackDeprecated
import com.meloda.fast.base.BaseAdapter
import com.meloda.fast.base.BaseHolder
import com.meloda.fast.util.ImageUtils
import com.meloda.fast.widget.CircleImageView
import com.meloda.vksdk.model.VKUser
import com.meloda.vksdk.util.VKUtil
class UsersAdapterDeprecated(context: Context, values: ArrayList<VKUser>) :
BaseAdapter<VKUser, UsersAdapterDeprecated.ViewHolder>(context, values) {
var isLoading: Boolean = false
var currentPosition: Int = 0
fun isLastItem() = currentPosition >= itemCount - 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(view(R.layout.item_user, parent))
}
open inner class ViewHolder(v: View) : BaseHolder(v) {
private val avatar: CircleImageView = v.findViewById(R.id.userAvatar)
private val name: TextView = v.findViewById(R.id.userName)
private val online: ImageView = v.findViewById(R.id.userOnline)
private val onlineText: TextView = v.findViewById(R.id.userOnlineText)
override fun bind(position: Int) {
currentPosition = position
val user = getItem(position)
name.text = user.toString()
// val avatarPlaceholder = VKUtil.getAvatarPlaceholder(context, user.toString())
// avatar.setImageDrawable(avatarPlaceholder)
// ImageUtils.loadImage(user.photo200, avatar, avatarPlaceholder)
online.isVisible = false
// VKUtil.getUserOnlineIcon(context, user)?.let {
// online.setImageDrawable(it)
// online.isVisible = true
// }
// onlineText.text = VKUtil.getUserOnline(user)
//TODO: отладить открытие чата
}
}
override fun notifyChanges(oldList: List<VKUser>, newList: List<VKUser>) {
val callback = UsersCallbackDeprecated(oldList, newList)
val diff = DiffUtil.calculateDiff(callback, false)
diff.dispatchUpdatesTo(this)
}
}
@@ -1,29 +0,0 @@
package com.meloda.fast.adapter.diffutil
import androidx.recyclerview.widget.DiffUtil
import com.meloda.vksdk.model.VKConversation
class ConversationsCallback(
private val oldList: List<VKConversation>,
private val newList: List<VKConversation>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
//TODO: rewrite
return false
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
//TODO: rewrite
return false
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
//TODO: rewrite
return null
}
}
@@ -1,95 +0,0 @@
package com.meloda.fast.adapter.diffutil
import androidx.recyclerview.widget.DiffUtil
import com.meloda.vksdk.model.VKConversation
class ConversationsCallbackDeprecated(
private val oldList: List<VKConversation>,
private val newList: List<VKConversation>
) : DiffUtil.Callback() {
companion object {
const val DATE = "date"
const val ONLINE = "online"
const val AVATAR = "avatar"
const val USER_AVATAR = "user_avatar"
const val ATTACHMENTS = "attachments"
const val READ = "read"
const val NOTIFICATIONS = "notifications"
const val EDIT_MESSAGE = "edit_message"
const val MESSAGE = "message"
const val USER = "user"
const val GROUP = "group"
const val CONVERSATION = "conversation"
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val old = oldList[oldItemPosition]
val new = newList[newItemPosition]
if (true) return false
return old.id == new.id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val old = oldList[oldItemPosition]
val new = newList[newItemPosition]
val oldMessage = old.lastMessage
val newMessage = new.lastMessage
if (true) return false else
return old.title == new.title &&
old.lastMessageId == new.lastMessageId &&
old.photo50 == new.photo50 &&
old.unreadCount == new.unreadCount &&
old.isNoSound == new.isNoSound &&
old.isDisabledForever == new.isDisabledForever &&
old.disabledUntil == new.disabledUntil &&
old.inReadMessageId == new.inReadMessageId &&
old.outReadMessageId == new.outReadMessageId &&
old.peerUser == new.peerUser &&
old.peerGroup == new.peerGroup &&
oldMessage == newMessage
// oldMessage.messageId == newMessage.messageId &&
// oldMessage.isOut == newMessage.isOut &&
// oldMessage.fromId == newMessage.fromId &&
// oldMessage.date == newMessage.date &&
// oldMessage.action == newMessage.action &&
// oldMessage.text == newMessage.text &&
// oldMessage.attachments == newMessage.attachments &&
// oldMessage.fwdMessages == newMessage.fwdMessages &&
// oldMessage.messageId == newMessage.messageId &&
//
// oldMessage.fromUser == newMessage.fromUser &&
// oldMessage.fromGroup == newMessage.fromGroup
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldConversation = oldList[oldItemPosition]
val newConversation = newList[newItemPosition]
val oldMessage = oldConversation.lastMessage
val newMessage = newConversation.lastMessage
val oldDate = oldMessage.date
val newDate = newMessage.date
// if (oldDate != newDate) return DATE
// if (oldMessage != newMessage) return MESSAGE
return super.getChangePayload(oldItemPosition, newItemPosition)
}
}
@@ -1,51 +0,0 @@
package com.meloda.fast.adapter.diffutil
import androidx.recyclerview.widget.DiffUtil
import com.meloda.vksdk.model.VKUser
class UsersCallbackDeprecated(private val oldList: List<VKUser>, private val newList: List<VKUser>) :
DiffUtil.Callback() {
companion object {
const val ONLINE = "online"
const val ONLINE_MOBILE = "online_mobile"
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val old = oldList[oldItemPosition]
val new = newList[newItemPosition]
return old.userId == new.userId
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val old = oldList[oldItemPosition]
val new = newList[newItemPosition]
return old.firstName == new.firstName &&
old.lastName == new.lastName &&
old.isOnline == new.isOnline &&
old.isOnlineMobile == new.isOnlineMobile &&
old.lastSeen == new.lastSeen &&
old.lastSeenPlatform == new.lastSeenPlatform &&
old.deactivated == new.deactivated
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val old = oldList[oldItemPosition]
val new = newList[newItemPosition]
if (old.isOnlineMobile != new.isOnlineMobile) {
if (old.isOnline != new.isOnline) return ONLINE
return ONLINE_MOBILE
}
return super.getChangePayload(oldItemPosition, newItemPosition)
}
}
@@ -1,4 +1,4 @@
package com.meloda.vksdk package com.meloda.fast.api
object ErrorCodes { object ErrorCodes {
const val UNKNOWN_ERROR = 1 const val UNKNOWN_ERROR = 1
@@ -1,4 +1,4 @@
package com.meloda.vksdk package com.meloda.fast.api
interface OnResponseListener<T> { interface OnResponseListener<T> {
@@ -0,0 +1,25 @@
package com.meloda.fast.api
data class Resource<out T> constructor(
val status: Status,
val responseData: T?,
val message: String?
) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(responseData: T?): Resource<T> =
Resource(Status.SUCCESS, responseData, null)
fun <T> error(message: String?, responseBody: T? = null): Resource<T> =
Resource(Status.ERROR, responseBody, message)
fun <T> loading(responseData: T? = null): Resource<T> =
Resource(Status.LOADING, responseData, null)
}
}
@@ -1,18 +1,21 @@
package com.meloda.vksdk package com.meloda.fast.api
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.meloda.concurrent.TaskManager import com.meloda.fast.BuildConfig
import com.meloda.netservices.HttpRequest import com.meloda.fast.api.method.MessageMethodSetter
import com.meloda.vksdk.method.MessageMethodSetter import com.meloda.fast.api.method.MethodSetter
import com.meloda.vksdk.method.MethodSetter import com.meloda.fast.api.method.UserMethodSetter
import com.meloda.vksdk.method.UserMethodSetter import kotlinx.coroutines.flow.Flow
import com.meloda.vksdk.model.* import kotlinx.coroutines.flow.asFlow
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
object VKApi { object VKApi {
@@ -41,7 +44,8 @@ object VKApi {
Log.w(TAG, "url: $url") Log.w(TAG, "url: $url")
} }
val buffer = HttpRequest[url].asString() val buffer = com.meloda.fast.net.HttpRequest[url].asString()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.i(TAG, "response: $buffer") Log.i(TAG, "response: $buffer")
} }
@@ -63,9 +67,9 @@ object VKApi {
when (cls) { when (cls) {
null -> return null null -> return null
VKLongPollServer::class.java -> { com.meloda.fast.api.model.VKLongPollServer::class.java -> {
json.optJSONObject("response")?.let { json.optJSONObject("response")?.let {
return arrayListOf(VKLongPollServer(it)) as ArrayList<T>? return arrayListOf(com.meloda.fast.api.model.VKLongPollServer(it)) as ArrayList<T>?
} }
} }
@@ -91,50 +95,56 @@ object VKApi {
val models = ArrayList<T>(array.length()) val models = ArrayList<T>(array.length())
when (cls) { when (cls) {
VKUser::class.java -> { com.meloda.fast.api.model.VKUser::class.java -> {
json.optJSONObject("response")?.let { r -> json.optJSONObject("response")?.let { r ->
VKUser.friendsCount = r.optInt("count") com.meloda.fast.api.model.VKUser.friendsCount = r.optInt("count")
} }
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
models.add(VKUser(array.optJSONObject(i)) as T) models.add(com.meloda.fast.api.model.VKUser(array.optJSONObject(i)) as T)
} }
} }
VKMessage::class.java -> { com.meloda.fast.api.model.VKMessage::class.java -> {
response as JSONObject response as JSONObject
if (url.contains("messages.getHistory")) { if (url.contains("messages.getHistory")) {
VKMessage.lastHistoryCount = response.optInt("count") com.meloda.fast.api.model.VKMessage.lastHistoryCount = response.optInt("count")
response.optJSONArray("profiles")?.let { response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<VKUser>() val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) { for (j in 0 until it.length()) {
profiles.add(VKUser(it.optJSONObject(j))) profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
} }
VKMessage.profiles = profiles com.meloda.fast.api.model.VKMessage.profiles = profiles
} }
response.optJSONArray("groups")?.let { response.optJSONArray("groups")?.let {
val groups = arrayListOf<VKGroup>() val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) { for (j in 0 until it.length()) {
groups.add(VKGroup(it.optJSONObject(j))) groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
} }
VKMessage.groups = groups com.meloda.fast.api.model.VKMessage.groups = groups
} }
response.optJSONArray("conversations")?.let { response.optJSONArray("conversations")?.let {
val conversations = arrayListOf<VKConversation>() val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
for (j in 0 until it.length()) { for (j in 0 until it.length()) {
conversations.add(VKConversation(it.optJSONObject(j))) conversations.add(
com.meloda.fast.api.model.VKConversation(
it.optJSONObject(
j
)
)
)
} }
VKMessage.conversations = conversations com.meloda.fast.api.model.VKMessage.conversations = conversations
} }
} }
@@ -144,35 +154,35 @@ object VKApi {
source = source.optJSONObject("message") source = source.optJSONObject("message")
} }
val message = VKMessage(source) val message = com.meloda.fast.api.model.VKMessage(source)
models.add(message as T) models.add(message as T)
} }
} }
VKGroup::class.java -> { com.meloda.fast.api.model.VKGroup::class.java -> {
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
models.add(VKGroup(array.optJSONObject(i)) as T) models.add(com.meloda.fast.api.model.VKGroup(array.optJSONObject(i)) as T)
} }
} }
VKModel::class.java -> { com.meloda.fast.api.model.VKModel::class.java -> {
if (url.contains("messages.getHistoryAttachments")) { if (url.contains("messages.getHistoryAttachments")) {
return VKAttachments.parse(array) as ArrayList<T> return com.meloda.fast.api.model.VKAttachments.parse(array) as ArrayList<T>
} }
} }
VKConversation::class.java -> { com.meloda.fast.api.model.VKConversation::class.java -> {
if (url.contains("getConversationsById")) { if (url.contains("getConversationsById")) {
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
val source = array.optJSONObject(i) val source = array.optJSONObject(i)
models.add(VKConversation(source) as T) models.add(com.meloda.fast.api.model.VKConversation(source) as T)
} }
return models return models
} }
json.optJSONObject("response")?.let { r -> json.optJSONObject("response")?.let { r ->
VKConversation.conversationsCount = r.optInt("count") com.meloda.fast.api.model.VKConversation.conversationsCount = r.optInt("count")
} }
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
@@ -182,28 +192,28 @@ object VKApi {
val oConversation = source.optJSONObject("conversation") ?: return null val oConversation = source.optJSONObject("conversation") ?: return null
val oLastMessage = source.optJSONObject("last_message") ?: return null val oLastMessage = source.optJSONObject("last_message") ?: return null
val conversation = VKConversation(oConversation).also { val conversation = com.meloda.fast.api.model.VKConversation(oConversation).also {
it.lastMessage = VKMessage(oLastMessage) it.lastMessage = com.meloda.fast.api.model.VKMessage(oLastMessage)
} }
response.optJSONArray("profiles")?.let { response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<VKUser>() val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) { for (j in 0 until it.length()) {
profiles.add(VKUser(it.optJSONObject(j))) profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
} }
VKConversation.profiles = profiles com.meloda.fast.api.model.VKConversation.profiles = profiles
} }
response.optJSONArray("groups")?.let { response.optJSONArray("groups")?.let {
val groups = arrayListOf<VKGroup>() val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) { for (j in 0 until it.length()) {
groups.add(VKGroup(it.optJSONObject(j))) groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
} }
VKConversation.groups = groups com.meloda.fast.api.model.VKConversation.groups = groups
} }
models.add(conversation as T) models.add(conversation as T)
@@ -215,21 +225,35 @@ object VKApi {
} }
fun <E> execute(url: String, cls: Class<E>, listener: OnResponseListener<E>?) { fun <E> execute(url: String, cls: Class<E>, listener: OnResponseListener<E>?) {
TaskManager.execute { com.meloda.fast.concurrent.TaskManager.execute {
try { try {
val models = execute(url, cls) val models = execute(url, cls) ?: return@execute
listener?.let { SuccessCallback(listener, models as E) } // listener?.onResponse(models)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
listener?.onError(e)
// it.resumeWithException(e)
}
}
listener?.let { ErrorCallback(listener, e) } }
suspend fun <E> suspendExecute(url: String, cls: Class<E>): Flow<E> {
return suspendCoroutine {
try {
val models = execute(url, cls)?.asFlow() ?: return@suspendCoroutine
it.resume(models)
} catch (e: Exception) {
e.printStackTrace()
it.resumeWithException(e)
} }
} }
} }
fun <E> executeArray(url: String, cls: Class<E>, listener: OnResponseListener<ArrayList<E>>) { fun <E> executeArray(url: String, cls: Class<E>, listener: OnResponseListener<ArrayList<E>>) {
TaskManager.execute { com.meloda.fast.concurrent.TaskManager.execute {
try { try {
val models = execute(url, cls) val models = execute(url, cls)
@@ -1,4 +1,4 @@
package com.meloda.vksdk package com.meloda.fast.api
enum class VKApiKeys(val value: String) { enum class VKApiKeys(val value: String) {
READ_MESSAGE("_read_message"), READ_MESSAGE("_read_message"),
@@ -1,7 +1,8 @@
package com.meloda.vksdk package com.meloda.fast.api
import android.util.Log import android.util.Log
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.BuildConfig
import com.meloda.fast.api.util.VKUtil
import java.net.URLEncoder import java.net.URLEncoder
object VKAuth { object VKAuth {
@@ -1,4 +1,4 @@
package com.meloda.vksdk package com.meloda.fast.api
object VKConstants { object VKConstants {
@@ -1,8 +1,8 @@
package com.meloda.vksdk package com.meloda.fast.api
import java.io.IOException import java.io.IOException
class VKException(var url: String, override var message: String, var code: Int) : class VKException(var url: String = "", override var message: String = "", var code: Int) :
IOException(message) { IOException(message) {
var captchaSid: String? = null var captchaSid: String? = null
var captchaImg: String? = null var captchaImg: String? = null
@@ -0,0 +1,4 @@
package com.meloda.fast.api.datasource
class MessagesDataSource constructor() {
}
@@ -0,0 +1,97 @@
package com.meloda.fast.api.datasource.base
import android.util.Log
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.JsonSyntaxException
import com.meloda.fast.api.Resource
import com.meloda.fast.api.model.ApiResponse
import com.meloda.fast.api.ErrorCodes
import com.meloda.fast.api.VKException
import okhttp3.ResponseBody
import retrofit2.HttpException
class BaseDataSource {
private val TAG = BaseDataSource::class.simpleName
//TODO: move to resources
private val DEFAULT_ERROR = "Internal server error"
protected suspend fun <T> getResult(apiCall: suspend () -> ApiResponse<T>): Resource<T> {
try {
val response = apiCall()
return if (response.isSuccessful) {
Resource.success(response.response)
} else {
Log.d(TAG, "Server response unsuccessful")
if (response.error != null) {
Log.w(TAG, "Unsuccessful response with code 2XX")
Resource.error(response.error.message, response.response)
} else {
Log.e(TAG, "Unsuccessful result without error!")
Resource.error(DEFAULT_ERROR)
}
}
} catch (e: HttpException) {
Log.e(TAG, "Error while executing request ${e.message}")
val errorBody = e.response()?.errorBody() ?: return Resource.error(DEFAULT_ERROR)
val errorResponse = parseErrorBody<T>(errorBody) ?: return Resource.error(DEFAULT_ERROR)
return Resource.error(errorResponse.message)
} catch (e: Exception) {
Log.e(TAG, "Error while executing request ${e.message}")
return Resource.error(DEFAULT_ERROR)
}
}
private fun <T> parseErrorBody(responseBody: ResponseBody?): Exception? {
if (responseBody == null) return null
val jsonResponse: JsonObject?
try {
jsonResponse = JsonParser.parseString(responseBody.string()) as? JsonObject
if (jsonResponse == null) {
Log.d(TAG, "Response body is empty while parsing error body.")
return null
}
} catch (e: JsonSyntaxException) {
Log.e(TAG, "Error while parsing json ${e.message}")
return null
} catch (e: java.lang.Exception) {
Log.e(TAG, "Unknown error ${e.message}")
return null
}
if (jsonResponse.has("error")) {
val error = jsonResponse["error"].asJsonObject
val message = error["error_msg"].asString
val code = error["error_code"].asInt
val e = VKException("", message, code)
//TODO: add checking invalid session
if (code == 5 && message.contains("invalid session")) {
// context?.startActivity(Intent(context, DropUserDataActivity::class.java).apply {
// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// })
}
if (code == ErrorCodes.CAPTCHA_NEEDED) {
e.captchaImg = error["captcha_img"].asString
e.captchaSid = error["captcha_sid"].asString
}
if (code == ErrorCodes.VALIDATION_REQUIRED) {
e.redirectUri = error["redirect_uri"].asString
}
return e
}
return null
}
}
@@ -1,6 +1,6 @@
package com.meloda.vksdk.method package com.meloda.fast.api.method
import com.meloda.arrayutils.ArrayUtils import com.meloda.fast.util.ArrayUtils
class MessageMethodSetter(name: String) : MethodSetter(name) { class MessageMethodSetter(name: String) : MethodSetter(name) {
@@ -60,7 +60,7 @@ class MessageMethodSetter(name: String) : MethodSetter(name) {
} }
fun peerIds(vararg values: Int): MessageMethodSetter { fun peerIds(vararg values: Int): MessageMethodSetter {
put("peer_ids", ArrayUtils.asString(values)) put("peer_ids", com.meloda.fast.util.ArrayUtils.asString(values))
return this return this
} }
@@ -100,22 +100,22 @@ class MessageMethodSetter(name: String) : MethodSetter(name) {
} }
fun attachment(attachments: Collection<String>): MessageMethodSetter { fun attachment(attachments: Collection<String>): MessageMethodSetter {
put("attachment", ArrayUtils.asString(attachments)) put("attachment", com.meloda.fast.util.ArrayUtils.asString(attachments))
return this return this
} }
fun attachment(vararg attachments: String): MessageMethodSetter { fun attachment(vararg attachments: String): MessageMethodSetter {
put("attachment", ArrayUtils.asString(*attachments)) put("attachment", com.meloda.fast.util.ArrayUtils.asString(*attachments))
return this return this
} }
fun forwardMessages(ids: Collection<String>): MessageMethodSetter { fun forwardMessages(ids: Collection<String>): MessageMethodSetter {
put("forward_messages", ArrayUtils.asString(ids)) put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this return this
} }
fun forwardMessages(vararg ids: Int): MessageMethodSetter { fun forwardMessages(vararg ids: Int): MessageMethodSetter {
put("forward_messages", ArrayUtils.asString(ids)) put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this return this
} }
@@ -160,12 +160,12 @@ class MessageMethodSetter(name: String) : MethodSetter(name) {
} }
fun chatIds(vararg ids: Int): MessageMethodSetter { fun chatIds(vararg ids: Int): MessageMethodSetter {
put("max_msg_id", ArrayUtils.asString(ids)) put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this return this
} }
fun chatIds(ids: Collection<Int>): MessageMethodSetter { fun chatIds(ids: Collection<Int>): MessageMethodSetter {
put("max_msg_id", ArrayUtils.asString(ids)) put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this return this
} }
@@ -1,11 +1,11 @@
package com.meloda.vksdk.method package com.meloda.fast.api.method
import android.util.ArrayMap import android.util.ArrayMap
import android.util.Log import android.util.Log
import com.meloda.arrayutils.ArrayUtils import com.meloda.fast.BuildConfig
import com.meloda.vksdk.BuildConfig import com.meloda.fast.api.OnResponseListener
import com.meloda.vksdk.OnResponseListener import com.meloda.fast.api.VKApi
import com.meloda.vksdk.VKApi import kotlinx.coroutines.flow.Flow
import java.net.URLEncoder import java.net.URLEncoder
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -79,6 +79,10 @@ open class MethodSetter(private val name: String) {
return params return params
} }
suspend fun <E> executeSuspend(cls: Class<E>): Flow<E> {
return VKApi.suspendExecute(getSignedUrl(), cls)
}
fun <E> execute(cls: Class<E>): ArrayList<E>? { fun <E> execute(cls: Class<E>): ArrayList<E>? {
return VKApi.execute(getSignedUrl(), cls) return VKApi.execute(getSignedUrl(), cls)
} }
@@ -96,11 +100,11 @@ open class MethodSetter(private val name: String) {
} }
fun userIds(vararg ids: Int): MethodSetter { fun userIds(vararg ids: Int): MethodSetter {
return put("user_ids", ArrayUtils.asString(ids)) return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
} }
fun userIds(ids: ArrayList<Int>): MethodSetter { fun userIds(ids: ArrayList<Int>): MethodSetter {
return put("user_ids", ArrayUtils.asString(ids)) return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
} }
fun ownerId(value: Int): MethodSetter { fun ownerId(value: Int): MethodSetter {
@@ -112,11 +116,11 @@ open class MethodSetter(private val name: String) {
} }
fun groupIds(vararg ids: Int): MethodSetter { fun groupIds(vararg ids: Int): MethodSetter {
return put("group_ids", ArrayUtils.asString(ids)) return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
} }
fun groupIds(ids: ArrayList<Int>): MethodSetter { fun groupIds(ids: ArrayList<Int>): MethodSetter {
return put("group_ids", ArrayUtils.asString(ids)) return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
} }
fun fields(values: String): MethodSetter { fun fields(values: String): MethodSetter {
@@ -1,4 +1,4 @@
package com.meloda.vksdk.method package com.meloda.fast.api.method
class UserMethodSetter(name: String) : MethodSetter(name) { class UserMethodSetter(name: String) : MethodSetter(name) {
@@ -0,0 +1,12 @@
package com.meloda.fast.api.model
data class ApiResponse<T> constructor(
val isSuccessful: Boolean,
val error: Error?,
val response: T?
)
data class Error constructor(
val code: Long,
val message: String
)
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONArray import org.json.JSONArray
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
import java.io.Serializable import java.io.Serializable
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
import java.io.Serializable import java.io.Serializable
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,7 +1,7 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import android.util.ArrayMap import android.util.ArrayMap
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject import org.json.JSONObject
open class VKMessage() : VKModel() { open class VKMessage() : VKModel() {
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import java.io.Serializable import java.io.Serializable
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -1,4 +1,4 @@
package com.meloda.vksdk.model package com.meloda.fast.api.model
import org.json.JSONObject import org.json.JSONObject
@@ -0,0 +1,21 @@
package com.meloda.fast.api.model.request
import com.google.gson.annotations.SerializedName
class RequestMessagesGetConversations(
@SerializedName("offset")
private val offset: Int = 0,
@SerializedName("count")
private val count: Int = 0,
//values = all, unread
@SerializedName("filter")
private val filter: String = "",
@SerializedName("extended")
private val extended: Boolean = false,
@SerializedName("fields")
private var fields: String = ""
)
@@ -0,0 +1,6 @@
package com.meloda.fast.api.model.response
class ResponseMessagesGetConversations(
val count: Int
) {
}
@@ -0,0 +1,13 @@
package com.meloda.fast.api.service
import com.meloda.fast.api.model.ApiResponse
import com.meloda.fast.api.model.request.RequestMessagesGetConversations
import retrofit2.http.GET
import retrofit2.http.QueryMap
interface MessagesService {
@GET("messages.getConversations")
suspend fun getConversations(@QueryMap params: RequestMessagesGetConversations): ApiResponse<Map<String, Any>>
}
@@ -1,7 +1,6 @@
package com.meloda.vksdk.util package com.meloda.fast.api.util
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.meloda.vksdk.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
@@ -28,9 +27,9 @@ object VKUtil {
} }
fun sortMessagesByDate( fun sortMessagesByDate(
values: ArrayList<VKMessage>, values: ArrayList<com.meloda.fast.api.model.VKMessage>,
firstOnTop: Boolean firstOnTop: Boolean
): ArrayList<VKMessage> { ): ArrayList<com.meloda.fast.api.model.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
@@ -46,9 +45,9 @@ object VKUtil {
} }
fun sortConversationsByDate( fun sortConversationsByDate(
values: ArrayList<VKConversation>, values: ArrayList<com.meloda.fast.api.model.VKConversation>,
firstOnTop: Boolean firstOnTop: Boolean
): ArrayList<VKConversation> { ): ArrayList<com.meloda.fast.api.model.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
@@ -122,7 +121,7 @@ object VKUtil {
} }
fun getTitle(conversation: VKConversation, peerUser: VKUser?, peerGroup: VKGroup?): String { fun getTitle(conversation: com.meloda.fast.api.model.VKConversation, peerUser: com.meloda.fast.api.model.VKUser?, peerGroup: com.meloda.fast.api.model.VKGroup?): String {
return when { return when {
conversation.isUser() -> { conversation.isUser() -> {
peerUser?.let { return it.toString() } ?: "" peerUser?.let { return it.toString() } ?: ""
@@ -140,7 +139,7 @@ object VKUtil {
} }
} }
fun getMessageTitle(message: VKMessage, fromUser: VKUser?, fromGroup: VKGroup?): String { fun getMessageTitle(message: com.meloda.fast.api.model.VKMessage, fromUser: com.meloda.fast.api.model.VKUser?, fromGroup: com.meloda.fast.api.model.VKGroup?): String {
return when { return when {
message.isFromUser() -> { message.isFromUser() -> {
fromUser?.let { return it.toString() } ?: "" fromUser?.let { return it.toString() } ?: ""
@@ -154,7 +153,7 @@ object VKUtil {
} }
} }
fun getAvatar(conversation: VKConversation, peerUser: VKUser?, peerGroup: VKGroup?): String { fun getAvatar(conversation: com.meloda.fast.api.model.VKConversation, peerUser: com.meloda.fast.api.model.VKUser?, peerGroup: com.meloda.fast.api.model.VKGroup?): String {
return when { return when {
conversation.isUser() -> { conversation.isUser() -> {
peerUser?.let { return it.photo200 } ?: "" peerUser?.let { return it.photo200 } ?: ""
@@ -172,7 +171,7 @@ object VKUtil {
} }
} }
fun getUserAvatar(message: VKMessage, fromUser: VKUser?, fromGroup: VKGroup?): String { fun getUserAvatar(message: com.meloda.fast.api.model.VKMessage, fromUser: com.meloda.fast.api.model.VKUser?, fromGroup: com.meloda.fast.api.model.VKGroup?): String {
return when { return when {
message.isFromUser() -> { message.isFromUser() -> {
fromUser?.let { return it.photo100 } ?: "" fromUser?.let { return it.photo100 } ?: ""
@@ -186,7 +185,7 @@ object VKUtil {
} }
} }
fun getUserPhoto(user: VKUser): String { fun getUserPhoto(user: com.meloda.fast.api.model.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()) {
@@ -202,7 +201,7 @@ object VKUtil {
return "" return ""
} }
fun getGroupPhoto(group: VKGroup): String { fun getGroupPhoto(group: com.meloda.fast.api.model.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()) {
@@ -219,26 +218,26 @@ object VKUtil {
} }
fun parseConversations(array: JSONArray): ArrayList<VKConversation> { fun parseConversations(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKConversation> {
val conversations = arrayListOf<VKConversation>() val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
conversations.add(VKConversation(array.optJSONObject(i))) conversations.add(com.meloda.fast.api.model.VKConversation(array.optJSONObject(i)))
} }
return conversations return conversations
} }
fun parseMessages(array: JSONArray): ArrayList<VKMessage> { fun parseMessages(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKMessage> {
val messages = arrayListOf<VKMessage>() val messages = arrayListOf<com.meloda.fast.api.model.VKMessage>()
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
messages.add(VKMessage(array.optJSONObject(i))) messages.add(com.meloda.fast.api.model.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? = VKMessage.flags[flagName] val o: Any? = com.meloda.fast.api.model.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
@@ -249,8 +248,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): VKMessage { fun parseLongPollMessage(array: JSONArray): com.meloda.fast.api.model.VKMessage {
val message = VKMessage() val message = com.meloda.fast.api.model.VKMessage()
val id = array.optInt(1) val id = array.optInt(1)
val flags = array.optInt(2) val flags = array.optInt(2)
@@ -277,33 +276,33 @@ object VKUtil {
} }
if (it.has("source_act")) { if (it.has("source_act")) {
message.action = VKMessageAction().also { action -> message.action = com.meloda.fast.api.model.VKMessageAction().also { action ->
action.type = VKMessageAction.Type.fromString(it.optString("source_act")) action.type = com.meloda.fast.api.model.VKMessageAction.Type.fromString(it.optString("source_act"))
when (action.type) { when (action.type) {
VKMessageAction.Type.CHAT_CREATE -> { com.meloda.fast.api.model.VKMessageAction.Type.CHAT_CREATE -> {
action.text = it.optString("source_text") action.text = it.optString("source_text")
} }
VKMessageAction.Type.TITLE_UPDATE -> { com.meloda.fast.api.model.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")
} }
VKMessageAction.Type.PIN_MESSAGE -> { com.meloda.fast.api.model.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 = VKMessage(message) action.message = com.meloda.fast.api.model.VKMessage(message)
} }
} }
VKMessageAction.Type.UNPIN_MESSAGE -> { com.meloda.fast.api.model.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")
} }
VKMessageAction.Type.INVITE_USER, com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER,
VKMessageAction.Type.KICK_USER, com.meloda.fast.api.model.VKMessageAction.Type.KICK_USER,
VKMessageAction.Type.SCREENSHOT, com.meloda.fast.api.model.VKMessageAction.Type.SCREENSHOT,
VKMessageAction.Type.INVITE_USER_BY_CALL -> { com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER_BY_CALL -> {
action.memberId = it.optInt("source_mid") action.memberId = it.optInt("source_mid")
} }
} }
@@ -1,33 +1,48 @@
package com.meloda.fast.base package com.meloda.fast.base
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.meloda.extensions.ContextExtensions.color import androidx.lifecycle.Lifecycle
import com.meloda.fast.R import androidx.lifecycle.LifecycleOwner
import com.meloda.fast.util.AndroidUtils import androidx.lifecycle.LifecycleRegistry
import com.meloda.fast.util.ColorUtils import com.google.android.material.snackbar.Snackbar
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity : AppCompatActivity, LifecycleOwner {
constructor() : super()
constructor(@LayoutRes resId: Int) : super(resId)
protected lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { lifecycleRegistry = LifecycleRegistry(this)
val navigationBarColor = lifecycleRegistry.currentState = Lifecycle.State.CREATED
if (AndroidUtils.isDarkTheme()) {
color(R.color.dark_primaryDark)
} else {
ColorUtils.darkenColor(color(R.color.primaryDark))
}
window.navigationBarColor = navigationBarColor
}
} }
fun getRootView(): View { override fun onStart() {
return findViewById(android.R.id.content) 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
}
val rootView: View? get() = findViewById(android.R.id.content)
fun requireRootView() = rootView!!
var errorSnackbar: Snackbar? = null
} }
@@ -1,177 +0,0 @@
package com.meloda.fast.base
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.recyclerview.widget.RecyclerView
import com.meloda.arrayutils.ArrayUtils.asArrayList
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.listener.ItemLongClickListener
import java.io.Serializable
import java.util.*
@Suppress("UNCHECKED_CAST")
abstract class BaseAdapter<Item, VH : BaseHolder>(
var context: Context,
var values: ArrayList<Item> = arrayListOf()
) : RecyclerView.Adapter<VH>() {
companion object {
private const val P_ITEMS = "BaseAdapter.values"
}
private var cleanValues: ArrayList<Item>? = null
private var inflater: LayoutInflater = LayoutInflater.from(context)
var itemClickListener: ItemClickListener? = null
var itemLongClickListener: ItemLongClickListener? = null
open fun destroy() {}
open fun getItem(position: Int): Item {
return values[position]
}
fun add(position: Int, item: Item) {
values.add(position, item)
cleanValues?.add(position, item)
}
fun add(item: Item) {
values.add(item)
cleanValues?.add(item)
}
fun addAll(items: List<Item>) {
values.addAll(items)
cleanValues?.addAll(items)
}
fun addAll(position: Int, items: List<Item>) {
values.addAll(position, items)
cleanValues?.addAll(position, items)
}
operator fun set(position: Int, item: Item) {
values[position] = item
cleanValues?.set(position, item)
}
fun indexOf(item: Item): Int {
return values.indexOf(item)
}
fun removeAt(index: Int) {
values.removeAt(index)
cleanValues?.removeAt(index)
}
fun remove(item: Item) {
values.remove(item)
cleanValues?.remove(item)
}
open fun notifyChanges(oldList: List<Item>, newList: List<Item> = values) {}
fun isEmpty() = values.isNullOrEmpty()
fun isNotEmpty() = !isEmpty()
fun view(resId: Int, viewGroup: ViewGroup): View {
return inflater.inflate(resId, viewGroup, false)
}
fun updateValues(arrayList: ArrayList<Item>) {
values.clear()
values.addAll(arrayList)
}
fun updateValues(list: List<Item>) = updateValues(list.asArrayList())
override fun onBindViewHolder(holder: VH, position: Int) {
onBindItemViewHolder(holder, position)
}
protected fun initListeners(itemView: View, position: Int) {
if (itemView is AdapterView<*>) return
itemView.setOnClickListener {
if (itemClickListener != null) itemClickListener!!.onItemClick(
position
)
}
itemView.setOnLongClickListener {
if (itemLongClickListener != null) itemLongClickListener!!.onItemLongClick(position)
itemClickListener == null
}
}
override fun getItemCount(): Int {
return values.size
}
private fun onBindItemViewHolder(holder: VH, position: Int) {
initListeners(holder.itemView, position)
holder.bind(position)
}
fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()
if (values.size > 0 && (values[0] is Parcelable || values[0] is Serializable)) {
bundle.putSerializable(P_ITEMS, values)
}
return bundle
}
fun post(runnable: Runnable) {
AppGlobal.handler.post(runnable)
}
fun onRestoreInstanceState(state: Parcelable?) {
if (state is Bundle) {
if (state.containsKey(P_ITEMS)) {
values = state.getSerializable(P_ITEMS) as ArrayList<Item>
}
}
}
fun clear() {
values.clear()
}
open fun filter(query: String) {
if (cleanValues == null) {
cleanValues = ArrayList(values)
}
values.clear()
if (query.isEmpty()) {
values.addAll(cleanValues!!)
} else {
for (item in cleanValues!!) {
if (onQueryItem(item, query)) {
values.add(item)
}
}
}
notifyDataSetChanged()
}
open fun onQueryItem(item: Item, query: String): Boolean {
return false
}
operator fun get(index: Int): Item {
return values[index]
}
}
@@ -1,23 +1,12 @@
package com.meloda.fast.base package com.meloda.fast.base
import androidx.annotation.IdRes import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.meloda.fast.activity.MainActivity
abstract class BaseFragment : Fragment() { abstract class BaseFragment : Fragment {
protected open fun initToolbar(@IdRes resId: Int) { constructor() : super()
val toolbar: Toolbar = requireView().findViewById(resId)
activity?.let { constructor(@LayoutRes resId: Int) : super(resId)
if (it is MainActivity && toolbar is com.meloda.fast.widget.Toolbar) it.initToolbar(
toolbar
)
}
}
fun runOnUiThread(runnable: Runnable) {
activity?.runOnUiThread(runnable)
}
} }
@@ -0,0 +1,144 @@
package com.meloda.fast.base.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView
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")
abstract class BaseAdapter<Item, VH : BaseHolder>(
var context: Context,
values: ArrayList<Item>
) : RecyclerView.Adapter<VH>() {
val cleanValues = MutableLiveData<MutableList<Item>>(arrayListOf())
val values = MutableLiveData<MutableList<Item>>(arrayListOf())
protected var inflater: LayoutInflater = LayoutInflater.from(context)
init {
this.values.value = values
}
var itemClickListener: OnItemClickListener? = null
var itemLongClickListener: OnItemLongClickListener? = null
open fun destroy() {
itemClickListener = null
itemLongClickListener = null
}
open fun getItem(position: Int): Item {
return values[position]
}
fun add(position: Int, item: Item) {
values.add(item, position)
cleanValues.add(item, position)
}
fun add(item: Item) {
values += item
cleanValues.add(item)
}
fun addAll(items: List<Item>) {
values += items
cleanValues.addAll(items)
}
fun addAll(position: Int, items: List<Item>) {
values.addAll(items, position)
cleanValues.addAll(items, position)
}
fun removeAll(items: List<Item>) {
values.removeAll(items)
cleanValues.removeAll(items)
}
fun removeAt(index: Int) {
values.removeAt(index)
cleanValues.removeAt(index)
}
fun remove(item: Item) {
values.remove(item)
cleanValues.remove(item)
}
fun clear() {
values.clear()
cleanValues.clear()
}
operator fun get(position: Int): Item {
return values[position]
}
operator fun set(position: Int, item: Item) {
values[position] = item
cleanValues[position] = item
}
open fun notifyChanges(oldList: List<Item>, newList: List<Item>) {}
fun isEmpty() = values.isEmpty()
fun isNotEmpty() = values.isNotEmpty()
fun view(resId: Int, viewGroup: ViewGroup, attachToRoot: Boolean = false): View {
return inflater.inflate(resId, viewGroup, attachToRoot)
}
fun updateValues(arrayList: ArrayList<Item>) {
values.clear()
values += arrayList
}
fun updateValues(list: List<Item>) = updateValues(ArrayList(list))
override fun onBindViewHolder(holder: VH, position: Int) {
onBindItemViewHolder(holder, position)
}
protected fun initListeners(itemView: View, position: Int) {
if (itemView is AdapterView<*>) return
itemView.setOnClickListener {
itemClickListener?.onItemClick(position)
}
itemView.setOnLongClickListener {
itemLongClickListener?.onItemLongClick(position)
return@setOnLongClickListener itemClickListener == null
}
}
override fun getItemCount(): Int {
return values.size
}
val size get() = itemCount
private fun onBindItemViewHolder(holder: VH, position: Int) {
initListeners(holder.itemView, position)
holder.bind(position)
}
}
@@ -0,0 +1,9 @@
package com.meloda.fast.base.adapter
interface OnItemClickListener {
fun onItemClick(position: Int)
}
interface OnItemLongClickListener {
fun onItemLongClick(position: Int)
}
@@ -2,32 +2,18 @@ package com.meloda.fast.common
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.app.DownloadManager
import android.content.ClipboardManager
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.net.ConnectivityManager
import android.os.Handler import android.os.Handler
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.facebook.drawee.backends.pipeline.Fresco
import com.meloda.fast.BuildConfig import com.meloda.fast.BuildConfig
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.UserConfig import com.meloda.fast.UserConfig
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.DatabaseHelper import com.meloda.fast.database.DatabaseHelper
import com.meloda.fast.fragment.SettingsFragment
import com.meloda.fast.util.AndroidUtils import com.meloda.fast.util.AndroidUtils
import com.meloda.mvp.MvpBase
import com.microsoft.appcenter.AppCenter
import com.microsoft.appcenter.analytics.Analytics
import com.microsoft.appcenter.crashes.Crashes
import org.acra.ACRA import org.acra.ACRA
import org.acra.ReportingInteractionMode import org.acra.ReportingInteractionMode
import org.acra.annotation.ReportsCrashes import org.acra.annotation.ReportsCrashes
@@ -46,13 +32,6 @@ import java.util.*
class AppGlobal : Application() { class AppGlobal : Application() {
companion object { companion object {
const val APP_CENTER_TOKEN = "c87e410a-d622-4c52-ad7e-7388ab511704"
lateinit var windowManager: WindowManager
lateinit var connectivityManager: ConnectivityManager
lateinit var inputMethodManager: InputMethodManager
lateinit var clipboardManager: ClipboardManager
lateinit var downloadManager: DownloadManager
lateinit var preferences: SharedPreferences lateinit var preferences: SharedPreferences
lateinit var locale: Locale lateinit var locale: Locale
@@ -82,15 +61,9 @@ class AppGlobal : Application() {
instance = this instance = this
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
AppCenter.start(
this, APP_CENTER_TOKEN, Analytics::class.java, Crashes::class.java
)
ACRA.init(this) ACRA.init(this)
} }
Fresco.initialize(this)
preferences = PreferenceManager.getDefaultSharedPreferences(this) preferences = PreferenceManager.getDefaultSharedPreferences(this)
handler = Handler(mainLooper) handler = Handler(mainLooper)
locale = Locale.getDefault() locale = Locale.getDefault()
@@ -106,62 +79,10 @@ class AppGlobal : Application() {
Companion.packageName = packageName Companion.packageName = packageName
Companion.packageManager = packageManager Companion.packageManager = packageManager
inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
screenWidth = AndroidUtils.getDisplayWidth() screenWidth = AndroidUtils.getDisplayWidth()
screenHeight = AndroidUtils.getDisplayHeight() screenHeight = AndroidUtils.getDisplayHeight()
UserConfig.restore() UserConfig.restore()
MvpBase.init(handler)
logDatabase()
fillMemoryCache()
applyNightMode()
}
private fun logDatabase() {
val users = CacheStorage.usersStorage.getAllValues()
val groups = CacheStorage.groupsStorage.getAllValues()
val chats = CacheStorage.chatsStorage.getAllValues()
val messages = CacheStorage.messagesStorage.getAllValues()
return
}
private fun fillMemoryCache() {
}
fun applyNightMode(value: String? = null): Int {
val mode = value ?: preferences.getString(SettingsFragment.KEY_THEME, "-1")!!
val nightMode = getNightMode(mode.toInt())
val oldNightMode = AppCompatDelegate.getDefaultNightMode()
AppCompatDelegate.setDefaultNightMode(nightMode)
return nightMode
}
fun getNightMode(nightMode: Int = -1): Int {
val mode = if (nightMode != -1) nightMode else preferences.getString(
SettingsFragment.KEY_THEME,
"-1"
)!!.toInt()
return when (mode) {
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
3 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
else -> AppCompatDelegate.MODE_NIGHT_NO
}
} }
} }
@@ -3,8 +3,6 @@ package com.meloda.fast.common
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.fragment.FragmentConversationsDeprecated
import com.meloda.fast.fragment.FragmentFriendsDeprecated
object FragmentSwitcher { object FragmentSwitcher {
@@ -62,8 +60,7 @@ object FragmentSwitcher {
val transaction = fragmentManager.beginTransaction() val transaction = fragmentManager.beginTransaction()
if (fragmentToShow == null) { if (fragmentToShow == null) {
fragmentToShow = createFragmentByTag(tag) throw NullPointerException("Required fragment is null")
transaction.add(containerId, fragmentToShow, tag)
} else { } else {
transaction.show(fragmentToShow) transaction.show(fragmentToShow)
} }
@@ -105,12 +102,4 @@ object FragmentSwitcher {
transaction.commitNow() transaction.commitNow()
} }
private fun createFragmentByTag(tag: String): Fragment {
return when (tag) {
"FragmentFriends" -> FragmentFriendsDeprecated()
"FragmentConversations" -> FragmentConversationsDeprecated()
else -> Fragment()
}
}
} }
@@ -2,10 +2,10 @@ package com.meloda.fast.common
import android.util.Log import android.util.Log
import androidx.collection.arrayMapOf import androidx.collection.arrayMapOf
import com.meloda.concurrent.TaskManager import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.BuildConfig import com.meloda.fast.BuildConfig
import com.meloda.fast.model.NewUpdateInfo import com.meloda.fast.model.NewUpdateInfo
import com.meloda.netservices.HttpRequest import com.meloda.fast.net.HttpRequest
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@@ -1,3 +1,3 @@
package com.meloda.concurrent package com.meloda.fast.concurrent
class EventInfo<T> constructor(var key: String, var data: T? = null) class EventInfo<T> constructor(var key: String, var data: T? = null)
@@ -1,4 +1,4 @@
package com.meloda.concurrent package com.meloda.fast.concurrent
import android.os.Process import android.os.Process
@@ -1,4 +1,4 @@
package com.meloda.concurrent package com.meloda.fast.concurrent
object TaskManager { object TaskManager {
@@ -12,9 +12,9 @@ import com.meloda.fast.database.storage.ChatsStorage
import com.meloda.fast.database.storage.GroupsStorage import com.meloda.fast.database.storage.GroupsStorage
import com.meloda.fast.database.storage.MessagesStorage import com.meloda.fast.database.storage.MessagesStorage
import com.meloda.fast.database.storage.UsersStorage import com.meloda.fast.database.storage.UsersStorage
import com.meloda.vksdk.model.VKConversation import com.meloda.fast.api.model.VKConversation
import com.meloda.vksdk.model.VKMessage import com.meloda.fast.api.model.VKMessage
import com.meloda.vksdk.model.VKUser import com.meloda.fast.api.model.VKUser
import java.util.* import java.util.*
object CacheStorage { object CacheStorage {
@@ -25,8 +25,8 @@ import com.meloda.fast.database.DatabaseKeys.TYPE
import com.meloda.fast.database.DatabaseKeys.UNREAD_COUNT import com.meloda.fast.database.DatabaseKeys.UNREAD_COUNT
import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS import com.meloda.fast.database.DatabaseUtils.TABLE_CHATS
import com.meloda.fast.database.base.Storage import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKConversation import com.meloda.fast.api.model.VKConversation
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject import org.json.JSONObject
@WorkerThread @WorkerThread
@@ -17,8 +17,8 @@ import com.meloda.fast.database.DatabaseKeys.SCREEN_NAME
import com.meloda.fast.database.DatabaseKeys.TYPE import com.meloda.fast.database.DatabaseKeys.TYPE
import com.meloda.fast.database.DatabaseUtils.TABLE_GROUPS import com.meloda.fast.database.DatabaseUtils.TABLE_GROUPS
import com.meloda.fast.database.base.Storage import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKGroup import com.meloda.fast.api.model.VKGroup
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject import org.json.JSONObject
class GroupsStorage : Storage<VKGroup>() { class GroupsStorage : Storage<VKGroup>() {
@@ -23,9 +23,9 @@ import com.meloda.fast.database.DatabaseKeys.TEXT
import com.meloda.fast.database.DatabaseUtils.TABLE_MESSAGES import com.meloda.fast.database.DatabaseUtils.TABLE_MESSAGES
import com.meloda.fast.database.base.Storage import com.meloda.fast.database.base.Storage
import com.meloda.fast.util.Utils import com.meloda.fast.util.Utils
import com.meloda.vksdk.model.VKMessage import com.meloda.fast.api.model.VKMessage
import com.meloda.vksdk.model.VKMessageAction import com.meloda.fast.api.model.VKMessageAction
import com.meloda.vksdk.model.VKModel import com.meloda.fast.api.model.VKModel
import java.util.stream.Collectors import java.util.stream.Collectors
@WorkerThread @WorkerThread
@@ -25,8 +25,8 @@ import com.meloda.fast.database.DatabaseUtils.TABLE_FRIENDS
import com.meloda.fast.database.DatabaseUtils.TABLE_USERS import com.meloda.fast.database.DatabaseUtils.TABLE_USERS
import com.meloda.fast.database.QueryBuilder import com.meloda.fast.database.QueryBuilder
import com.meloda.fast.database.base.Storage import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKUser import com.meloda.fast.api.model.VKUser
import com.meloda.vksdk.util.VKUtil import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject import org.json.JSONObject
@WorkerThread @WorkerThread
@@ -1,113 +0,0 @@
package com.meloda.fast.dialog
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.facebook.imagepipeline.cache.MemoryCache
import com.meloda.extensions.ContextExtensions.drawable
import com.meloda.extensions.DrawableExtensions.tint
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.activity.SettingsActivityDeprecated
import com.meloda.fast.adapter.SimpleItemAdapter
import com.meloda.fast.base.BaseFullscreenDialog
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.item.SimpleMenuItem
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.util.ColorUtils
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.Toolbar
class AccountDialog : BaseFullscreenDialog(), ItemClickListener {
companion object {
const val TAG = "account_fullscreen_dialog"
}
private lateinit var adapter: SimpleItemAdapter
private lateinit var toolbar: Toolbar
private lateinit var recyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var headerRoot: RelativeLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_account, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews()
prepareToolbar()
prepareRecyclerView()
}
private fun initViews() {
toolbar = requireView().findViewById(R.id.toolbar)
recyclerView = requireView().findViewById(R.id.recyclerView)
refreshLayout = requireView().findViewById(R.id.refreshLayout)
headerRoot = requireView().findViewById(R.id.headerRoot)
}
private fun prepareToolbar() {
toolbar.navigationIcon = requireContext().drawable(R.drawable.ic_close)
toolbar.tintNavigationIcon(ColorUtils.getColorAccent(requireContext()))
toolbar.setTitle(R.string.account_dialog_title)
toolbar.setTitleMode(Toolbar.TitleMode.SIMPLE)
toolbar.setNavigationClickListener { dismiss() }
// MemoryCache.getUserById(UserConfig.userId)?.let {
// AppGlobal.handler.post { ViewUtils.prepareNavigationHeader(headerRoot, it) }
// }
}
private fun prepareRecyclerView() {
refreshLayout.isEnabled = false
recyclerView.layoutManager =
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
recyclerView.setHasFixedSize(true)
createItemsAndAdapter()
}
private fun createItemsAndAdapter() {
val items = arrayListOf<SimpleMenuItem>()
SimpleMenuItem(
requireContext().drawable(R.drawable.ic_settings_outline)
.tint(ColorUtils.getColorAccent(requireContext())),
requireContext().getString(R.string.navigation_settings)
) { openSettingsScreen() }.let { items.add(it) }
adapter = SimpleItemAdapter(requireContext(), items).also {
it.itemClickListener = this
}
recyclerView.adapter = adapter
}
private fun openSettingsScreen() {
startActivity(Intent(requireContext(), SettingsActivityDeprecated::class.java))
}
override fun onItemClick(position: Int) {
val item = adapter.getItem(position)
item.clickListener?.let {
it.onClick(requireView().findViewById(android.R.id.content))
dismiss()
}
}
}
@@ -1,115 +0,0 @@
package com.meloda.fast.dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.meloda.fast.R
import com.meloda.fast.adapter.SimpleItemAdapter
import com.meloda.fast.item.SimpleMenuItem
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKUser
open class ProfileDialog(
private val conversation: VKConversation,
private val chatTitle: String
) : BottomSheetDialogFragment() {
companion object {
const val TAG = "profile_bottom_sheet_dialog"
}
private lateinit var title: TextView
private lateinit var subtitle: TextView
private lateinit var recyclerView: RecyclerView
private lateinit var root: LinearLayout
private var adapter: SimpleItemAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.AppTheme_ProfileDialog)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_profile_bottom, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
title = view.findViewById(R.id.profileTitle)
subtitle = view.findViewById(R.id.profileSubtitle)
recyclerView = view.findViewById(R.id.profileItemMenu)
root = view.findViewById(R.id.profileRoot)
val layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
recyclerView.layoutManager = layoutManager
title.text = chatTitle
subtitle.text = getSubtitle()
val items = ArrayList<SimpleMenuItem>()
items.add(
SimpleMenuItem(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_search
)!!, "Search"
)
)
createAdapter(items)
}
private fun createAdapter(items: ArrayList<SimpleMenuItem>) {
adapter = SimpleItemAdapter(requireContext(), items)
recyclerView.adapter = adapter
}
private fun getSubtitle(): String {
return when (conversation.type) {
VKConversation.Type.CHAT -> getString(
R.string.chat_members,
conversation.membersCount
)
VKConversation.Type.GROUP -> {
// val group = MemoryCache.getGroupById(conversation.conversationId) ?: return ""
//
// "@${group.screenName}"
""
}
VKConversation.Type.USER -> {
// val user = MemoryCache.getUserById(conversation.id) ?: return ""
//TODO: придумать чо делать
val user: VKUser = null ?: return ""
var str =
if (user.screenName.contains("id${user.userId}")) "" else "@${user.screenName}"
val online =
getString(if (user.isOnlineMobile) R.string.user_online_mobile else if (user.isOnline) R.string.user_online else R.string.user_offline)
str += if (str.isEmpty()) online else " · $online"
str
}
else -> ""
}
}
}
@@ -1,4 +1,4 @@
package com.meloda.extensions package com.meloda.fast.extensions
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
@@ -1,4 +1,4 @@
package com.meloda.extensions package com.meloda.fast.extensions
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@@ -1,4 +1,4 @@
package com.meloda.extensions package com.meloda.fast.extensions
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -0,0 +1,116 @@
package com.meloda.fast.extensions
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
object LiveDataExtensions {
operator fun <T> MutableLiveData<MutableList<T>>.set(position: Int, v: T) {
val value = (this.value ?: arrayListOf()).apply { this[position] = v }
this.value = value
}
operator fun <T> MutableLiveData<MutableList<T>>.get(position: Int): T {
return (value as MutableList<T>)[position]
}
@JvmOverloads
fun <T> MutableLiveData<MutableList<T>>.add(v: T, position: Int = -1) {
val value = (this.value ?: arrayListOf()).apply {
if (position == -1) this.add(v) else this.add(position, v)
}
this.value = value
}
@JvmOverloads
fun <T> MutableLiveData<MutableList<T>>.addAll(values: List<T>, position: Int = -1) {
val value = (this.value ?: arrayListOf()).apply {
if (position == -1) this.addAll(values)
else this.addAll(position, values)
}
this.value = value
}
@Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
fun <T> MutableLiveData<MutableList<T>>.removeAll(values: List<T>) {
val value = (this.value ?: arrayListOf()).apply {
this.removeAll(values)
}
this.value = value
}
fun <T> MutableLiveData<MutableList<T>>.removeAt(index: Int) {
val value = (this.value ?: arrayListOf()).apply {
this.removeAt(index)
}
this.value = value
}
fun <T> MutableLiveData<MutableList<T>>.remove(item: T) {
val value = (this.value ?: arrayListOf()).apply {
this.remove(item)
}
this.value = value
}
operator fun <T> MutableLiveData<MutableList<T>>.iterator(): Iterator<T> {
return (value as MutableList<T>).iterator()
}
fun <T> MutableLiveData<MutableList<T>>.clear() {
value = arrayListOf()
}
val <T> MutableLiveData<MutableList<T>>.indices get() = (value as MutableList<T>).indices
val <T> MutableLiveData<MutableList<T>>.size get() = (value as MutableList<T>).size
fun <T> MutableLiveData<MutableList<T>>.isEmpty(): Boolean {
return (value as MutableList<T>).isEmpty()
}
fun <T> MutableLiveData<MutableList<T>>.isNotEmpty(): Boolean {
return !isEmpty()
}
fun <T> MutableLiveData<MutableList<T>>.requireValue() = value!!
@UiThread
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(values: List<T>) {
val value = (this.value ?: arrayListOf()).apply {
this.addAll(values)
}
this.value = value
}
operator fun <T> MutableLiveData<MutableList<T>>.plusAssign(v: T) {
val value = (this.value ?: arrayListOf()).apply {
this.add(v)
}
this.value = value
}
operator fun <T> MutableLiveData<MutableList<T>>.minusAssign(values: List<T>) {
val value = (this.value ?: arrayListOf()).apply {
this.removeAll(values)
}
this.value = value
}
operator fun <T> MutableLiveData<MutableList<T>>.minusAssign(v: T) {
val value = (this.value ?: arrayListOf()).apply {
this.remove(v)
}
this.value = value
}
}
@@ -1,4 +1,4 @@
package com.meloda.extensions package com.meloda.fast.extensions
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package com.meloda.extensions package com.meloda.fast.extensions
import android.widget.TextView import android.widget.TextView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@@ -1,115 +0,0 @@
package com.meloda.fast.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.adapter.ConversationsAdapterDeprecated
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.widget.Toolbar
import com.meloda.vksdk.OnResponseListener
import com.meloda.vksdk.VKApi
import com.meloda.vksdk.VKConstants
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
class ChatsFragment : BaseFragment() {
private lateinit var toolbar: Toolbar
private lateinit var progressBar: ProgressBar
private lateinit var recyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var noItemsView: LinearLayout
private lateinit var noInternetView: LinearLayout
private lateinit var errorView: LinearLayout
private lateinit var adapter: ConversationsAdapterDeprecated
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_conversations, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews()
prepareViews()
createAdapter()
loadConversations()
}
private fun initViews() {
toolbar = requireView().findViewById(R.id.toolbar)
progressBar = requireView().findViewById(R.id.progressBar)
recyclerView = requireView().findViewById(R.id.recyclerView)
refreshLayout = requireView().findViewById(R.id.refreshLayout)
noItemsView = requireView().findViewById(R.id.noItemsView)
noInternetView = requireView().findViewById(R.id.noInternetView)
errorView = requireView().findViewById(R.id.errorView)
}
private fun prepareViews() {
prepareRecyclerView()
}
private fun prepareRecyclerView() {
val manager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
recyclerView.layoutManager = manager
}
private fun createAdapter() {
adapter = ConversationsAdapterDeprecated(recyclerView, arrayListOf())
recyclerView.adapter = adapter
}
private fun loadConversations() {
TaskManager.execute {
VKApi.messages()
.getConversations()
.filter("all")
.extended(true)
.fields(VKConstants.USER_FIELDS)
.offset(0)
.count(30)
.executeArray(
VKConversation::class.java,
object : OnResponseListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
TaskManager.execute {
CacheStorage.chatsStorage.insertValues(response)
val lastMessages = arrayListOf<VKMessage>()
response.forEach { lastMessages.add(it.lastMessage) }
CacheStorage.messagesStorage.insertValues(lastMessages)
CacheStorage.usersStorage.insertValues(VKConversation.profiles)
CacheStorage.groupsStorage.insertValues(VKConversation.groups)
}
adapter.updateValues(response)
adapter.notifyDataSetChanged()
}
override fun onError(t: Throwable) {
}
})
}
}
}
@@ -1,186 +0,0 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.activity.MessagesActivityDeprecated
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.fragment.ui.presenter.ConversationsPresenterDeprecated
import com.meloda.fast.fragment.ui.view.ConversationsViewDeprecated
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.Toolbar
import com.meloda.vksdk.VKApiKeys
@Suppress("UNCHECKED_CAST")
class FragmentConversationsDeprecated : BaseFragment(), ConversationsViewDeprecated {
private lateinit var presenterDeprecated: ConversationsPresenterDeprecated
private lateinit var toolbar: Toolbar
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var recyclerView: RecyclerView
private lateinit var progressBar: ProgressBar
private lateinit var noItemsView: LinearLayout
private lateinit var noInternetView: LinearLayout
private lateinit var errorView: LinearLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_conversations, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews()
prepareToolbar()
prepareRecyclerView()
prepareRefreshLayout()
presenterDeprecated = ConversationsPresenterDeprecated(this)
presenterDeprecated.setup(recyclerView, refreshLayout)
}
private fun initViews() {
toolbar = requireView().findViewById(R.id.toolbar)
recyclerView = requireView().findViewById(R.id.recyclerView)
refreshLayout = requireView().findViewById(R.id.refreshLayout)
progressBar = requireView().findViewById(R.id.progressBar)
noItemsView = requireView().findViewById(R.id.noItemsView)
noInternetView = requireView().findViewById(R.id.noInternetView)
errorView = requireView().findViewById(R.id.errorView)
}
private fun prepareToolbar() {
initToolbar(R.id.toolbar)
toolbar.title = getString(R.string.navigation_chats)
setProfileAvatar()
TaskManager.addOnEventListener(object : TaskManager.OnEventListener {
override fun onNewEvent(info: EventInfo<*>) {
if (info.key == VKApiKeys.UPDATE_USER.name) {
val userIds = info.data as ArrayList<Int>
if (userIds.contains(UserConfig.userId)) {
setProfileAvatar()
}
}
}
})
}
private fun prepareRefreshLayout() {
refreshLayout.setColorSchemeResources(R.color.accent)
}
private fun prepareRecyclerView() {
val manager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
val decoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
decoration.setDrawable(
ColorDrawable(
AndroidUtils.getThemeAttrColor(
requireContext(),
R.attr.dividerHorizontal
)
)
)
recyclerView.itemAnimator = null
recyclerView.addItemDecoration(decoration)
recyclerView.layoutManager = manager
}
private fun setProfileAvatar() {
TaskManager.execute {
// AppGlobal.database.users.getById(UserConfig.userId)?.let {
// if (it.photo100.isNotEmpty()) {
// runOnUiThread {
// toolbar.getAvatar().setImageURI(it.photo100)
// }
// }
// }
}
}
override fun openChat(extras: Bundle) {
startActivity(
Intent(requireContext(), MessagesActivityDeprecated::class.java).putExtras(
extras
)
)
}
override fun showErrorSnackbar(t: Throwable) {
ViewUtils.showErrorSnackbar(requireView(), t)
}
override fun prepareNoItemsView() {
}
override fun showNoItemsView() {
noItemsView.isVisible = true
}
override fun hideNoItemsView() {
noItemsView.isVisible = false
}
override fun prepareNoInternetView() {
}
override fun showNoInternetView() {
noInternetView.isVisible = true
}
override fun hideNoInternetView() {
noInternetView.isVisible = false
}
override fun prepareErrorView() {
}
override fun showErrorView() {
errorView.isVisible = true
}
override fun hideErrorView() {
errorView.isVisible = false
}
override fun showProgressBar() {
progressBar.isVisible = true
}
override fun hideProgressBar() {
progressBar.isVisible = false
}
override fun showRefreshLayout() {
refreshLayout.isRefreshing = true
}
override fun hideRefreshLayout() {
refreshLayout.isRefreshing = false
}
}
@@ -1,194 +0,0 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.activity.MessagesActivityDeprecated
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.fragment.ui.presenter.FriendsPresenterDeprecated
import com.meloda.fast.fragment.ui.view.FriendsViewDeprecated
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.Toolbar
import com.meloda.vksdk.VKApiKeys
class FragmentFriendsDeprecated(private val userId: Int = 0) : BaseFragment(),
FriendsViewDeprecated {
private lateinit var presenterDeprecated: FriendsPresenterDeprecated
private lateinit var toolbar: Toolbar
private lateinit var recyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var progressBar: ProgressBar
private lateinit var noItemsView: LinearLayout
private lateinit var noInternetView: LinearLayout
private lateinit var errorView: LinearLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_friends, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews()
prepareToolbar()
prepareRecyclerView()
prepareRefreshLayout()
presenterDeprecated = FriendsPresenterDeprecated(this)
presenterDeprecated.setup(userId, recyclerView, refreshLayout)
}
private fun initViews() {
toolbar = requireView().findViewById(R.id.toolbar)
recyclerView = requireView().findViewById(R.id.recyclerView)
refreshLayout = requireView().findViewById(R.id.refreshLayout)
progressBar = requireView().findViewById(R.id.progressBar)
noItemsView = requireView().findViewById(R.id.noItemsView)
noInternetView = requireView().findViewById(R.id.noInternetView)
errorView = requireView().findViewById(R.id.errorView)
}
private fun prepareToolbar() {
initToolbar(R.id.toolbar)
toolbar.title = getString(R.string.navigation_friends)
setProfileAvatar()
toolbar.inflateMenu(R.menu.fragment_friends)
TaskManager.addOnEventListener(object : TaskManager.OnEventListener {
override fun onNewEvent(info: EventInfo<*>) {
if (info.key == VKApiKeys.UPDATE_USER.name) {
val userId = info.data as ArrayList<Int>
if (userId[0] == UserConfig.userId) {
setProfileAvatar()
}
}
}
})
}
private fun setProfileAvatar() {
TaskManager.execute {
// AppGlobal.database.users.getById(UserConfig.userId)?.let {
// if (it.photo100.isNotEmpty()) {
// runOnUi {
// toolbar.getAvatar().setImageURI(it.photo100)
// }
// }
// }
}
}
override fun onDetach() {
presenterDeprecated.destroy()
super.onDetach()
}
private fun prepareRefreshLayout() {
refreshLayout.setColorSchemeResources(R.color.accent)
}
private fun prepareRecyclerView() {
val manager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
val decoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
decoration.setDrawable(
ColorDrawable(
ContextCompat.getColor(
requireContext(),
R.color.divider
)
)
)
recyclerView.addItemDecoration(decoration)
recyclerView.layoutManager = manager
}
override fun openChat(extras: Bundle) {
startActivity(
Intent(requireContext(), MessagesActivityDeprecated::class.java).putExtras(
extras
)
)
}
override fun showErrorSnackbar(t: Throwable) {
ViewUtils.showErrorSnackbar(requireView(), t)
}
override fun prepareNoItemsView() {
}
override fun showNoItemsView() {
noItemsView.isVisible = true
}
override fun hideNoItemsView() {
noItemsView.isVisible = false
}
override fun prepareNoInternetView() {
}
override fun showNoInternetView() {
noInternetView.isVisible = true
}
override fun hideNoInternetView() {
noInternetView.isVisible = false
}
override fun prepareErrorView() {
}
override fun showErrorView() {
errorView.isVisible = true
}
override fun hideErrorView() {
errorView.isVisible = false
}
override fun showProgressBar() {
progressBar.isVisible = true
}
override fun hideProgressBar() {
progressBar.isVisible = false
}
override fun showRefreshLayout() {
refreshLayout.isRefreshing = true
}
override fun hideRefreshLayout() {
refreshLayout.isRefreshing = false
}
}
@@ -1,171 +1,22 @@
package com.meloda.fast.fragment package com.meloda.fast.fragment
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.viewbinding.library.fragment.viewBinding
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.meloda.fast.R import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment import com.meloda.fast.base.BaseFragment
import com.meloda.fast.fragment.ui.presenter.LoginPresenter import com.meloda.fast.databinding.FragmentLoginBinding
import com.meloda.fast.fragment.ui.view.LoginView
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.KeyboardUtils
import kotlin.math.roundToInt
class LoginFragment : BaseFragment(), LoginView { class LoginFragment : BaseFragment(R.layout.fragment_login) {
private lateinit var presenter: LoginPresenter private val binding: FragmentLoginBinding by viewBinding()
private lateinit var email: EditText
private lateinit var password: EditText
private lateinit var authorize: MaterialButton
private lateinit var card: MaterialCardView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = LoginPresenter(this)
presenter.onCreate(requireContext(), this, savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
presenter.onCreateView(savedInstanceState)
return inflater.inflate(R.layout.fragment_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
presenter.onViewCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
} }
override fun initViews() {
email = requireView().findViewById(R.id.loginEmailEditText)
password = requireView().findViewById(R.id.loginPasswordEditText)
authorize = requireView().findViewById(R.id.loginAuthorize)
card = requireView().findViewById(R.id.loginCard)
}
override fun prepareViews() {
prepareEmailEditText()
preparePasswordEditText()
prepareAuthorizeButton()
prepareCardView()
}
private fun prepareEmailEditText() {
email.addTextChangedListener(onTextChangedListener)
}
private fun preparePasswordEditText() {
password.addTextChangedListener(onTextChangedListener)
password.setOnEditorActionListener { _, _, event ->
if (event == null) return@setOnEditorActionListener false
return@setOnEditorActionListener if (event.action == EditorInfo.IME_ACTION_GO ||
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
) {
KeyboardUtils.hideKeyboardFrom(password)
authorize.performClick()
true
} else false
}
}
private fun prepareAuthorizeButton() {
authorize.isEnabled = false
authorize.setOnClickListener {
val emailString = email.text.toString().trim()
val passwordString = password.text.toString().trim()
presenter.login(emailString, passwordString)
}
}
private fun prepareCardView() {
val width = AndroidUtils.dp(resources.displayMetrics.widthPixels).roundToInt()
if (width < 380) {
card.strokeWidth = 0
}
}
override fun showErrorSnackbar(t: Throwable) {
TODO("Not yet implemented")
}
override fun prepareNoItemsView() {
TODO("Not yet implemented")
}
override fun showNoItemsView() {
TODO("Not yet implemented")
}
override fun hideNoItemsView() {
TODO("Not yet implemented")
}
override fun prepareNoInternetView() {
TODO("Not yet implemented")
}
override fun showNoInternetView() {
TODO("Not yet implemented")
}
override fun hideNoInternetView() {
TODO("Not yet implemented")
}
override fun prepareErrorView() {
TODO("Not yet implemented")
}
override fun showErrorView() {
TODO("Not yet implemented")
}
override fun hideErrorView() {
TODO("Not yet implemented")
}
override fun showProgressBar() {
TODO("Not yet implemented")
}
override fun hideProgressBar() {
TODO("Not yet implemented")
}
override fun showRefreshLayout() {
TODO("Not yet implemented")
}
override fun hideRefreshLayout() {
TODO("Not yet implemented")
}
private val onTextChangedListener = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
authorize.isEnabled =
email.text.toString().trim().isNotEmpty() &&
password.text.toString().trim().isNotEmpty()
}
override fun afterTextChanged(s: Editable?) {
}
}
} }
@@ -1,203 +0,0 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import com.meloda.concurrent.TaskManager
import com.meloda.extensions.ContextExtensions.color
import com.meloda.fast.R
import com.meloda.fast.activity.DropUserDataActivity
import com.meloda.fast.activity.UpdateActivityDeprecated
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.util.AndroidUtils
class SettingsFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceClickListener,
Preference.OnPreferenceChangeListener {
companion object {
const val CATEGORY_GENERAL = "general"
const val KEY_HIDE_KEYBOARD_ON_SCROLL_UP = "hide_keyboard_on_scroll_up"
const val CATEGORY_APPEARANCE = "appearance"
const val KEY_EXTENDED_CONVERSATIONS = "appearance_extended_conversations"
const val KEY_THEME = "appearance_theme"
const val CATEGORY_ABOUT = "about"
const val KEY_APP_VERSION = "app_version"
const val CATEGORY_ACCOUNT = "account"
const val KEY_ACCOUNT_LOGOUT = "account_logout"
const val CATEGORY_DEBUG = "debug"
const val KEY_CLEAR_USERS_GROUPS_CACHE = "clear_users_groups_cache"
}
private var currentPreferenceLayout = 0
private var isRestoringState = false
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.fragment_settings, rootKey)
currentPreferenceLayout = R.xml.fragment_settings
init()
}
private fun init() {
setTitle()
setPreferencesFromResource(currentPreferenceLayout, null)
val general = findPreference<Preference>(CATEGORY_GENERAL)
general?.onPreferenceClickListener = rootLayoutClickListener
val account = findPreference<Preference>(CATEGORY_ACCOUNT)
account?.onPreferenceClickListener = rootLayoutClickListener
val logout = findPreference<Preference>(KEY_ACCOUNT_LOGOUT)
logout?.onPreferenceClickListener = this
val about = findPreference<Preference>(CATEGORY_ABOUT)
about?.onPreferenceClickListener = rootLayoutClickListener
val appVersion = findPreference<Preference>(KEY_APP_VERSION)
appVersion?.onPreferenceClickListener = this
val appearance = findPreference<Preference>(CATEGORY_APPEARANCE)
appearance?.onPreferenceClickListener = rootLayoutClickListener
val extendedConversations = findPreference<Preference>(KEY_EXTENDED_CONVERSATIONS)
extendedConversations?.onPreferenceChangeListener = this
val theme = findPreference<Preference>(KEY_THEME)
theme?.onPreferenceChangeListener = this
val debug = findPreference<Preference>(CATEGORY_DEBUG)
debug?.onPreferenceClickListener = rootLayoutClickListener
updateDebugCategoryVisibility()
val clearUsersGroupsCache = findPreference<Preference>(KEY_CLEAR_USERS_GROUPS_CACHE)
clearUsersGroupsCache?.onPreferenceClickListener = this
applyTintInPreferenceScreen(preferenceScreen)
}
override fun onResume() {
super.onResume()
updateDebugCategoryVisibility()
}
private fun updateDebugCategoryVisibility() {
findPreference<Preference>(CATEGORY_DEBUG)?.isVisible =
AndroidUtils.isDeveloperSettingsEnabled(requireContext())
}
private val rootLayoutClickListener =
Preference.OnPreferenceClickListener { changeRootLayout(it) }
private fun setTitle() {
var title = R.string.navigation_settings
when (currentPreferenceLayout) {
R.xml.fragment_settings_general -> title = R.string.prefs_general
R.xml.fragment_settings_appearance -> title = R.string.prefs_appearance
R.xml.fragment_settings_about -> title = R.string.prefs_about
R.xml.fragment_settings_account -> title = R.string.prefs_account
}
requireActivity().setTitle(title)
}
private fun changeRootLayout(preference: Preference): Boolean {
currentPreferenceLayout = when (preference.key) {
CATEGORY_GENERAL -> R.xml.fragment_settings_general
CATEGORY_ABOUT -> R.xml.fragment_settings_about
CATEGORY_ACCOUNT -> R.xml.fragment_settings_account
CATEGORY_APPEARANCE -> R.xml.fragment_settings_appearance
CATEGORY_DEBUG -> R.xml.fragment_settings_debug
else -> R.xml.fragment_settings
}
init()
return true
}
private fun applyTintInPreferenceScreen(rootScreen: PreferenceScreen) {
if (rootScreen.preferenceCount > 0) {
for (i in 0 until rootScreen.preferenceCount) {
val preference = rootScreen.getPreference(i)
tintPreference(preference)
}
}
}
private fun tintPreference(preference: Preference) {
if (preference.icon != null && context != null) {
preference.icon.setTint(requireContext().color(R.color.accent))
}
}
override fun onPreferenceClick(preference: Preference): Boolean {
when (preference.key) {
KEY_ACCOUNT_LOGOUT -> {
logout()
return true
}
KEY_APP_VERSION -> {
openUpdateScreen()
return true
}
KEY_CLEAR_USERS_GROUPS_CACHE -> {
showClearCacheConfirmation()
}
}
return false
}
private fun showClearCacheConfirmation() {
val builder = AlertDialog.Builder(requireContext())
builder.setMessage("Clear cache?")
builder.setPositiveButton("Yes") { _, _ ->
TaskManager.execute {
// AppGlobal.database.users.clear()
// AppGlobal.database.groups.clear()
}
}
builder.setNegativeButton("No", null)
builder.show()
}
private fun openUpdateScreen() {
startActivity(Intent(requireContext(), UpdateActivityDeprecated::class.java))
}
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
when (preference.key) {
KEY_EXTENDED_CONVERSATIONS -> {
return true
}
KEY_THEME -> {
val nightMode = AppGlobal.instance.applyNightMode(newValue as String)
(requireActivity() as AppCompatActivity).delegate.localNightMode = nightMode
return true
}
}
return false
}
fun onBackPressed() = if (currentPreferenceLayout == R.xml.fragment_settings) {
true
} else {
currentPreferenceLayout = R.xml.fragment_settings
init()
false
}
private fun logout() {
startActivity(Intent(requireContext(), DropUserDataActivity::class.java))
}
}
@@ -1,88 +0,0 @@
package com.meloda.fast.fragment
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.os.bundleOf
import com.meloda.fast.base.BaseFragment
import com.meloda.vksdk.VKAuth
class ValidationFragment : BaseFragment() {
private var url: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null && requireArguments().isEmpty.not()) {
url = requireArguments().getString("url") ?: ""
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val webView = WebView(requireContext())
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
parseUrl(url ?: "")
}
}
webView.settings.domStorageEnabled = true
webView.clearCache(true)
webView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val manager = CookieManager.getInstance()
manager.removeAllCookies(null)
manager.flush()
manager.setAcceptCookie(true)
return webView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
(requireView() as WebView).loadUrl(url)
}
private fun parseUrl(url: String) {
Log.d("WebView url", url)
try {
if (url.startsWith("https://oauth.vk.com/blank.html#success=1")) {
Log.d("Success WebView", "")
if (!url.contains("error=")) {
val auth = VKAuth.parseRedirectUrl(url)
val token = auth[0]
val userId = auth[1].toInt()
parentFragmentManager.setFragmentResult(
"validation",
bundleOf(
Pair("token", token),
Pair("userId", userId)
)
)
parentFragmentManager.popBackStack()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -1,85 +0,0 @@
package com.meloda.fast.fragment.ui.presenter
import android.os.Bundle
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.fast.adapter.ChatsAdapter
import com.meloda.fast.common.TimeManager
import com.meloda.fast.fragment.ui.repository.ChatsRepository
import com.meloda.fast.fragment.ui.view.ChatsView
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.listener.ItemLongClickListener
import com.meloda.fast.util.AndroidUtils
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpPresenter
import com.meloda.vksdk.model.VKConversation
class ChatsPresenter(viewState: ChatsView) :
MvpPresenter<VKConversation, ChatsRepository, ChatsView>(
viewState, ChatsRepository::class.java.name
),
ItemClickListener,
ItemLongClickListener,
TimeManager.OnMinuteChangeListener {
companion object {
const val DEFAULT_CONVERSATIONS_COUNT = 30
}
private lateinit var adapter: ChatsAdapter
override fun onViewCreated(bundle: Bundle?) {
viewState.initViews()
}
fun setup(recyclerView: RecyclerView, refreshLayout: SwipeRefreshLayout) {
viewState.prepareViews()
createAdapter()
}
private fun createAdapter() {
adapter = ChatsAdapter(requireContext(), arrayListOf()).also {
it.itemClickListener = this
it.itemLongClickListener = this
}
}
private fun fillAdapter(conversations: ArrayList<VKConversation>, offset: Int) {
}
private fun getCachedConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnLoadListener? = null
) {
listener?.onSuccess()
}
private fun loadConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnLoadListener? = null
) {
if (AndroidUtils.hasConnection()) {
} else {
}
}
override fun onItemClick(position: Int) {
TODO("Not yet implemented")
}
override fun onItemLongClick(position: Int) {
TODO("Not yet implemented")
}
override fun onMinuteChange(currentMinute: Int) {
TODO("Not yet implemented")
}
}
@@ -1,256 +0,0 @@
package com.meloda.fast.fragment.ui.presenter
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.arrayutils.ArrayUtils
import com.meloda.fast.BuildConfig
import com.meloda.fast.adapter.ConversationsAdapterDeprecated
import com.meloda.fast.adapter.diffutil.ConversationsCallbackDeprecated
import com.meloda.fast.common.TimeManager
import com.meloda.fast.fragment.ui.repository.ConversationsRepositoryDeprecated
import com.meloda.fast.fragment.ui.view.ConversationsViewDeprecated
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.listener.ItemLongClickListener
import com.meloda.fast.util.AndroidUtils
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpPresenter
import com.meloda.vksdk.model.VKConversation
import java.util.*
class ConversationsPresenterDeprecated(viewState: ConversationsViewDeprecated) :
MvpPresenter<VKConversation, ConversationsRepositoryDeprecated, ConversationsViewDeprecated>(
viewState,
ConversationsRepositoryDeprecated::class.java.name
),
ItemClickListener,
ItemLongClickListener,
TimeManager.OnMinuteChangeListener {
companion object {
const val DEFAULT_CONVERSATIONS_COUNT = 30
}
private var conversationsCount: Int = 0
private lateinit var adapter: ConversationsAdapterDeprecated
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
fun setup(recyclerView: RecyclerView, refreshLayout: SwipeRefreshLayout) {
this.recyclerView = recyclerView
this.context = recyclerView.context
this.layoutManager = recyclerView.layoutManager as LinearLayoutManager
prepareViews()
// setRecyclerViewScrollListener(recyclerView)
setRefreshLayoutListener(refreshLayout)
createAdapter()
TimeManager.addOnMinuteChangeListener(this)
loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// getCachedConversations(0, DEFAULT_CONVERSATIONS_COUNT, object : MvpOnLoadListener<Any?> {
// override fun onResponse(response: Any?) {
// setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
// loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// }
//
// override fun onError(t: Throwable) {
// setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
// loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// }
// })
}
private fun setRecyclerViewScrollListener(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
if (adapter.isLastItem() && !adapter.isLoading && adapter.itemCount < conversationsCount) {
adapter.isLoading = true
val position = adapter.itemCount - 1
// adapter.itemCount - 1 - (layoutManager.findLastCompletelyVisibleItemPosition() - layoutManager.findFirstCompletelyVisibleItemPosition())
setState(ListState.FILLED_LOADING)
if (AndroidUtils.hasConnection()) {
loadConversations(adapter.itemCount, DEFAULT_CONVERSATIONS_COUNT,
object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
} else {
getCachedConversations(adapter.itemCount, DEFAULT_CONVERSATIONS_COUNT,
object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
}
if (BuildConfig.DEBUG)
Log.d("RecyclerView", "Bottom reached")
}
}
}
})
}
private fun setRefreshLayoutListener(refreshLayout: SwipeRefreshLayout) {
refreshLayout.setOnRefreshListener { loadConversations() }
}
private fun getCachedConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnResponseListener<Any?>? = null
) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
repository.getCachedConversations(offset, count,
object : MvpOnResponseListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
conversationsCount = response.size
val conversations = ArrayUtils.cut(response, offset, count)
fillAdapter(conversations, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun loadConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnResponseListener<Any?>? = null
) {
if (!AndroidUtils.hasConnection()) {
setState(if (adapter.isEmpty()) ListState.EMPTY_NO_INTERNET else ListState.FILLED)
return
} else {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
}
repository.loadConversations(offset, count,
object : MvpOnResponseListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
conversationsCount = VKConversation.conversationsCount
fillAdapter(response, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
override fun destroy() {
adapter.destroy()
TimeManager.removeOnMinuteChangeListener(this)
}
private fun createAdapter() {
adapter = ConversationsAdapterDeprecated(recyclerView, arrayListOf()).also {
it.itemClickListener = this
it.itemLongClickListener = this
}
recyclerView.adapter = adapter
}
private fun fillAdapter(conversations: ArrayList<VKConversation>, offset: Int) {
val oldItems = ArrayList(adapter.values)
if (offset > 0) {
adapter.addAll(conversations)
} else {
adapter.updateValues(conversations)
}
adapter.notifyChanges(oldItems)
if (offset == 0) recyclerView.scrollToPosition(0)
}
override fun onItemClick(position: Int) {
openChat(adapter[position])
}
override fun onItemLongClick(position: Int) {
}
override fun onMinuteChange(currentMinute: Int) {
post {
adapter.notifyItemRangeChanged(
0,
adapter.itemCount,
ConversationsCallbackDeprecated.DATE
)
}
}
private fun openChat(conversation: VKConversation) {
// TaskManager.execute {
// val peerUser = MemoryCache.getUserById(conversation.conversationId)
// val peerGroup = MemoryCache.getGroupById(conversation.conversationId)
//
// val extras = Bundle().also {
// it.putInt(MessagesActivityDeprecated.TAG_EXTRA_ID, conversation.conversationId)
// it.putString(
// MessagesActivityDeprecated.TAG_EXTRA_TITLE,
// VKUtil.getTitle(conversation, peerUser, peerGroup)
// )
// it.putString(
// MessagesActivityDeprecated.TAG_EXTRA_AVATAR,
// VKUtil.getAvatar(conversation, peerUser, peerGroup)
// )
// it.putSerializable(MessagesActivityDeprecated.TAG_EXTRA_USER, peerUser)
// it.putSerializable(MessagesActivityDeprecated.TAG_EXTRA_GROUP, peerGroup)
// }
//
// post { viewState.openChat(extras) }
// }
}
}
@@ -1,232 +0,0 @@
package com.meloda.fast.fragment.ui.presenter
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.arrayutils.ArrayUtils
import com.meloda.fast.activity.MessagesActivityDeprecated
import com.meloda.fast.adapter.UsersAdapterDeprecated
import com.meloda.fast.fragment.ui.repository.FriendsRepositoryDeprecated
import com.meloda.fast.fragment.ui.view.FriendsViewDeprecated
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.util.AndroidUtils
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpPresenter
import com.meloda.vksdk.model.VKUser
class FriendsPresenterDeprecated(viewState: FriendsViewDeprecated) :
MvpPresenter<VKUser, FriendsRepositoryDeprecated, FriendsViewDeprecated>(
viewState,
FriendsRepositoryDeprecated::class.java.name
),
ItemClickListener {
companion object {
const val ONLY_ONLINE = "_only_online"
const val DEFAULT_FRIENDS_COUNT = 30
}
private var userId: Int = 0
private var friendsCount: Int = 0
private lateinit var adapter: UsersAdapterDeprecated
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
fun setup(userId: Int, recyclerView: RecyclerView, refreshLayout: SwipeRefreshLayout) {
this.userId = userId
this.recyclerView = recyclerView
this.context = recyclerView.context
this.layoutManager = recyclerView.layoutManager as LinearLayoutManager
setRecyclerViewScrollListener(recyclerView)
setRefreshListener(refreshLayout)
createAdapter()
getCachedFriends(userId, 0, DEFAULT_FRIENDS_COUNT, false, object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
setState(if (adapter.isEmpty()) MvpPresenter.ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
loadFriends(userId, 0, DEFAULT_FRIENDS_COUNT)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
loadFriends(userId, 0, DEFAULT_FRIENDS_COUNT)
}
})
}
private fun getCachedFriends(
userId: Int,
offset: Int = 0,
count: Int = DEFAULT_FRIENDS_COUNT,
onlyOnline: Boolean = false,
listener: MvpOnResponseListener<Any?>? = null
) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
repository.getCachedFriends(
userId,
offset,
count,
onlyOnline,
object : MvpOnResponseListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
val friends = ArrayUtils.cut(response, offset, count)
fillAdapter(friends, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun loadFriends(
userId: Int,
offset: Int = 0,
count: Int = DEFAULT_FRIENDS_COUNT,
onlyOnline: Boolean = false,
listener: MvpOnResponseListener<Any?>? = null
) {
if (!AndroidUtils.hasConnection()) {
setState(if (adapter.isEmpty()) ListState.EMPTY_NO_INTERNET else ListState.FILLED)
return
} else {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
}
repository.loadFriends(
userId,
offset,
count,
object : MvpOnResponseListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
friendsCount = VKUser.friendsCount
fillAdapter(response, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun setRecyclerViewScrollListener(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
if (adapter.isLastItem() && !adapter.isLoading && adapter.itemCount < friendsCount) {
adapter.isLoading = true
val position = adapter.itemCount - 1
// adapter.itemCount - 1 - (layoutManager.findLastCompletelyVisibleItemPosition() - layoutManager.findFirstCompletelyVisibleItemPosition())
setState(ListState.FILLED_LOADING)
if (AndroidUtils.hasConnection()) {
loadFriends(
userId,
adapter.itemCount,
DEFAULT_FRIENDS_COUNT,
false,
object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
} else {
getCachedFriends(
userId,
adapter.itemCount,
DEFAULT_FRIENDS_COUNT,
false,
object : MvpOnResponseListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
}
Log.d("RecyclerView", "Bottom reached")
}
}
}
})
}
private fun setRefreshListener(refreshLayout: SwipeRefreshLayout) {
refreshLayout.setOnRefreshListener { loadFriends(userId) }
}
private fun createAdapter() {
adapter = UsersAdapterDeprecated(context!!, arrayListOf()).also {
it.itemClickListener = this
}
recyclerView.adapter = adapter
}
private fun fillAdapter(values: ArrayList<VKUser>, offset: Int) {
val oldItems = ArrayList(adapter.values)
if (offset > 0) {
adapter.addAll(values)
} else {
adapter.updateValues(values)
}
// adapter.notifyDataSetChanged()
adapter.notifyChanges(oldItems)
if (offset == 0) recyclerView.scrollToPosition(0)
}
private fun openChat(position: Int) {
val user = adapter[position]
val data = Bundle().apply {
putInt(MessagesActivityDeprecated.TAG_EXTRA_ID, user.userId)
putString(MessagesActivityDeprecated.TAG_EXTRA_TITLE, user.toString())
putString(MessagesActivityDeprecated.TAG_EXTRA_AVATAR, user.photo200)
}
}
override fun onItemClick(position: Int) {
}
}
@@ -1,190 +0,0 @@
package com.meloda.fast.fragment.ui.presenter
import android.content.Context
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import com.google.android.material.textfield.TextInputEditText
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.activity.MainActivity
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.fragment.ChatsFragment
import com.meloda.fast.fragment.LoginFragment
import com.meloda.fast.fragment.ValidationFragment
import com.meloda.fast.fragment.ui.repository.LoginRepository
import com.meloda.fast.fragment.ui.view.LoginView
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpPresenter
import com.meloda.vksdk.VKApi
import com.squareup.picasso.Picasso
import org.json.JSONObject
import java.util.*
class LoginPresenter(
viewState: LoginView
) : MvpPresenter<Any, LoginRepository, LoginView>(
viewState,
LoginRepository::class.java.name
) {
private var lastEmail: String = ""
private var lastPassword: String = ""
private lateinit var fragment: LoginFragment
fun onCreate(context: Context, fragment: LoginFragment, bundle: Bundle?) {
super.onCreate(context, bundle)
this.fragment = fragment
}
override fun onViewCreated(bundle: Bundle?) {
viewState.initViews()
viewState.prepareViews()
}
fun login(
email: String,
password: String,
captcha: String = "",
onResponseListener: MvpOnResponseListener<Any?>? = null
) {
lastEmail = email
lastPassword = password
repository.login(requireContext(), email, password, captcha,
object : MvpOnResponseListener<JSONObject> {
override fun onResponse(response: JSONObject) {
checkResponse(response, onResponseListener)
}
override fun onError(t: Throwable) {
onResponseListener?.onError(t)
}
})
}
@Suppress("MoveVariableDeclarationIntoWhen")
private fun checkResponse(
response: JSONObject,
onResponseListener: MvpOnResponseListener<Any?>? = null
) {
if (response.has("error")) {
val errorString = response.optString("error")
when (errorString) {
"need_validation" -> {
val redirectUrl = response.optString("redirect_uri")
val bundle = Bundle()
bundle.putString("url", redirectUrl)
fragment.runOnUiThread {
fragment.setFragmentResultListener("validation") { _, bundle ->
val userId = bundle.getInt("userId")
val token = bundle.getString("token") ?: ""
saveUserData(userId, token)
openMainScreen()
}
}
fragment.parentFragmentManager.beginTransaction()
.replace(
R.id.fragmentContainer,
ValidationFragment().apply { arguments = bundle })
.addToBackStack("")
.commit()
}
"need_captcha" -> {
val captchaImage = response.optString("captcha_img")
val captchaSid = response.optString("captcha_sid")
showCaptchaDialog(captchaImage, captchaSid)
}
}
} else {
val userId = response.optInt("user_id", -1)
val token = response.optString("access_token")
saveUserData(userId, token)
openMainScreen()
onResponseListener?.onResponse(null)
}
}
private fun openMainScreen() {
fragment.runOnUiThread {
VKApi.init(Locale.getDefault().language, UserConfig.token, AppGlobal.handler)
(fragment.requireActivity() as MainActivity).bottomBar.isVisible = true
fragment.parentFragmentManager.beginTransaction()
.replace(
R.id.fragmentContainer,
ChatsFragment()
).commit()
}
}
private fun saveUserData(userId: Int, token: String) {
UserConfig.userId = userId
UserConfig.token = token
UserConfig.save()
}
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
val resources = fragment.resources
val metrics = resources.displayMetrics
fragment.runOnUiThread {
val image = ImageView(requireContext())
image.layoutParams = ViewGroup.LayoutParams(
(metrics.widthPixels / 3.5).toInt(), metrics.heightPixels / 7
)
Picasso.get().load(captchaImage).priority(Picasso.Priority.HIGH).into(image)
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 layout = LinearLayout(requireContext())
layout.orientation = LinearLayout.VERTICAL
layout.gravity = Gravity.CENTER
layout.addView(image)
layout.addView(captchaCodeEditText)
builder.setView(layout)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
val captchaCode = captchaCodeEditText.text.toString().trim()
login(
lastEmail,
lastPassword,
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
)
}
builder.setTitle(R.string.input_captcha)
builder.show()
}
}
}
@@ -1,7 +0,0 @@
package com.meloda.fast.fragment.ui.repository
import com.meloda.mvp.MvpRepository
import com.meloda.vksdk.model.VKConversation
class ChatsRepository : MvpRepository<VKConversation>() {
}

Some files were not shown because too many files have changed in this diff Show More