forked from melod1n/fast-messenger
Initial commit
This commit is contained in:
+13
-81
@@ -1,83 +1,15 @@
|
|||||||
# Built application files
|
|
||||||
*.apk
|
|
||||||
*.ap_
|
|
||||||
*.aab
|
|
||||||
|
|
||||||
# Files for the ART/Dalvik VM
|
|
||||||
*.dex
|
|
||||||
|
|
||||||
# Java class files
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
bin/
|
|
||||||
gen/
|
|
||||||
out/
|
|
||||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
|
||||||
# release/
|
|
||||||
|
|
||||||
# Gradle files
|
|
||||||
.gradle/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
|
||||||
local.properties
|
|
||||||
|
|
||||||
# Proguard folder generated by Eclipse
|
|
||||||
proguard/
|
|
||||||
|
|
||||||
# Log Files
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Android Studio Navigation editor temp files
|
|
||||||
.navigation/
|
|
||||||
|
|
||||||
# Android Studio captures folder
|
|
||||||
captures/
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
*.iml
|
*.iml
|
||||||
.idea/workspace.xml
|
.gradle
|
||||||
.idea/tasks.xml
|
/local.properties
|
||||||
.idea/gradle.xml
|
/.idea/caches
|
||||||
.idea/assetWizardSettings.xml
|
/.idea/libraries
|
||||||
.idea/dictionaries
|
/.idea/modules.xml
|
||||||
.idea/libraries
|
/.idea/workspace.xml
|
||||||
# Android Studio 3 in .gitignore file.
|
/.idea/navEditor.xml
|
||||||
.idea/caches
|
/.idea/assetWizardSettings.xml
|
||||||
.idea/modules.xml
|
.DS_Store
|
||||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
/build
|
||||||
.idea/navEditor.xml
|
/captures
|
||||||
|
|
||||||
# Keystore files
|
|
||||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
|
||||||
#*.jks
|
|
||||||
#*.keystore
|
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
# Google Services (e.g. APIs or Firebase)
|
local.properties
|
||||||
# google-services.json
|
|
||||||
|
|
||||||
# Freeline
|
|
||||||
freeline.py
|
|
||||||
freeline/
|
|
||||||
freeline_project_description.json
|
|
||||||
|
|
||||||
# fastlane
|
|
||||||
fastlane/report.xml
|
|
||||||
fastlane/Preview.html
|
|
||||||
fastlane/screenshots
|
|
||||||
fastlane/test_output
|
|
||||||
fastlane/readme.md
|
|
||||||
|
|
||||||
# Version control
|
|
||||||
vcs.xml
|
|
||||||
|
|
||||||
# lint
|
|
||||||
lint/intermediates/
|
|
||||||
lint/generated/
|
|
||||||
lint/outputs/
|
|
||||||
lint/tmp/
|
|
||||||
# lint/reports/
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'kotlin-android'
|
||||||
|
id 'kotlin-kapt'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "ru.melod1n.project.vkm"
|
||||||
|
minSdkVersion 23
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
|
||||||
|
|
||||||
|
implementation 'androidx.core:core-ktx:1.5.0-beta01'
|
||||||
|
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
|
||||||
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01'
|
||||||
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.3.0'
|
||||||
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
|
|
||||||
|
implementation 'androidx.room:room-runtime:2.3.0-beta01'
|
||||||
|
kapt 'androidx.room:room-compiler:2.3.0-beta01'
|
||||||
|
|
||||||
|
implementation 'com.facebook.fresco:fresco:2.3.0'
|
||||||
|
|
||||||
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
|
|
||||||
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
|
||||||
|
implementation 'com.github.rahatarmanahmed:circularprogressview:2.5.0'
|
||||||
|
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
|
||||||
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
|
|
||||||
|
implementation 'ch.acra:acra:4.11.1'
|
||||||
|
|
||||||
|
def appCenterSdkVersion = '4.1.0'
|
||||||
|
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
|
||||||
|
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
|
||||||
|
implementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
|
||||||
|
}
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="ru.melod1n.project.vkm">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".common.AppGlobal"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<activity android:name=".activity.MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.LongPollService"
|
||||||
|
android:enabled="true" />
|
||||||
|
|
||||||
|
<activity android:name=".activity.MessagesActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activity.StartActivity"
|
||||||
|
android:theme="@style/AppTheme.Start" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activity.LoginActivity"
|
||||||
|
android:label="@string/activity_login" />
|
||||||
|
|
||||||
|
<activity android:name=".activity.DropUserDataActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activity.SettingsActivity"
|
||||||
|
android:label="@string/navigation_settings" />
|
||||||
|
|
||||||
|
<activity android:name=".activity.UpdateActivity" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.MinuteReceiver"
|
||||||
|
android:enabled="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.ACTION_TIME_CHANGED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKAuth
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.DrawableExtensions.tint
|
||||||
|
import ru.melod1n.project.vkm.widget.Toolbar
|
||||||
|
|
||||||
|
class LoginActivity : 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.FragmentSwitcher
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.common.TimeManager
|
||||||
|
import ru.melod1n.project.vkm.dialog.AccountDialog
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.DrawableExtensions.tint
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentConversations
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentFriends
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentSettings
|
||||||
|
import ru.melod1n.project.vkm.fragment.LoginFragment
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.service.LongPollService
|
||||||
|
import ru.melod1n.project.vkm.util.AndroidUtils
|
||||||
|
import ru.melod1n.project.vkm.util.ViewUtils
|
||||||
|
import ru.melod1n.project.vkm.widget.Toolbar
|
||||||
|
|
||||||
|
|
||||||
|
class MainActivity : BaseActivity(),
|
||||||
|
NavigationView.OnNavigationItemSelectedListener,
|
||||||
|
BottomNavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
|
private lateinit var fragmentConversations: FragmentConversations
|
||||||
|
private lateinit var fragmentFriends: FragmentFriends
|
||||||
|
private lateinit var fragmentSettings: FragmentSettings
|
||||||
|
|
||||||
|
private var selectedId = 0
|
||||||
|
|
||||||
|
private lateinit var drawerLayout: DrawerLayout
|
||||||
|
lateinit var bottomBar: BottomNavigationView
|
||||||
|
private lateinit var navigationView: NavigationView
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
initViews()
|
||||||
|
|
||||||
|
// checkLogin()
|
||||||
|
|
||||||
|
if (UserConfig.isLoggedIn()) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, FragmentConversations())
|
||||||
|
.commit()
|
||||||
|
} else {
|
||||||
|
bottomBar.isVisible = false
|
||||||
|
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, LoginFragment())
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TimeManager.init(this)
|
||||||
|
|
||||||
|
// prepareFragments()
|
||||||
|
|
||||||
|
// prepareNavigationView()
|
||||||
|
// prepareBottomBar()
|
||||||
|
// checkLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
drawerLayout = findViewById(R.id.drawerLayout)
|
||||||
|
bottomBar = findViewById(R.id.bottomBar)
|
||||||
|
navigationView = findViewById(R.id.navigationView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
TimeManager.destroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareFragments() {
|
||||||
|
fragmentConversations = FragmentConversations()
|
||||||
|
fragmentFriends = FragmentFriends(UserConfig.userId)
|
||||||
|
fragmentSettings = FragmentSettings()
|
||||||
|
|
||||||
|
val containerId = R.id.fragmentContainer
|
||||||
|
|
||||||
|
FragmentSwitcher.addFragments(
|
||||||
|
supportFragmentManager,
|
||||||
|
containerId,
|
||||||
|
listOf(fragmentConversations)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
openStartScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openMainScreen() {
|
||||||
|
selectedId = R.id.navigationConversations
|
||||||
|
bottomBar.selectedItemId = selectedId
|
||||||
|
openConversationsScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startLongPoll() {
|
||||||
|
startService(Intent(this, LongPollService::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openStartScreen() {
|
||||||
|
finish()
|
||||||
|
startActivity(Intent(this, StartActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openConversationsScreen() {
|
||||||
|
FragmentSwitcher.showFragment(
|
||||||
|
supportFragmentManager,
|
||||||
|
fragmentConversations.javaClass.simpleName,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openFriendsScreen() {
|
||||||
|
FragmentSwitcher.showFragment(
|
||||||
|
supportFragmentManager,
|
||||||
|
fragmentFriends.javaClass.simpleName,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openSettingsScreen() {
|
||||||
|
startActivity(Intent(this, SettingsActivity::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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.activity.ui.presenter.MessagesPresenter
|
||||||
|
import ru.melod1n.project.vkm.activity.ui.view.MessagesView
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKGroup
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKModel
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.dialog.ProfileDialog
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.DrawableExtensions.tint
|
||||||
|
import ru.melod1n.project.vkm.extensions.ImageViewExtensions.loadImage
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentSettings
|
||||||
|
import ru.melod1n.project.vkm.util.KeyboardUtils
|
||||||
|
import ru.melod1n.project.vkm.util.TextUtils
|
||||||
|
import ru.melod1n.project.vkm.util.ViewUtils
|
||||||
|
import ru.melod1n.project.vkm.widget.CircleImageView
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesActivity : BaseActivity(), MessagesView {
|
||||||
|
|
||||||
|
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 presenter: MessagesPresenter
|
||||||
|
|
||||||
|
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()
|
||||||
|
presenter.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_messages)
|
||||||
|
initViews()
|
||||||
|
|
||||||
|
initExtraData()
|
||||||
|
|
||||||
|
prepareToolbar()
|
||||||
|
prepareRefreshLayout()
|
||||||
|
prepareRecyclerView()
|
||||||
|
prepareEditText()
|
||||||
|
|
||||||
|
presenter = MessagesPresenter(this)
|
||||||
|
presenter.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 { presenter.openProfile() }
|
||||||
|
|
||||||
|
chatAvatar.setOnClickListener { presenter.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(
|
||||||
|
FragmentSettings.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 -> {
|
||||||
|
presenter.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 {
|
||||||
|
presenter.sendMessage(chatMessage.text.toString(), attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnLongClickListener {
|
||||||
|
presenter.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) { _, _ ->
|
||||||
|
presenter.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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.common.FragmentSwitcher
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.DrawableExtensions.tint
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentSettings
|
||||||
|
import ru.melod1n.project.vkm.widget.Toolbar
|
||||||
|
|
||||||
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private lateinit var toolbar: Toolbar
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_settings)
|
||||||
|
initViews()
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
|
toolbar.navigationIcon = drawable(R.drawable.ic_arrow_back).tint(color(R.color.accent))
|
||||||
|
|
||||||
|
toolbar.setNavigationClickListener { onBackPressed() }
|
||||||
|
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, FragmentSettings()).commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
toolbar = findViewById(R.id.toolbar)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
val currentFragment = FragmentSwitcher.getCurrentFragment(supportFragmentManager) ?: return
|
||||||
|
|
||||||
|
if (currentFragment.javaClass == FragmentSettings::class.java && (currentFragment as FragmentSettings).onBackPressed()) {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
class StartActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private lateinit var startEnter: MaterialButton
|
||||||
|
private lateinit var startLoginSettings: MaterialButton
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_start)
|
||||||
|
initViews()
|
||||||
|
|
||||||
|
prepareEnterButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
startEnter = findViewById(R.id.startEnter)
|
||||||
|
startLoginSettings = findViewById(R.id.startLoginSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareEnterButton() {
|
||||||
|
startEnter.setOnClickListener {
|
||||||
|
startActivity(Intent(this, LoginActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
startEnter.setOnLongClickListener {
|
||||||
|
showUserIdTokenDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
startLoginSettings.setOnClickListener {
|
||||||
|
Toast.makeText(this, R.string.in_progress_placeholder, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUserIdTokenDialog() {
|
||||||
|
AlertDialog.Builder(this).apply {
|
||||||
|
setTitle(R.string.custom_data)
|
||||||
|
|
||||||
|
val view = LayoutInflater.from(this@StartActivity)
|
||||||
|
.inflate(R.layout.activity_login_custom_data, null, false) as View
|
||||||
|
setView(view)
|
||||||
|
|
||||||
|
val userId = view.findViewById<AppCompatEditText>(R.id.customDataUserId)
|
||||||
|
val token = view.findViewById<AppCompatEditText>(R.id.customDataToken)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (userId.text.toString().isEmpty() || token.text.toString().isEmpty())
|
||||||
|
return@setPositiveButton
|
||||||
|
val id = userId.text.toString().toInt()
|
||||||
|
val accessToken = token.text.toString()
|
||||||
|
|
||||||
|
if (id < 1) return@setPositiveButton
|
||||||
|
|
||||||
|
UserConfig.userId = id
|
||||||
|
UserConfig.token = accessToken
|
||||||
|
UserConfig.save()
|
||||||
|
|
||||||
|
finish()
|
||||||
|
startActivity(Intent(this@StartActivity, MainActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
setCancelable(false)
|
||||||
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.base.BaseActivity
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.common.UpdateManager
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.FloatExtensions.int
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.model.NewUpdateInfo
|
||||||
|
import ru.melod1n.project.vkm.receiver.DownloadUpdateReceiver
|
||||||
|
import ru.melod1n.project.vkm.util.AndroidUtils
|
||||||
|
import ru.melod1n.project.vkm.util.TimeUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateActivity : 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@UpdateActivity, 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@UpdateActivity.newUpdate = updateInfo
|
||||||
|
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNoUpdates() {
|
||||||
|
isNewUpdate = false
|
||||||
|
isChecking = false
|
||||||
|
|
||||||
|
this@UpdateActivity.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity.ui.presenter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.activity.ui.repository.MessagesRepository
|
||||||
|
import ru.melod1n.project.vkm.activity.ui.view.MessagesView
|
||||||
|
import ru.melod1n.project.vkm.adapter.MessagesAdapter
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKModel
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpOnLoadListener
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpPresenter
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
import ru.melod1n.project.vkm.listener.ItemClickListener
|
||||||
|
import ru.melod1n.project.vkm.listener.ItemLongClickListener
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class MessagesPresenter(viewState: MessagesView) :
|
||||||
|
MvpPresenter<VKMessage, MessagesRepository, MessagesView>(
|
||||||
|
viewState,
|
||||||
|
MessagesRepository::class.java.name
|
||||||
|
),
|
||||||
|
ItemClickListener,
|
||||||
|
ItemLongClickListener,
|
||||||
|
TaskManager.OnEventListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_MESSAGES_COUNT = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var adapter: MessagesAdapter
|
||||||
|
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 = MessagesAdapter(context!!, arrayListOf(), conversation).also {
|
||||||
|
it.itemClickListener = this
|
||||||
|
it.itemLongClickListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCachedConversation(peerId: Int) {
|
||||||
|
repository.getCachedConversation(peerId, object : MvpOnLoadListener<VKConversation> {
|
||||||
|
override fun onResponse(response: VKConversation) {
|
||||||
|
conversation = response
|
||||||
|
|
||||||
|
createAdapter()
|
||||||
|
refreshConversation(response)
|
||||||
|
|
||||||
|
getCachedMessages(peerId, 0, DEFAULT_MESSAGES_COUNT,
|
||||||
|
object : MvpOnLoadListener<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 : MvpOnLoadListener<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 : MvpOnLoadListener<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: MvpOnLoadListener<Any?>? = null
|
||||||
|
) {
|
||||||
|
repository.getCachedMessages(peerId, offset, count,
|
||||||
|
object : MvpOnLoadListener<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 : MvpOnLoadListener<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 : MvpOnLoadListener<Int> {
|
||||||
|
override fun onResponse(response: Int) {
|
||||||
|
message.messageId = response
|
||||||
|
|
||||||
|
TaskManager.execute { MemoryCache.put(message) }
|
||||||
|
TaskManager.loadMessage(VKApiKeys.UPDATE_MESSAGE, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
viewState.showErrorSnackbar(t)
|
||||||
|
|
||||||
|
viewState.setMessageText(lastMessageText)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity.ui.repository
|
||||||
|
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.VKApi
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKGroup
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpOnLoadListener
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpRepository
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.extensions.ArrayExtensions.asArrayList
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.util.ArrayUtils
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class MessagesRepository : MvpRepository<VKMessage>() {
|
||||||
|
|
||||||
|
fun loadMessages(
|
||||||
|
peerId: Int,
|
||||||
|
offset: Int,
|
||||||
|
count: Int,
|
||||||
|
listener: MvpOnLoadListener<ArrayList<VKMessage>>
|
||||||
|
) {
|
||||||
|
TaskManager.execute {
|
||||||
|
VKApi.messages()
|
||||||
|
.getHistory()
|
||||||
|
.peerId(peerId)
|
||||||
|
.reversed(false)
|
||||||
|
.extended(true)
|
||||||
|
.fields(VKUser.DEFAULT_FIELDS + "," + VKGroup.DEFAULT_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: MvpOnLoadListener<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: MvpOnLoadListener<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: MvpOnLoadListener<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: MvpOnLoadListener<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: MvpOnLoadListener<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.melod1n.project.vkm.activity.ui.view
|
||||||
|
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpView
|
||||||
|
|
||||||
|
interface MessagesView : 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)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,724 @@
|
|||||||
|
package ru.melod1n.project.vkm.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.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.adapter.diffutil.ConversationsCallback
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKGroup
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import ru.melod1n.project.vkm.base.BaseAdapter
|
||||||
|
import ru.melod1n.project.vkm.base.BaseHolder
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.widget.CircleImageView
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class ConversationsAdapter(
|
||||||
|
val recyclerView: RecyclerView,
|
||||||
|
values: ArrayList<VKConversation>
|
||||||
|
) : BaseAdapter<VKConversation, ConversationsAdapter.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 -> addMessage(info.data as VKMessage)
|
||||||
|
VKApiKeys.EDIT_MESSAGE -> editMessage(info.data as VKMessage)
|
||||||
|
VKApiKeys.RESTORE_MESSAGE -> restoreMessage(info.data as VKMessage)
|
||||||
|
VKApiKeys.READ_MESSAGE -> readMessage(
|
||||||
|
(info.data as Array<Int>)[0],
|
||||||
|
(info.data as Array<Int>)[1]
|
||||||
|
)
|
||||||
|
VKApiKeys.DELETE_MESSAGE -> deleteMessage(
|
||||||
|
(info.data as Array<Int>)[0],
|
||||||
|
(info.data as Array<Int>)[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
VKApiKeys.UPDATE_CONVERSATION -> updateConversation(info.data as Int)
|
||||||
|
VKApiKeys.UPDATE_MESSAGE -> updateMessage(info.data as Int)
|
||||||
|
VKApiKeys.UPDATE_USER -> updateUsers(info.data as ArrayList<Int>)
|
||||||
|
VKApiKeys.UPDATE_GROUP -> 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 notifyChanges(oldList: List<VKConversation>, newList: List<VKConversation> = values) {
|
||||||
|
val callback = ConversationsCallback(oldList, newList)
|
||||||
|
val diff = DiffUtil.calculateDiff(callback)
|
||||||
|
|
||||||
|
diff.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLastItem() = currentPosition >= itemCount - 1
|
||||||
|
|
||||||
|
inner class ConversationHolder(v: View) : BaseHolder(v) {
|
||||||
|
|
||||||
|
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: LinearLayout = v.findViewById(R.id.conversationRoot)
|
||||||
|
|
||||||
|
private val colorHighlight = context.color(R.color.accent)
|
||||||
|
|
||||||
|
override fun bind(position: Int) {
|
||||||
|
bind(position, mutableListOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(position: Int, payloads: MutableList<Any>) {
|
||||||
|
Log.d(TAG, "bind position: $position")
|
||||||
|
|
||||||
|
val conversation = this@ConversationsAdapter[position]
|
||||||
|
|
||||||
|
val lastMessage = conversation.lastMessage
|
||||||
|
|
||||||
|
TaskManager.execute {
|
||||||
|
val peerUser: VKUser? =
|
||||||
|
if (conversation.isUser()) VKUtil.searchUser(conversation.conversationId) else null
|
||||||
|
|
||||||
|
val peerGroup: VKGroup? =
|
||||||
|
if (conversation.isGroup()) VKUtil.searchGroup(conversation.conversationId) else null
|
||||||
|
|
||||||
|
val fromUser: VKUser? =
|
||||||
|
if (lastMessage.isFromUser()) VKUtil.searchUser(lastMessage.fromId) else null
|
||||||
|
|
||||||
|
val fromGroup: VKGroup? =
|
||||||
|
if (lastMessage.isFromGroup()) VKUtil.searchGroup(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.isNotEmpty()) {
|
||||||
|
for (payload in payloads) {
|
||||||
|
when (payload) {
|
||||||
|
ConversationsCallback.CONVERSATION -> {
|
||||||
|
setUserOnline(conversation, peerUser)
|
||||||
|
prepareUserAvatar(
|
||||||
|
conversation,
|
||||||
|
lastMessage,
|
||||||
|
fromUser,
|
||||||
|
fromGroup
|
||||||
|
)
|
||||||
|
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
|
||||||
|
setDialogType(conversation)
|
||||||
|
setIsRead(lastMessage, conversation)
|
||||||
|
setCounterBackground(conversation)
|
||||||
|
}
|
||||||
|
ConversationsCallback.MESSAGE -> {
|
||||||
|
prepareUserAvatar(
|
||||||
|
conversation,
|
||||||
|
lastMessage,
|
||||||
|
fromUser,
|
||||||
|
fromGroup
|
||||||
|
)
|
||||||
|
prepareAttachments(lastMessage)
|
||||||
|
setIsRead(lastMessage, conversation)
|
||||||
|
setDate(lastMessage)
|
||||||
|
}
|
||||||
|
ConversationsCallback.GROUP -> {
|
||||||
|
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
|
||||||
|
}
|
||||||
|
ConversationsCallback.USER -> {
|
||||||
|
setUserOnline(conversation, peerUser)
|
||||||
|
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
|
||||||
|
}
|
||||||
|
ConversationsCallback.EDIT_MESSAGE -> {
|
||||||
|
prepareUserAvatar(
|
||||||
|
conversation,
|
||||||
|
lastMessage,
|
||||||
|
fromUser,
|
||||||
|
fromGroup
|
||||||
|
)
|
||||||
|
prepareAttachments(lastMessage)
|
||||||
|
setIsRead(lastMessage, conversation)
|
||||||
|
setDate(lastMessage)
|
||||||
|
}
|
||||||
|
ConversationsCallback.DATE -> {
|
||||||
|
setDate(lastMessage)
|
||||||
|
}
|
||||||
|
ConversationsCallback.ONLINE -> {
|
||||||
|
setUserOnline(conversation, peerUser)
|
||||||
|
}
|
||||||
|
ConversationsCallback.ATTACHMENTS -> {
|
||||||
|
prepareAttachments(lastMessage)
|
||||||
|
}
|
||||||
|
ConversationsCallback.AVATAR -> {
|
||||||
|
prepareAvatar(dialogTitle, conversation, peerUser, peerGroup)
|
||||||
|
}
|
||||||
|
ConversationsCallback.USER_AVATAR -> {
|
||||||
|
prepareUserAvatar(
|
||||||
|
conversation,
|
||||||
|
lastMessage,
|
||||||
|
fromUser,
|
||||||
|
fromGroup
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ConversationsCallback.READ -> {
|
||||||
|
setIsRead(lastMessage, conversation)
|
||||||
|
}
|
||||||
|
ConversationsCallback.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 = VKUtil.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageUtil.loadImage(
|
||||||
|
// VKUtil.getUserAvatar(lastMessage, fromUser, fromGroup),
|
||||||
|
// object : ImageUtil.OnLoadListener {
|
||||||
|
// override fun onLoad(bitmap: Bitmap) {
|
||||||
|
// userAvatar.setImageBitmap(bitmap)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onError(e: Exception) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// ImageUtil.loadImage(
|
||||||
|
// VKUtil.getUserAvatar(lastMessage, fromUser, fromGroup),
|
||||||
|
// userAvatar,
|
||||||
|
// placeholderNormal
|
||||||
|
// )
|
||||||
|
} else {
|
||||||
|
userAvatar.isVisible = false
|
||||||
|
userAvatar.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAvatar(
|
||||||
|
dialogTitle: String,
|
||||||
|
conversation: VKConversation,
|
||||||
|
peerUser: VKUser?,
|
||||||
|
peerGroup: VKGroup?
|
||||||
|
) {
|
||||||
|
val dialogAvatarPlaceholder = VKUtil.getAvatarPlaceholder(context, dialogTitle)
|
||||||
|
|
||||||
|
avatar.setImageDrawable(dialogAvatarPlaceholder)
|
||||||
|
|
||||||
|
val avatarLink = VKUtil.getAvatar(conversation, peerUser, peerGroup)
|
||||||
|
|
||||||
|
if (avatarLink.isNotEmpty()) {
|
||||||
|
avatar.setImageURI(Uri.parse(avatarLink))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageUtil.loadImage(
|
||||||
|
// VKUtil.getAvatar(conversation, peerUser, peerGroup),
|
||||||
|
// object : ImageUtil.OnLoadListener {
|
||||||
|
// override fun onLoad(bitmap: Bitmap) {
|
||||||
|
// avatar.setImageBitmap(bitmap)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onError(e: Exception) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// ImageUtil.loadImage(
|
||||||
|
// VKUtil.getAvatar(conversation, peerUser, peerGroup),
|
||||||
|
// avatar,
|
||||||
|
// dialogAvatarPlaceholder
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDialogType(conversation: VKConversation) {
|
||||||
|
val dDialogType = VKUtil.getDialogType(context, conversation)
|
||||||
|
|
||||||
|
type.setImageDrawable(dDialogType)
|
||||||
|
type.isVisible = dDialogType != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAttachments(lastMessage: VKMessage) {
|
||||||
|
text.apply {
|
||||||
|
compoundDrawablePadding = 0
|
||||||
|
setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMessage.action == null) {
|
||||||
|
when {
|
||||||
|
lastMessage.attachments.isNotEmpty() -> {
|
||||||
|
val attachmentString =
|
||||||
|
VKUtil.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 =
|
||||||
|
VKUtil.getAttachmentDrawable(context, lastMessage.attachments)
|
||||||
|
|
||||||
|
text.apply {
|
||||||
|
text = span
|
||||||
|
setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||||
|
attachmentDrawable,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
compoundDrawablePadding = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastMessage.fwdMessages.isNotEmpty() -> {
|
||||||
|
val fwdText = VKUtil.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 {
|
||||||
|
VKUtil.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.outRead == conversation.lastMessageId ||
|
||||||
|
!lastMessage.isOut && conversation.inRead == conversation.lastMessageId) && conversation.lastMessageId == lastMessage.messageId) && 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 = VKUtil.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 {
|
||||||
|
conversationId = message.peerId
|
||||||
|
|
||||||
|
localId =
|
||||||
|
if (VKUtil.isChatId(conversationId)) conversationId - 2000000000 else conversationId
|
||||||
|
type =
|
||||||
|
if (conversationId < 0) VKConversation.TYPE_GROUP else if (conversationId > 2000000000) VKConversation.TYPE_CHAT else VKConversation.TYPE_USER
|
||||||
|
|
||||||
|
lastMessage = message
|
||||||
|
lastMessageId = message.messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
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.messageId) return
|
||||||
|
|
||||||
|
conversation.lastMessage = message
|
||||||
|
|
||||||
|
notifyItemChanged(index, ConversationsCallback.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.inRead = messageId
|
||||||
|
} else {
|
||||||
|
conversation.outRead = messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.unreadCount = if (conversation.lastMessageId == messageId) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
conversation.lastMessageId - messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyItemChanged(index, ConversationsCallback.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.messageId
|
||||||
|
|
||||||
|
if (newMessage.isOut) {
|
||||||
|
conversation.unreadCount = 0
|
||||||
|
newMessage.isRead = false
|
||||||
|
} else {
|
||||||
|
conversation.unreadCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMessage.peerId == newMessage.fromId && newMessage.fromId == UserConfig.userId) { //для лс
|
||||||
|
conversation.outRead = newMessage.messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversation
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchConversationIndex(peerId: Int): Int {
|
||||||
|
for (i in values.indices) {
|
||||||
|
if (getItem(i).conversationId == 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, ConversationsCallback.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, ConversationsCallback.MESSAGE) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,581 @@
|
|||||||
|
package ru.melod1n.project.vkm.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.style.StyleSpan
|
||||||
|
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 ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.activity.MessagesActivity
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.*
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import ru.melod1n.project.vkm.base.BaseAdapter
|
||||||
|
import ru.melod1n.project.vkm.base.BaseHolder
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
import ru.melod1n.project.vkm.extensions.FloatExtensions.int
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.util.AndroidUtils
|
||||||
|
import ru.melod1n.project.vkm.util.ImageUtils
|
||||||
|
import ru.melod1n.project.vkm.widget.BoundedLinearLayout
|
||||||
|
import ru.melod1n.project.vkm.widget.CircleImageView
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class MessagesAdapter(
|
||||||
|
context: Context,
|
||||||
|
values: ArrayList<VKMessage>,
|
||||||
|
var conversation: VKConversation
|
||||||
|
|
||||||
|
) : BaseAdapter<VKMessage, MessagesAdapter.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 MessagesActivity).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 -> addMessage(info.data as VKMessage)
|
||||||
|
|
||||||
|
VKApiKeys.READ_MESSAGE -> readMessage(
|
||||||
|
(info.data as Array<Int>)[0],
|
||||||
|
(info.data as Array<Int>)[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
VKApiKeys.RESTORE_MESSAGE -> restoreMessage(info.data as VKMessage)
|
||||||
|
VKApiKeys.EDIT_MESSAGE -> editMessage(info.data as VKMessage)
|
||||||
|
VKApiKeys.DELETE_MESSAGE -> deleteMessage(
|
||||||
|
(info.data as Array<Int>)[0],
|
||||||
|
(info.data as Array<Int>)[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
VKApiKeys.UPDATE_MESSAGE -> updateMessage(info.data as Int)
|
||||||
|
VKApiKeys.UPDATE_USER -> updateUser(info.data as ArrayList<Int>)
|
||||||
|
VKApiKeys.UPDATE_GROUP -> 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.isUnreaded()) {
|
||||||
|
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 && !VKGroup.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 VKDoc -> 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.text = VKUtil.matchMentions(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchGroup(message: VKMessage): VKGroup? {
|
||||||
|
if (!message.isFromGroup()) return null
|
||||||
|
|
||||||
|
return VKUtil.searchGroup(message.fromId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.messageId == 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).messageId == 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.conversationId) 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.conversationId) return
|
||||||
|
|
||||||
|
val index = searchMessagePosition(messageId)
|
||||||
|
if (index == -1) return
|
||||||
|
|
||||||
|
val message = this[index]
|
||||||
|
message.isRead = true
|
||||||
|
|
||||||
|
notifyDataSetChanged()
|
||||||
|
|
||||||
|
if (message.isInbox()) {
|
||||||
|
conversation.inRead = messageId
|
||||||
|
} else {
|
||||||
|
conversation.outRead = messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.unreadCount--
|
||||||
|
|
||||||
|
TaskManager.execute {
|
||||||
|
MemoryCache.put(message)
|
||||||
|
MemoryCache.put(conversation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editMessage(message: VKMessage) {
|
||||||
|
val index = searchMessagePosition(message.messageId)
|
||||||
|
if (index == -1) return
|
||||||
|
|
||||||
|
set(index, message)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteMessage(messageId: Int, peerId: Int) {
|
||||||
|
if (peerId != conversation.conversationId) return
|
||||||
|
|
||||||
|
val index = searchMessagePosition(messageId)
|
||||||
|
if (index == -1) return
|
||||||
|
|
||||||
|
removeAt(index)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: кривое сообщение
|
||||||
|
fun restoreMessage(message: VKMessage) {
|
||||||
|
if (message.peerId != conversation.conversationId) return
|
||||||
|
|
||||||
|
updateValues(VKUtil.sortMessagesByDate(values.apply { add(message) }, false))
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package ru.melod1n.project.vkm.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.base.BaseAdapter
|
||||||
|
import ru.melod1n.project.vkm.base.BaseHolder
|
||||||
|
import ru.melod1n.project.vkm.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.adapter.diffutil.UsersCallback
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import ru.melod1n.project.vkm.base.BaseAdapter
|
||||||
|
import ru.melod1n.project.vkm.base.BaseHolder
|
||||||
|
import ru.melod1n.project.vkm.util.ImageUtils
|
||||||
|
import ru.melod1n.project.vkm.widget.CircleImageView
|
||||||
|
|
||||||
|
class UsersAdapter(context: Context, values: ArrayList<VKUser>) :
|
||||||
|
BaseAdapter<VKUser, UsersAdapter.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: отладить открытие чата
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyChanges(oldList: List<VKUser>, newList: List<VKUser> = values) {
|
||||||
|
val callback = UsersCallback(oldList, newList)
|
||||||
|
val diff = DiffUtil.calculateDiff(callback, false)
|
||||||
|
|
||||||
|
diff.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package ru.melod1n.project.vkm.adapter.diffutil
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
|
||||||
|
class ConversationsCallback(
|
||||||
|
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.conversationId == new.conversationId
|
||||||
|
}
|
||||||
|
|
||||||
|
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.inRead == new.inRead &&
|
||||||
|
old.outRead == new.outRead &&
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package ru.melod1n.project.vkm.adapter.diffutil
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
|
||||||
|
class UsersCallback(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 ru.melod1n.project.vkm.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,41 @@
|
|||||||
|
package ru.melod1n.project.vkm.api
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
|
||||||
|
object UserConfig {
|
||||||
|
|
||||||
|
private const val TOKEN = "token"
|
||||||
|
private const val USER_ID = "user_id"
|
||||||
|
|
||||||
|
const val API_ID = "6964679"
|
||||||
|
|
||||||
|
var token = ""
|
||||||
|
var userId = 0
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
AppGlobal.preferences.edit()
|
||||||
|
.putString(TOKEN, token)
|
||||||
|
.putInt(USER_ID, userId)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restore() {
|
||||||
|
token = AppGlobal.preferences.getString(TOKEN, "")!!
|
||||||
|
userId = AppGlobal.preferences.getInt(USER_ID, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
token = ""
|
||||||
|
userId = -1
|
||||||
|
|
||||||
|
AppGlobal.preferences.edit()
|
||||||
|
.remove(TOKEN)
|
||||||
|
.remove(USER_ID)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLoggedIn(): Boolean {
|
||||||
|
return userId > 0 && !TextUtils.isEmpty(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,504 @@
|
|||||||
|
package ru.melod1n.project.vkm.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.activity.DropUserDataActivity
|
||||||
|
import ru.melod1n.project.vkm.api.method.MessageMethodSetter
|
||||||
|
import ru.melod1n.project.vkm.api.method.MethodSetter
|
||||||
|
import ru.melod1n.project.vkm.api.method.UserMethodSetter
|
||||||
|
import ru.melod1n.project.vkm.api.model.*
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.net.HttpRequest
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
object VKApi {
|
||||||
|
|
||||||
|
private var context: Context? = null
|
||||||
|
|
||||||
|
private const val TAG = "VKM:VKApi"
|
||||||
|
|
||||||
|
const val BASE_URL = "https://api.vk.com/method/"
|
||||||
|
|
||||||
|
const val API_VERSION = "5.132"
|
||||||
|
|
||||||
|
val language: String = AppGlobal.locale.language
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T> execute(url: String, cls: Class<T>?): ArrayList<T>? {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.w(TAG, "url: $url")
|
||||||
|
}
|
||||||
|
|
||||||
|
val buffer = 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
|
||||||
|
|
||||||
|
VKLongPollServer::class.java -> {
|
||||||
|
json.optJSONObject("response")?.let {
|
||||||
|
return arrayListOf(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) {
|
||||||
|
VKUser::class.java -> {
|
||||||
|
json.optJSONObject("response")?.let { r ->
|
||||||
|
VKUser.friendsCount = r.optInt("count")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
models.add(VKUser(array.optJSONObject(i)) as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VKMessage::class.java -> {
|
||||||
|
response as JSONObject
|
||||||
|
|
||||||
|
if (url.contains("messages.getHistory")) {
|
||||||
|
VKMessage.lastHistoryCount = response.optInt("count")
|
||||||
|
|
||||||
|
response.optJSONArray("profiles")?.let {
|
||||||
|
val profiles = arrayListOf<VKUser>()
|
||||||
|
|
||||||
|
for (j in 0 until it.length()) {
|
||||||
|
profiles.add(VKUser(it.optJSONObject(j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
VKMessage.profiles = profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
response.optJSONArray("groups")?.let {
|
||||||
|
val groups = arrayListOf<VKGroup>()
|
||||||
|
|
||||||
|
for (j in 0 until it.length()) {
|
||||||
|
groups.add(VKGroup(it.optJSONObject(j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
VKMessage.groups = groups
|
||||||
|
}
|
||||||
|
|
||||||
|
response.optJSONArray("conversations")?.let {
|
||||||
|
val conversations = arrayListOf<VKConversation>()
|
||||||
|
|
||||||
|
for (j in 0 until it.length()) {
|
||||||
|
conversations.add(VKConversation(it.optJSONObject(j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = VKMessage(source)
|
||||||
|
models.add(message as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
val profiles = ArrayList<VKUser>()
|
||||||
|
response.optJSONArray("profiles")?.let {
|
||||||
|
profiles.addAll(VKUser.parse(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
val groups = ArrayList<VKGroup>()
|
||||||
|
response.optJSONArray("groups")?.let {
|
||||||
|
groups.addAll(VKGroup.parse(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGlobal.database.let {
|
||||||
|
it.users.insert(profiles)
|
||||||
|
it.groups.insert(groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VKGroup::class.java -> {
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
models.add(VKGroup(array.optJSONObject(i)) as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VKModel::class.java -> {
|
||||||
|
if (url.contains("messages.getHistoryAttachments")) {
|
||||||
|
return VKAttachments.parse(array) as ArrayList<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VKConversation::class.java -> {
|
||||||
|
if (url.contains("getConversationsById")) {
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val source = array.optJSONObject(i)
|
||||||
|
models.add(VKConversation(source) as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
json.optJSONObject("response")?.let { r ->
|
||||||
|
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 = VKConversation(oConversation).also {
|
||||||
|
it.lastMessage = VKMessage(oLastMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.optJSONArray("profiles")?.let {
|
||||||
|
val profiles = arrayListOf<VKUser>()
|
||||||
|
|
||||||
|
for (j in 0 until it.length()) {
|
||||||
|
profiles.add(VKUser(it.optJSONObject(j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
VKConversation.profiles = profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
response.optJSONArray("groups")?.let {
|
||||||
|
val groups = arrayListOf<VKGroup>()
|
||||||
|
|
||||||
|
for (j in 0 until it.length()) {
|
||||||
|
groups.add(VKGroup(it.optJSONObject(j)))
|
||||||
|
}
|
||||||
|
|
||||||
|
VKConversation.groups = groups
|
||||||
|
}
|
||||||
|
|
||||||
|
models.add(conversation as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> execute(url: String, cls: Class<E>, listener: OnResponseListener<E>?) {
|
||||||
|
TaskManager.execute {
|
||||||
|
try {
|
||||||
|
val models = execute(url, cls)
|
||||||
|
|
||||||
|
listener?.let {
|
||||||
|
AppGlobal.handler.post(SuccessCallback(listener, models as E))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
listener?.let {
|
||||||
|
AppGlobal.handler.post(ErrorCallback(listener, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> executeArray(url: String, cls: Class<E>, listener: OnResponseListener<ArrayList<E>>?) {
|
||||||
|
TaskManager.execute {
|
||||||
|
try {
|
||||||
|
val models = execute(url, cls)
|
||||||
|
|
||||||
|
listener?.let {
|
||||||
|
AppGlobal.handler.post(SuccessArrayCallback(listener, models as ArrayList<E>))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
listener?.let {
|
||||||
|
AppGlobal.handler.post(ErrorCallback(listener, 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)
|
||||||
|
|
||||||
|
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,15 @@
|
|||||||
|
package ru.melod1n.project.vkm.api
|
||||||
|
|
||||||
|
enum class VKApiKeys {
|
||||||
|
READ_MESSAGE,
|
||||||
|
RESTORE_MESSAGE,
|
||||||
|
UPDATE_MESSAGE,
|
||||||
|
NEW_MESSAGE,
|
||||||
|
EDIT_MESSAGE,
|
||||||
|
DELETE_MESSAGE,
|
||||||
|
UPDATE_CONVERSATION,
|
||||||
|
|
||||||
|
UPDATE_USER,
|
||||||
|
|
||||||
|
UPDATE_GROUP
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package ru.melod1n.project.vkm.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
object VKAuth {
|
||||||
|
|
||||||
|
private const val TAG = "VKM.VKAuth"
|
||||||
|
|
||||||
|
var 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=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")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
const val settings =
|
||||||
|
"notify,friends,photos,audio,video,docs,status,notes,pages,wall,groups,messages,offline,notifications"
|
||||||
|
|
||||||
|
@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,14 @@
|
|||||||
|
package ru.melod1n.project.vkm.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,146 @@
|
|||||||
|
package ru.melod1n.project.vkm.api
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import org.json.JSONArray
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.api.util.VKUtil
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class VKLongPollParser {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun parse(updates: JSONArray) {
|
||||||
|
if (updates.length() == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until updates.length()) {
|
||||||
|
val item = updates.optJSONArray(i)
|
||||||
|
when (item.optInt(0)) {
|
||||||
|
2 -> messageSetFlags(item)
|
||||||
|
3 -> messageClearFlags(item)
|
||||||
|
4 -> messageEvent(item)
|
||||||
|
5 -> messageEdit(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "VKLongPollParser"
|
||||||
|
|
||||||
|
private fun messageEvent(item: JSONArray) {
|
||||||
|
val message = VKUtil.parseLongPollMessage(item)
|
||||||
|
|
||||||
|
TaskManager.execute {
|
||||||
|
if (message.isFromUser()) {
|
||||||
|
VKUtil.searchUser(message.fromId)?.let { message.fromUser = it }
|
||||||
|
} else {
|
||||||
|
VKUtil.searchGroup(message.fromId)?.let { message.fromGroup = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCache.getConversationById(message.peerId)?.let {
|
||||||
|
it.lastMessage = message
|
||||||
|
it.lastMessageId = message.messageId
|
||||||
|
|
||||||
|
MemoryCache.put(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCache.put(message)
|
||||||
|
|
||||||
|
val info = EventInfo(VKApiKeys.NEW_MESSAGE, message)
|
||||||
|
|
||||||
|
sendEvent(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageEdit(item: JSONArray) {
|
||||||
|
val message = VKUtil.parseLongPollMessage(item)
|
||||||
|
val info = EventInfo(VKApiKeys.EDIT_MESSAGE, message)
|
||||||
|
|
||||||
|
MemoryCache.put(message)
|
||||||
|
|
||||||
|
sendEvent(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageDelete(item: JSONArray) {
|
||||||
|
val messageId = item.optInt(1)
|
||||||
|
val peerId = item.optInt(3)
|
||||||
|
val info = EventInfo(VKApiKeys.DELETE_MESSAGE, arrayOf(peerId, messageId))
|
||||||
|
|
||||||
|
MemoryCache.deleteMessage(messageId)
|
||||||
|
|
||||||
|
sendEvent(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageRestored(item: JSONArray) {
|
||||||
|
val message = VKUtil.parseLongPollMessage(item)
|
||||||
|
val info = EventInfo(VKApiKeys.RESTORE_MESSAGE, message)
|
||||||
|
|
||||||
|
MemoryCache.put(message)
|
||||||
|
|
||||||
|
sendEvent(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageRead(item: JSONArray) {
|
||||||
|
val messageId = item.optInt(1)
|
||||||
|
val peerId = item.optInt(3)
|
||||||
|
val info = EventInfo(VKApiKeys.READ_MESSAGE, arrayOf(peerId, messageId))
|
||||||
|
|
||||||
|
MemoryCache.edit(MemoryCache.getMessageById(messageId)?.apply { isRead = true })
|
||||||
|
|
||||||
|
sendEvent(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageClearFlags(item: JSONArray) {
|
||||||
|
val id = item.optInt(1)
|
||||||
|
val flags = item.optInt(2)
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "cancel_spam")) {
|
||||||
|
Log.i(TAG, "Message with id $id: Not spam")
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "deleted")) {
|
||||||
|
messageRestored(item)
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "important")) {
|
||||||
|
Log.i(TAG, "Message with id $id: Not Important")
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "unread")) {
|
||||||
|
messageRead(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageSetFlags(item: JSONArray) {
|
||||||
|
val id = item.optInt(1)
|
||||||
|
val flags = item.optInt(2)
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "delete_for_all")) {
|
||||||
|
messageDelete(item)
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "deleted")) {
|
||||||
|
messageDelete(item)
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "spam")) {
|
||||||
|
Log.i(TAG, "Message with id $id: Spam")
|
||||||
|
}
|
||||||
|
if (VKUtil.isMessageHasFlag(flags, "important")) {
|
||||||
|
Log.i(TAG, "Message with id $id: Important")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendEvent(eventInfo: EventInfo<*>) {
|
||||||
|
TaskManager.sendEvent(eventInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnMessagesListener {
|
||||||
|
fun onNewMessage(message: VKMessage)
|
||||||
|
fun onEditMessage(message: VKMessage)
|
||||||
|
fun onReadMessage(messageId: Int, peerId: Int)
|
||||||
|
fun onDeleteMessage(messageId: Int, peerId: Int)
|
||||||
|
fun onRestoredMessage(message: VKMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.method
|
||||||
|
|
||||||
|
import ru.melod1n.project.vkm.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", 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", ArrayUtils.asString(attachments))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachment(vararg attachments: String): MessageMethodSetter {
|
||||||
|
put("attachment", ArrayUtils.asString(*attachments))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forwardMessages(ids: Collection<String>): MessageMethodSetter {
|
||||||
|
put("forward_messages", ArrayUtils.asString(ids))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forwardMessages(vararg ids: Int): MessageMethodSetter {
|
||||||
|
put("forward_messages", 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", ArrayUtils.asString(ids))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun chatIds(ids: Collection<Int>): MessageMethodSetter {
|
||||||
|
put("max_msg_id", 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,166 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.method
|
||||||
|
|
||||||
|
import android.util.ArrayMap
|
||||||
|
import android.util.Log
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApi
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.util.ArrayUtils
|
||||||
|
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"] = UserConfig.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
|
||||||
|
}
|
||||||
|
|
||||||
|
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", ArrayUtils.asString(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun userIds(ids: ArrayList<Int>): MethodSetter {
|
||||||
|
return put("user_ids", 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", ArrayUtils.asString(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun groupIds(ids: ArrayList<Int>): MethodSetter {
|
||||||
|
return put("group_ids", 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 ru.melod1n.project.vkm.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,59 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object VKAttachments {
|
||||||
|
|
||||||
|
private const val TYPE_PHOTO = "photo"
|
||||||
|
private const val TYPE_VIDEO = "video"
|
||||||
|
private const val TYPE_AUDIO = "audio"
|
||||||
|
private const val TYPE_DOC = "doc"
|
||||||
|
private const val TYPE_LINK = "link"
|
||||||
|
private const val TYPE_STICKER = "sticker"
|
||||||
|
private const val TYPE_GIFT = "gift"
|
||||||
|
private const val TYPE_AUDIO_MESSAGE = "audio_message"
|
||||||
|
private const val TYPE_GRAFFITI = "graffiti"
|
||||||
|
private const val TYPE_POLL = "poll"
|
||||||
|
private const val TYPE_GEO = "geo"
|
||||||
|
private const val TYPE_WALL = "wall"
|
||||||
|
private const val TYPE_CALL = "call"
|
||||||
|
private const val TYPE_STORY = "story"
|
||||||
|
private const val TYPE_POINT = "point"
|
||||||
|
private const val TYPE_MARKET = "market"
|
||||||
|
private const val TYPE_ARTICLE = "article"
|
||||||
|
private const val TYPE_PODCAST = "podcast"
|
||||||
|
private const val TYPE_WALL_REPLY = "wall_reply"
|
||||||
|
private const val TYPE_MONEY_REQUEST = "money_request"
|
||||||
|
private const val TYPE_AUDIO_PLAYLIST = "audio_playlist"
|
||||||
|
|
||||||
|
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 = attachment.optString("type")
|
||||||
|
val jsonObject = attachment.optJSONObject(type) ?: continue
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
TYPE_PHOTO -> attachments.add(VKPhoto(jsonObject))
|
||||||
|
TYPE_AUDIO -> attachments.add(VKAudio(jsonObject))
|
||||||
|
TYPE_VIDEO -> attachments.add(VKVideo(jsonObject))
|
||||||
|
TYPE_DOC -> attachments.add(VKDoc(jsonObject))
|
||||||
|
TYPE_STICKER -> attachments.add(VKSticker(jsonObject))
|
||||||
|
TYPE_LINK -> attachments.add(VKLink(jsonObject))
|
||||||
|
TYPE_GIFT -> attachments.add(VKGift(jsonObject))
|
||||||
|
TYPE_AUDIO_MESSAGE -> attachments.add(VKAudioMessage(jsonObject))
|
||||||
|
TYPE_GRAFFITI -> attachments.add(VKGraffiti(jsonObject))
|
||||||
|
TYPE_POLL -> attachments.add(VKPoll(jsonObject))
|
||||||
|
TYPE_CALL -> attachments.add(VKCall(jsonObject))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKAudio(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var ownerId = o.optInt("owner_id", -1)
|
||||||
|
var artist: String = o.optString("artist")
|
||||||
|
var title: String = o.optString("title")
|
||||||
|
var duration = o.optInt("duration")
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var date = o.optInt("date")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKAudioMessage(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var duration = o.optInt("duration")
|
||||||
|
var waveform = ArrayList<Int>()
|
||||||
|
var linkOgg: String = o.optString("link_ogg")
|
||||||
|
var linkMp3: String = o.optString("link_mp3")
|
||||||
|
|
||||||
|
init {
|
||||||
|
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,15 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKCall(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var initiatorId = o.optInt("initiator_id", -1)
|
||||||
|
var receiverId = o.optInt("receiver_id", -1)
|
||||||
|
var state: String = o.optString("state") //reached, canceled_by_initiator, canceled_by_receiver
|
||||||
|
var time = o.optInt("time")
|
||||||
|
var duration = o.optInt("duration")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
class VKComment { //https://vk.com/dev/objects/comment
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
@SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
|
||||||
|
@Entity(tableName = "conversations")
|
||||||
|
class VKConversation() : VKModel(), Cloneable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var profiles = arrayListOf<VKUser>()
|
||||||
|
var groups = arrayListOf<VKGroup>()
|
||||||
|
|
||||||
|
var conversationsCount: Int = 0
|
||||||
|
|
||||||
|
const val STATE_IN = "in"
|
||||||
|
const val STATE_KICKED = "kicked"
|
||||||
|
const val STATE_LEFT = "left"
|
||||||
|
|
||||||
|
const val TYPE_USER = "user"
|
||||||
|
const val TYPE_CHAT = "chat"
|
||||||
|
const val TYPE_GROUP = "group"
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
18 — пользователь заблокирован или удален;
|
||||||
|
900 — нельзя отправить сообщение пользователю, который в чёрном списке;
|
||||||
|
901 — пользователь запретил сообщения от сообщества;
|
||||||
|
902 — пользователь запретил присылать ему сообщения с помощью настроек приватности;
|
||||||
|
915 — в сообществе отключены сообщения;
|
||||||
|
916 — в сообществе заблокированы сообщения;
|
||||||
|
917 — нет доступа к чату;
|
||||||
|
918 — нет доступа к e-mail;
|
||||||
|
203 — нет доступа к сообществу
|
||||||
|
*/
|
||||||
|
|
||||||
|
var isAllowed = false
|
||||||
|
var reason = -1
|
||||||
|
|
||||||
|
var inRead = 0
|
||||||
|
var outRead = 0
|
||||||
|
var lastMessageId = 0
|
||||||
|
var unreadCount = 0
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
var conversationId = 0
|
||||||
|
|
||||||
|
var type: String = ""
|
||||||
|
var localId = 0
|
||||||
|
|
||||||
|
var disabledUntil = 0
|
||||||
|
var isDisabledForever = false
|
||||||
|
var isNoSound = false
|
||||||
|
|
||||||
|
var membersCount = 0
|
||||||
|
var title: String = ""
|
||||||
|
|
||||||
|
var pinnedMessageId = 0
|
||||||
|
|
||||||
|
var state: String = ""
|
||||||
|
|
||||||
|
@Embedded(prefix = "cMessage")
|
||||||
|
var lastMessage = VKMessage()
|
||||||
|
|
||||||
|
var isGroupChannel = false
|
||||||
|
|
||||||
|
var photo50: String = ""
|
||||||
|
var photo100: String = ""
|
||||||
|
var photo200: String = ""
|
||||||
|
|
||||||
|
@Embedded(prefix = "cUser")
|
||||||
|
var peerUser: VKUser? = null
|
||||||
|
|
||||||
|
@Embedded(prefix = "cGroup")
|
||||||
|
var peerGroup: VKGroup? = null
|
||||||
|
|
||||||
|
constructor(o: JSONObject) : this() {
|
||||||
|
inRead = o.optInt("in_read")
|
||||||
|
outRead = o.optInt("out_read")
|
||||||
|
lastMessageId = o.optInt("last_message_id", -1)
|
||||||
|
unreadCount = o.optInt("unread_count", 0)
|
||||||
|
|
||||||
|
o.optJSONObject("peer")?.let {
|
||||||
|
conversationId = it.optInt("id", -1)
|
||||||
|
type = 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")
|
||||||
|
reason = it.optInt("reason", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.optJSONObject("chat_settings")?.let {
|
||||||
|
membersCount = it.optInt("members_count")
|
||||||
|
title = it.optString("title")
|
||||||
|
|
||||||
|
it.optJSONObject("pinned_message")?.let { pinned ->
|
||||||
|
pinnedMessageId = VKPinnedMessage(pinned).id
|
||||||
|
}
|
||||||
|
|
||||||
|
state = 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 isChatId() = conversationId > 2_000_000_000
|
||||||
|
|
||||||
|
fun isChat() = type == TYPE_CHAT
|
||||||
|
|
||||||
|
fun isUser() = type == TYPE_USER
|
||||||
|
|
||||||
|
fun isNotUser() = !isUser()
|
||||||
|
|
||||||
|
fun isGroup() = type == TYPE_GROUP
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun clone(): VKConversation {
|
||||||
|
return super.clone() as VKConversation
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKDoc(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE_NONE = 0
|
||||||
|
const val TYPE_TEXT = 1
|
||||||
|
const val TYPE_ARCHIVE = 2
|
||||||
|
const val TYPE_GIF = 3
|
||||||
|
const val TYPE_IMAGE = 4
|
||||||
|
const val TYPE_AUDIO = 5
|
||||||
|
const val TYPE_VIDEO = 6
|
||||||
|
const val TYPE_BOOK = 7
|
||||||
|
const val TYPE_UNKNOWN = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var ownerId = o.optInt("owner_id", -1)
|
||||||
|
var title: String = o.optString("title")
|
||||||
|
var size = o.optInt("size")
|
||||||
|
var ext: String = o.optString("ext")
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var date = o.optInt("date")
|
||||||
|
var type = o.optInt("type")
|
||||||
|
var preview: Preview? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
o.optJSONObject("preview")?.let {
|
||||||
|
preview = Preview(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Preview(o: JSONObject) {
|
||||||
|
var photo: Photo? = null
|
||||||
|
var graffiti: Graffiti? = null
|
||||||
|
|
||||||
|
inner class Photo(o: JSONObject) {
|
||||||
|
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) {
|
||||||
|
var src: String = o.optString("src")
|
||||||
|
var width = o.optInt("width")
|
||||||
|
var height = o.optInt("height")
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
o.optJSONObject("photo")?.let {
|
||||||
|
photo = Photo(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.optJSONObject("graffiti")?.let {
|
||||||
|
graffiti = Graffiti(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "friends")
|
||||||
|
class VKFriend() {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
var friendId: Int = -1
|
||||||
|
|
||||||
|
var userId: Int = -1
|
||||||
|
|
||||||
|
constructor(friendId: Int, userId: Int): this() {
|
||||||
|
this.friendId = friendId
|
||||||
|
this.userId = userId
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKGift(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var thumb256: String = o.optString("thumb_256")
|
||||||
|
var thumb96: String = o.optString("thumb_96")
|
||||||
|
var thumb48: String = o.optString("thumb_48")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKGraffiti(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var ownerId = o.optInt("owner_id", -1)
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var width = o.optInt("width")
|
||||||
|
var height = o.optInt("height")
|
||||||
|
var accessKey: String = o.optString("access_key")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
@Entity(tableName = "groups")
|
||||||
|
open class VKGroup(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_FIELDS = "description,members_count,counters,status,verified"
|
||||||
|
|
||||||
|
fun isGroupId(id: Int): Boolean {
|
||||||
|
return id < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val EMPTY: VKGroup = object : VKGroup() {
|
||||||
|
init {
|
||||||
|
name = "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
var groupId = o.optInt("id", -1)
|
||||||
|
var name: String = o.optString("name")
|
||||||
|
var screenName: String = o.optString("screen_name")
|
||||||
|
var isClosed = o.optInt("is_closed") == 1
|
||||||
|
var deactivated: String = o.optString("deactivated")
|
||||||
|
var type: String = o.optString("type")
|
||||||
|
var photo50: String = o.optString("photo_50")
|
||||||
|
var photo100: String = o.optString("photo_100")
|
||||||
|
var photo200: String = o.optString("photo_200")
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class VKLink(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var title: String = o.optString("title")
|
||||||
|
var caption: String = o.optString("caption")
|
||||||
|
var description: String = o.optString("description")
|
||||||
|
var previewPage: String = o.optString("preview_page")
|
||||||
|
var previewUrl: String = o.optString("preview_url")
|
||||||
|
var photo: VKPhoto? = null
|
||||||
|
var button: Button? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
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,12 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKLongPollHistory : VKModel() {
|
||||||
|
|
||||||
|
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,13 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKLongPollServer(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var key: String = o.optString("key")
|
||||||
|
var server = o.optString("server").replace("\\", "")
|
||||||
|
var ts: Long = o.optLong("ts")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
class VKMarketAlbum { //https://vk.com/dev/objects/market_album
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
class VKMarketItem { //https://vk.com/dev/objects/market_item
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import android.util.ArrayMap
|
||||||
|
import androidx.room.*
|
||||||
|
import org.json.JSONObject
|
||||||
|
import ru.melod1n.project.vkm.database.dao.converters.ArrayListToByteArrayConverter
|
||||||
|
import ru.melod1n.project.vkm.database.dao.converters.ForwardedConverter
|
||||||
|
|
||||||
|
@SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
|
||||||
|
@Entity(tableName = "messages")
|
||||||
|
open class VKMessage() : VKModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
var profiles = arrayListOf<VKUser>()
|
||||||
|
var groups = arrayListOf<VKGroup>()
|
||||||
|
var conversations = arrayListOf<VKConversation>()
|
||||||
|
|
||||||
|
const val serialVersionUID = 1L
|
||||||
|
|
||||||
|
var lastHistoryCount = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
var messageId = 0
|
||||||
|
var date = 0
|
||||||
|
var peerId = 0
|
||||||
|
var fromId = 0
|
||||||
|
var editTime = 0
|
||||||
|
var isOut = false
|
||||||
|
var text: String = ""
|
||||||
|
var randomId = 0
|
||||||
|
var conversationMessageId = 0
|
||||||
|
|
||||||
|
var hasEmoji = false
|
||||||
|
var isImportant = false
|
||||||
|
var isRead = false
|
||||||
|
|
||||||
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
|
@TypeConverters(ArrayListToByteArrayConverter::class)
|
||||||
|
var attachments: ArrayList<VKModel> = arrayListOf()
|
||||||
|
|
||||||
|
// @Ignore
|
||||||
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
|
@TypeConverters(ForwardedConverter::class)
|
||||||
|
var fwdMessages: ArrayList<VKMessage> = arrayListOf()
|
||||||
|
|
||||||
|
var replyMessageId = 0
|
||||||
|
|
||||||
|
@Embedded(prefix = "mAction")
|
||||||
|
var action: VKMessageAction? = null
|
||||||
|
|
||||||
|
@Embedded(prefix = "mUser")
|
||||||
|
var fromUser: VKUser? = null
|
||||||
|
|
||||||
|
@Embedded(prefix = "Group")
|
||||||
|
var fromGroup: VKGroup? = null
|
||||||
|
|
||||||
|
constructor(o: JSONObject) : this() {
|
||||||
|
messageId = 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 = 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 {
|
||||||
|
replyMessageId = VKMessage(it).messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
o.optJSONObject("action")?.let {
|
||||||
|
action = VKMessageAction(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getForwardedMessages() = ArrayList<VKMessage>().apply {
|
||||||
|
for (model in fwdMessages) add(model as VKMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isFromUser() = fromId > 0
|
||||||
|
|
||||||
|
fun isFromGroup() = fromId < 0
|
||||||
|
|
||||||
|
fun isOutbox() = isOut
|
||||||
|
|
||||||
|
fun isInbox() = !isOutbox()
|
||||||
|
|
||||||
|
fun isReaded() = isRead
|
||||||
|
|
||||||
|
fun isUnreaded() = !isReaded()
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return if (text.isNotEmpty()) {
|
||||||
|
text
|
||||||
|
} else {
|
||||||
|
super.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKMessageAction() : VKModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
chat_photo_update — обновлена фотография беседы;
|
||||||
|
chat_photo_remove — удалена фотография беседы;
|
||||||
|
chat_create — создана беседа;
|
||||||
|
chat_title_update — обновлено название беседы;
|
||||||
|
chat_invite_user — приглашен пользователь;
|
||||||
|
chat_kick_user — исключен пользователь;
|
||||||
|
chat_pin_message — закреплено сообщение;
|
||||||
|
chat_unpin_message — откреплено сообщение;
|
||||||
|
chat_invite_user_by_link — пользователь присоединился к беседе по ссылке.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var type: String = ""
|
||||||
|
|
||||||
|
var memberId = 0
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
var message: VKMessage? = null
|
||||||
|
|
||||||
|
var conversationMessageId = 0
|
||||||
|
|
||||||
|
var text: String = ""
|
||||||
|
var oldText: String = ""
|
||||||
|
|
||||||
|
// @Embedded(prefix = "photo")
|
||||||
|
// var photo: Photo? = null
|
||||||
|
|
||||||
|
//TODO: add photo
|
||||||
|
|
||||||
|
constructor(o: JSONObject) : this() {
|
||||||
|
type = o.optString("type")
|
||||||
|
memberId = o.optInt("member_id", -1)
|
||||||
|
text = o.optString("text")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
abstract class VKModel : Serializable {
|
||||||
|
companion object {
|
||||||
|
const val serialVersionUID = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKPhoto(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var albumId = o.optInt("album_id", -1)
|
||||||
|
var ownerId = o.optInt("owner_id", -1)
|
||||||
|
var text: String = o.optString("text")
|
||||||
|
var date = o.optInt("date")
|
||||||
|
var width = o.optInt("width")
|
||||||
|
var height = o.optInt("height")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKPhotoSize(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var type: String = o.optString("type")
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var height = o.optInt("height")
|
||||||
|
var width = o.optInt("width")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKPinnedMessage(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var id = o.optInt("id", -1)
|
||||||
|
var date = o.optInt("date")
|
||||||
|
var fromId = o.optInt("from_id", -1)
|
||||||
|
var text: String = o.optString("text")
|
||||||
|
var attachments: ArrayList<VKModel>? = null
|
||||||
|
var fwdMessages: ArrayList<VKMessage>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
o.optJSONArray("attachments")?.let {
|
||||||
|
attachments = VKAttachments.parse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: parse forwarded
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKPoll(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
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,31 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class VKSticker(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
var productId = o.optInt("product_id", -1)
|
||||||
|
var stickerId = o.optInt("sticker_id", -1)
|
||||||
|
var images: ArrayList<Image>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
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() {
|
||||||
|
|
||||||
|
var url: String = o.optString("url")
|
||||||
|
var width = o.optInt("width")
|
||||||
|
var height = o.optInt("height")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
@Entity(tableName = "users")
|
||||||
|
open class VKUser(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
var friendsCount: Int = 0
|
||||||
|
|
||||||
|
const val DEFAULT_FIELDS =
|
||||||
|
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
|
||||||
|
|
||||||
|
val EMPTY: VKUser = object : VKUser() {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Unknown Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUserId(id: Int): Boolean {
|
||||||
|
return id > 0 && id < 2000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
var userId = o.optInt("id", -1)
|
||||||
|
var firstName: String = o.optString("first_name")
|
||||||
|
var lastName: String = o.optString("last_name")
|
||||||
|
var deactivated: String = o.optString("deactivated")
|
||||||
|
var isClosed = o.optBoolean("is_closed")
|
||||||
|
var isCanAccessClosed = o.optBoolean("can_access_closed")
|
||||||
|
var sex = o.optInt("sex")
|
||||||
|
var screenName: String = o.optString("screen_name")
|
||||||
|
var photo50: String = o.optString("photo_50")
|
||||||
|
var photo100: String = o.optString("photo_100")
|
||||||
|
var photo200: String = o.optString("photo_200")
|
||||||
|
var isOnline = o.optInt("online") == 1
|
||||||
|
var isOnlineMobile = isOnline && o.optInt("online_mobile") == 1
|
||||||
|
var status: String = o.optString("status")
|
||||||
|
|
||||||
|
var lastSeen = 0
|
||||||
|
var lastSeenPlatform = 0
|
||||||
|
|
||||||
|
var isVerified = o.optInt("verified") == 1
|
||||||
|
|
||||||
|
init {
|
||||||
|
o.optJSONObject("last_seen")?.let {
|
||||||
|
lastSeen = it.optInt("time")
|
||||||
|
lastSeenPlatform = it.optInt("platform")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$firstName $lastName"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class VKVideo(o: JSONObject) : VKModel() {
|
||||||
|
|
||||||
|
constructor() : this(JSONObject())
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.model
|
||||||
|
|
||||||
|
class VKWall { //https://vk.com/dev/objects/post
|
||||||
|
}
|
||||||
@@ -0,0 +1,680 @@
|
|||||||
|
package ru.melod1n.project.vkm.api.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import org.json.JSONArray
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.model.*
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.StringExtensions.lowerCase
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import ru.melod1n.project.vkm.util.TextUtils
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
object VKUtil {
|
||||||
|
|
||||||
|
private const val TAG = "VKM: 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<VKMessage>,
|
||||||
|
firstOnTop: Boolean
|
||||||
|
): ArrayList<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<VKConversation>,
|
||||||
|
firstOnTop: Boolean
|
||||||
|
): ArrayList<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 matchMentions(text: String): String {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
|
||||||
|
fun getUserOnline(user: VKUser): String {
|
||||||
|
val r = AppGlobal.resources
|
||||||
|
return if (user.isOnline) {
|
||||||
|
if (user.isOnlineMobile) {
|
||||||
|
r.getString(R.string.user_online_mobile)
|
||||||
|
} else {
|
||||||
|
r.getString(R.string.user_online)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (user.lastSeen == 0) {
|
||||||
|
r.getString(R.string.user_last_seen_recently)
|
||||||
|
} else {
|
||||||
|
r.getString(R.string.user_last_seen_at, getLastSeenTime(user.lastSeen * 1000L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserOnlineIcon(
|
||||||
|
context: Context,
|
||||||
|
conversation: VKConversation?,
|
||||||
|
peerUser: VKUser?
|
||||||
|
): Drawable? {
|
||||||
|
return if (conversation != null) {
|
||||||
|
if (conversation.isUser() && peerUser != null) {
|
||||||
|
if (!peerUser.isOnline) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
if (peerUser.isOnlineMobile) R.drawable.ic_online_mobile else R.drawable.ic_online_pc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
} else {
|
||||||
|
if (peerUser!!.isOnline) {
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
if (peerUser.isOnlineMobile) R.drawable.ic_online_mobile else R.drawable.ic_online_pc
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserOnlineIcon(context: Context, user: VKUser): Drawable? {
|
||||||
|
return getUserOnlineIcon(context, null, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: нормальное время
|
||||||
|
fun getLastSeenTime(date: Long): String {
|
||||||
|
return SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvatarPlaceholder(context: Context, dialogTitle: String): TextDrawable {
|
||||||
|
return TextDrawable.builder().buildRound(
|
||||||
|
if (dialogTitle.isEmpty()) "" else {
|
||||||
|
TextUtils.getFirstLetterFromString(dialogTitle)
|
||||||
|
},
|
||||||
|
context.color(R.color.accent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun searchUser(id: Int, onResponseListener: OnResponseListener<VKUser>? = null): VKUser? {
|
||||||
|
return if (VKGroup.isGroupId(id) || isChatId(id)) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
MemoryCache.getUserById(id)?.let { return it }
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "User with id $id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskManager.loadUser(VKApiKeys.UPDATE_USER, id, onResponseListener)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun searchGroup(id: Int, onResponseListener: OnResponseListener<VKGroup>? = null): VKGroup? {
|
||||||
|
return if (!VKGroup.isGroupId(id) || isChatId(id)) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
MemoryCache.getGroupById(abs(id))?.let { return it }
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "Group with id $id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskManager.loadGroup(VKApiKeys.UPDATE_GROUP, abs(id), onResponseListener)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTitle(conversation: VKConversation, peerUser: VKUser?, peerGroup: 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: VKMessage, fromUser: VKUser?, fromGroup: VKGroup?): String {
|
||||||
|
return when {
|
||||||
|
message.isFromUser() -> {
|
||||||
|
fromUser?.let { return it.toString() } ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
message.isFromGroup() -> {
|
||||||
|
fromGroup?.let { return it.name } ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvatar(conversation: VKConversation, peerUser: VKUser?, peerGroup: 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: VKMessage, fromUser: VKUser?, fromGroup: VKGroup?): String {
|
||||||
|
return when {
|
||||||
|
message.isFromUser() -> {
|
||||||
|
fromUser?.let { return it.photo100 } ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
message.isFromGroup() -> {
|
||||||
|
fromGroup?.let { return it.photo100 } ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserPhoto(user: 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: 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 getDialogType(context: Context, conversation: VKConversation): Drawable? {
|
||||||
|
return when {
|
||||||
|
conversation.isGroupChannel -> {
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_dialog_type_channel)
|
||||||
|
}
|
||||||
|
conversation.isChat() -> {
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_dialog_type_conversation)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAttachmentText(context: Context, attachments: List<VKModel>): String {
|
||||||
|
val resId: Int
|
||||||
|
|
||||||
|
if (attachments.isNotEmpty()) {
|
||||||
|
if (attachments.size > 1) {
|
||||||
|
var oneType = true
|
||||||
|
val className = attachments[0].javaClass.simpleName
|
||||||
|
|
||||||
|
for (model in attachments) {
|
||||||
|
if (model.javaClass.simpleName != className) {
|
||||||
|
oneType = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (oneType) {
|
||||||
|
val objectClass: Class<VKModel> = attachments[0].javaClass
|
||||||
|
resId = when (objectClass) {
|
||||||
|
VKPhoto::class.java -> {
|
||||||
|
R.string.message_attachment_photos
|
||||||
|
}
|
||||||
|
VKVideo::class.java -> {
|
||||||
|
R.string.message_attachment_videos
|
||||||
|
}
|
||||||
|
VKAudio::class.java -> {
|
||||||
|
R.string.message_attachment_audios
|
||||||
|
}
|
||||||
|
VKDoc::class.java -> {
|
||||||
|
R.string.message_attachment_docs
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resId == -1) "Unknown attachments" else context.getString(
|
||||||
|
resId,
|
||||||
|
attachments.size
|
||||||
|
).toLowerCase(Locale.getDefault())
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.message_attachments_many)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val objectClass: Class<VKModel> = attachments[0].javaClass
|
||||||
|
|
||||||
|
resId = when (objectClass) {
|
||||||
|
VKPhoto::class.java -> {
|
||||||
|
R.string.message_attachment_photo
|
||||||
|
}
|
||||||
|
VKAudio::class.java -> {
|
||||||
|
R.string.message_attachment_audio
|
||||||
|
}
|
||||||
|
VKVideo::class.java -> {
|
||||||
|
R.string.message_attachment_video
|
||||||
|
}
|
||||||
|
VKDoc::class.java -> {
|
||||||
|
R.string.message_attachment_doc
|
||||||
|
}
|
||||||
|
VKGraffiti::class.java -> {
|
||||||
|
R.string.message_attachment_graffiti
|
||||||
|
}
|
||||||
|
VKAudioMessage::class.java -> {
|
||||||
|
R.string.message_attachment_voice
|
||||||
|
}
|
||||||
|
VKSticker::class.java -> {
|
||||||
|
R.string.message_attachment_sticker
|
||||||
|
}
|
||||||
|
VKGift::class.java -> {
|
||||||
|
R.string.message_attachment_gift
|
||||||
|
}
|
||||||
|
VKLink::class.java -> {
|
||||||
|
R.string.message_attachment_link
|
||||||
|
}
|
||||||
|
VKPoll::class.java -> {
|
||||||
|
R.string.message_attachment_poll
|
||||||
|
}
|
||||||
|
VKCall::class.java -> {
|
||||||
|
R.string.message_attachment_call
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return context.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAttachmentDrawable(context: Context, attachments: List<VKModel>): Drawable? {
|
||||||
|
if (attachments.isEmpty() || attachments.size > 1) return null
|
||||||
|
|
||||||
|
var resId = -1
|
||||||
|
|
||||||
|
when (attachments[0].javaClass) {
|
||||||
|
VKPhoto::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_camera
|
||||||
|
}
|
||||||
|
VKAudio::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_audio
|
||||||
|
}
|
||||||
|
VKVideo::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_video
|
||||||
|
}
|
||||||
|
VKDoc::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_doc
|
||||||
|
}
|
||||||
|
VKGraffiti::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_graffiti
|
||||||
|
}
|
||||||
|
VKAudioMessage::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_audio_message
|
||||||
|
}
|
||||||
|
VKSticker::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_sticker
|
||||||
|
}
|
||||||
|
VKGift::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_gift
|
||||||
|
}
|
||||||
|
VKLink::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_link
|
||||||
|
}
|
||||||
|
VKPoll::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_poll
|
||||||
|
}
|
||||||
|
VKCall::class.java -> {
|
||||||
|
resId = R.drawable.ic_message_attachment_call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resId != -1) {
|
||||||
|
val drawable = context.drawable(resId)
|
||||||
|
|
||||||
|
drawable?.setTint(context.color(R.color.accent))
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFwdText(context: Context, forwardedMessages: List<VKMessage>): String {
|
||||||
|
return if (forwardedMessages.isNotEmpty()) {
|
||||||
|
if (forwardedMessages.size > 1) {
|
||||||
|
context.getString(R.string.message_fwd_many, forwardedMessages.size).lowerCase()
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.message_fwd_one)
|
||||||
|
}
|
||||||
|
} else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("need to rewrite")
|
||||||
|
fun getActionText(
|
||||||
|
context: Context,
|
||||||
|
lastMessage: VKMessage,
|
||||||
|
onResponseListener: OnResponseListener<String>
|
||||||
|
) {
|
||||||
|
TaskManager.execute {
|
||||||
|
lastMessage.action?.let {
|
||||||
|
var result = ""
|
||||||
|
|
||||||
|
when (it.type) {
|
||||||
|
VKMessageAction.ACTION_CHAT_CREATE -> result = context.getString(
|
||||||
|
R.string.message_action_created_chat,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_INVITE_USER -> result =
|
||||||
|
if (lastMessage.fromId == lastMessage.action!!.memberId) {
|
||||||
|
context.getString(R.string.message_action_returned_to_chat, "")
|
||||||
|
} else {
|
||||||
|
val invited = MemoryCache.getUserById(lastMessage.action!!.memberId)
|
||||||
|
context.getString(R.string.message_action_invited_user, invited)
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_INVITE_USER_BY_LINK -> result = context.getString(
|
||||||
|
R.string.message_action_invited_by_link,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_KICK_USER -> result =
|
||||||
|
if (lastMessage.fromId == lastMessage.action!!.memberId) {
|
||||||
|
context.getString(R.string.message_action_left_from_chat, "")
|
||||||
|
} else {
|
||||||
|
val kicked = MemoryCache.getUserById(lastMessage.action!!.memberId)
|
||||||
|
context.getString(R.string.message_action_kicked_user, kicked)
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_PHOTO_REMOVE -> result = context.getString(
|
||||||
|
R.string.message_action_removed_photo,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_PHOTO_UPDATE -> result = context.getString(
|
||||||
|
R.string.message_action_updated_photo,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_PIN_MESSAGE -> result = context.getString(
|
||||||
|
R.string.message_action_pinned_message,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_UNPIN_MESSAGE -> result = context.getString(
|
||||||
|
R.string.message_action_unpinned_message,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
VKMessageAction.ACTION_TITLE_UPDATE -> result = context.getString(
|
||||||
|
R.string.message_action_updated_title,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGlobal.post { onResponseListener.onResponse(result) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTime(context: Context, lastMessage: VKMessage): String {
|
||||||
|
val then = lastMessage.date * 1000L
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
val change = abs(now - then)
|
||||||
|
|
||||||
|
val seconds = change / 1000
|
||||||
|
|
||||||
|
if (seconds == 0L) {
|
||||||
|
return context.getString(R.string.time_format_now)
|
||||||
|
}
|
||||||
|
|
||||||
|
val minutes = seconds / 60
|
||||||
|
|
||||||
|
if (minutes == 0L) {
|
||||||
|
return context.getString(R.string.time_format_second, seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hours = minutes / 60
|
||||||
|
|
||||||
|
if (hours == 0L) {
|
||||||
|
return context.getString(R.string.time_format_minute, minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
val days = hours / 24
|
||||||
|
|
||||||
|
if (days == 0L) {
|
||||||
|
return context.getString(R.string.time_format_hour, hours)
|
||||||
|
}
|
||||||
|
|
||||||
|
val months = days / 30
|
||||||
|
|
||||||
|
if (months == 0L) {
|
||||||
|
return context.getString(R.string.time_format_day, days)
|
||||||
|
}
|
||||||
|
|
||||||
|
val years = months / 12
|
||||||
|
|
||||||
|
if (years == 0L) {
|
||||||
|
return context.getString(R.string.time_format_month, months)
|
||||||
|
} else if (years > 0L) {
|
||||||
|
return context.getString(R.string.time_format_year, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SimpleDateFormat("HH:mm", Locale.getDefault()).format(then)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseConversations(array: JSONArray): ArrayList<VKConversation> {
|
||||||
|
val conversations = arrayListOf<VKConversation>()
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
conversations.add(VKConversation(array.optJSONObject(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversations
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseMessages(array: JSONArray): ArrayList<VKMessage> {
|
||||||
|
val messages = arrayListOf<VKMessage>()
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
messages.add(VKMessage(array.optJSONObject(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isChatId(id: Int) = id > 2_000_000_000
|
||||||
|
|
||||||
|
fun isMessageHasFlag(mask: Int, flagName: String): Boolean {
|
||||||
|
val o: Any? = 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): VKMessage {
|
||||||
|
val message = 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.messageId = id
|
||||||
|
message.peerId = peerId
|
||||||
|
message.date = date
|
||||||
|
message.text = text
|
||||||
|
|
||||||
|
val fromId =
|
||||||
|
if (isMessageHasFlag(flags, "outbox")) UserConfig.userId
|
||||||
|
else peerId
|
||||||
|
|
||||||
|
message.fromId = fromId
|
||||||
|
|
||||||
|
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 = VKMessageAction().also { action ->
|
||||||
|
action.type = it.optString("source_act")
|
||||||
|
|
||||||
|
when (action.type) {
|
||||||
|
VKMessageAction.ACTION_CHAT_CREATE -> {
|
||||||
|
action.text = it.optString("source_text")
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_TITLE_UPDATE -> {
|
||||||
|
action.oldText = it.optString("source_old_text")
|
||||||
|
action.text = it.optString("source_text")
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_PIN_MESSAGE -> {
|
||||||
|
action.memberId = it.optInt("source_mid")
|
||||||
|
action.conversationMessageId = it.optInt("source_chat_local_id")
|
||||||
|
|
||||||
|
it.optJSONObject("source_message")?.let { message ->
|
||||||
|
action.message = VKMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_UNPIN_MESSAGE -> {
|
||||||
|
action.memberId = it.optInt("source_mid")
|
||||||
|
action.conversationMessageId = it.optInt("source_chat_local_id")
|
||||||
|
}
|
||||||
|
VKMessageAction.ACTION_INVITE_USER,
|
||||||
|
VKMessageAction.ACTION_KICK_USER,
|
||||||
|
VKMessageAction.ACTION_SCREENSHOT,
|
||||||
|
VKMessageAction.ACTION_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 == UserConfig.userId
|
||||||
|
message.isOut = out
|
||||||
|
|
||||||
|
if (message.isFromUser()) {
|
||||||
|
message.fromUser = MemoryCache.getUserById(fromId)
|
||||||
|
} else {
|
||||||
|
message.fromGroup = MemoryCache.getGroupById(abs(fromId))
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.melod1n.project.vkm.base
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
fun getRootView(): View {
|
||||||
|
return findViewById(android.R.id.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.extensions.ArrayExtensions.asArrayList
|
||||||
|
import ru.melod1n.project.vkm.listener.ItemClickListener
|
||||||
|
import ru.melod1n.project.vkm.listener.ItemLongClickListener
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
abstract class BaseAdapter<T, VH : BaseHolder>(
|
||||||
|
var context: Context,
|
||||||
|
var values: ArrayList<T> = arrayListOf()
|
||||||
|
) : RecyclerView.Adapter<VH>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val P_ITEMS = "BaseAdapter.values"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cleanValues: ArrayList<T>? = null
|
||||||
|
|
||||||
|
private var inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
var itemClickListener: ItemClickListener? = null
|
||||||
|
var itemLongClickListener: ItemLongClickListener? = null
|
||||||
|
|
||||||
|
open fun destroy() {}
|
||||||
|
|
||||||
|
open fun getItem(position: Int): T {
|
||||||
|
return values[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(position: Int, item: T) {
|
||||||
|
values.add(position, item)
|
||||||
|
cleanValues?.add(position, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(item: T) {
|
||||||
|
values.add(item)
|
||||||
|
cleanValues?.add(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(items: List<T>) {
|
||||||
|
values.addAll(items)
|
||||||
|
cleanValues?.addAll(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(position: Int, items: List<T>) {
|
||||||
|
values.addAll(position, items)
|
||||||
|
cleanValues?.addAll(position, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(position: Int, item: T) {
|
||||||
|
values[position] = item
|
||||||
|
cleanValues?.set(position, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun indexOf(item: T): Int {
|
||||||
|
return values.indexOf(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAt(index: Int) {
|
||||||
|
values.removeAt(index)
|
||||||
|
cleanValues?.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(item: T) {
|
||||||
|
values.remove(item)
|
||||||
|
cleanValues?.remove(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmpty() = values.isNullOrEmpty()
|
||||||
|
|
||||||
|
fun isNotEmpty() = !isEmpty()
|
||||||
|
|
||||||
|
fun view(resId: Int, viewGroup: ViewGroup): View {
|
||||||
|
return inflater.inflate(resId, viewGroup, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateValues(arrayList: ArrayList<T>) {
|
||||||
|
values.clear()
|
||||||
|
values.addAll(arrayList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateValues(list: List<T>) = 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<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: T, query: String): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(index: Int): T {
|
||||||
|
return values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.melod1n.project.vkm.base
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import ru.melod1n.project.vkm.activity.MainActivity
|
||||||
|
|
||||||
|
abstract class BaseFragment : Fragment() {
|
||||||
|
|
||||||
|
protected open fun initToolbar(@IdRes resId: Int) {
|
||||||
|
val toolbar: Toolbar = requireView().findViewById(resId)
|
||||||
|
|
||||||
|
activity?.let {
|
||||||
|
if (it is MainActivity && toolbar is ru.melod1n.project.vkm.widget.Toolbar) it.initToolbar(toolbar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun runOnUi(runnable: Runnable) {
|
||||||
|
activity?.runOnUiThread(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ru.melod1n.project.vkm.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
|
||||||
|
abstract class BaseFullscreenDialog : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setStyle(STYLE_NORMAL, R.style.AppTheme_FullScreenDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
|
||||||
|
dialog?.let { dialog ->
|
||||||
|
val width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
val height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
|
||||||
|
dialog.window?.let {
|
||||||
|
it.setLayout(width, height)
|
||||||
|
it.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
|
||||||
|
it.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
|
||||||
|
it.setWindowAnimations(R.style.AppTheme_Slide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.melod1n.project.vkm.base
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
abstract class BaseHolder(v: View) : RecyclerView.ViewHolder(v) {
|
||||||
|
abstract fun bind(position: Int)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Handler
|
||||||
|
|
||||||
|
object MvpBase {
|
||||||
|
|
||||||
|
lateinit var handler: Handler
|
||||||
|
|
||||||
|
fun init(application: Application) {
|
||||||
|
handler = Handler(application.mainLooper)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(appHandler: Handler) {
|
||||||
|
handler = appHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
fun post(runnable: Runnable) {
|
||||||
|
handler.post(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
object MvpConstants {
|
||||||
|
|
||||||
|
const val PEER_ID = "_peer_id"
|
||||||
|
const val ID = "_id"
|
||||||
|
const val COUNT = "_count"
|
||||||
|
const val OFFSET = "_offset"
|
||||||
|
const val FROM_CACHE = "_from_cache"
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
class MvpException(var errorId: String) : Exception(errorId)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
import androidx.collection.arrayMapOf
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class MvpFields {
|
||||||
|
private val fields = arrayMapOf<String, Any>()
|
||||||
|
|
||||||
|
fun put(key: String, value: Any): MvpFields {
|
||||||
|
fields[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun <T> get(key: String): T {
|
||||||
|
return fields[key] as T
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNonNull(key: String): Any {
|
||||||
|
return fields[key]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNonNull(`object`: Any): Any {
|
||||||
|
return Objects.requireNonNull(`object`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFields(): Map<String, Any> {
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
interface MvpOnLoadListener<T> {
|
||||||
|
|
||||||
|
fun onResponse(response: T)
|
||||||
|
|
||||||
|
fun onError(t: Throwable)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
abstract class MvpPresenter<MainItem, Repository : MvpRepository<MainItem>, V : MvpView>(
|
||||||
|
protected var viewState: V,
|
||||||
|
private val repositoryStringClassName: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
protected var context: Context? = null
|
||||||
|
|
||||||
|
protected fun requireContext(): Context {
|
||||||
|
if (context == null) throw IllegalStateException("context is null")
|
||||||
|
|
||||||
|
return context!!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ListState {
|
||||||
|
EMPTY, EMPTY_LOADING, EMPTY_NO_INTERNET, EMPTY_ERROR, FILLED, FILLED_LOADING
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var tag: String = ""
|
||||||
|
|
||||||
|
lateinit var repository: Repository
|
||||||
|
|
||||||
|
init {
|
||||||
|
initRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initRepository() {
|
||||||
|
val clazz = Class.forName(repositoryStringClassName)
|
||||||
|
|
||||||
|
this.repository = clazz.newInstance() as Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onCreate(context: Context, bundle: Bundle? = null) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onCreateView(bundle: Bundle? = null) {}
|
||||||
|
open fun onViewCreated(bundle: Bundle? = null) {}
|
||||||
|
|
||||||
|
protected fun post(runnable: Runnable) {
|
||||||
|
MvpBase.post(runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun destroy() {}
|
||||||
|
|
||||||
|
fun prepareViews() {
|
||||||
|
viewState.prepareNoItemsView()
|
||||||
|
viewState.prepareNoInternetView()
|
||||||
|
viewState.prepareErrorView()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setState(state: ListState) {
|
||||||
|
when (state) {
|
||||||
|
ListState.EMPTY -> {
|
||||||
|
viewState.hideRefreshLayout()
|
||||||
|
viewState.hideProgressBar()
|
||||||
|
viewState.showNoItemsView()
|
||||||
|
viewState.hideNoInternetView()
|
||||||
|
viewState.hideErrorView()
|
||||||
|
}
|
||||||
|
ListState.EMPTY_LOADING -> {
|
||||||
|
viewState.hideRefreshLayout()
|
||||||
|
viewState.showProgressBar()
|
||||||
|
viewState.hideNoItemsView()
|
||||||
|
viewState.hideNoInternetView()
|
||||||
|
viewState.hideErrorView()
|
||||||
|
}
|
||||||
|
ListState.EMPTY_NO_INTERNET -> {
|
||||||
|
viewState.hideRefreshLayout()
|
||||||
|
viewState.hideProgressBar()
|
||||||
|
viewState.hideNoItemsView()
|
||||||
|
viewState.showNoInternetView()
|
||||||
|
viewState.hideErrorView()
|
||||||
|
}
|
||||||
|
ListState.EMPTY_ERROR -> {
|
||||||
|
viewState.hideRefreshLayout()
|
||||||
|
viewState.hideProgressBar()
|
||||||
|
viewState.hideNoItemsView()
|
||||||
|
viewState.hideNoInternetView()
|
||||||
|
viewState.showErrorView()
|
||||||
|
}
|
||||||
|
ListState.FILLED -> {
|
||||||
|
viewState.hideRefreshLayout()
|
||||||
|
viewState.hideProgressBar()
|
||||||
|
viewState.hideNoItemsView()
|
||||||
|
viewState.hideNoInternetView()
|
||||||
|
viewState.hideErrorView()
|
||||||
|
}
|
||||||
|
ListState.FILLED_LOADING -> {
|
||||||
|
viewState.showRefreshLayout()
|
||||||
|
viewState.hideProgressBar()
|
||||||
|
viewState.hideNoItemsView()
|
||||||
|
viewState.hideNoInternetView()
|
||||||
|
viewState.hideErrorView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
abstract class MvpRepository<T> {
|
||||||
|
|
||||||
|
// abstract fun loadValues(fields: MvpFields, listener: MvpOnLoadListener<T>?)
|
||||||
|
//
|
||||||
|
// abstract fun loadCachedValues(fields: MvpFields, listener: MvpOnLoadListener<T>?)
|
||||||
|
//
|
||||||
|
// protected fun sendError(listener: MvpOnLoadListener<T>?, errorId: String) {
|
||||||
|
// listener?.onErrorLoad(MvpException(errorId))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
protected fun <Item> sendError(
|
||||||
|
listener: MvpOnLoadListener<Item>,
|
||||||
|
t: Throwable
|
||||||
|
) {
|
||||||
|
// if (listener !is MvpOnLoadListener) return
|
||||||
|
|
||||||
|
MvpBase.post { listener.onError(t) }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <Item> sendResponseArray(
|
||||||
|
listener: MvpOnLoadListener<ArrayList<Item>>,
|
||||||
|
response: ArrayList<Item>
|
||||||
|
) {
|
||||||
|
listener.let { MvpBase.handler.post { listener.onResponse(response) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <Item> sendResponse(
|
||||||
|
listener: MvpOnLoadListener<Item>,
|
||||||
|
response: Item
|
||||||
|
) {
|
||||||
|
listener.let {
|
||||||
|
MvpBase.handler.post { listener.onResponse(response) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected open fun cacheLoadedValues(values: ArrayList<T>) {}
|
||||||
|
//
|
||||||
|
// protected fun startNewThread(runnable: Runnable) {
|
||||||
|
// Thread(runnable).start()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected fun post(runnable: Runnable) {
|
||||||
|
// MvpBase.handler.post(runnable)
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ru.melod1n.project.vkm.base.mvp
|
||||||
|
|
||||||
|
interface MvpView {
|
||||||
|
|
||||||
|
fun showErrorSnackbar(t: Throwable)
|
||||||
|
|
||||||
|
fun prepareNoItemsView()
|
||||||
|
|
||||||
|
fun showNoItemsView()
|
||||||
|
|
||||||
|
fun hideNoItemsView()
|
||||||
|
|
||||||
|
fun prepareNoInternetView()
|
||||||
|
|
||||||
|
fun showNoInternetView()
|
||||||
|
|
||||||
|
fun hideNoInternetView()
|
||||||
|
|
||||||
|
fun prepareErrorView()
|
||||||
|
|
||||||
|
fun showErrorView()
|
||||||
|
|
||||||
|
fun hideErrorView()
|
||||||
|
|
||||||
|
fun showProgressBar()
|
||||||
|
|
||||||
|
fun hideProgressBar()
|
||||||
|
|
||||||
|
fun showRefreshLayout()
|
||||||
|
|
||||||
|
fun hideRefreshLayout()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package ru.melod1n.project.vkm.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.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 androidx.room.Room
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
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
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApi
|
||||||
|
import ru.melod1n.project.vkm.base.mvp.MvpBase
|
||||||
|
import ru.melod1n.project.vkm.database.AppDatabase
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentSettings
|
||||||
|
import ru.melod1n.project.vkm.util.AndroidUtils
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@SuppressLint("NonConstantResourceId")
|
||||||
|
@ReportsCrashes(
|
||||||
|
mailTo = "lischenkodev@gmail.com",
|
||||||
|
mode = ReportingInteractionMode.DIALOG,
|
||||||
|
resDialogTitle = R.string.app_has_been_crashed,
|
||||||
|
resDialogText = R.string.empty,
|
||||||
|
resDialogTheme = R.style.AppTheme_Dialog,
|
||||||
|
resDialogPositiveButtonText = R.string.send_crash_report,
|
||||||
|
resDialogNegativeButtonText = R.string.ok
|
||||||
|
)
|
||||||
|
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
|
||||||
|
lateinit var handler: Handler
|
||||||
|
lateinit var resources: Resources
|
||||||
|
lateinit var packageName: String
|
||||||
|
lateinit var database: AppDatabase
|
||||||
|
lateinit var instance: AppGlobal
|
||||||
|
|
||||||
|
lateinit var packageManager: PackageManager
|
||||||
|
|
||||||
|
var versionName = ""
|
||||||
|
var versionCode = 0L
|
||||||
|
|
||||||
|
var screenWidth = 0
|
||||||
|
var screenHeight = 0
|
||||||
|
|
||||||
|
fun post(runnable: Runnable) {
|
||||||
|
handler.post(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
instance = this
|
||||||
|
|
||||||
|
if (!BuildConfig.DEBUG) {
|
||||||
|
AppCenter.start(
|
||||||
|
this, APP_CENTER_TOKEN, Analytics::class.java, Crashes::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
ACRA.init(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
Fresco.initialize(this)
|
||||||
|
|
||||||
|
database = Room.databaseBuilder(this, AppDatabase::class.java, "cache")
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
handler = Handler(mainLooper)
|
||||||
|
locale = Locale.getDefault()
|
||||||
|
|
||||||
|
val info = packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES)
|
||||||
|
versionName = info.versionName
|
||||||
|
versionCode = PackageInfoCompat.getLongVersionCode(info)
|
||||||
|
|
||||||
|
Companion.resources = resources
|
||||||
|
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()
|
||||||
|
|
||||||
|
VKApi.init(this)
|
||||||
|
|
||||||
|
MvpBase.init(handler)
|
||||||
|
|
||||||
|
fillMemoryCache()
|
||||||
|
|
||||||
|
applyNightMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillMemoryCache() {
|
||||||
|
TaskManager.execute {
|
||||||
|
val users = database.users.getAll()
|
||||||
|
val groups = database.groups.getAll()
|
||||||
|
|
||||||
|
MemoryCache.appendUsers(users)
|
||||||
|
MemoryCache.appendGroups(groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyNightMode(value: String? = null) {
|
||||||
|
val mode = value ?: preferences.getString(FragmentSettings.KEY_THEME, "-1")!!
|
||||||
|
|
||||||
|
val nightMode = getNightMode(mode.toInt())
|
||||||
|
|
||||||
|
val oldNightMode = AppCompatDelegate.getDefaultNightMode()
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(nightMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNightMode(nightMode: Int = -1): Int {
|
||||||
|
val mode = if (nightMode != -1) nightMode else preferences.getString(
|
||||||
|
FragmentSettings.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package ru.melod1n.project.vkm.common
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentConversations
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentFriends
|
||||||
|
import ru.melod1n.project.vkm.fragment.FragmentImportant
|
||||||
|
|
||||||
|
object FragmentSwitcher {
|
||||||
|
|
||||||
|
fun getCurrentFragment(fragmentManager: FragmentManager): Fragment? {
|
||||||
|
val fragments = fragmentManager.fragments
|
||||||
|
|
||||||
|
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
|
||||||
|
|
||||||
|
for (fragment in fragments) {
|
||||||
|
if (fragment.isVisible) {
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFragments(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
containerId: Int,
|
||||||
|
fragments: Collection<Fragment>
|
||||||
|
) {
|
||||||
|
val transaction = fragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
for (fragment in fragments) {
|
||||||
|
transaction.add(containerId, fragment, fragment.javaClass.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showFragment(fragmentManager: FragmentManager, tag: String) {
|
||||||
|
showFragment(fragmentManager, tag, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showFragment(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
tag: String,
|
||||||
|
hideOthers: Boolean,
|
||||||
|
containerId: Int = R.id.fragmentContainer
|
||||||
|
) {
|
||||||
|
val fragments = fragmentManager.fragments
|
||||||
|
|
||||||
|
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
|
||||||
|
|
||||||
|
var fragmentToShow: Fragment? = null
|
||||||
|
|
||||||
|
for (fragment in fragments) {
|
||||||
|
if (fragment.tag != null && fragment.tag == tag) {
|
||||||
|
fragmentToShow = fragment
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val transaction = fragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
if (fragmentToShow == null) {
|
||||||
|
fragmentToShow = createFragmentByTag(tag)
|
||||||
|
transaction.add(containerId, fragmentToShow, tag)
|
||||||
|
} else {
|
||||||
|
transaction.show(fragmentToShow)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideOthers) {
|
||||||
|
for (fragment in fragments) {
|
||||||
|
if (fragment.tag != null && fragment.tag == tag) continue
|
||||||
|
transaction.hide(fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearFragments(fragmentManager: FragmentManager) {
|
||||||
|
val fragments = fragmentManager.fragments
|
||||||
|
|
||||||
|
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
|
||||||
|
|
||||||
|
val transaction = fragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
for (fragment in fragments) {
|
||||||
|
transaction.remove(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideFragments(fragmentManager: FragmentManager) {
|
||||||
|
val fragments = fragmentManager.fragments
|
||||||
|
|
||||||
|
if (fragments.isEmpty()) throw RuntimeException("FragmentManager's fragments is empty")
|
||||||
|
|
||||||
|
val transaction = fragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
for (fragment in fragments) {
|
||||||
|
transaction.hide(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFragmentByTag(tag: String): Fragment {
|
||||||
|
return when (tag) {
|
||||||
|
"FragmentFriends" -> FragmentFriends()
|
||||||
|
"FragmentImportant" -> FragmentImportant()
|
||||||
|
"FragmentConversations" -> FragmentConversations()
|
||||||
|
else -> Fragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
package ru.melod1n.project.vkm.common
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApi
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.api.method.MethodSetter
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKGroup
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.concurrent.LowThread
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
import ru.melod1n.project.vkm.listener.OnResponseListener
|
||||||
|
import java.util.*
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
object TaskManager {
|
||||||
|
|
||||||
|
private const val TAG = "TaskManager"
|
||||||
|
|
||||||
|
private val groupsTasksIds = arrayListOf<Int>()
|
||||||
|
|
||||||
|
private var groupsTimer: Timer? = null
|
||||||
|
|
||||||
|
private val usersTasksIds = arrayListOf<Int>()
|
||||||
|
|
||||||
|
private var usersTimer: Timer? = null
|
||||||
|
|
||||||
|
private val messagesTasksIds = arrayListOf<Int>()
|
||||||
|
|
||||||
|
private val messagesReadIds = arrayListOf<Int>()
|
||||||
|
|
||||||
|
private var messagesReadTimer: Timer? = null
|
||||||
|
|
||||||
|
private val conversationsTasksIds = arrayListOf<Int>()
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> addProcedure(
|
||||||
|
methodSetter: MethodSetter,
|
||||||
|
className: Class<T>,
|
||||||
|
pushInfo: EventInfo<*>?,
|
||||||
|
responseListener: OnResponseListener<T>?
|
||||||
|
) {
|
||||||
|
execute {
|
||||||
|
methodSetter.executeArray(className, object : OnResponseListener<ArrayList<T>> {
|
||||||
|
override fun onResponse(response: ArrayList<T>) {
|
||||||
|
if (response.isEmpty()) return
|
||||||
|
|
||||||
|
responseListener?.onResponse(response[0])
|
||||||
|
|
||||||
|
pushInfo?.let { sendEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendEvent(eventInfo: EventInfo<*>) {
|
||||||
|
AppGlobal.handler.post {
|
||||||
|
for (listener in listeners) {
|
||||||
|
listener?.onNewEvent(eventInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadUser(
|
||||||
|
eventKey: VKApiKeys,
|
||||||
|
userId: Int,
|
||||||
|
responseListener: OnResponseListener<VKUser>? = null
|
||||||
|
) {
|
||||||
|
if (usersTasksIds.contains(userId)) return
|
||||||
|
usersTasksIds.add(userId)
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.i(TAG, "Load user: $userId")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usersTimer != null) {
|
||||||
|
usersTimer?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
usersTimer = Timer()
|
||||||
|
usersTimer?.schedule(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
val setter = VKApi.users()
|
||||||
|
.get()
|
||||||
|
.userIds(usersTasksIds)
|
||||||
|
.fields(VKUser.DEFAULT_FIELDS)
|
||||||
|
|
||||||
|
val usersIds = arrayListOf<String>()
|
||||||
|
usersTasksIds.forEach { usersIds.add(it.toString()) }
|
||||||
|
|
||||||
|
addProcedure(
|
||||||
|
setter,
|
||||||
|
VKUser::class.java,
|
||||||
|
EventInfo(eventKey, usersTasksIds),
|
||||||
|
object : OnResponseListener<VKUser> {
|
||||||
|
override fun onResponse(response: VKUser) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Loaded users: ${
|
||||||
|
usersIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
|
usersTasksIds.remove(userId)
|
||||||
|
responseListener?.onResponse(response)
|
||||||
|
|
||||||
|
execute { MemoryCache.put(response) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Loaded users: ${
|
||||||
|
usersIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}\nStack:${Log.getStackTraceString(t)}"
|
||||||
|
)
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
usersTimer = null
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadGroup(
|
||||||
|
eventKey: VKApiKeys,
|
||||||
|
groupId: Int,
|
||||||
|
responseListener: OnResponseListener<VKGroup>? = null
|
||||||
|
) {
|
||||||
|
if (groupsTasksIds.contains(groupId)) return
|
||||||
|
groupsTasksIds.add(groupId)
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.i(TAG, "Load group: $groupId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val setter = VKApi.groups().getById()
|
||||||
|
.groupIds(groupsTasksIds)
|
||||||
|
.fields(VKGroup.DEFAULT_FIELDS)
|
||||||
|
|
||||||
|
if (groupsTimer != null) {
|
||||||
|
groupsTimer?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsTimer = Timer()
|
||||||
|
groupsTimer?.schedule(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
val groupsIds = arrayListOf<String>()
|
||||||
|
groupsTasksIds.forEach { groupsIds.add(it.toString()) }
|
||||||
|
|
||||||
|
addProcedure(
|
||||||
|
setter,
|
||||||
|
VKGroup::class.java,
|
||||||
|
EventInfo(eventKey, groupsTasksIds),
|
||||||
|
object : OnResponseListener<VKGroup> {
|
||||||
|
override fun onResponse(response: VKGroup) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Loaded groups: ${
|
||||||
|
groupsIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
|
groupsTasksIds.remove(groupId)
|
||||||
|
responseListener?.onResponse(response)
|
||||||
|
|
||||||
|
execute { MemoryCache.put(response) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Not loaded Group: ${
|
||||||
|
groupsIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}\nStack: " + Log.getStackTraceString(
|
||||||
|
t
|
||||||
|
)
|
||||||
|
)
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadMessage(
|
||||||
|
eventKey: VKApiKeys,
|
||||||
|
messageId: Int,
|
||||||
|
responseListener: OnResponseListener<VKMessage>? = null
|
||||||
|
) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.i(TAG, "Load message: $messageId")
|
||||||
|
}
|
||||||
|
if (messagesTasksIds.contains(messageId)) return
|
||||||
|
messagesTasksIds.add(messageId)
|
||||||
|
|
||||||
|
val setter = VKApi.messages().getById()
|
||||||
|
.messageIds(messageId)
|
||||||
|
.extended(true)
|
||||||
|
.filter(VKUser.DEFAULT_FIELDS + "," + VKGroup.DEFAULT_FIELDS)
|
||||||
|
|
||||||
|
addProcedure(
|
||||||
|
setter,
|
||||||
|
VKMessage::class.java,
|
||||||
|
EventInfo(eventKey, messageId),
|
||||||
|
object : OnResponseListener<VKMessage> {
|
||||||
|
override fun onResponse(response: VKMessage) {
|
||||||
|
Log.d(TAG, "Loaded message: $messageId")
|
||||||
|
|
||||||
|
messagesTasksIds.remove(messageId)
|
||||||
|
responseListener?.onResponse(response)
|
||||||
|
|
||||||
|
execute { MemoryCache.put(response) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Not loaded message: $messageId. Stack: " + Log.getStackTraceString(t)
|
||||||
|
)
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readMessage(
|
||||||
|
eventKey: VKApiKeys,
|
||||||
|
peerId: Int,
|
||||||
|
messageId: Int,
|
||||||
|
responseListener: OnResponseListener<Any?>? = null
|
||||||
|
) {
|
||||||
|
if (messagesReadIds.contains(messageId)) return
|
||||||
|
messagesReadIds.add(messageId)
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.i(TAG, "Read message: $messageId")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messagesReadTimer != null) {
|
||||||
|
messagesReadTimer?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesReadTimer = Timer()
|
||||||
|
messagesReadTimer?.schedule(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
val messagesIds = arrayListOf<String>()
|
||||||
|
messagesReadIds.forEach { messagesIds.add(it.toString()) }
|
||||||
|
|
||||||
|
val setter = VKApi.messages().markAsRead()
|
||||||
|
// .startMessageId(messageId)
|
||||||
|
.markConversationAsRead(true)
|
||||||
|
.peerId(peerId)
|
||||||
|
|
||||||
|
addProcedure(
|
||||||
|
setter,
|
||||||
|
Int::class.java,
|
||||||
|
EventInfo(eventKey, arrayOf(peerId, messageId)),
|
||||||
|
object : OnResponseListener<Int> {
|
||||||
|
override fun onResponse(response: Int) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Readed messages: ${
|
||||||
|
messagesIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
|
messagesReadIds.remove(messageId)
|
||||||
|
responseListener?.onResponse(response)
|
||||||
|
|
||||||
|
//TODO: update readed messages in cache
|
||||||
|
// execute { MemoryCache.put(response) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Not readed messages: ${
|
||||||
|
messagesIds.stream().collect(Collectors.joining(", "))
|
||||||
|
}\nStack: " + Log.getStackTraceString(
|
||||||
|
t
|
||||||
|
)
|
||||||
|
)
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadConversation(
|
||||||
|
eventKey: VKApiKeys,
|
||||||
|
conversationId: Int,
|
||||||
|
responseListener: OnResponseListener<VKConversation>? = null
|
||||||
|
) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.i(TAG, "Load conversation: $conversationId")
|
||||||
|
}
|
||||||
|
if (conversationsTasksIds.contains(conversationId)) return
|
||||||
|
conversationsTasksIds.add(conversationId)
|
||||||
|
|
||||||
|
val setter = VKApi.messages()
|
||||||
|
.getConversationsById()
|
||||||
|
.peerIds(conversationId)
|
||||||
|
.extended(true)
|
||||||
|
.fields(VKUser.DEFAULT_FIELDS + "," + VKGroup.DEFAULT_FIELDS)
|
||||||
|
|
||||||
|
addProcedure(
|
||||||
|
setter,
|
||||||
|
VKConversation::class.java,
|
||||||
|
EventInfo(eventKey, conversationId),
|
||||||
|
object : OnResponseListener<VKConversation> {
|
||||||
|
override fun onResponse(response: VKConversation) {
|
||||||
|
Log.d(TAG, "Loaded conversation: $conversationId")
|
||||||
|
|
||||||
|
conversationsTasksIds.remove(conversationId)
|
||||||
|
responseListener?.onResponse(response)
|
||||||
|
|
||||||
|
execute { MemoryCache.put(response) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Not loaded conversation: $conversationId. Stack: " + Log.getStackTraceString(
|
||||||
|
t
|
||||||
|
)
|
||||||
|
)
|
||||||
|
responseListener?.onError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnEventListener {
|
||||||
|
fun onNewEvent(info: EventInfo<*>)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package ru.melod1n.project.vkm.common
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import ru.melod1n.project.vkm.receiver.MinuteReceiver
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object TimeManager {
|
||||||
|
|
||||||
|
var currentHour = 0
|
||||||
|
var currentMinute = 0
|
||||||
|
var currentSecond = 0
|
||||||
|
|
||||||
|
private val onHourChangeListeners: ArrayList<OnHourChangeListener> = ArrayList()
|
||||||
|
private val onMinuteChangeListeners: ArrayList<OnMinuteChangeListener> = ArrayList()
|
||||||
|
private val onSecondChangeListeners: ArrayList<OnSecondChangeListener> = ArrayList()
|
||||||
|
private val onTimeChangeListeners: ArrayList<OnTimeChangeListener> = ArrayList()
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
context.registerReceiver(MinuteReceiver(), IntentFilter("android.intent.action.TIME_TICK"))
|
||||||
|
|
||||||
|
addOnMinuteChangeListener(minuteChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var minuteChangeListener = object : OnMinuteChangeListener {
|
||||||
|
override fun onMinuteChange(currentMinute: Int) {
|
||||||
|
TimeManager.currentMinute = currentMinute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
removeOnMinuteChangeListener(minuteChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun broadcastMinute() {
|
||||||
|
for (onMinuteChangeListener in onMinuteChangeListeners) {
|
||||||
|
onMinuteChangeListener.onMinuteChange(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isMorning = currentHour in 7..11
|
||||||
|
|
||||||
|
val isAfternoon = currentHour in 12..16
|
||||||
|
|
||||||
|
val isEvening = currentHour in 17..22
|
||||||
|
|
||||||
|
val isNight = currentHour == 23 || currentHour < 6 && currentHour > -1
|
||||||
|
|
||||||
|
fun addOnHourChangeListener(onHourChangeListeners: OnHourChangeListener) {
|
||||||
|
TimeManager.onHourChangeListeners.add(onHourChangeListeners)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnHourChangeListener(onHourChangeListener: OnHourChangeListener?) {
|
||||||
|
onHourChangeListeners.remove(onHourChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addOnMinuteChangeListener(onMinuteChangeListener: OnMinuteChangeListener) {
|
||||||
|
onMinuteChangeListeners.add(onMinuteChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnMinuteChangeListener(onMinuteChangeListener: OnMinuteChangeListener?) {
|
||||||
|
onMinuteChangeListeners.remove(onMinuteChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addOnSecondChangeListener(onSecondChangeListener: OnSecondChangeListener) {
|
||||||
|
onSecondChangeListeners.add(onSecondChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnSecondChangeListener(onSecondChangeListener: OnSecondChangeListener?) {
|
||||||
|
onSecondChangeListeners.remove(onSecondChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addOnTimeChangeListener(onTimeChangeListener: OnTimeChangeListener) {
|
||||||
|
onTimeChangeListeners.add(onTimeChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnTimeChangeListener(onTimeChangeListener: OnTimeChangeListener?) {
|
||||||
|
onTimeChangeListeners.remove(onTimeChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnHourChangeListener {
|
||||||
|
fun onHourChange(currentHour: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnMinuteChangeListener {
|
||||||
|
fun onMinuteChange(currentMinute: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnSecondChangeListener {
|
||||||
|
fun onSecondChange(currentSecond: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnTimeChangeListener {
|
||||||
|
fun onHourChange(currentHour: Int)
|
||||||
|
fun onMinuteChange(currentMinute: Int)
|
||||||
|
fun onSecondChange(currentSecond: Int)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package ru.melod1n.project.vkm.common
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.collection.arrayMapOf
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
import ru.melod1n.project.vkm.model.NewUpdateInfo
|
||||||
|
import ru.melod1n.project.vkm.net.HttpRequest
|
||||||
|
|
||||||
|
object UpdateManager {
|
||||||
|
|
||||||
|
interface OnUpdateListener {
|
||||||
|
fun onNewUpdate(updateInfo: NewUpdateInfo)
|
||||||
|
|
||||||
|
fun onNoUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val checkLink = "https://melodev.procsec.top/vkm/project_vkm_ota.json"
|
||||||
|
|
||||||
|
private const val PRODUCT_NAME = "project_vkm"
|
||||||
|
private const val BRANCH = "alpha"
|
||||||
|
private const val OFFSET = 0
|
||||||
|
|
||||||
|
private const val TAG = "UpdateManager"
|
||||||
|
|
||||||
|
fun checkUpdates(onUpdateListener: OnUpdateListener) {
|
||||||
|
TaskManager.execute {
|
||||||
|
val newLink = "https://temply.procsec.top/prop/deploy/api/method/getOTA"
|
||||||
|
|
||||||
|
val params = arrayMapOf<String, String>()
|
||||||
|
params["product"] = PRODUCT_NAME
|
||||||
|
params["branch"] = BRANCH
|
||||||
|
params["offset"] = OFFSET.toString()
|
||||||
|
params["code"] = AppGlobal.versionCode.toString()
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "Request started")
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest[newLink, params].asString().let {
|
||||||
|
AppGlobal.post {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "response: $it")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response: Any = if (it == "[]") JSONArray(it) else JSONObject(it)
|
||||||
|
|
||||||
|
val newUpdateInfo: NewUpdateInfo? =
|
||||||
|
if (response is JSONArray) null else NewUpdateInfo(response as JSONObject)
|
||||||
|
|
||||||
|
if (response is JSONArray || newUpdateInfo?.version?.isEmpty() == true || newUpdateInfo?.version == AppGlobal.versionName) {
|
||||||
|
onUpdateListener.onNoUpdates()
|
||||||
|
return@post
|
||||||
|
} else {
|
||||||
|
newUpdateInfo?.let { onUpdateListener.onNewUpdate(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpRequest[checkLink].asString().let {
|
||||||
|
// val response = JSONObject(it)
|
||||||
|
//
|
||||||
|
// val updateInfo = UpdateInfo(response)
|
||||||
|
//
|
||||||
|
// AppGlobal.handler.post {
|
||||||
|
// if (updateInfo.version.isEmpty() || updateInfo.version == AppGlobal.versionName) {
|
||||||
|
// onUpdateListener.onNoUpdates()
|
||||||
|
// return@post
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (AppGlobal.versionName != updateInfo.version) {
|
||||||
|
// onUpdateListener.onNewUpdate(updateInfo)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.melod1n.project.vkm.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,20 @@
|
|||||||
|
package ru.melod1n.project.vkm.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import ru.melod1n.project.vkm.api.model.*
|
||||||
|
import ru.melod1n.project.vkm.database.dao.*
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [VKConversation::class, VKMessage::class, VKUser::class, VKGroup::class, VKFriend::class],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract val conversations: ConversationsDao
|
||||||
|
abstract val messages: MessagesDao
|
||||||
|
abstract val users: UsersDao
|
||||||
|
abstract val groups: GroupsDao
|
||||||
|
abstract val friends: FriendsDao
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package ru.melod1n.project.vkm.database
|
||||||
|
|
||||||
|
import android.util.SparseArray
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import ru.melod1n.project.vkm.api.model.*
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
|
||||||
|
object MemoryCache {
|
||||||
|
|
||||||
|
private val users = SparseArray<VKUser>()
|
||||||
|
private val groups = SparseArray<VKGroup>()
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getUserById(id: Int): VKUser? {
|
||||||
|
var user = users[id]
|
||||||
|
if (user == null) {
|
||||||
|
user = AppGlobal.database.users.getById(id)
|
||||||
|
|
||||||
|
user?.let { append(it) }
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getGroupById(positiveId: Int): VKGroup? {
|
||||||
|
var group = groups[positiveId]
|
||||||
|
if (group == null) {
|
||||||
|
group = AppGlobal.database.groups.getById(positiveId)
|
||||||
|
|
||||||
|
group?.let { append(it) }
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getMessageById(id: Int): VKMessage? {
|
||||||
|
return AppGlobal.database.messages.getById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getMessagesByPeerId(peerId: Int): List<VKMessage> {
|
||||||
|
return AppGlobal.database.messages.getByPeerId(peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getMessages(): List<VKMessage> {
|
||||||
|
return AppGlobal.database.messages.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getConversationById(id: Int): VKConversation? {
|
||||||
|
return AppGlobal.database.conversations.getById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getConversations(): List<VKConversation> {
|
||||||
|
return AppGlobal.database.conversations.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun getFriends(userId: Int): List<VKFriend> {
|
||||||
|
return AppGlobal.database.friends.getByUserId(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun appendUsers(users: Collection<VKUser>) {
|
||||||
|
for (user in users) {
|
||||||
|
append(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun appendGroups(groups: Collection<VKGroup>) {
|
||||||
|
for (group in groups) {
|
||||||
|
append(group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(value: VKGroup) {
|
||||||
|
groups.append(value.groupId, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(value: VKUser) {
|
||||||
|
users.append(value.userId, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun put(value: VKUser) {
|
||||||
|
append(value)
|
||||||
|
|
||||||
|
AppGlobal.database.users.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun putUsers(users: List<VKUser>) {
|
||||||
|
appendUsers(users)
|
||||||
|
|
||||||
|
AppGlobal.database.users.insert(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun put(value: VKFriend) {
|
||||||
|
AppGlobal.database.friends.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun putFriends(friends: List<VKFriend>) {
|
||||||
|
AppGlobal.database.friends.insert(friends)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun put(value: VKMessage) {
|
||||||
|
AppGlobal.database.messages.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun putMessages(messages: List<VKMessage>) {
|
||||||
|
AppGlobal.database.messages.insert(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun put(value: VKGroup) {
|
||||||
|
append(value)
|
||||||
|
|
||||||
|
AppGlobal.database.groups.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun putGroups(groups: List<VKGroup>) {
|
||||||
|
appendGroups(groups)
|
||||||
|
|
||||||
|
AppGlobal.database.groups.insert(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun put(value: VKConversation) {
|
||||||
|
AppGlobal.database.conversations.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun putConversations(conversations: List<VKConversation>) {
|
||||||
|
AppGlobal.database.conversations.insert(conversations)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun deleteMessage(messageId: Int, safe: Boolean = true) {
|
||||||
|
if (safe) {
|
||||||
|
AppGlobal.database.messages.getById(messageId) ?: return
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGlobal.database.messages.deleteById(messageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun deleteConversation(conversationId: Int, safe: Boolean = true) {
|
||||||
|
if (safe) {
|
||||||
|
AppGlobal.database.conversations.getById(conversationId) ?: return
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGlobal.database.conversations.deleteById(conversationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun edit(message: VKMessage?, safe: Boolean = true) {
|
||||||
|
message ?: return
|
||||||
|
|
||||||
|
if (safe) {
|
||||||
|
AppGlobal.database.messages.getById(message.messageId) ?: return
|
||||||
|
}
|
||||||
|
|
||||||
|
AppGlobal.database.messages.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
users.clear()
|
||||||
|
groups.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ConversationsDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM conversations")
|
||||||
|
fun getAll(): List<VKConversation>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM conversations WHERE conversationId = :id")
|
||||||
|
fun getById(id: Int): VKConversation?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(item: VKConversation)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(items: List<VKConversation>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(item: VKConversation)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(items: List<VKConversation>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: VKConversation)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(items: List<VKConversation>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM conversations WHERE conversationId = :id")
|
||||||
|
fun deleteById(id: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM conversations")
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKFriend
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface FriendsDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM friends")
|
||||||
|
fun getAll(): List<VKFriend>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM friends WHERE userId = :id")
|
||||||
|
fun getById(id: Int): VKFriend?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(item: VKFriend)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(items: List<VKFriend>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(item: VKFriend)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(items: List<VKFriend>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: VKFriend)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(items: List<VKFriend>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM friends WHERE userId = :id")
|
||||||
|
fun deleteById(id: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM friends")
|
||||||
|
fun clear()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM friends WHERE userId = :id")
|
||||||
|
fun getByUserId(id: Int): List<VKFriend>
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKGroup
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface GroupsDao {
|
||||||
|
@Query("SELECT * FROM groups")
|
||||||
|
fun getAll(): List<VKGroup>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM groups WHERE groupId = :id")
|
||||||
|
fun getById(id: Int): VKGroup?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(item: VKGroup)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(items: List<VKGroup>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(item: VKGroup)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(items: List<VKGroup>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: VKGroup)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(items: List<VKGroup>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM groups WHERE groupId = :id")
|
||||||
|
fun deleteById(id: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM groups")
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface MessagesDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM messages")
|
||||||
|
fun getAll(): List<VKMessage>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM messages WHERE messageId = :id")
|
||||||
|
fun getById(id: Int): VKMessage?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(item: VKMessage)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(items: List<VKMessage>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(item: VKMessage)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(items: List<VKMessage>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: VKMessage)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(items: List<VKMessage>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM messages WHERE messageId = :id")
|
||||||
|
fun deleteById(id: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM messages")
|
||||||
|
fun clear()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM messages WHERE peerId = :peerId")
|
||||||
|
fun getByPeerId(peerId: Int): List<VKMessage>
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UsersDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM users")
|
||||||
|
fun getAll(): List<VKUser>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM users WHERE userId = :id")
|
||||||
|
fun getById(id: Int): VKUser?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(item: VKUser)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(items: List<VKUser>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(item: VKUser)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(items: List<VKUser>)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: VKUser)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(items: List<VKUser>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users WHERE userId = :id")
|
||||||
|
fun deleteById(id: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM users")
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao.converters
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKModel
|
||||||
|
import ru.melod1n.project.vkm.extensions.ArrayExtensions.isNullOrEmpty
|
||||||
|
import ru.melod1n.project.vkm.util.Utils
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class ArrayListToByteArrayConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toForwarded(data: ByteArray?): ArrayList<VKModel> {
|
||||||
|
return if (data.isNullOrEmpty()) arrayListOf() else {
|
||||||
|
val deserializedData = Utils.deserialize(data)
|
||||||
|
if (deserializedData == null) arrayListOf() else deserializedData as ArrayList<VKModel>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromForwarded(forwarded: List<VKModel>): ByteArray {
|
||||||
|
return Utils.serialize(forwarded) ?: return byteArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.melod1n.project.vkm.database.dao.converters
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKMessage
|
||||||
|
import ru.melod1n.project.vkm.extensions.ArrayExtensions.isNullOrEmpty
|
||||||
|
import ru.melod1n.project.vkm.util.Utils
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class ForwardedConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toForwarded(data: ByteArray?): ArrayList<VKMessage> {
|
||||||
|
return if (data.isNullOrEmpty()) arrayListOf() else {
|
||||||
|
val deserializedData = Utils.deserialize(data)
|
||||||
|
if (deserializedData == null) arrayListOf() else deserializedData as ArrayList<VKMessage>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromForwarded(forwarded: List<VKMessage>): ByteArray {
|
||||||
|
return Utils.serialize(forwarded) ?: return byteArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.activity.SettingsActivity
|
||||||
|
import ru.melod1n.project.vkm.adapter.SimpleItemAdapter
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.base.BaseFullscreenDialog
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.color
|
||||||
|
import ru.melod1n.project.vkm.extensions.ContextExtensions.drawable
|
||||||
|
import ru.melod1n.project.vkm.extensions.DrawableExtensions.tint
|
||||||
|
import ru.melod1n.project.vkm.extensions.FragmentExtensions.findViewById
|
||||||
|
import ru.melod1n.project.vkm.item.SimpleMenuItem
|
||||||
|
import ru.melod1n.project.vkm.listener.ItemClickListener
|
||||||
|
import ru.melod1n.project.vkm.util.ViewUtils
|
||||||
|
import ru.melod1n.project.vkm.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 = findViewById(R.id.toolbar)
|
||||||
|
recyclerView = findViewById(R.id.recyclerView)
|
||||||
|
refreshLayout = findViewById(R.id.refreshLayout)
|
||||||
|
headerRoot = findViewById(R.id.headerRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareToolbar() {
|
||||||
|
toolbar.navigationIcon = requireContext().drawable(R.drawable.ic_close)
|
||||||
|
.tint(requireContext().color(R.color.accent))
|
||||||
|
|
||||||
|
|
||||||
|
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(requireContext().color(R.color.accent)),
|
||||||
|
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(), SettingsActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(position: Int) {
|
||||||
|
val item = adapter.getItem(position)
|
||||||
|
|
||||||
|
item.clickListener?.let {
|
||||||
|
it.onClick(requireView().findViewById(android.R.id.content))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.adapter.SimpleItemAdapter
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKConversation
|
||||||
|
import ru.melod1n.project.vkm.api.model.VKUser
|
||||||
|
import ru.melod1n.project.vkm.database.MemoryCache
|
||||||
|
import ru.melod1n.project.vkm.item.SimpleMenuItem
|
||||||
|
|
||||||
|
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,5 @@
|
|||||||
|
package ru.melod1n.project.vkm.event
|
||||||
|
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
|
||||||
|
class EventInfo<T> constructor(var key: VKApiKeys, var data: T? = null)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
object ArrayExtensions {
|
||||||
|
|
||||||
|
fun ByteArray?.isNullOrEmpty() = this == null || this.isEmpty()
|
||||||
|
|
||||||
|
fun <E> List<E>.asArrayList(): ArrayList<E> {
|
||||||
|
return ArrayList(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package ru.melod1n.project.vkm.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,16 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
|
object DrawableExtensions {
|
||||||
|
|
||||||
|
fun Drawable?.tint(@ColorInt color: Int): Drawable? {
|
||||||
|
this?.setTint(color)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
object FloatExtensions {
|
||||||
|
|
||||||
|
fun Float.int(): Int {
|
||||||
|
return roundToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
object FragmentExtensions {
|
||||||
|
|
||||||
|
fun <T : View> Fragment.findViewById(resId: Int): T {
|
||||||
|
return requireView().findViewById(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.runOnUiThread(runnable: Runnable) {
|
||||||
|
requireActivity().runOnUiThread(runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.squareup.picasso.Callback
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import ru.melod1n.project.vkm.BuildConfig
|
||||||
|
|
||||||
|
object ImageViewExtensions {
|
||||||
|
|
||||||
|
fun ImageView.loadImage(
|
||||||
|
sourceUrl: String,
|
||||||
|
placeholder: Drawable? = null,
|
||||||
|
callback: Callback? = null
|
||||||
|
) {
|
||||||
|
if (sourceUrl.trim().isEmpty()) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d("ImageView", "sourceUrl is empty")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = Picasso.get().load(sourceUrl)
|
||||||
|
|
||||||
|
placeholder?.let { builder.placeholder(it) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
builder.into(this, object : Callback {
|
||||||
|
override fun onSuccess() {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d("ImageView", "loaded photo from $sourceUrl")
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.onSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Exception?) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d("ImageView", "error loading photo from $sourceUrl")
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.onError(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d("ImageView", "Error loading photo from $sourceUrl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object StringExtensions {
|
||||||
|
|
||||||
|
fun String.lowerCase(): String {
|
||||||
|
return toLowerCase(Locale.getDefault())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.melod1n.project.vkm.extensions
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
|
object TextViewExtensions {
|
||||||
|
|
||||||
|
fun TextView.clear() {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextInputLayout.clear() {
|
||||||
|
editText?.setText("")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package ru.melod1n.project.vkm.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 ru.melod1n.project.vkm.R
|
||||||
|
import ru.melod1n.project.vkm.activity.MessagesActivity
|
||||||
|
import ru.melod1n.project.vkm.api.UserConfig
|
||||||
|
import ru.melod1n.project.vkm.api.VKApiKeys
|
||||||
|
import ru.melod1n.project.vkm.base.BaseFragment
|
||||||
|
import ru.melod1n.project.vkm.common.AppGlobal
|
||||||
|
import ru.melod1n.project.vkm.common.TaskManager
|
||||||
|
import ru.melod1n.project.vkm.event.EventInfo
|
||||||
|
import ru.melod1n.project.vkm.extensions.FragmentExtensions.findViewById
|
||||||
|
import ru.melod1n.project.vkm.fragment.ui.presenter.ConversationsPresenter
|
||||||
|
import ru.melod1n.project.vkm.fragment.ui.view.ConversationsView
|
||||||
|
import ru.melod1n.project.vkm.util.ViewUtils
|
||||||
|
import ru.melod1n.project.vkm.widget.Toolbar
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class FragmentConversations : BaseFragment(), ConversationsView {
|
||||||
|
|
||||||
|
private lateinit var presenter: ConversationsPresenter
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
presenter = ConversationsPresenter(this)
|
||||||
|
presenter.setup(recyclerView, refreshLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
toolbar = findViewById(R.id.toolbar)
|
||||||
|
recyclerView = findViewById(R.id.recyclerView)
|
||||||
|
refreshLayout = findViewById(R.id.refreshLayout)
|
||||||
|
progressBar = findViewById(R.id.progressBar)
|
||||||
|
|
||||||
|
noItemsView = findViewById(R.id.noItemsView)
|
||||||
|
noInternetView = findViewById(R.id.noInternetView)
|
||||||
|
errorView = findViewById(R.id.errorView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareToolbar() {
|
||||||
|
initToolbar(R.id.toolbar)
|
||||||
|
toolbar.title = getString(R.string.navigation_conversations)
|
||||||
|
setProfileAvatar()
|
||||||
|
|
||||||
|
TaskManager.addOnEventListener(object : TaskManager.OnEventListener {
|
||||||
|
override fun onNewEvent(info: EventInfo<*>) {
|
||||||
|
if (info.key == VKApiKeys.UPDATE_USER) {
|
||||||
|
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(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
requireContext(),
|
||||||
|
R.color.divider
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
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()) {
|
||||||
|
runOnUi {
|
||||||
|
toolbar.getAvatar().setImageURI(it.photo100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openChat(extras: Bundle) {
|
||||||
|
startActivity(Intent(requireContext(), MessagesActivity::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
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user