Dark theme

This commit is contained in:
2021-02-20 23:21:25 +03:00
parent 88dddcb133
commit 06aa41cab1
179 changed files with 1011 additions and 1133 deletions
@@ -0,0 +1,181 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.fast.R
import com.meloda.fast.activity.MessagesActivity
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.VKApiKeys
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.common.TaskManager
import com.meloda.fast.event.EventInfo
import com.meloda.fast.extensions.FragmentExtensions.findViewById
import com.meloda.fast.extensions.FragmentExtensions.runOnUiThread
import com.meloda.fast.fragment.ui.presenter.ConversationsPresenter
import com.meloda.fast.fragment.ui.view.ConversationsView
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.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(AndroidUtils.getThemeAttrColor(requireContext(), R.attr.dividerHorizontal))
)
recyclerView.itemAnimator = null
recyclerView.addItemDecoration(decoration)
recyclerView.layoutManager = manager
}
private fun setProfileAvatar() {
TaskManager.execute {
AppGlobal.database.users.getById(UserConfig.userId)?.let {
if (it.photo100.isNotEmpty()) {
runOnUiThread {
toolbar.getAvatar().setImageURI(it.photo100)
}
}
}
}
}
override fun openChat(extras: Bundle) {
startActivity(Intent(requireContext(), 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
}
}
@@ -0,0 +1,191 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.fast.R
import com.meloda.fast.activity.MessagesActivity
import com.meloda.fast.api.UserConfig
import com.meloda.fast.api.VKApiKeys
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.common.TaskManager
import com.meloda.fast.event.EventInfo
import com.meloda.fast.extensions.FragmentExtensions.findViewById
import com.meloda.fast.fragment.ui.presenter.FriendsPresenter
import com.meloda.fast.fragment.ui.view.FriendsView
import com.meloda.fast.util.ViewUtils
import com.meloda.fast.widget.Toolbar
class FragmentFriends(private val userId: Int = 0) : BaseFragment(), FriendsView {
private lateinit var presenter: FriendsPresenter
private lateinit var toolbar: Toolbar
private lateinit var recyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var progressBar: ProgressBar
private lateinit var noItemsView: LinearLayout
private lateinit var noInternetView: LinearLayout
private lateinit var errorView: LinearLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_friends, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initViews()
prepareToolbar()
prepareRecyclerView()
prepareRefreshLayout()
presenter = FriendsPresenter(this)
presenter.setup(userId, 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_friends)
setProfileAvatar()
toolbar.inflateMenu(R.menu.fragment_friends)
TaskManager.addOnEventListener(object : TaskManager.OnEventListener {
override fun onNewEvent(info: EventInfo<*>) {
if (info.key == VKApiKeys.UPDATE_USER) {
val userId = info.data as ArrayList<Int>
if (userId[0] == UserConfig.userId) {
setProfileAvatar()
}
}
}
})
}
private fun setProfileAvatar() {
TaskManager.execute {
AppGlobal.database.users.getById(UserConfig.userId)?.let {
if (it.photo100.isNotEmpty()) {
runOnUi {
toolbar.getAvatar().setImageURI(it.photo100)
}
}
}
}
}
override fun onDetach() {
presenter.destroy()
super.onDetach()
}
private fun prepareRefreshLayout() {
refreshLayout.setColorSchemeResources(R.color.accent)
}
private fun prepareRecyclerView() {
val manager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
val decoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
decoration.setDrawable(
ColorDrawable(
ContextCompat.getColor(
requireContext(),
R.color.divider
)
)
)
recyclerView.addItemDecoration(decoration)
recyclerView.layoutManager = manager
}
override fun openChat(extras: Bundle) {
startActivity(Intent(requireContext(), 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
}
}
@@ -0,0 +1,21 @@
package com.meloda.fast.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
class FragmentImportant : BaseFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_important, container, false)
}
}
@@ -0,0 +1,8 @@
package com.meloda.fast.fragment
import com.meloda.fast.base.BaseFragment
class FragmentSearch : BaseFragment() {
inner class SearchConversations : BaseFragment()
inner class SearchMessages : BaseFragment()
}
@@ -0,0 +1,218 @@
package com.meloda.fast.fragment
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import com.meloda.fast.R
import com.meloda.fast.activity.DropUserDataActivity
import com.meloda.fast.activity.UpdateActivity
import com.meloda.fast.base.BaseActivity
import com.meloda.fast.common.AppGlobal
import com.meloda.fast.common.TaskManager
import com.meloda.fast.extensions.ContextExtensions.color
import com.meloda.fast.extensions.ContextExtensions.drawable
import com.meloda.fast.util.AndroidUtils
class FragmentSettings : PreferenceFragmentCompat(),
Preference.OnPreferenceClickListener,
Preference.OnPreferenceChangeListener {
companion object {
const val CATEGORY_GENERAL = "general"
const val KEY_HIDE_KEYBOARD_ON_SCROLL_UP = "hide_keyboard_on_scroll_up"
const val CATEGORY_APPEARANCE = "appearance"
const val KEY_EXTENDED_CONVERSATIONS = "appearance_extended_conversations"
const val KEY_THEME = "appearance_theme"
const val CATEGORY_ABOUT = "about"
const val KEY_APP_VERSION = "app_version"
const val CATEGORY_ACCOUNT = "account"
const val KEY_ACCOUNT_LOGOUT = "account_logout"
const val CATEGORY_DEBUG = "debug"
const val KEY_CLEAR_USERS_GROUPS_CACHE = "clear_users_groups_cache"
}
private var currentPreferenceLayout = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.fragment_settings, rootKey)
currentPreferenceLayout = R.xml.fragment_settings
init()
}
private fun init() {
setTitle()
setNavigationIcon()
setPreferencesFromResource(currentPreferenceLayout, null)
val general = findPreference<Preference>(CATEGORY_GENERAL)
general?.onPreferenceClickListener = rootLayoutClickListener
val account = findPreference<Preference>(CATEGORY_ACCOUNT)
account?.onPreferenceClickListener = rootLayoutClickListener
val logout = findPreference<Preference>(KEY_ACCOUNT_LOGOUT)
logout?.onPreferenceClickListener = this
val about = findPreference<Preference>(CATEGORY_ABOUT)
about?.onPreferenceClickListener = rootLayoutClickListener
val appVersion = findPreference<Preference>(KEY_APP_VERSION)
appVersion?.onPreferenceClickListener = this
val appearance = findPreference<Preference>(CATEGORY_APPEARANCE)
appearance?.onPreferenceClickListener = rootLayoutClickListener
val extendedConversations = findPreference<Preference>(KEY_EXTENDED_CONVERSATIONS)
extendedConversations?.onPreferenceChangeListener = this
val theme = findPreference<Preference>(KEY_THEME)
theme?.onPreferenceChangeListener = this
val debug = findPreference<Preference>(CATEGORY_DEBUG)
debug?.onPreferenceClickListener = rootLayoutClickListener
updateDebugCategoryVisibility()
val clearUsersGroupsCache = findPreference<Preference>(KEY_CLEAR_USERS_GROUPS_CACHE)
clearUsersGroupsCache?.onPreferenceClickListener = this
applyTintInPreferenceScreen(preferenceScreen)
}
override fun onResume() {
super.onResume()
updateDebugCategoryVisibility()
}
private fun updateDebugCategoryVisibility() {
findPreference<Preference>(CATEGORY_DEBUG)?.isVisible =
AndroidUtils.isDeveloperSettingsEnabled(requireContext())
}
private val rootLayoutClickListener =
Preference.OnPreferenceClickListener { changeRootLayout(it) }
private fun setNavigationIcon() {
val drawable =
if (currentPreferenceLayout == R.xml.fragment_settings) null
else requireContext().drawable(R.drawable.ic_arrow_back)
drawable?.setTint(requireContext().color(R.color.accent))
}
private fun setTitle() {
var title = R.string.navigation_settings
when (currentPreferenceLayout) {
R.xml.fragment_settings_general -> title = R.string.prefs_general
R.xml.fragment_settings_appearance -> title = R.string.prefs_appearance
R.xml.fragment_settings_about -> title = R.string.prefs_about
R.xml.fragment_settings_account -> title = R.string.prefs_account
}
requireActivity().setTitle(title)
}
private fun changeRootLayout(preference: Preference): Boolean {
currentPreferenceLayout = when (preference.key) {
CATEGORY_GENERAL -> R.xml.fragment_settings_general
CATEGORY_ABOUT -> R.xml.fragment_settings_about
CATEGORY_ACCOUNT -> R.xml.fragment_settings_account
CATEGORY_APPEARANCE -> R.xml.fragment_settings_appearance
CATEGORY_DEBUG -> R.xml.fragment_settings_debug
else -> R.xml.fragment_settings
}
init()
return true
}
private fun applyTintInPreferenceScreen(rootScreen: PreferenceScreen) {
if (rootScreen.preferenceCount > 0) {
for (i in 0 until rootScreen.preferenceCount) {
val preference = rootScreen.getPreference(i)
tintPreference(preference)
}
}
}
private fun tintPreference(preference: Preference) {
if (preference.icon != null && context != null) {
preference.icon.setTint(requireContext().color(R.color.accent))
}
}
override fun onPreferenceClick(preference: Preference): Boolean {
when (preference.key) {
KEY_ACCOUNT_LOGOUT -> {
logout()
return true
}
KEY_APP_VERSION -> {
openUpdateScreen()
return true
}
KEY_CLEAR_USERS_GROUPS_CACHE -> {
showClearCacheConfirmation()
}
}
return false
}
private fun showClearCacheConfirmation() {
val builder = AlertDialog.Builder(requireContext())
builder.setMessage("Clear cache?")
builder.setPositiveButton("Yes") { _, _ ->
TaskManager.execute {
AppGlobal.database.users.clear()
AppGlobal.database.groups.clear()
}
}
builder.setNegativeButton("No", null)
builder.show()
}
private fun openUpdateScreen() {
startActivity(Intent(requireContext(), UpdateActivity::class.java))
}
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
when (preference.key) {
KEY_EXTENDED_CONVERSATIONS -> {
return true
}
KEY_THEME -> {
AppGlobal.instance.applyNightMode(newValue as String)
(requireActivity() as BaseActivity).apply {
// applyNightMode()
finish()
startActivity(intent)
// recreate()
}
return true
}
}
return false
}
fun onBackPressed() = if (currentPreferenceLayout == R.xml.fragment_settings) {
true
} else {
currentPreferenceLayout = R.xml.fragment_settings
init()
false
}
private fun logout() {
startActivity(Intent(requireContext(), DropUserDataActivity::class.java))
}
}
@@ -0,0 +1,157 @@
package com.meloda.fast.fragment
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import com.google.android.material.button.MaterialButton
import com.meloda.fast.R
import com.meloda.fast.base.BaseFragment
import com.meloda.fast.fragment.ui.presenter.LoginPresenter
import com.meloda.fast.fragment.ui.view.LoginView
import com.meloda.fast.util.KeyboardUtils
class LoginFragment : BaseFragment(), LoginView {
private lateinit var presenter: LoginPresenter
private lateinit var email: EditText
private lateinit var password: EditText
private lateinit var authorize: MaterialButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = LoginPresenter(this)
presenter.onCreate(requireContext(), this, savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
presenter.onCreateView(savedInstanceState)
return inflater.inflate(R.layout.fragment_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
presenter.onViewCreated(savedInstanceState)
}
override fun initViews() {
email = requireView().findViewById(R.id.loginEmailEditText)
password = requireView().findViewById(R.id.loginPasswordEditText)
authorize = requireView().findViewById(R.id.loginAuthorize)
}
override fun prepareViews() {
prepareEmailEditText()
preparePasswordEditText()
prepareAuthorizeButton()
}
private fun prepareEmailEditText() {
email.addTextChangedListener(onTextChangedListener)
}
private fun preparePasswordEditText() {
password.addTextChangedListener(onTextChangedListener)
password.setOnEditorActionListener { _, _, event ->
if (event == null) return@setOnEditorActionListener false
return@setOnEditorActionListener if (event.action == EditorInfo.IME_ACTION_DONE ||
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
) {
KeyboardUtils.hideKeyboardFrom(password)
authorize.performClick()
true
} else false
}
}
private fun prepareAuthorizeButton() {
authorize.isEnabled = false
authorize.setOnClickListener {
val emailString = email.text.toString().trim()
val passwordString = password.text.toString().trim()
presenter.login(emailString, passwordString)
}
}
override fun showErrorSnackbar(t: Throwable) {
TODO("Not yet implemented")
}
override fun prepareNoItemsView() {
TODO("Not yet implemented")
}
override fun showNoItemsView() {
TODO("Not yet implemented")
}
override fun hideNoItemsView() {
TODO("Not yet implemented")
}
override fun prepareNoInternetView() {
TODO("Not yet implemented")
}
override fun showNoInternetView() {
TODO("Not yet implemented")
}
override fun hideNoInternetView() {
TODO("Not yet implemented")
}
override fun prepareErrorView() {
TODO("Not yet implemented")
}
override fun showErrorView() {
TODO("Not yet implemented")
}
override fun hideErrorView() {
TODO("Not yet implemented")
}
override fun showProgressBar() {
TODO("Not yet implemented")
}
override fun hideProgressBar() {
TODO("Not yet implemented")
}
override fun showRefreshLayout() {
TODO("Not yet implemented")
}
override fun hideRefreshLayout() {
TODO("Not yet implemented")
}
private val onTextChangedListener = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
authorize.isEnabled =
email.text.toString().trim().isNotEmpty() &&
password.text.toString().trim().isNotEmpty()
}
override fun afterTextChanged(s: Editable?) {
}
}
}
@@ -0,0 +1,88 @@
package com.meloda.fast.fragment
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.os.bundleOf
import com.meloda.fast.api.VKAuth
import com.meloda.fast.base.BaseFragment
class ValidationFragment : BaseFragment() {
private var url: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null && requireArguments().isEmpty.not()) {
url = requireArguments().getString("url") ?: ""
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val webView = WebView(requireContext())
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
parseUrl(url ?: "")
}
}
webView.settings.domStorageEnabled = true
webView.clearCache(true)
webView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val manager = CookieManager.getInstance()
manager.removeAllCookies(null)
manager.flush()
manager.setAcceptCookie(true)
return webView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
(requireView() as WebView).loadUrl(url)
}
private fun parseUrl(url: String) {
Log.d("WebView url", url)
try {
if (url.startsWith("https://oauth.vk.com/blank.html#success=1")) {
Log.d("Success WebView", "")
if (!url.contains("error=")) {
val auth = VKAuth.parseRedirectUrl(url)
val token = auth[0]
val userId = auth[1].toInt()
parentFragmentManager.setFragmentResult(
"validation",
bundleOf(
Pair("token", token),
Pair("userId", userId)
)
)
parentFragmentManager.popBackStack()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -0,0 +1,255 @@
package com.meloda.fast.fragment.ui.presenter
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.fast.BuildConfig
import com.meloda.fast.activity.MessagesActivity
import com.meloda.fast.adapter.ConversationsAdapter
import com.meloda.fast.adapter.diffutil.ConversationsCallback
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.util.VKUtil
import com.meloda.fast.common.TaskManager
import com.meloda.fast.common.TimeManager
import com.meloda.fast.database.MemoryCache
import com.meloda.fast.fragment.ui.repository.ConversationsRepository
import com.meloda.fast.fragment.ui.view.ConversationsView
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.listener.ItemLongClickListener
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ArrayUtils
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpPresenter
import java.util.*
class ConversationsPresenter(viewState: ConversationsView) :
MvpPresenter<VKConversation, ConversationsRepository, ConversationsView>(
viewState,
ConversationsRepository::class.java.name
),
ItemClickListener,
ItemLongClickListener,
TimeManager.OnMinuteChangeListener {
companion object {
const val DEFAULT_CONVERSATIONS_COUNT = 30
}
private var conversationsCount: Int = 0
private lateinit var adapter: ConversationsAdapter
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
fun setup(recyclerView: RecyclerView, refreshLayout: SwipeRefreshLayout) {
this.recyclerView = recyclerView
this.context = recyclerView.context
this.layoutManager = recyclerView.layoutManager as LinearLayoutManager
prepareViews()
// setRecyclerViewScrollListener(recyclerView)
setRefreshLayoutListener(refreshLayout)
createAdapter()
TimeManager.addOnMinuteChangeListener(this)
loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// getCachedConversations(0, DEFAULT_CONVERSATIONS_COUNT, object : MvpOnLoadListener<Any?> {
// override fun onResponse(response: Any?) {
// setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
// loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// }
//
// override fun onError(t: Throwable) {
// setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
// loadConversations(0, DEFAULT_CONVERSATIONS_COUNT)
// }
// })
}
private fun setRecyclerViewScrollListener(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
if (adapter.isLastItem() && !adapter.isLoading && adapter.itemCount < conversationsCount) {
adapter.isLoading = true
val position = adapter.itemCount - 1
// adapter.itemCount - 1 - (layoutManager.findLastCompletelyVisibleItemPosition() - layoutManager.findFirstCompletelyVisibleItemPosition())
setState(ListState.FILLED_LOADING)
if (AndroidUtils.hasConnection()) {
loadConversations(adapter.itemCount, DEFAULT_CONVERSATIONS_COUNT,
object : MvpOnLoadListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
} else {
getCachedConversations(adapter.itemCount, DEFAULT_CONVERSATIONS_COUNT,
object : MvpOnLoadListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
}
if (BuildConfig.DEBUG)
Log.d("RecyclerView", "Bottom reached")
}
}
}
})
}
private fun setRefreshLayoutListener(refreshLayout: SwipeRefreshLayout) {
refreshLayout.setOnRefreshListener { loadConversations() }
}
private fun getCachedConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnLoadListener<Any?>? = null
) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
repository.getCachedConversations(offset, count,
object : MvpOnLoadListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
conversationsCount = response.size
val conversations = ArrayUtils.cut(response, offset, count)
fillAdapter(conversations, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun loadConversations(
offset: Int = 0,
count: Int = DEFAULT_CONVERSATIONS_COUNT,
listener: MvpOnLoadListener<Any?>? = null
) {
if (!AndroidUtils.hasConnection()) {
setState(if (adapter.isEmpty()) ListState.EMPTY_NO_INTERNET else ListState.FILLED)
return
} else {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
}
repository.loadConversations(offset, count,
object : MvpOnLoadListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
conversationsCount = VKConversation.conversationsCount
fillAdapter(response, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
override fun destroy() {
adapter.destroy()
TimeManager.removeOnMinuteChangeListener(this)
}
private fun createAdapter() {
adapter = ConversationsAdapter(recyclerView, arrayListOf()).also {
it.itemClickListener = this
it.itemLongClickListener = this
}
recyclerView.adapter = adapter
}
private fun fillAdapter(conversations: ArrayList<VKConversation>, offset: Int) {
val oldItems = ArrayList(adapter.values)
if (offset > 0) {
adapter.addAll(conversations)
} else {
adapter.updateValues(conversations)
}
adapter.notifyChanges(oldItems)
if (offset == 0) recyclerView.scrollToPosition(0)
}
override fun onItemClick(position: Int) {
openChat(adapter[position])
}
override fun onItemLongClick(position: Int) {
}
override fun onMinuteChange(currentMinute: Int) {
post { adapter.notifyItemRangeChanged(0, adapter.itemCount, ConversationsCallback.DATE) }
}
private fun openChat(conversation: VKConversation) {
TaskManager.execute {
val peerUser = MemoryCache.getUserById(conversation.conversationId)
val peerGroup = MemoryCache.getGroupById(conversation.conversationId)
val extras = Bundle().also {
it.putInt(MessagesActivity.TAG_EXTRA_ID, conversation.conversationId)
it.putString(
MessagesActivity.TAG_EXTRA_TITLE,
VKUtil.getTitle(conversation, peerUser, peerGroup)
)
it.putString(
MessagesActivity.TAG_EXTRA_AVATAR,
VKUtil.getAvatar(conversation, peerUser, peerGroup)
)
it.putSerializable(MessagesActivity.TAG_EXTRA_USER, peerUser)
it.putSerializable(MessagesActivity.TAG_EXTRA_GROUP, peerGroup)
}
post { viewState.openChat(extras) }
}
}
}
@@ -0,0 +1,232 @@
package com.meloda.fast.fragment.ui.presenter
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.meloda.fast.activity.MessagesActivity
import com.meloda.fast.adapter.UsersAdapter
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.fragment.ui.repository.FriendsRepository
import com.meloda.fast.fragment.ui.view.FriendsView
import com.meloda.fast.listener.ItemClickListener
import com.meloda.fast.util.AndroidUtils
import com.meloda.fast.util.ArrayUtils
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpPresenter
class FriendsPresenter(viewState: FriendsView) :
MvpPresenter<VKUser, FriendsRepository, FriendsView>(
viewState,
FriendsRepository::class.java.name
),
ItemClickListener {
companion object {
const val ONLY_ONLINE = "_only_online"
const val DEFAULT_FRIENDS_COUNT = 30
}
private var userId: Int = 0
private var friendsCount: Int = 0
private lateinit var adapter: UsersAdapter
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
fun setup(userId: Int, recyclerView: RecyclerView, refreshLayout: SwipeRefreshLayout) {
this.userId = userId
this.recyclerView = recyclerView
this.context = recyclerView.context
this.layoutManager = recyclerView.layoutManager as LinearLayoutManager
setRecyclerViewScrollListener(recyclerView)
setRefreshListener(refreshLayout)
createAdapter()
getCachedFriends(userId, 0, DEFAULT_FRIENDS_COUNT, false, object : MvpOnLoadListener<Any?> {
override fun onResponse(response: Any?) {
setState(if (adapter.isEmpty()) MvpPresenter.ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
loadFriends(userId, 0, ConversationsPresenter.DEFAULT_CONVERSATIONS_COUNT)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
loadFriends(userId, 0, ConversationsPresenter.DEFAULT_CONVERSATIONS_COUNT)
}
})
}
private fun getCachedFriends(
userId: Int,
offset: Int = 0,
count: Int = DEFAULT_FRIENDS_COUNT,
onlyOnline: Boolean = false,
listener: MvpOnLoadListener<Any?>? = null
) {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
repository.getCachedFriends(
userId,
offset,
count,
onlyOnline,
object : MvpOnLoadListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
val friends = ArrayUtils.cut(response, offset, count)
fillAdapter(friends, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun loadFriends(
userId: Int,
offset: Int = 0,
count: Int = DEFAULT_FRIENDS_COUNT,
onlyOnline: Boolean = false,
listener: MvpOnLoadListener<Any?>? = null
) {
if (!AndroidUtils.hasConnection()) {
setState(if (adapter.isEmpty()) ListState.EMPTY_NO_INTERNET else ListState.FILLED)
return
} else {
setState(if (adapter.isEmpty()) ListState.EMPTY_LOADING else ListState.FILLED_LOADING)
}
repository.loadFriends(
userId,
offset,
count,
object : MvpOnLoadListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
friendsCount = VKUser.friendsCount
fillAdapter(response, offset)
setState(if (adapter.isEmpty()) ListState.EMPTY else ListState.FILLED)
listener?.onResponse(null)
}
override fun onError(t: Throwable) {
setState(if (adapter.isEmpty()) ListState.EMPTY_ERROR else ListState.FILLED)
listener?.onError(t)
}
})
}
private fun setRecyclerViewScrollListener(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) {
if (adapter.isLastItem() && !adapter.isLoading && adapter.itemCount < friendsCount) {
adapter.isLoading = true
val position = adapter.itemCount - 1
// adapter.itemCount - 1 - (layoutManager.findLastCompletelyVisibleItemPosition() - layoutManager.findFirstCompletelyVisibleItemPosition())
setState(ListState.FILLED_LOADING)
if (AndroidUtils.hasConnection()) {
loadFriends(
userId,
adapter.itemCount,
DEFAULT_FRIENDS_COUNT,
false,
object : MvpOnLoadListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
} else {
getCachedFriends(
userId,
adapter.itemCount,
DEFAULT_FRIENDS_COUNT,
false,
object : MvpOnLoadListener<Any?> {
override fun onResponse(response: Any?) {
recyclerView.scrollToPosition(position)
adapter.isLoading = false
}
override fun onError(t: Throwable) {
viewState.showErrorSnackbar(t)
}
})
}
Log.d("RecyclerView", "Bottom reached")
}
}
}
})
}
private fun setRefreshListener(refreshLayout: SwipeRefreshLayout) {
refreshLayout.setOnRefreshListener { loadFriends(userId) }
}
private fun createAdapter() {
adapter = UsersAdapter(context!!, arrayListOf()).also {
it.itemClickListener = this
}
recyclerView.adapter = adapter
}
private fun fillAdapter(values: ArrayList<VKUser>, offset: Int) {
val oldItems = ArrayList(adapter.values)
if (offset > 0) {
adapter.addAll(values)
} else {
adapter.updateValues(values)
}
// adapter.notifyDataSetChanged()
adapter.notifyChanges(oldItems)
if (offset == 0) recyclerView.scrollToPosition(0)
}
private fun openChat(position: Int) {
val user = adapter[position]
val data = Bundle().apply {
putInt(MessagesActivity.TAG_EXTRA_ID, user.userId)
putString(MessagesActivity.TAG_EXTRA_TITLE, user.toString())
putString(MessagesActivity.TAG_EXTRA_AVATAR, user.photo200)
}
}
override fun onItemClick(position: Int) {
}
}
@@ -0,0 +1,186 @@
package com.meloda.fast.fragment.ui.presenter
import android.content.Context
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import com.google.android.material.textfield.TextInputEditText
import com.meloda.fast.R
import com.meloda.fast.activity.MainActivity
import com.meloda.fast.api.UserConfig
import com.meloda.fast.extensions.FragmentExtensions.runOnUiThread
import com.meloda.fast.fragment.FragmentConversations
import com.meloda.fast.fragment.LoginFragment
import com.meloda.fast.fragment.ValidationFragment
import com.meloda.fast.fragment.ui.repository.LoginRepository
import com.meloda.fast.fragment.ui.view.LoginView
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpPresenter
import com.squareup.picasso.Picasso
import org.json.JSONObject
class LoginPresenter(
viewState: LoginView
) : MvpPresenter<Any, LoginRepository, LoginView>(
viewState,
LoginRepository::class.java.name
) {
private var lastEmail: String = ""
private var lastPassword: String = ""
private lateinit var fragment: LoginFragment
fun onCreate(context: Context, fragment: LoginFragment, bundle: Bundle?) {
super.onCreate(context, bundle)
this.fragment = fragment
}
override fun onViewCreated(bundle: Bundle?) {
viewState.initViews()
viewState.prepareViews()
}
fun login(
email: String,
password: String,
captcha: String = "",
onLoadListener: MvpOnLoadListener<Any?>? = null
) {
lastEmail = email
lastPassword = password
repository.login(requireContext(), email, password, captcha,
object : MvpOnLoadListener<JSONObject> {
override fun onResponse(response: JSONObject) {
checkResponse(response, onLoadListener)
}
override fun onError(t: Throwable) {
onLoadListener?.onError(t)
}
})
}
@Suppress("MoveVariableDeclarationIntoWhen")
private fun checkResponse(
response: JSONObject,
onLoadListener: MvpOnLoadListener<Any?>? = null
) {
if (response.has("error")) {
val errorString = response.optString("error")
when (errorString) {
"need_validation" -> {
val redirectUrl = response.optString("redirect_uri")
val bundle = Bundle()
bundle.putString("url", redirectUrl)
fragment.runOnUiThread {
fragment.setFragmentResultListener("validation") { _, bundle ->
val userId = bundle.getInt("userId")
val token = bundle.getString("token") ?: ""
saveUserData(userId, token)
openMainScreen()
}
}
fragment.parentFragmentManager.beginTransaction()
.replace(
R.id.fragmentContainer,
ValidationFragment().apply { arguments = bundle })
.addToBackStack("")
.commit()
}
"need_captcha" -> {
val captchaImage = response.optString("captcha_img")
val captchaSid = response.optString("captcha_sid")
showCaptchaDialog(captchaImage, captchaSid)
}
}
} else {
val userId = response.optInt("user_id", -1)
val token = response.optString("access_token")
saveUserData(userId, token)
openMainScreen()
onLoadListener?.onResponse(null)
}
}
private fun openMainScreen() {
fragment.runOnUiThread {
(fragment.requireActivity() as MainActivity).bottomBar.isVisible = true
fragment.parentFragmentManager.beginTransaction()
.replace(
R.id.fragmentContainer,
FragmentConversations()
).commit()
}
}
private fun saveUserData(userId: Int, token: String) {
UserConfig.userId = userId
UserConfig.token = token
UserConfig.save()
}
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
val resources = fragment.resources
val metrics = resources.displayMetrics
fragment.runOnUiThread {
val image = ImageView(requireContext())
image.layoutParams = ViewGroup.LayoutParams(
(metrics.widthPixels / 3.5).toInt(), metrics.heightPixels / 7
)
Picasso.get().load(captchaImage).priority(Picasso.Priority.HIGH).into(image)
val captchaCodeEditText = TextInputEditText(requireContext())
captchaCodeEditText.setHint(R.string.captcha_hint)
captchaCodeEditText.layoutParams =
LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
val builder = AlertDialog.Builder(requireContext())
val layout = LinearLayout(requireContext())
layout.orientation = LinearLayout.VERTICAL
layout.gravity = Gravity.CENTER
layout.addView(image)
layout.addView(captchaCodeEditText)
builder.setView(layout)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
val captchaCode = captchaCodeEditText.text.toString().trim()
login(
lastEmail,
lastPassword,
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
)
}
builder.setTitle(R.string.input_captcha)
builder.show()
}
}
}
@@ -0,0 +1,106 @@
package com.meloda.fast.fragment.ui.repository
import com.meloda.fast.api.VKApi
import com.meloda.fast.api.model.VKConversation
import com.meloda.fast.api.model.VKMessage
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.api.util.VKUtil
import com.meloda.fast.common.TaskManager
import com.meloda.fast.database.MemoryCache
import com.meloda.fast.extensions.ArrayExtensions.asArrayList
import com.meloda.fast.listener.OnResponseListener
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpRepository
class ConversationsRepository : MvpRepository<VKConversation>() {
fun loadConversations(
offset: Int, count: Int,
listener: MvpOnLoadListener<ArrayList<VKConversation>>
) {
TaskManager.execute {
VKApi.messages()
.getConversations()
.filter("all")
.extended(true)
.fields(VKUser.DEFAULT_FIELDS)
.offset(offset)
.count(count)
.executeArray(VKConversation::class.java,
object : OnResponseListener<ArrayList<VKConversation>> {
override fun onResponse(response: ArrayList<VKConversation>) {
TaskManager.execute {
cacheLoadedConversations(response)
MemoryCache.putUsers(VKConversation.profiles)
MemoryCache.putGroups(VKConversation.groups)
sendResponse(listener, response)
}
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
fun getCachedConversations(
offset: Int, count: Int,
listener: MvpOnLoadListener<ArrayList<VKConversation>>
) {
if (true) {
sendResponse(listener, arrayListOf())
return
}
TaskManager.execute {
val conversations = MemoryCache.getConversations().asArrayList()
VKUtil.sortConversationsByDate(conversations, true)
sendResponse(listener, conversations)
}
}
private fun fillConversationsWithProfilesAndGroups(conversations: ArrayList<VKConversation>) {
for (conversation in conversations) {
val lastMessage = conversation.lastMessage
when (conversation.type) {
VKConversation.TYPE_USER -> {
VKUtil.searchUser(conversation.conversationId)?.let {
conversation.peerUser = it
}
}
VKConversation.TYPE_GROUP -> {
VKUtil.searchGroup(conversation.conversationId)?.let {
conversation.peerGroup = it
}
}
}
if (lastMessage.isFromGroup()) {
VKUtil.searchGroup(lastMessage.fromId)?.let {
lastMessage.fromGroup = it
}
} else {
VKUtil.searchUser(lastMessage.fromId)?.let {
lastMessage.fromUser = it
}
}
}
}
private fun cacheLoadedConversations(conversations: List<VKConversation>) {
val messages = arrayListOf<VKMessage>()
for (conversation in conversations) {
messages.add(conversation.lastMessage)
}
MemoryCache.putMessages(messages)
MemoryCache.putConversations(conversations)
}
}
@@ -0,0 +1,89 @@
package com.meloda.fast.fragment.ui.repository
import android.util.Log
import com.meloda.fast.api.VKApi
import com.meloda.fast.api.model.VKFriend
import com.meloda.fast.api.model.VKUser
import com.meloda.fast.common.TaskManager
import com.meloda.fast.database.MemoryCache
import com.meloda.fast.listener.OnResponseListener
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpRepository
class FriendsRepository : MvpRepository<VKUser>() {
fun loadFriends(
userId: Int,
offset: Int,
count: Int,
listener: MvpOnLoadListener<ArrayList<VKUser>>
) {
TaskManager.execute {
VKApi.friends()
.get()
.order("hints")
.userId(userId)
.fields(VKUser.DEFAULT_FIELDS)
.count(count)
.offset(offset)
.executeArray(VKUser::class.java,
object : OnResponseListener<ArrayList<VKUser>> {
override fun onResponse(response: ArrayList<VKUser>) {
Log.d("FriendsRepository", "get ${response.size} friends from api")
TaskManager.execute {
cacheLoadedUsers(userId, response)
}
sendResponse(listener, response)
}
override fun onError(t: Throwable) {
sendError(listener, t)
}
})
}
}
fun getCachedFriends(
userId: Int, offset: Int, count: Int, onlyOnline: Boolean,
listener: MvpOnLoadListener<ArrayList<VKUser>>
) {
TaskManager.execute {
val friendsArray = MemoryCache.getFriends(userId)
Log.d("FriendsRepository", "get ${friendsArray.size} friends from cache")
if (friendsArray.isEmpty()) {
sendError(listener, NullPointerException("Friends list is empty"))
return@execute
}
val friends = arrayListOf<VKUser>()
for (friend in friendsArray) {
val user = MemoryCache.getUserById(friend.friendId)
user?.let {
if (onlyOnline && user.isOnline || !onlyOnline) {
friends.add(user)
}
}
}
sendResponse(listener, friends)
}
}
private fun cacheLoadedUsers(userId: Int, users: ArrayList<VKUser>) {
MemoryCache.putUsers(users)
val friends = ArrayList<VKFriend>()
for (user in users) {
friends.add(VKFriend(user.userId, userId))
}
MemoryCache.putFriends(friends)
}
}
@@ -0,0 +1,74 @@
package com.meloda.fast.fragment.ui.repository
import android.annotation.SuppressLint
import android.content.Context
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import com.meloda.fast.api.VKAuth
import com.meloda.mvp.MvpOnLoadListener
import com.meloda.mvp.MvpRepository
import org.json.JSONObject
import org.jsoup.Jsoup
class LoginRepository : MvpRepository<Any>() {
fun login(
context: Context,
email: String,
password: String,
captcha: String,
onLoadListener: MvpOnLoadListener<JSONObject>
) {
if (email.trim().isEmpty() || password.trim().isEmpty()) return
val loadingUrl = VKAuth.getDirectAuthUrl(email, password, captcha)
val webView = createWebView(context)
webView.addJavascriptInterface(WebViewHandlerInterface(onLoadListener), "HtmlHandler")
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
webView.loadUrl(
"javascript:window.HtmlHandler.handleHtml" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
)
}
}
webView.loadUrl(loadingUrl)
}
@SuppressLint("SetJavaScriptEnabled")
private fun createWebView(context: Context): WebView {
val loginWebView = WebView(context)
loginWebView.settings.javaScriptEnabled = true
loginWebView.settings.domStorageEnabled = true
loginWebView.settings.loadsImagesAutomatically = false
loginWebView.settings.userAgentString = "Chrome/41.0.2228.0 Safari/537.36"
loginWebView.clearCache(true)
val cookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies(null)
cookieManager.flush()
cookieManager.setAcceptCookie(false)
return loginWebView
}
private class WebViewHandlerInterface(private var onLoadListener: MvpOnLoadListener<JSONObject>) {
@JavascriptInterface
fun handleHtml(html: String?) {
val doc = Jsoup.parse(html)
val responseString =
doc.select("pre[style=\"word-wrap: break-word; white-space: pre-wrap;\"]")
.first()
.text()
onLoadListener.onResponse(JSONObject(responseString))
}
}
}
@@ -0,0 +1,10 @@
package com.meloda.fast.fragment.ui.view
import android.os.Bundle
import com.meloda.mvp.MvpView
interface ConversationsView : MvpView {
fun openChat(extras: Bundle)
}
@@ -0,0 +1,10 @@
package com.meloda.fast.fragment.ui.view
import android.os.Bundle
import com.meloda.mvp.MvpView
interface FriendsView : MvpView {
fun openChat(extras: Bundle)
}
@@ -0,0 +1,11 @@
package com.meloda.fast.fragment.ui.view
import com.meloda.mvp.MvpView
interface LoginView : MvpView {
fun initViews()
fun prepareViews()
}