Dark theme
This commit is contained in:
@@ -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()
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user