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
@@ -2,11 +2,11 @@ package com.meloda.fast
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.concurrent.EventInfo
import com.meloda.concurrent.TaskManager
import com.meloda.vksdk.VKApiKeys
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.util.VKUtil
import com.meloda.fast.concurrent.EventInfo
import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.api.VKApiKeys
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.util.VKUtil
import org.json.JSONArray
@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
import android.content.Intent
import android.os.Bundle
import android.view.Menu
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 android.viewbinding.library.activity.viewBinding
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.common.FragmentSwitcher
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.*
import com.meloda.fast.databinding.ActivityMainBinding
import com.meloda.fast.fragment.LoginFragment
class MainActivity : BaseActivity(),
NavigationView.OnNavigationItemSelectedListener,
BottomNavigationView.OnNavigationItemSelectedListener {
class MainActivity : BaseActivity(R.layout.activity_main) {
private lateinit var fragmentConversationsDeprecated: FragmentConversationsDeprecated
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
private val binding: ActivityMainBinding by viewBinding()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews()
// checkLogin()
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()
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, LoginFragment())
.commit()
}
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)
}
}
@@ -0,0 +1,42 @@
package com.meloda.fast.api
object ErrorCodes {
const val UNKNOWN_ERROR = 1
const val APP_DISABLED = 2
const val UNKNOWN_METHOD = 3
const val INVALID_SIGNATURE = 4
const val USER_AUTHORIZATION_FAILED = 5
const val TOO_MANY_REQUESTS = 6
const val NO_RIGHTS = 7
const val BAD_REQUEST = 8
const val TOO_MANY_SIMILAR_ACTIONS = 9
const val INTERNAL_SERVER_ERROR = 10
const val IN_TEST_MODE = 11
const val EXECUTE_CODE_COMPILE_ERROR = 12
const val EXECUTE_CODE_RUNTIME_ERROR = 13
const val CAPTCHA_NEEDED = 14
const val ACCESS_DENIED = 15
const val REQUIRES_REQUESTS_OVER_HTTPS = 16
const val VALIDATION_REQUIRED = 17
const val USER_BANNED_OR_DELETED = 18
const val ACTION_PROHIBITED = 20
const val ACTION_ALLOWED_ONLY_FOR_STANDALONE = 21
const val METHOD_OFF = 23
const val CONFIRMATION_REQUIRED = 24
const val PARAMETER_IS_NOT_SPECIFIED = 100
const val INCORRECT_APP_ID = 101
const val OUT_OF_LIMITS = 103
const val INCORRECT_USER_ID = 113
const val INCORRECT_TIMESTAMP = 150
const val ACCESS_TO_ALBUM_DENIED = 200
const val ACCESS_TO_AUDIO_DENIED = 201
const val ACCESS_TO_GROUP_DENIED = 203
const val ALBUM_IS_FULL = 300
const val ACTION_DENIED = 500
const val PERMISSION_DENIED = 600
const val CANNOT_SEND_MESSAGE_BLACK_LIST = 900
const val CANNOT_SEND_MESSAGE_GROUP = 901
const val INVALID_DOC_ID = 1150
const val INVALID_DOC_TITLE = 1152
const val ACCESS_TO_DOC_DENIED = 1153
}
@@ -0,0 +1,9 @@
package com.meloda.fast.api
interface OnResponseListener<T> {
fun onResponse(response: T)
fun onError(t: Throwable)
}
@@ -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)
}
}
@@ -0,0 +1,504 @@
package com.meloda.fast.api
import android.os.Handler
import android.util.Log
import androidx.annotation.WorkerThread
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.method.MessageMethodSetter
import com.meloda.fast.api.method.MethodSetter
import com.meloda.fast.api.method.UserMethodSetter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import kotlin.collections.ArrayList
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Suppress("UNCHECKED_CAST")
object VKApi {
private const val TAG = "VKM:VKApi"
const val BASE_URL = "https://api.vk.com/method/"
const val API_VERSION = "5.132"
var language: String = ""
var token: String = ""
private lateinit var handler: Handler
fun init(language: String, token: String, handler: Handler) {
VKApi.language = language
VKApi.token = token
VKApi.handler = handler
}
@WorkerThread
@Suppress("UNCHECKED_CAST")
fun <T> execute(url: String, cls: Class<T>?): ArrayList<T>? {
if (BuildConfig.DEBUG) {
Log.w(TAG, "url: $url")
}
val buffer = com.meloda.fast.net.HttpRequest[url].asString()
if (BuildConfig.DEBUG) {
Log.i(TAG, "response: $buffer")
}
val json = JSONObject(buffer)
try {
checkError(json, url)
} catch (ex: VKException) {
if (ex.code == ErrorCodes.TOO_MANY_REQUESTS) {
Timer().schedule(object : TimerTask() {
override fun run() {
execute(url, cls)
}
}, 1000)
} else throw ex
}
when (cls) {
null -> return null
com.meloda.fast.api.model.VKLongPollServer::class.java -> {
json.optJSONObject("response")?.let {
return arrayListOf(com.meloda.fast.api.model.VKLongPollServer(it)) as ArrayList<T>?
}
}
Boolean::class.java -> {
val value = json.optInt("response") == 1
return arrayListOf(value) as ArrayList<T>?
}
Long::class.java -> {
val value = json.optLong("response")
return arrayListOf(value) as ArrayList<T>?
}
Int::class.java -> {
val value = json.optInt("response")
return arrayListOf(value) as ArrayList<T>?
}
}
val response = json.opt("response") ?: return null
val array = optItems(json) ?: return null
val models = ArrayList<T>(array.length())
when (cls) {
com.meloda.fast.api.model.VKUser::class.java -> {
json.optJSONObject("response")?.let { r ->
com.meloda.fast.api.model.VKUser.friendsCount = r.optInt("count")
}
for (i in 0 until array.length()) {
models.add(com.meloda.fast.api.model.VKUser(array.optJSONObject(i)) as T)
}
}
com.meloda.fast.api.model.VKMessage::class.java -> {
response as JSONObject
if (url.contains("messages.getHistory")) {
com.meloda.fast.api.model.VKMessage.lastHistoryCount = response.optInt("count")
response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) {
profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKMessage.profiles = profiles
}
response.optJSONArray("groups")?.let {
val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) {
groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKMessage.groups = groups
}
response.optJSONArray("conversations")?.let {
val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
for (j in 0 until it.length()) {
conversations.add(
com.meloda.fast.api.model.VKConversation(
it.optJSONObject(
j
)
)
)
}
com.meloda.fast.api.model.VKMessage.conversations = conversations
}
}
for (i in 0 until array.length()) {
var source = array.optJSONObject(i)
if (source.has("message")) {
source = source.optJSONObject("message")
}
val message = com.meloda.fast.api.model.VKMessage(source)
models.add(message as T)
}
}
com.meloda.fast.api.model.VKGroup::class.java -> {
for (i in 0 until array.length()) {
models.add(com.meloda.fast.api.model.VKGroup(array.optJSONObject(i)) as T)
}
}
com.meloda.fast.api.model.VKModel::class.java -> {
if (url.contains("messages.getHistoryAttachments")) {
return com.meloda.fast.api.model.VKAttachments.parse(array) as ArrayList<T>
}
}
com.meloda.fast.api.model.VKConversation::class.java -> {
if (url.contains("getConversationsById")) {
for (i in 0 until array.length()) {
val source = array.optJSONObject(i)
models.add(com.meloda.fast.api.model.VKConversation(source) as T)
}
return models
}
json.optJSONObject("response")?.let { r ->
com.meloda.fast.api.model.VKConversation.conversationsCount = r.optInt("count")
}
for (i in 0 until array.length()) {
response as JSONObject
val source = array.optJSONObject(i)
val oConversation = source.optJSONObject("conversation") ?: return null
val oLastMessage = source.optJSONObject("last_message") ?: return null
val conversation = com.meloda.fast.api.model.VKConversation(oConversation).also {
it.lastMessage = com.meloda.fast.api.model.VKMessage(oLastMessage)
}
response.optJSONArray("profiles")?.let {
val profiles = arrayListOf<com.meloda.fast.api.model.VKUser>()
for (j in 0 until it.length()) {
profiles.add(com.meloda.fast.api.model.VKUser(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKConversation.profiles = profiles
}
response.optJSONArray("groups")?.let {
val groups = arrayListOf<com.meloda.fast.api.model.VKGroup>()
for (j in 0 until it.length()) {
groups.add(com.meloda.fast.api.model.VKGroup(it.optJSONObject(j)))
}
com.meloda.fast.api.model.VKConversation.groups = groups
}
models.add(conversation as T)
}
}
}
return models
}
fun <E> execute(url: String, cls: Class<E>, listener: OnResponseListener<E>?) {
com.meloda.fast.concurrent.TaskManager.execute {
try {
val models = execute(url, cls) ?: return@execute
// listener?.onResponse(models)
} catch (e: Exception) {
e.printStackTrace()
listener?.onError(e)
// it.resumeWithException(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>>) {
com.meloda.fast.concurrent.TaskManager.execute {
try {
val models = execute(url, cls)
handler.post { listener.onResponse(models as ArrayList<E>) }
} catch (e: Exception) {
e.printStackTrace()
listener.onError(e)
}
}
}
private fun optItems(source: JSONObject): JSONArray? {
val response = source.opt("response")
return when (response) {
is JSONArray -> response
is JSONObject -> response.optJSONArray("items")
else -> null
}
}
private fun checkError(json: JSONObject, url: String) {
if (json.has("error")) {
val error = json.optJSONObject("error") ?: return
val code = error.optInt("error_code", -1)
val message = error.optString("error_msg", "")
val e = VKException(url, 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.optString("captcha_img")
e.captchaSid = error.optString("captcha_sid")
}
if (code == ErrorCodes.VALIDATION_REQUIRED) {
e.redirectUri = error.optString("redirect_uri")
}
throw e
}
}
fun users(): VKUsers {
return VKUsers()
}
fun friends(): VKFriends {
return VKFriends()
}
fun messages(): VKMessages {
return VKMessages()
}
fun groups(): VKGroups {
return VKGroups()
}
fun account(): VKAccounts {
return VKAccounts()
}
class VKFriends {
fun get(): MethodSetter {
return MethodSetter("friends.get")
}
}
class VKUsers {
fun get(): UserMethodSetter {
return UserMethodSetter("users.get")
}
}
class VKMessages {
fun get(): MessageMethodSetter {
return MessageMethodSetter("messages.get")
}
fun getConversations(): MessageMethodSetter {
return MessageMethodSetter("messages.getConversations")
}
fun getConversationsById(): MessageMethodSetter {
return MessageMethodSetter("messages.getConversationsById")
}
fun getById(): MessageMethodSetter {
return MessageMethodSetter("messages.getById")
}
fun search(): MessageMethodSetter {
return MessageMethodSetter("messages.search")
}
fun getHistory(): MessageMethodSetter {
return MessageMethodSetter("messages.getHistory")
}
fun getHistoryAttachments(): MessageMethodSetter {
return MessageMethodSetter("messages.getHistoryAttachments")
}
fun send(): MessageMethodSetter {
return MessageMethodSetter("messages.send")
}
fun sendSticker(): MessageMethodSetter {
return MessageMethodSetter("messages.sendSticker")
}
fun delete(): MessageMethodSetter {
return MessageMethodSetter("messages.delete")
}
fun deleteDialog(): MessageMethodSetter {
return MessageMethodSetter("messages.deleteDialog")
}
fun restore(): MessageMethodSetter {
return MessageMethodSetter("messages.restore")
}
fun markAsRead(): MessageMethodSetter {
return MessageMethodSetter("messages.markAsRead")
}
fun markAsImportant(): MessageMethodSetter {
return MessageMethodSetter("messages.markAsImportant")
}
fun getLongPollServer(): MessageMethodSetter {
return MessageMethodSetter("messages.getLongPollServer")
}
/**
* Returns updates in user's private messages.
* To speed up handling of private messages,
* it can be useful to cache previously loaded messages on
* a user's mobile device/desktop, to prevent re-receipt at each call.
* With this method, you can synchronize a local copy of
* the message list with the actual version.
*
*
* Result:
* Returns an object that contains the following fields:
* 1 — history: An array similar to updates field returned
* from the Long Poll server,
* with these exceptions:
* - For events with code 4 (addition of a new message),
* there are no fields except the first three.
* - There are no events with codes 8, 9 (friend goes online/offline)
* or with codes 61, 62 (typing during conversation/chat).
*
*
* 2 — messages: An array of private message objects that were found
* among events with code 4 (addition of a new message)
* from the history field.
* Each object of message contains a set of fields described here.
* The first array element is the total number of messages
*/
fun getLongPollHistory(): MessageMethodSetter {
return MessageMethodSetter("messages.getLongPollHistory")
}
fun getChat(): MessageMethodSetter {
return MessageMethodSetter("messages.getChat")
}
fun createChat(): MessageMethodSetter {
return MessageMethodSetter("messages.createChat")
}
fun editChat(): MessageMethodSetter {
return MessageMethodSetter("messages.editChat")
}
val chatUsers: MessageMethodSetter
get() = MessageMethodSetter("messages.getChatUsers")
fun setActivity(): MessageMethodSetter {
return MessageMethodSetter("messages.setActivity").type(true)
}
fun addChatUser(): MessageMethodSetter {
return MessageMethodSetter("messages.addChatUser")
}
fun removeChatUser(): MessageMethodSetter {
return MessageMethodSetter("messages.removeChatUser")
}
}
class VKGroups {
fun getById(): MethodSetter {
return MethodSetter("groups.getById")
}
fun join(): MethodSetter {
return MethodSetter("groups.join")
}
}
class VKAccounts {
fun setOffline(): MethodSetter {
return MethodSetter("account.setOffline")
}
fun setOnline(): MethodSetter {
return MethodSetter("account.setOnline")
}
}
class SuccessCallback<E>(
private val listener: OnResponseListener<E>?,
private val response: E
) : Runnable {
override fun run() {
listener?.onResponse(response)
}
}
class SuccessArrayCallback<E>(
private val listener: OnResponseListener<ArrayList<E>>?,
private val response: ArrayList<E>
) : Runnable {
override fun run() {
listener?.onResponse(response)
}
}
class ErrorCallback<E>(
private val listener: OnResponseListener<E>?,
private val exception: Exception
) : Runnable {
override fun run() {
listener?.onError(exception)
}
}
}
@@ -0,0 +1,14 @@
package com.meloda.fast.api
enum class VKApiKeys(val value: String) {
READ_MESSAGE("_read_message"),
RESTORE_MESSAGE("_restore_message"),
NEW_MESSAGE("_new_message"),
EDIT_MESSAGE("_edit_message"),
DELETE_MESSAGE("_delete_message"),
UPDATE_MESSAGE("_update_message"),
UPDATE_CONVERSATION("_update_conversation"),
UPDATE_USER("_update_user"),
UPDATE_GROUP("_update_group")
}
@@ -0,0 +1,80 @@
package com.meloda.fast.api
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.util.VKUtil
import java.net.URLEncoder
object VKAuth {
private const val TAG = "VKM.VKAuth"
const val settings =
"notify," +
"friends," +
"photos," +
"audio," +
"video," +
"docs," +
"status," +
"notes," +
"pages," +
"wall," +
"groups," +
"messages," +
"offline," +
"notifications"
const val redirectUrl = "https://oauth.vk.com/blank.html"
fun getDirectAuthUrl(login: String, password: String, captcha: String = ""): String {
return "https://oauth.vk.com/token?grant_type=password&" +
"client_id=6146827&" +
"scope=$settings&" +
"client_secret=qVxWRF1CwHERuIrKBnqe&" +
"username=$login&" +
"password=$password" +
(if (captcha.isEmpty()) "" else "&$captcha") +
"&v=${VKApi.API_VERSION}"
// return "https://oauth.vk.com/token?grant_type=password&" +
// "client_id=2274003&" +
// "scope=notify,friends,photos,audio,video,docs,notes,pages,status,offers,questions,wall,groups,messages,email,notifications,stats,ads,market,offline&" +
// "client_secret=hHbZxrka2uZ6jB1inYsH&" +
// "username=$login&" +
// "password=$password" +
// (if (captcha.isEmpty()) "" else "&$captcha") +
// "&v=${VKApi.API_VERSION}"
}
fun getUrl(api_id: String, settings: String): String {
return "https://oauth.vk.com/authorize?" +
"client_id=$api_id&" +
"display=mobile&" +
"scope=$settings&" +
"redirect_uri=${
URLEncoder.encode(
redirectUrl,
"utf-8"
)
}&" +
"response_type=token&" +
"v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
}
@Throws(Exception::class)
fun parseRedirectUrl(url: String): Array<String> {
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&")
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")
if (BuildConfig.DEBUG) {
Log.i(TAG, "access_token=$accessToken")
Log.i(TAG, "user_id=$userId")
}
if (userId == null || userId.isEmpty() || accessToken == null || accessToken.isEmpty()) throw Exception(
"Failed to parse redirect url $url"
)
return arrayOf(accessToken, userId)
}
}
@@ -0,0 +1,26 @@
package com.meloda.fast.api
object VKConstants {
const val GROUP_FIELDS = "description,members_count,counters,status,verified"
const val USER_FIELDS =
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
/*
const val ACTION_CHAT_CREATE = "chat_create"
const val ACTION_PHOTO_UPDATE = "chat_photo_update"
const val ACTION_PHOTO_REMOVE = "chat_photo_remove"
const val ACTION_TITLE_UPDATE = "chat_title_update"
const val ACTION_PIN_MESSAGE = "chat_pin_message"
const val ACTION_UNPIN_MESSAGE = "chat_unpin_message"
const val ACTION_INVITE_USER = "chat_invite_user"
const val ACTION_INVITE_USER_BY_LINK = "chat_invite_user_by_link"
const val ACTION_KICK_USER = "chat_kick_user"
const val ACTION_SCREENSHOT = "chat_screenshot"
const val ACTION_INVITE_USER_BY_CALL = "chat_invite_user_by_call"
const val ACTION_INVITE_USER_BY_CALL_JOIN_LINK = "chat_invite_user_by_call_link"
*/
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api
import java.io.IOException
class VKException(var url: String = "", override var message: String = "", var code: Int) :
IOException(message) {
var captchaSid: String? = null
var captchaImg: String? = null
var redirectUri: String? = null
override fun toString(): String {
return "code: $code, message: $message"
}
}
@@ -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
}
}
@@ -0,0 +1,205 @@
package com.meloda.fast.api.method
import com.meloda.fast.util.ArrayUtils
class MessageMethodSetter(name: String) : MethodSetter(name) {
fun out(value: Boolean): MessageMethodSetter {
put("out", value)
return this
}
fun timeOffset(value: Int): MessageMethodSetter {
put("time_offset", value)
return this
}
fun filters(value: Int): MessageMethodSetter {
put("filters", value)
return this
}
fun previewLength(value: Int): MessageMethodSetter {
put("preview_length", value)
return this
}
fun lastMessageId(value: Int): MessageMethodSetter {
put("last_message_id", value)
return this
}
fun unread(value: Boolean): MessageMethodSetter {
put("unread", value)
return this
}
fun messageIds(vararg ids: Int): MessageMethodSetter {
put("message_ids", ArrayUtils.asString(ids))
return this
}
fun messageIds(ids: ArrayList<Int>): MessageMethodSetter {
put("message_ids", ArrayUtils.asString(ids))
return this
}
fun q(query: String): MessageMethodSetter {
put("q", query)
return this
}
fun startMessageId(id: Int): MessageMethodSetter {
put("start_message_id", id)
return this
}
fun peerId(value: Int): MessageMethodSetter {
put("peer_id", value)
return this
}
fun peerIds(vararg values: Int): MessageMethodSetter {
put("peer_ids", com.meloda.fast.util.ArrayUtils.asString(values))
return this
}
fun reversed(value: Boolean): MessageMethodSetter {
put("rev", value)
return this
}
fun domain(value: String): MessageMethodSetter {
put("domain", value)
return this
}
fun chatId(value: Int): MessageMethodSetter {
put("chat_id", value)
return this
}
fun message(message: String): MessageMethodSetter {
put("message", message)
return this
}
fun randomId(value: Int): MessageMethodSetter {
put("random_id", value)
return this
}
fun lat(lat: Double): MessageMethodSetter {
put("lat", lat)
return this
}
fun longitude(value: Long): MessageMethodSetter {
put("LONG", value)
return this
}
fun attachment(attachments: Collection<String>): MessageMethodSetter {
put("attachment", com.meloda.fast.util.ArrayUtils.asString(attachments))
return this
}
fun attachment(vararg attachments: String): MessageMethodSetter {
put("attachment", com.meloda.fast.util.ArrayUtils.asString(*attachments))
return this
}
fun forwardMessages(ids: Collection<String>): MessageMethodSetter {
put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun forwardMessages(vararg ids: Int): MessageMethodSetter {
put("forward_messages", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun stickerId(value: Int): MessageMethodSetter {
put("sticker_id", value)
return this
}
fun messageId(value: Int): MessageMethodSetter {
put("message_id", value)
return this
}
fun important(value: Boolean): MessageMethodSetter {
put("important", value)
return this
}
fun ts(value: Long): MessageMethodSetter {
put("ts", value)
return this
}
fun pts(value: Int): MessageMethodSetter {
put("pts", value)
return this
}
fun msgsLimit(limit: Int): MessageMethodSetter {
put("msgs_limit", limit)
return this
}
fun onlines(onlines: Boolean): MessageMethodSetter {
put("onlines", onlines)
return this
}
fun maxMsgId(id: Int): MessageMethodSetter {
put("max_msg_id", id)
return this
}
fun chatIds(vararg ids: Int): MessageMethodSetter {
put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun chatIds(ids: Collection<Int>): MessageMethodSetter {
put("max_msg_id", com.meloda.fast.util.ArrayUtils.asString(ids))
return this
}
fun title(title: String): MessageMethodSetter {
put("title", title)
return this
}
fun type(typing: Boolean): MessageMethodSetter {
if (typing) {
put("type", "typing")
}
return this
}
fun mediaType(type: String): MessageMethodSetter {
put("media_type", type)
return this
}
fun photoSizes(value: Boolean): MessageMethodSetter {
return put("photo_sizes", value) as MessageMethodSetter
}
fun filter(value: String): MessageMethodSetter {
return put("filter", value) as MessageMethodSetter
}
fun extended(value: Boolean): MessageMethodSetter {
return put("extended", value) as MessageMethodSetter
}
fun markConversationAsRead(asRead: Boolean): MessageMethodSetter {
put("mark_conversation_as_read", asRead)
return this
}
}
@@ -0,0 +1,169 @@
package com.meloda.fast.api.method
import android.util.ArrayMap
import android.util.Log
import com.meloda.fast.BuildConfig
import com.meloda.fast.api.OnResponseListener
import com.meloda.fast.api.VKApi
import kotlinx.coroutines.flow.Flow
import java.net.URLEncoder
@Suppress("UNCHECKED_CAST")
open class MethodSetter(private val name: String) {
private val params: ArrayMap<String, String> = ArrayMap()
fun put(key: String, value: Any): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: String): MethodSetter {
params[key] = value
return this
}
fun put(key: String, value: Int): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: Long): MethodSetter {
params[key] = value.toString()
return this
}
fun put(key: String, value: Boolean): MethodSetter {
params[key] = if (value) "1" else "0"
return this
}
private fun getSignedUrl(): String {
if (!params.containsKey("access_token")) {
params["access_token"] = VKApi.token
}
if (!params.containsKey("v")) {
params["v"] = VKApi.API_VERSION
}
if (!params.containsKey("lang")) {
params["lang"] = VKApi.language
}
return "${VKApi.BASE_URL}$name?${retrieveParams()}"
}
private fun retrieveParams(): String {
val builder = StringBuilder()
for (i in 0 until params.size) {
val key = params.keyAt(i)
val value = params.valueAt(i)
if (builder.isNotEmpty()) {
builder.append("&")
}
builder.append(key)
builder.append("=")
builder.append(URLEncoder.encode(value, "UTF-8"))
}
val params = builder.toString()
if (BuildConfig.DEBUG) {
Log.i("MethodSetter", "retrieved params: $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>? {
return VKApi.execute(getSignedUrl(), cls)
}
fun <E> executeArray(cls: Class<E>, listener: OnResponseListener<ArrayList<E>>) {
VKApi.executeArray(getSignedUrl(), cls, listener)
}
fun <E> execute(cls: Class<E>, listener: OnResponseListener<E>?) {
VKApi.execute(getSignedUrl(), cls, listener)
}
fun userId(value: Int): MethodSetter {
return put("user_id", value)
}
fun userIds(vararg ids: Int): MethodSetter {
return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun userIds(ids: ArrayList<Int>): MethodSetter {
return put("user_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun ownerId(value: Int): MethodSetter {
return put("owner_id", value)
}
fun groupId(value: Int): MethodSetter {
return put("group_id", value)
}
fun groupIds(vararg ids: Int): MethodSetter {
return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun groupIds(ids: ArrayList<Int>): MethodSetter {
return put("group_ids", com.meloda.fast.util.ArrayUtils.asString(ids))
}
fun fields(values: String): MethodSetter {
return put("fields", values)
}
fun count(value: Int): MethodSetter {
return put("count", value)
}
fun sort(value: Int): MethodSetter {
put("sort", value)
return this
}
/**
*
* hints — сортировать по рейтингу, аналогично тому, как друзья сортируются в разделе Мои друзья
* random — возвращает друзей в случайном порядке.
* mobile — возвращает выше тех друзей, у которых установлены мобильные приложения.
* name — сортировать по имени (долго)
*
*/
fun order(value: String): MethodSetter {
put("order", value)
return this
}
fun offset(value: Int = 0): MethodSetter {
return put("offset", value)
}
fun nameCase(value: String): MethodSetter {
return put("name_case", value)
}
fun captchaSid(value: String): MethodSetter {
return put("captcha_sid", value)
}
fun captchaKey(value: String): MethodSetter {
return put("captcha_key", value)
}
}
@@ -0,0 +1,44 @@
package com.meloda.fast.api.method
class UserMethodSetter(name: String) : MethodSetter(name) {
fun extended(extended: Boolean): UserMethodSetter {
put("extended", extended)
return this
}
fun type(type: String): UserMethodSetter {
put("type", type)
return this
}
fun comment(comment: String): UserMethodSetter {
put("comment", comment)
return this
}
fun latitude(latitude: Float): UserMethodSetter {
put("latitude", latitude)
return this
}
fun longitude(longitude: Float): UserMethodSetter {
put("longitude", longitude)
return this
}
fun accuracy(accuracy: Int): UserMethodSetter {
put("accuracy", accuracy)
return this
}
fun timeout(timeout: Int): UserMethodSetter {
put("timeout", timeout)
return this
}
fun radius(radius: Int): UserMethodSetter {
put("radius", radius)
return this
}
}
@@ -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
)
@@ -0,0 +1,76 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import java.util.*
object VKAttachments {
fun parse(array: JSONArray): ArrayList<VKModel> {
val attachments = ArrayList<VKModel>(array.length())
for (i in 0 until array.length()) {
var attachment = array.optJSONObject(i) ?: continue
if (attachment.has("attachment")) {
attachment = attachment.optJSONObject("attachment") ?: continue
}
val type = Type.fromString(attachment.optString("type"))
val jsonObject = attachment.optJSONObject(type.value) ?: continue
when (type) {
Type.PHOTO -> attachments.add(VKPhoto(jsonObject))
Type.AUDIO -> attachments.add(VKAudio(jsonObject))
Type.VIDEO -> attachments.add(VKVideo(jsonObject))
Type.DOCUMENT -> attachments.add(VKDocument(jsonObject))
Type.STICKER -> attachments.add(VKSticker(jsonObject))
Type.LINK -> attachments.add(VKLink(jsonObject))
Type.GIFT -> attachments.add(VKGift(jsonObject))
Type.VOICE_MESSAGE -> attachments.add(VKAudioMessage(jsonObject))
Type.GRAFFITI -> attachments.add(VKGraffiti(jsonObject))
Type.POLL -> attachments.add(VKPoll(jsonObject))
Type.CALL -> attachments.add(VKCall(jsonObject))
Type.WALL_POST -> attachments.add(VKWall(jsonObject))
Type.WALL_REPLY -> attachments.add(VKComment(jsonObject))
Type.GEOLOCATION -> attachments.add(VKGeolocation(jsonObject))
else -> continue
}
}
return attachments
}
enum class Type(val value: String) {
NONE("none"),
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
AUDIO_PLAYLIST("audio_playlist"),
DOCUMENT("doc"),
LINK("link"),
STICKER("sticker"),
GIFT("gift"),
VOICE_MESSAGE("audio_message"),
GRAFFITI("graffiti"),
POLL("poll"),
GEOLOCATION("geo"),
WALL_POST("wall"),
WALL_REPLY("wall_reply"),
CALL("call"),
STORY("story"),
POINT("point"),
MARKET("market"),
ARTICLE("article"),
PODCAST("podcast"),
MONEY_REQUEST("money_request");
companion object {
fun fromString(value: String): Type {
for (v in values()) {
if (v.value == value) return v
}
return NONE
}
}
}
}
@@ -0,0 +1,31 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKAudio() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.AUDIO
var id: Int = 0
var ownerId: Int = 0
var artist: String = ""
var title: String = ""
var duration: Int = 0
var url: String = ""
var date: Int = 0
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
artist = o.optString("artist")
title = o.optString("title")
duration = o.optInt("duration")
url = o.optString("url")
date = o.optInt("date")
}
}
@@ -0,0 +1,31 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKAudioMessage() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.VOICE_MESSAGE
var duration: Int = 0
var waveform: ArrayList<Int> = arrayListOf()
var linkOgg: String = ""
var linkMp3: String = ""
constructor(o: JSONObject) : this() {
duration = o.optInt("duration")
linkOgg = o.optString("link_ogg")
linkMp3 = o.optString("link_mp3")
o.optJSONArray("waveform")?.let {
val waveform = ArrayList<Int>()
for (i in 0 until it.length()) {
waveform.add(it.optInt(i))
}
this.waveform = waveform
}
}
}
@@ -0,0 +1,38 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKCall() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.CALL
var initiatorId: Int = 0
var receiverId: Int = 0
var state: State = State.NONE
var time: Int = 0
var duration: Int = 0
constructor(o: JSONObject) : this() {
initiatorId = o.optInt("initiator_id", -1)
receiverId = o.optInt("receiver_id", -1)
state = State.fromString(o.optString("state"))
time = o.optInt("time")
duration = o.optInt("duration")
}
enum class State(val value: String) {
NONE("none"),
REACHED("reached"),
CANCELLED_INITIATOR("canceled_by_initiator"),
CANCELLED_RECEIVER("canceled_by_receiver");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKComment() : VKModel() { //https://vk.com/dev/objects/comment
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.WALL_REPLY
constructor(o: JSONObject) : this() {}
}
@@ -0,0 +1,159 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKConversation() : VKModel(), Cloneable {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
var profiles = arrayListOf<VKUser>()
var groups = arrayListOf<VKGroup>()
var conversationsCount: Int = 0
var count: Int = 0
}
var isAllowed: Boolean = false
var notAllowedReason: Reason = Reason.NULL
var inReadMessageId: Int = 0
var outReadMessageId: Int = 0
var lastMessageId: Int = 0
var unreadCount: Int = 0
var id: Int = 0
var intType: Int = 0
var type: Type = Type.NULL
var localId: Int = 0
var notificationsEnabled: Boolean = false
var disabledUntil: Int = 0
var isDisabledForever: Boolean = false
var isNoSound: Boolean = false
var membersCount: Int = 0
var title: String = ""
var pinnedMessage: VKMessage? = null
var intState: Int = 0
var state: State = State.IN
var lastMessage: VKMessage = VKMessage()
var isGroupChannel: Boolean = false
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
var peerUser: VKUser? = null
var peerGroup: VKGroup? = null
constructor(o: JSONObject) : this() {
inReadMessageId = o.optInt("in_read")
outReadMessageId = o.optInt("out_read")
lastMessageId = o.optInt("last_message_id", -1)
unreadCount = o.optInt("unread_count", 0)
o.optJSONObject("peer")?.let {
id = it.optInt("id", -1)
type = Type.fromString(it.optString("type"))
localId = it.optInt("local_id")
}
o.optJSONObject("push_settings")?.let {
disabledUntil = it.optInt("disabled_until")
isDisabledForever = it.optBoolean("disabled_forever")
isNoSound = it.optBoolean("no_sound")
}
o.optJSONObject("can_write")?.let {
isAllowed = it.optBoolean("allowed")
notAllowedReason = Reason.fromInt(it.optInt("reason", -1))
}
o.optJSONObject("chat_settings")?.let {
membersCount = it.optInt("members_count")
title = it.optString("title")
it.optJSONObject("pinned_message")?.let { pinned ->
pinnedMessage = VKMessage(pinned)
}
state = State.fromString(it.optString("state"))
it.optJSONObject("photo")?.let { photo ->
photo50 = photo.optString("photo_50")
photo100 = photo.optString("photo_100")
photo200 = photo.optString("photo_200")
}
isGroupChannel = it.optBoolean("is_group_channel")
}
}
fun isNotificationsDisabled() = (isDisabledForever || disabledUntil > 0 || isNoSound)
fun isChat() = type == Type.CHAT
fun isUser() = type == Type.USER
fun isGroup() = type == Type.GROUP
override fun toString(): String {
return title
}
public override fun clone(): VKConversation {
return super.clone() as VKConversation
}
enum class Type(val value: String) {
NULL("null"),
USER("user"),
CHAT("chat"),
GROUP("group");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
enum class State(val value: String) {
IN("in"),
KICKED("kicked"),
LEFT("left");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
enum class Reason(val value: Int) {
NULL(-1),
U(0),
BLOCKED_DELETED(18),
BLACKLISTED(900),
BLOCKED_GROUP_MESSAGES(901),
PRIVACY_SETTINGS(902),
GROUP_DISABLED_MESSAGES(915),
GROUP_BLOCKED_MESSAGES(916),
NO_ACCESS_CHAT(917),
NO_ACCESS_EMAIL(918),
U1(925),
NO_ACCESS_COMMUNITY(203);
companion object {
fun fromInt(value: Int) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,101 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.io.Serializable
import java.util.*
class VKDocument() : VKModel() {
override val attachmentType = VKAttachments.Type.DOCUMENT
companion object {
const val serialVersionUID: Long = 1L
}
var id: Int = 0
var ownerId: Int = 0
var title: String = ""
var size: Int = 0
var ext: String = ""
var url: String = ""
var date: Int = 0
var type: Type = Type.UNKNOWN
var preview: Preview? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
title = o.optString("title")
size = o.optInt("size")
ext = o.optString("ext")
url = o.optString("url")
date = o.optInt("date")
type = Type.fromInt(o.optInt("type"))
o.optJSONObject("preview")?.let {
preview = Preview(it)
}
}
class Preview(o: JSONObject) : Serializable {
companion object {
const val serialVersionUID: Long = 1L
}
var photo: Photo? = null
var graffiti: Graffiti? = null
inner class Photo(o: JSONObject) : Serializable {
var sizes: ArrayList<VKPhotoSize>? = null
init {
o.optJSONArray("sizes")?.let {
val sizes = ArrayList<VKPhotoSize>()
for (i in 0 until it.length()) {
sizes.add(VKPhotoSize(it.optJSONObject(i)))
}
this.sizes = sizes
}
}
}
class Graffiti(o: JSONObject) : Serializable {
companion object {
const val serialVersionUID: Long = 1L
}
var src: String = o.optString("src")
var width: Int = o.optInt("width")
var height: Int = o.optInt("height")
}
init {
o.optJSONObject("photo")?.let {
photo = Photo(it)
}
o.optJSONObject("graffiti")?.let {
graffiti = Graffiti(it)
}
}
}
enum class Type(val value: Int) {
NONE(0),
TEXT(1),
ARCHIVE(2),
GIF(3),
IMAGE(4),
AUDIO(5),
VIDEO(6),
BOOK(7),
UNKNOWN(8);
companion object {
fun fromInt(value: Int) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGeolocation() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GEOLOCATION
constructor(o: JSONObject) : this() {}
}
@@ -0,0 +1,25 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGift() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GIFT
var id: Int = 0
var thumb256: String = ""
var thumb96: String = ""
var thumb48: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
thumb256 = o.optString("thumb_256")
thumb96 = o.optString("thumb_96")
thumb48 = o.optString("thumb_48")
}
}
@@ -0,0 +1,29 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKGraffiti() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.GRAFFITI
var id: Int = 0
var ownerId: Int = 0
var url: String = ""
var width: Int = 0
var height: Int = 0
var accessKey: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
ownerId = o.optInt("owner_id", -1)
url = o.optString("url")
width = o.optInt("width")
height = o.optInt("height")
accessKey = o.optString("access_key")
}
}
@@ -0,0 +1,56 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import org.json.JSONObject
open class VKGroup() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
fun parse(array: JSONArray): ArrayList<VKGroup> {
val groups = ArrayList<VKGroup>()
for (i in 0 until array.length()) {
groups.add(VKGroup(array.optJSONObject(i)))
}
return groups
}
}
var id: Int = 0
var name: String = ""
var screenName: String = ""
var isClosed: Boolean = false
var deactivated: String = ""
var type: Type = Type.NULL
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
name = o.optString("name")
screenName = o.optString("screen_name")
isClosed = o.optInt("is_closed") == 1
deactivated = o.optString("deactivated")
type = Type.fromString(o.optString("type"))
photo50 = o.optString("photo_50")
photo100 = o.optString("photo_100")
photo200 = o.optString("photo_200")
}
enum class Type(val value: String) {
NULL("null"),
GROUP("group"),
PAGE("page"),
EVENT("event");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,57 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.io.Serializable
class VKLink() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.LINK
var url: String = ""
var title: String = ""
var caption: String = ""
var description: String = ""
var previewPage: String = ""
var previewUrl: String = ""
var photo: VKPhoto? = null
var button: Button? = null
constructor(o: JSONObject): this() {
url = o.optString("url")
title = o.optString("title")
caption = o.optString("caption")
description = o.optString("description")
previewPage = o.optString("preview_page")
previewUrl = o.optString("preview_url")
o.optJSONObject("photo")?.let {
photo = VKPhoto(it)
}
o.optJSONObject("button")?.let {
button = Button(it)
}
}
class Button(o: JSONObject) : Serializable {
var title: String = o.optString("title")
var action: Action? = null
init {
o.optJSONObject("action")?.let {
action = Action(it)
}
}
class Action(o: JSONObject) : Serializable {
var type: String = o.optString("type")
var url: String = o.optString("url")
}
}
}
@@ -0,0 +1,14 @@
package com.meloda.fast.api.model
import java.util.*
class VKLongPollHistory : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
private val lpMessages: ArrayList<VKMessage>? = null
private val messages: ArrayList<VKMessage>? = null
private val profiles: ArrayList<VKUser>? = null
private val groups: ArrayList<VKGroup>? = null //TODO: использовать
}
@@ -0,0 +1,19 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKLongPollServer() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
var key: String = ""
var server: String = ""
var ts: Long = 0
constructor(o: JSONObject) : this() {
key = o.optString("key")
server = o.optString("server").replace("\\", "")
ts = o.optLong("ts")
}
}
@@ -0,0 +1,164 @@
package com.meloda.fast.api.model
import android.util.ArrayMap
import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject
open class VKMessage() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
var profiles = arrayListOf<VKUser>()
var groups = arrayListOf<VKGroup>()
var conversations = arrayListOf<VKConversation>()
const val serialVersionUID: Long = 1L
var lastHistoryCount: Int = 0
const val UNREAD = 1 // Оно просто есть
const val OUTBOX = 1 shl 1 // Исходящее сообщение
const val REPLIED = 1 shl 2 // На сообщение был создан ответ
const val IMPORTANT = 1 shl 3 // Важное сообщение
const val FRIENDS = 1 shl 5 // Сообщение в чат друга
const val SPAM = 1 shl 6 // Сообщение помечено как спам
const val DELETED = 1 shl 7 // Удаление сообщения
const val AUDIO_LISTENED = 1 shl 12 // ГС прослушано
const val CHAT = 1 shl 13 // Сообщение отправлено в беседу
const val CANCEL_SPAM = 1 shl 15 // Отмена пометки спама
const val HIDDEN = 1 shl 16 // Приветственное сообщение сообщества
const val DELETE_FOR_ALL = 1 shl 17 // Сообщение удалено для всех
const val CHAT_IN = 1 shl 19 // Входящее сообщение в беседе
const val REPLY_MSG = 1 shl 21 // Ответ на сообщение
val flags = ArrayMap<String, Int>()
fun isOut(flags: Int): Boolean {
return OUTBOX and flags > 0
}
fun isDeleted(flags: Int): Boolean {
return DELETED and flags > 0
}
fun isUnread(flags: Int): Boolean {
return UNREAD and flags > 0
}
fun isSpam(flags: Int): Boolean {
return SPAM and flags > 0
}
fun isCanceledSpam(flags: Int): Boolean {
return CANCEL_SPAM and flags > 0
}
fun isImportant(flags: Int): Boolean {
return IMPORTANT and flags > 0
}
fun isDeletedForAll(flags: Int): Boolean {
return DELETE_FOR_ALL and flags > 0
}
init {
flags["unread"] = UNREAD
flags["outbox"] = OUTBOX
flags["replied"] = REPLIED
flags["important"] = IMPORTANT
flags["friends"] = FRIENDS
flags["spam"] = SPAM
flags["deleted"] = DELETED
flags["audio_listened"] = AUDIO_LISTENED
flags["chat"] = CHAT
flags["cancel_spam"] = CANCEL_SPAM
flags["hidden"] = HIDDEN
flags["delete_for_all"] = DELETE_FOR_ALL
flags["chat_in"] = CHAT_IN
flags["reply_msg"] = REPLY_MSG
}
}
var id: Int = 0
var date: Int = 0
var peerId: Int = 0
var fromId: Int = 0
var editTime: Int = 0
var isOut: Boolean = false
var text: String = ""
var randomId: Int = 0
var conversationMessageId: Int = 0
var hasEmoji: Boolean = false
var isImportant: Boolean = false
var isRead: Boolean = false
var attachments: ArrayList<VKModel> = arrayListOf()
var fwdMessages: ArrayList<VKMessage> = arrayListOf()
var replyMessage: VKMessage? = null
var action: VKMessageAction? = null
var fromUser: VKUser? = null
var fromGroup: VKGroup? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
date = o.optInt("date")
peerId = o.optInt("peer_id", -1)
fromId = o.optInt("from_id", -1)
editTime = o.optInt("edit_time", -1)
isOut = o.optInt("out") == 1
text = VKUtil.prepareMessageText(o.optString("text"))
randomId = o.optInt("random_id", -1)
conversationMessageId = o.optInt("conversation_message_id", -1)
isImportant = o.optBoolean("important")
o.optJSONArray("attachments")?.let {
attachments = VKAttachments.parse(it)
}
o.optJSONArray("fwd_messages")?.let {
val fwdMessages = ArrayList<VKMessage>(it.length())
for (i in 0 until it.length()) {
fwdMessages.add(VKMessage(it.optJSONObject(i)))
}
this.fwdMessages = fwdMessages
}
o.optJSONObject("reply_message")?.let {
replyMessage = VKMessage(it)
}
o.optJSONObject("action")?.let {
action = VKMessageAction(it)
}
}
fun getForwardedMessages() = ArrayList<VKMessage>().apply {
for (model in fwdMessages) add(model)
}
fun isFromUser() = fromId > 0
fun isFromGroup() = fromId < 0
fun isOutbox() = isOut
fun isInbox() = !isOutbox()
override fun toString(): String {
return if (text.isNotEmpty()) {
text
} else {
super.toString()
}
}
}
@@ -0,0 +1,47 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKMessageAction() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var type: Type = Type.NONE
var memberId = 0
var message: VKMessage? = null
var conversationMessageId: Int = 0
var text: String = ""
var oldText: String = ""
//TODO: add photo
constructor(o: JSONObject) : this() {
type = Type.fromString(o.optString("type"))
memberId = o.optInt("member_id", -1)
text = o.optString("text")
}
enum class Type(val value: String) {
NONE("none"),
CHAT_CREATE("chat_create"),
PHOTO_UPDATE("chat_photo_update"),
PHOTO_REMOVE("chat_photo_remove"),
TITLE_UPDATE("chat_title_update"),
PIN_MESSAGE("chat_pin_message"),
UNPIN_MESSAGE("chat_unpin_message"),
INVITE_USER("chat_invite_user"),
INVITE_USER_BY_LINK("chat_invite_user_by_link"),
KICK_USER("chat_kick_user"),
SCREENSHOT("chat_screenshot"),
INVITE_USER_BY_CALL("chat_invite_user_by_call"),
INVITE_USER_BY_CALL_LINK("chat_invite_user_by_call_link");
companion object {
fun fromString(value: String) = values().first { it.value == value }
}
}
}
@@ -0,0 +1,13 @@
package com.meloda.fast.api.model
import java.io.Serializable
abstract class VKModel : Serializable {
abstract val attachmentType: VKAttachments.Type
companion object {
const val serialVersionUID = 1L
}
}
@@ -0,0 +1,40 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.util.*
class VKPhoto() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.PHOTO
var id: Int = 0
var albumId: Int = 0
var ownerId: Int = 0
var text: String = ""
var date: Int = 0
var width: Int = 0
var height: Int = 0
var sizes: ArrayList<VKPhotoSize>? = null
constructor(o: JSONObject) : this() {
id = o.optInt("id", -1)
albumId = o.optInt("album_id", -1)
ownerId = o.optInt("owner_id", -1)
text = o.optString("text")
date = o.optInt("date")
width = o.optInt("width")
height = o.optInt("height")
o.optJSONArray("sizes")?.let {
val sizes = ArrayList<VKPhotoSize>()
for (i in 0 until it.length()) {
sizes.add(VKPhotoSize(it.optJSONObject(i)))
}
this.sizes = sizes
}
}
}
@@ -0,0 +1,18 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKPhotoSize(o: JSONObject) : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var type: String = o.optString("type")
var url: String = o.optString("url")
var height: Int = o.optInt("height")
var width: Int = o.optInt("width")
}
@@ -0,0 +1,58 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKPoll() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.POLL
constructor(o: JSONObject): this() {}
// var id = o.optInt("id", -1)
// var ownerId = o.optInt("owner_id", -1)
// var created = o.optInt("created")
// var question: String = o.optString("question")
// var votes = o.optInt("votes")
// var answers = ArrayList<Answer>()
// var isAnonymous = o.optBoolean("anonymous")
// var isMultiple = o.optBoolean("multiple")
// var answerIds = ArrayList<Int>()
// var endDate = o.optInt("end_date")
// var isClosed = o.optBoolean("closed")
// var isBoard = o.optBoolean("is_board")
// var isCanEdit = o.optBoolean("can_edit")
// var isCanVote = false
// var isCanReport = false
// var isCanShare = false
// var authorId = 0
// var background = Color.WHITE
//TODO: private ArrayList friends
// init {
// o.optJSONArray("answers")?.let {
// val answers = ArrayList<Answer>()
// for (i in 0 until it.length()) {
// answers.add(Answer(it.optJSONObject(i)))
// }
// this.answers = answers
// }
// //setAnswerIds();
// // ...
// }
// class Answer(o: JSONObject) : Serializable {
// var id = o.optInt("id", -1)
// var text: String = o.optString("text")
// var votes = o.optInt("votes")
// var rate = o.optInt("rate")
// }
}
@@ -0,0 +1,44 @@
package com.meloda.fast.api.model
import org.json.JSONObject
import java.util.*
class VKSticker() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.STICKER
var productId: Int = 0
var stickerId: Int = 0
var images: ArrayList<Image>? = null
constructor(o: JSONObject) : this() {
productId = o.optInt("product_id", -1)
stickerId = o.optInt("sticker_id", -1)
o.optJSONArray("images")?.let {
val images = ArrayList<Image>()
for (i in 0 until it.length()) {
images.add(Image(it.optJSONObject(i)))
}
this.images = images
}
}
class Image(o: JSONObject) : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.NONE
var url: String = o.optString("url")
var width = o.optInt("width")
var height = o.optInt("height")
}
}
@@ -0,0 +1,80 @@
package com.meloda.fast.api.model
import org.json.JSONArray
import org.json.JSONObject
open class VKUser() : VKModel() {
override val attachmentType = VKAttachments.Type.NONE
companion object {
const val serialVersionUID: Long = 1L
var friendsCount: Int = 0
fun parse(array: JSONArray): ArrayList<VKUser> {
val users = ArrayList<VKUser>()
for (i in 0 until array.length()) {
users.add(VKUser(array.optJSONObject(i)))
}
return users
}
}
var sortId: Int = 0
var userId: Int = 0
var firstName: String = ""
var lastName: String = ""
var deactivated: String = ""
var isClosed: Boolean = false
var isCanAccessClosed: Boolean = true
var sex: Int = 0
var screenName: String = ""
var photo50: String = ""
var photo100: String = ""
var photo200: String = ""
var isOnline: Boolean = false
var isOnlineMobile: Boolean = false
var status: String = ""
var lastSeen: Int = 0
var lastSeenPlatform: Int = 0
var isVerified: Boolean = false
constructor(o: JSONObject) : this() {
sortId = 0
userId = o.optInt("id", -1)
firstName = o.optString("first_name")
lastName = o.optString("last_name")
deactivated = o.optString("deactivated", "")
isClosed = o.optBoolean("is_closed")
isCanAccessClosed = o.optBoolean("can_access_closed")
sex = o.optInt("sex")
screenName = o.optString("screen_name")
photo50 = o.optString("photo_50")
photo100 = o.optString("photo_100")
photo200 = o.optString("photo_200")
isOnline = o.optInt("online") == 1
isOnlineMobile = isOnline && o.optInt("online_mobile") == 1
status = o.optString("status")
lastSeen = 0
lastSeenPlatform = 0
isVerified = o.optInt("verified") == 1
o.optJSONObject("last_seen")?.let {
lastSeen = it.optInt("time")
lastSeenPlatform = it.optInt("platform")
}
}
fun isDeactivated() = deactivated.isNotEmpty()
override fun toString(): String {
return "$firstName $lastName"
}
}
@@ -0,0 +1,43 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKVideo() : VKModel() {
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.VIDEO
// var id = o.optInt("id", -1)
// var ownerId = o.optInt("owner_id", -1)
// var title: String = o.optString("title")
// var description: String = o.optString("description")
// var duration = o.optInt("duration", -1)
// var photo130: String = o.optString("photo_130")
// var photo320: String = o.optString("photo_320")
// var photo640: String = o.optString("photo_640")
// var photo800: String = o.optString("photo_800")
// var photo1280: String = o.optString("photo_1280")
// var firstFrame130: String = o.optString("first_frame_130")
// var firstFrame320: String = o.optString("first_frame_320")
// var firstFrame640: String = o.optString("first_frame_640")
// var firstFrame800: String = o.optString("first_frame_800")
// var firstFrame1280: String = o.optString("first_frame_1280")
// var date = o.optInt("date")
// var views = o.optInt("views")
// var comments = o.optInt("comments")
// var player: String = o.optString("player")
// var isCanEdit = o.optInt("can_edit", 0) == 1
// var isCanAdd = o.optInt("can_add") == 1
// var isPrivate = o.optInt("is_private", 0) == 1
// var accessKey: String = o.optString("access_key")
// var isProcessing = o.optInt("processing", 0) == 1
// var isLive = o.optInt("live", 0) == 1
// var isUpcoming = o.optInt("upcoming", 0) == 1
// var isFavorite = o.optBoolean("favorite")
constructor(o: JSONObject) : this() {}
}
@@ -0,0 +1,15 @@
package com.meloda.fast.api.model
import org.json.JSONObject
class VKWall() : VKModel() { //https://vk.com/dev/objects/post
companion object {
const val serialVersionUID: Long = 1L
}
override val attachmentType = VKAttachments.Type.WALL_POST
constructor(o: JSONObject) : this() {}
}
@@ -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>>
}
@@ -0,0 +1,368 @@
package com.meloda.fast.api.util
import androidx.annotation.WorkerThread
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
object VKUtil {
private const val TAG = "VKUtil"
fun extractPattern(string: String, pattern: String): String? {
val p = Pattern.compile(pattern)
val m = p.matcher(string)
return if (!m.find()) null else m.toMatchResult().group(1)
}
private const val pattern_string_profile_id = "^(id)?(\\d{1,10})$"
private val pattern_profile_id = Pattern.compile(pattern_string_profile_id)
fun parseProfileId(text: String): String? {
val m = pattern_profile_id.matcher(text)
return if (!m.find()) null else m.group(2)
}
fun sortMessagesByDate(
values: ArrayList<com.meloda.fast.api.model.VKMessage>,
firstOnTop: Boolean
): ArrayList<com.meloda.fast.api.model.VKMessage> {
values.sortWith { m1, m2 ->
val d1 = m1.date
val d2 = m2.date
if (firstOnTop) {
d2 - d1
} else {
d1 - d2
}
}
return values
}
fun sortConversationsByDate(
values: ArrayList<com.meloda.fast.api.model.VKConversation>,
firstOnTop: Boolean
): ArrayList<com.meloda.fast.api.model.VKConversation> {
values.sortWith { c1, c2 ->
val d1 = c1.lastMessage.date
val d2 = c2.lastMessage.date
return@sortWith if (firstOnTop) {
d2 - d1
} else {
d1 - d2
}
}
return values
}
fun prepareMessageText(message: String): String {
if (message.isEmpty()) return message
var newText = message
val mentions = hashMapOf<String, String>()
var startFrom = 0
while (true) {
val leftBracketIndex = newText.indexOf('[', startFrom)
val verticalLineIndex = newText.indexOf('|', startFrom)
val rightBracketIndex = newText.indexOf(']', startFrom)
if (leftBracketIndex == -1 ||
verticalLineIndex == -1 ||
rightBracketIndex == -1
) {
break
}
val id = newText.substring(leftBracketIndex + 1, verticalLineIndex)
if (!id.matches(Regex("^id(\\d+)\$")) || rightBracketIndex - verticalLineIndex < 2) {
break
}
val text = newText.substring(verticalLineIndex + 1, rightBracketIndex)
val str = "[$id|$text]"
mentions[str] = text
startFrom = rightBracketIndex + 1
}
mentions.forEach {
newText = newText.replace(it.key, it.value)
}
return newText
}
// fun removeTime(date: Date): Long {
// return Calendar.getInstance().apply {
// time = date
// this[Calendar.HOUR_OF_DAY] = 0
// this[Calendar.MINUTE] = 0
// this[Calendar.SECOND] = 0
// this[Calendar.MILLISECOND] = 0
// }.timeInMillis
// }
//TODO: нормальное время
fun getLastSeenTime(date: Long): String {
return SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)
}
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 {
conversation.isUser() -> {
peerUser?.let { return it.toString() } ?: ""
}
conversation.isGroup() -> {
peerGroup?.let { return it.name } ?: ""
}
conversation.isChat() -> {
conversation.title
}
else -> ""
}
}
fun getMessageTitle(message: com.meloda.fast.api.model.VKMessage, fromUser: com.meloda.fast.api.model.VKUser?, fromGroup: com.meloda.fast.api.model.VKGroup?): String {
return when {
message.isFromUser() -> {
fromUser?.let { return it.toString() } ?: ""
}
message.isFromGroup() -> {
fromGroup?.let { return it.name } ?: ""
}
else -> ""
}
}
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 {
conversation.isUser() -> {
peerUser?.let { return it.photo200 } ?: ""
}
conversation.isGroup() -> {
peerGroup?.let { return it.photo200 } ?: ""
}
conversation.isChat() -> {
conversation.photo200
}
else -> ""
}
}
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 {
message.isFromUser() -> {
fromUser?.let { return it.photo100 } ?: ""
}
message.isFromGroup() -> {
fromGroup?.let { return it.photo100 } ?: ""
}
else -> ""
}
}
fun getUserPhoto(user: com.meloda.fast.api.model.VKUser): String {
if (user.photo200.isEmpty()) {
if (user.photo100.isEmpty()) {
if (user.photo50.isEmpty()) {
return ""
}
} else {
return user.photo100
}
} else {
return user.photo200
}
return ""
}
fun getGroupPhoto(group: com.meloda.fast.api.model.VKGroup): String {
if (group.photo200.isEmpty()) {
if (group.photo100.isEmpty()) {
if (group.photo50.isEmpty()) {
return ""
}
} else {
return group.photo100
}
} else {
return group.photo200
}
return ""
}
fun parseConversations(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKConversation> {
val conversations = arrayListOf<com.meloda.fast.api.model.VKConversation>()
for (i in 0 until array.length()) {
conversations.add(com.meloda.fast.api.model.VKConversation(array.optJSONObject(i)))
}
return conversations
}
fun parseMessages(array: JSONArray): ArrayList<com.meloda.fast.api.model.VKMessage> {
val messages = arrayListOf<com.meloda.fast.api.model.VKMessage>()
for (i in 0 until array.length()) {
messages.add(com.meloda.fast.api.model.VKMessage(array.optJSONObject(i)))
}
return messages
}
fun isMessageHasFlag(mask: Int, flagName: String): Boolean {
val o: Any? = com.meloda.fast.api.model.VKMessage.flags[flagName]
return if (o != null) { //has flag
val flag = o as Int
flag and mask > 0
} else false
}
//TODO: rewrite parsing
//fromUser and fromGroup are null
@Deprecated("need to rewrite")
@WorkerThread
fun parseLongPollMessage(array: JSONArray): com.meloda.fast.api.model.VKMessage {
val message = com.meloda.fast.api.model.VKMessage()
val id = array.optInt(1)
val flags = array.optInt(2)
val peerId = array.optInt(3)
val date = array.optInt(4)
val text = array.optString(5)
message.id = id
message.peerId = peerId
message.date = date
message.text = text
// val fromId =
// if (isMessageHasFlag(flags, "outbox")) com.meloda.fast.UserConfig.userId
// else peerId
message.fromId = peerId
array.optJSONObject(6)?.let {
if (it.has("emoji")) message.hasEmoji = true
if (it.has("from")) {
message.fromId = it.optInt("from", -1)
}
if (it.has("source_act")) {
message.action = com.meloda.fast.api.model.VKMessageAction().also { action ->
action.type = com.meloda.fast.api.model.VKMessageAction.Type.fromString(it.optString("source_act"))
when (action.type) {
com.meloda.fast.api.model.VKMessageAction.Type.CHAT_CREATE -> {
action.text = it.optString("source_text")
}
com.meloda.fast.api.model.VKMessageAction.Type.TITLE_UPDATE -> {
action.oldText = it.optString("source_old_text")
action.text = it.optString("source_text")
}
com.meloda.fast.api.model.VKMessageAction.Type.PIN_MESSAGE -> {
action.memberId = it.optInt("source_mid")
action.conversationMessageId = it.optInt("source_chat_local_id")
it.optJSONObject("source_message")?.let { message ->
action.message = com.meloda.fast.api.model.VKMessage(message)
}
}
com.meloda.fast.api.model.VKMessageAction.Type.UNPIN_MESSAGE -> {
action.memberId = it.optInt("source_mid")
action.conversationMessageId = it.optInt("source_chat_local_id")
}
com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER,
com.meloda.fast.api.model.VKMessageAction.Type.KICK_USER,
com.meloda.fast.api.model.VKMessageAction.Type.SCREENSHOT,
com.meloda.fast.api.model.VKMessageAction.Type.INVITE_USER_BY_CALL -> {
action.memberId = it.optInt("source_mid")
}
}
}
}
}
array.optJSONObject(7)?.let {
/**
*
* fwd? reply? attachments_count? attachments?
*
*/
}
val randomId = array.optInt(8)
message.randomId = randomId
val conversationMessageId = array.optInt(9)
message.conversationMessageId = conversationMessageId
val editTime = array.optInt(10)
message.editTime = editTime
// val out = fromId == com.meloda.fast.UserConfig.userId
// message.isOut = out
//
// if (message.isFromUser()) {
// message.fromUser = MemoryCache.getUserById(fromId)
// } else {
// message.fromGroup = MemoryCache.getGroupById(abs(fromId))
// }
return message
}
fun parseJsonPhotos(jsonPhotos: JSONObject): List<String> {
val photos = arrayListOf<String>()
for (key in jsonPhotos.keys()) {
photos.add(jsonPhotos.getString(key))
}
return photos
}
fun putPhotosToJson(photo50: String, photo100: String, photo200: String): JSONObject {
val json = JSONObject()
json.put("photo_50", photo50)
json.put("photo_100", photo100)
json.put("photo_200", photo200)
return json
}
fun isGroupId(id: Int) = id < 0
fun isUserId(id: Int) = id in 1..1999999999
fun isChatId(id: Int) = id > 2_000_000_000
}
@@ -1,33 +1,48 @@
package com.meloda.fast.base
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import com.meloda.extensions.ContextExtensions.color
import com.meloda.fast.R
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ColorUtils
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
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?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
val navigationBarColor =
if (AndroidUtils.isDarkTheme()) {
color(R.color.dark_primaryDark)
} else {
ColorUtils.darkenColor(color(R.color.primaryDark))
}
window.navigationBarColor = navigationBarColor
}
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}
fun getRootView(): View {
return findViewById(android.R.id.content)
override fun onStart() {
super.onStart()
lifecycleRegistry.currentState = Lifecycle.State.STARTED
}
override fun onResume() {
super.onResume()
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
}
override fun onDestroy() {
super.onDestroy()
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
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
import androidx.annotation.IdRes
import androidx.appcompat.widget.Toolbar
import androidx.annotation.LayoutRes
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) {
val toolbar: Toolbar = requireView().findViewById(resId)
constructor() : super()
activity?.let {
if (it is MainActivity && toolbar is com.meloda.fast.widget.Toolbar) it.initToolbar(
toolbar
)
}
}
constructor(@LayoutRes resId: Int) : super(resId)
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.app.Application
import android.app.DownloadManager
import android.content.ClipboardManager
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.database.sqlite.SQLiteDatabase
import android.net.ConnectivityManager
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.preference.PreferenceManager
import com.facebook.drawee.backends.pipeline.Fresco
import com.meloda.fast.BuildConfig
import com.meloda.fast.R
import com.meloda.fast.UserConfig
import com.meloda.fast.database.CacheStorage
import com.meloda.fast.database.DatabaseHelper
import com.meloda.fast.fragment.SettingsFragment
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.ReportingInteractionMode
import org.acra.annotation.ReportsCrashes
@@ -46,13 +32,6 @@ import java.util.*
class AppGlobal : Application() {
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 locale: Locale
@@ -82,15 +61,9 @@ class AppGlobal : Application() {
instance = this
if (!BuildConfig.DEBUG) {
AppCenter.start(
this, APP_CENTER_TOKEN, Analytics::class.java, Crashes::class.java
)
ACRA.init(this)
}
Fresco.initialize(this)
preferences = PreferenceManager.getDefaultSharedPreferences(this)
handler = Handler(mainLooper)
locale = Locale.getDefault()
@@ -106,62 +79,10 @@ class AppGlobal : Application() {
Companion.packageName = packageName
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()
screenHeight = AndroidUtils.getDisplayHeight()
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.FragmentManager
import com.meloda.fast.R
import com.meloda.fast.fragment.FragmentConversationsDeprecated
import com.meloda.fast.fragment.FragmentFriendsDeprecated
object FragmentSwitcher {
@@ -62,8 +60,7 @@ object FragmentSwitcher {
val transaction = fragmentManager.beginTransaction()
if (fragmentToShow == null) {
fragmentToShow = createFragmentByTag(tag)
transaction.add(containerId, fragmentToShow, tag)
throw NullPointerException("Required fragment is null")
} else {
transaction.show(fragmentToShow)
}
@@ -105,12 +102,4 @@ object FragmentSwitcher {
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 androidx.collection.arrayMapOf
import com.meloda.concurrent.TaskManager
import com.meloda.fast.concurrent.TaskManager
import com.meloda.fast.BuildConfig
import com.meloda.fast.model.NewUpdateInfo
import com.meloda.netservices.HttpRequest
import com.meloda.fast.net.HttpRequest
import org.json.JSONArray
import org.json.JSONObject
@@ -0,0 +1,3 @@
package com.meloda.fast.concurrent
class EventInfo<T> constructor(var key: String, var data: T? = null)
@@ -0,0 +1,12 @@
package com.meloda.fast.concurrent
import android.os.Process
class LowThread(runnable: Runnable?) : Thread(runnable) {
override fun run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
super.run()
}
}
@@ -0,0 +1,30 @@
package com.meloda.fast.concurrent
object TaskManager {
private const val TAG = "TaskManager"
private val listeners = arrayListOf<OnEventListener>()
fun addOnEventListener(listener: OnEventListener) {
listeners.add(listener)
}
fun removeOnEventListener(listener: OnEventListener?) {
listeners.remove(listener)
}
fun execute(runnable: Runnable?) {
LowThread(runnable).start()
}
fun sendEvent(eventInfo: EventInfo<*>) {
for (listener in listeners) {
listener.onNewEvent(eventInfo)
}
}
interface OnEventListener {
fun onNewEvent(info: EventInfo<*>)
}
}
@@ -12,9 +12,9 @@ import com.meloda.fast.database.storage.ChatsStorage
import com.meloda.fast.database.storage.GroupsStorage
import com.meloda.fast.database.storage.MessagesStorage
import com.meloda.fast.database.storage.UsersStorage
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKUser
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.model.VKUser
import java.util.*
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.DatabaseUtils.TABLE_CHATS
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKConversation
import com.meloda.vksdk.util.VKUtil
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject
@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.DatabaseUtils.TABLE_GROUPS
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKGroup
import com.meloda.vksdk.util.VKUtil
import com.meloda.fast.api.model.VKGroup
import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject
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.base.Storage
import com.meloda.fast.util.Utils
import com.meloda.vksdk.model.VKMessage
import com.meloda.vksdk.model.VKMessageAction
import com.meloda.vksdk.model.VKModel
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.model.VKMessageAction
import com.meloda.fast.api.model.VKModel
import java.util.stream.Collectors
@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.QueryBuilder
import com.meloda.fast.database.base.Storage
import com.meloda.vksdk.model.VKUser
import com.meloda.vksdk.util.VKUtil
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.api.util.VKUtil
import org.json.JSONObject
@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 -> ""
}
}
}
@@ -0,0 +1,37 @@
package com.meloda.fast.extensions
import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.*
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
object ContextExtensions {
fun Context.drawable(@DrawableRes resId: Int): Drawable? {
return ContextCompat.getDrawable(this, resId)
}
@ColorInt
fun Context.color(@ColorRes resId: Int): Int {
return ContextCompat.getColor(this, resId)
}
fun Context.font(@FontRes resId: Int): Typeface? {
return ResourcesCompat.getFont(this, resId)
}
fun Context.string(@StringRes resId: Int): String {
return getString(resId)
}
fun Context.view(resId: Int, root: ViewGroup? = null, attachToRoot: Boolean = false): View {
return LayoutInflater.from(this).inflate(resId, root, attachToRoot)
}
}
@@ -0,0 +1,13 @@
package com.meloda.fast.extensions
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
object DrawableExtensions {
fun Drawable?.tint(@ColorInt color: Int): Drawable? {
this?.setTint(color)
return this
}
}
@@ -0,0 +1,11 @@
package com.meloda.fast.extensions
import kotlin.math.roundToInt
object FloatExtensions {
fun Float.int(): Int {
return 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
}
}
@@ -0,0 +1,11 @@
package com.meloda.fast.extensions
import java.util.*
object StringExtensions {
fun String.lowerCase(): String {
return toLowerCase(Locale.getDefault())
}
}
@@ -0,0 +1,17 @@
package com.meloda.fast.extensions
import android.widget.TextView
import com.google.android.material.textfield.TextInputLayout
object TextViewExtensions {
fun TextView.clear() {
text = ""
}
fun TextInputLayout.clear() {
editText?.setText("")
}
}
@@ -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
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.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import android.viewbinding.library.fragment.viewBinding
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.fragment.ui.presenter.LoginPresenter
import com.meloda.fast.fragment.ui.view.LoginView
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.KeyboardUtils
import kotlin.math.roundToInt
import com.meloda.fast.databinding.FragmentLoginBinding
class LoginFragment : BaseFragment(), LoginView {
class LoginFragment : BaseFragment(R.layout.fragment_login) {
private lateinit var presenter: LoginPresenter
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)
}
private val binding: FragmentLoginBinding by viewBinding()
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>() {
}
@@ -1,103 +0,0 @@
package com.meloda.fast.fragment.ui.repository
import com.meloda.concurrent.TaskManager
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
class ConversationsRepositoryDeprecated : MvpRepository<VKConversation>() {
fun loadConversations(
offset: Int, count: Int,
listener: MvpOnResponseListener<ArrayList<VKConversation>>
) {
TaskManager.execute {
VKApi.messages()
.getConversations()
.filter("all")
.extended(true)
.fields(VKConstants.USER_FIELDS)
.offset(offset)
.count(count)
.executeArray(VKConversation::class.java,
object : OnResponseListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
TaskManager.execute {
cacheLoadedConversations(response)
// MemoryCache.putUsers(VKConversation.profiles)
// MemoryCache.putGroups(VKConversation.groups)
sendResponse(listener, response)
}
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
fun getCachedConversations(
offset: Int, count: Int,
listener: MvpOnResponseListener<ArrayList<VKConversation>>
) {
if (true) {
sendResponse(listener, arrayListOf())
return
}
TaskManager.execute {
// val conversations = MemoryCache.getConversations().asArrayList()
//
// VKUtil.sortConversationsByDate(conversations, true)
// sendResponse(listener, conversations)
}
}
private fun fillConversationsWithProfilesAndGroups(conversations: ArrayList<VKConversation>) {
for (conversation in conversations) {
val lastMessage = conversation.lastMessage
when (conversation.type) {
VKConversation.Type.USER -> {
// VKUtil.searchUser(conversation.conversationId)?.let {
// conversation.peerUser = it
// }
}
VKConversation.Type.GROUP -> {
// VKUtil.searchGroup(conversation.conversationId)?.let {
// conversation.peerGroup = it
// }
}
}
if (lastMessage.isFromGroup()) {
// VKUtil.searchGroup(lastMessage.fromId)?.let {
// lastMessage.fromGroup = it
// }
} else {
// VKUtil.searchUser(lastMessage.fromId)?.let {
// lastMessage.fromUser = it
// }
}
}
}
private fun cacheLoadedConversations(conversations: List<VKConversation>) {
val messages = arrayListOf<VKMessage>()
for (conversation in conversations) {
messages.add(conversation.lastMessage)
}
// MemoryCache.putMessages(messages)
// MemoryCache.putConversations(conversations)
}
}
@@ -1,88 +0,0 @@
package com.meloda.fast.fragment.ui.repository
import android.util.Log
import com.meloda.concurrent.TaskManager
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.VKUser
class FriendsRepositoryDeprecated : MvpRepository<VKUser>() {
fun loadFriends(
userId: Int,
offset: Int,
count: Int,
listener: MvpOnResponseListener<ArrayList<VKUser>>
) {
TaskManager.execute {
VKApi.friends()
.get()
.order("hints")
.userId(userId)
.fields(VKConstants.USER_FIELDS)
.count(count)
.offset(offset)
.executeArray(VKUser::class.java,
object : OnResponseListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
Log.d("FriendsRepository", "get ${response.size} friends from api")
TaskManager.execute {
cacheLoadedUsers(userId, response)
}
sendResponse(listener, response)
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
fun getCachedFriends(
userId: Int, offset: Int, count: Int, onlyOnline: Boolean,
listener: MvpOnResponseListener<ArrayList<VKUser>>
) {
// TaskManager.execute {
// val friendsArray = MemoryCache.getFriends(userId)
//
// Log.d("FriendsRepository", "get ${friendsArray.size} friends from cache")
//
// if (friendsArray.isEmpty()) {
// sendError(listener, NullPointerException("Friends list is empty"))
// return@execute
// }
//
// val friends = arrayListOf<VKUser>()
//
// for (friend in friendsArray) {
// val user = MemoryCache.getUserById(friend.friendId)
//
// user?.let {
// if (onlyOnline && user.isOnline || !onlyOnline) {
// friends.add(user)
// }
// }
// }
//
// sendResponse(listener, friends)
// }
}
private fun cacheLoadedUsers(userId: Int, users: ArrayList<VKUser>) {
// MemoryCache.putUsers(users)
//
// val friends = ArrayList<VKFriend>()
//
// for (user in users) {
// friends.add(VKFriend(user.userId, userId))
// }
// MemoryCache.putFriends(friends)
}
}
@@ -1,74 +0,0 @@
package com.meloda.fast.fragment.ui.repository
import android.annotation.SuppressLint
import android.content.Context
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import com.meloda.mvp.MvpOnResponseListener
import com.meloda.mvp.MvpRepository
import com.meloda.vksdk.VKAuth
import org.json.JSONObject
import org.jsoup.Jsoup
class LoginRepository : MvpRepository<Any>() {
fun login(
context: Context,
email: String,
password: String,
captcha: String,
onResponseListener: MvpOnResponseListener<JSONObject>
) {
if (email.trim().isEmpty() || password.trim().isEmpty()) return
val loadingUrl = VKAuth.getDirectAuthUrl(email, password, captcha)
val webView = createWebView(context)
webView.addJavascriptInterface(WebViewHandlerInterface(onResponseListener), "HtmlHandler")
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.loadUrl(
"javascript:window.HtmlHandler.handleHtml" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
)
}
}
webView.loadUrl(loadingUrl)
}
@SuppressLint("SetJavaScriptEnabled")
private fun createWebView(context: Context): WebView {
val loginWebView = WebView(context)
loginWebView.settings.javaScriptEnabled = true
loginWebView.settings.domStorageEnabled = true
loginWebView.settings.loadsImagesAutomatically = false
loginWebView.settings.userAgentString = "Chrome/41.0.2228.0 Safari/537.36"
loginWebView.clearCache(true)
val cookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies(null)
cookieManager.flush()
cookieManager.setAcceptCookie(false)
return loginWebView
}
private class WebViewHandlerInterface(private var onResponseListener: MvpOnResponseListener<JSONObject>) {
@JavascriptInterface
fun handleHtml(html: String?) {
val doc = Jsoup.parse(html)
val responseString =
doc.select("pre[style=\"word-wrap: break-word; white-space: pre-wrap;\"]")
.first()
.text()
onResponseListener.onResponse(JSONObject(responseString))
}
}
}
@@ -1,11 +0,0 @@
package com.meloda.fast.fragment.ui.view
import com.meloda.mvp.MvpView
interface ChatsView : MvpView {
fun initViews()
fun prepareViews()
}

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