Android Navigation Component, Dagger Hilt and other things
This commit is contained in:
+59
-42
@@ -2,18 +2,26 @@ plugins {
|
|||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
|
id("dagger.hilt.android.plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion(ConfigData.compileSdkVersion)
|
compileSdkVersion(30)
|
||||||
buildToolsVersion(ConfigData.buildToolsVersion)
|
buildToolsVersion("30.0.3")
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.meloda.fast"
|
applicationId = "com.meloda.fast"
|
||||||
minSdkVersion(ConfigData.minSdkVersion)
|
minSdkVersion(23)
|
||||||
targetSdkVersion(ConfigData.targetSdkVersion)
|
targetSdkVersion(30)
|
||||||
versionCode = ConfigData.versionCode
|
versionCode = 1
|
||||||
versionName = ConfigData.versionName
|
versionName = "1.0"
|
||||||
|
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -43,53 +51,62 @@ android {
|
|||||||
dataBinding = true
|
dataBinding = true
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
kapt {
|
||||||
val kotlinSrcDir = "src/main/kotlin"
|
correctErrorTypes = true
|
||||||
println(sourceSets.names)
|
|
||||||
// val mainJavaSourceSet: SourceDirectorySet = sourceSets.getByName("main").java
|
//use this shit if you don't want to have hilt errors
|
||||||
// mainJavaSourceSet.srcDir(kotlinSrcDir)
|
javacOptions {
|
||||||
// println(mainJavaSourceSet.srcDirs)
|
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//java.sourceSets.create("src/main/kotlin")
|
|
||||||
|
|
||||||
//sourceSets {
|
|
||||||
// main.java.srcDirs += "src/main/kotlin"
|
|
||||||
//}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(Deps.kotlin)
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.20")
|
||||||
|
|
||||||
coreLibraryDesugaring(Deps.desugaring)
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||||
|
|
||||||
implementation(Deps.appCompat)
|
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
|
||||||
implementation(Deps.material)
|
implementation("com.google.android.material:material:1.4.0")
|
||||||
implementation(Deps.core)
|
implementation("androidx.core:core-ktx:1.7.0-alpha01")
|
||||||
implementation(Deps.preferences)
|
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||||
implementation(Deps.swipeRefreshLayout)
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||||
implementation(Deps.recyclerView)
|
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||||
implementation(Deps.cardView)
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
implementation(Deps.fragment)
|
implementation("androidx.fragment:fragment-ktx:1.3.5")
|
||||||
|
|
||||||
implementation(Deps.coroutineCore)
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
|
||||||
implementation(Deps.coroutineAndroid)
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
|
||||||
|
|
||||||
implementation(Deps.roomRuntime)
|
implementation("androidx.room:room-runtime:2.3.0")
|
||||||
kapt(Deps.roomCompiler)
|
kapt("androidx.room:room-compiler:2.3.0")
|
||||||
|
|
||||||
|
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
|
||||||
|
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
|
||||||
|
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.3.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-common-java8:2.3.1")
|
||||||
|
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.2")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2")
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||||
|
|
||||||
|
implementation("com.google.dagger:hilt-android:2.37")
|
||||||
|
kapt("com.google.dagger:hilt-android-compiler:2.37")
|
||||||
|
implementation("androidx.hilt:hilt-navigation-fragment:1.0.0")
|
||||||
|
|
||||||
implementation(Deps.gson)
|
|
||||||
implementation(Deps.jsoup)
|
|
||||||
implementation(Deps.acra)
|
|
||||||
implementation("com.github.yogacp:android-viewbinding:1.0.2")
|
implementation("com.github.yogacp:android-viewbinding:1.0.2")
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}")
|
implementation("io.coil-kt:coil:1.2.2")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}")
|
implementation("com.google.code.gson:gson:2.8.7")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}")
|
implementation("org.jsoup:jsoup:1.14.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}")
|
implementation("ch.acra:acra:4.11.1")
|
||||||
|
|
||||||
implementation("com.squareup.retrofit2:retrofit:${Versions.retrofit}")
|
|
||||||
implementation("com.squareup.retrofit2:converter-gson:${Versions.retrofit}")
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.meloda.fast">
|
package="com.meloda.fast">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|||||||
@@ -10,32 +10,26 @@ object UserConfig {
|
|||||||
|
|
||||||
const val API_ID = "6964679"
|
const val API_ID = "6964679"
|
||||||
|
|
||||||
var token = ""
|
var userId: Int = -1
|
||||||
var userId = 0
|
get() = AppGlobal.preferences.getInt(USER_ID, -1)
|
||||||
|
set(value) {
|
||||||
fun save() {
|
field = value
|
||||||
AppGlobal.preferences.edit()
|
AppGlobal.preferences.edit().putInt(USER_ID, value).apply()
|
||||||
.putString(TOKEN, token)
|
|
||||||
.putInt(USER_ID, userId)
|
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore() {
|
var accessToken: String = ""
|
||||||
token = AppGlobal.preferences.getString(TOKEN, "") ?: ""
|
get() = AppGlobal.preferences.getString(TOKEN, "") ?: ""
|
||||||
userId = AppGlobal.preferences.getInt(USER_ID, -1)
|
set(value) {
|
||||||
|
field = value
|
||||||
|
AppGlobal.preferences.edit().putString(TOKEN, value).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
token = ""
|
accessToken = ""
|
||||||
userId = -1
|
userId = -1
|
||||||
|
|
||||||
AppGlobal.preferences.edit()
|
|
||||||
.remove(TOKEN)
|
|
||||||
.remove(USER_ID)
|
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLoggedIn(): Boolean {
|
fun isLoggedIn(): Boolean {
|
||||||
return userId > 0 && !TextUtils.isEmpty(token)
|
return userId > 0 && !TextUtils.isEmpty(accessToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,19 +5,15 @@ import android.viewbinding.library.activity.viewBinding
|
|||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.base.BaseActivity
|
import com.meloda.fast.base.BaseActivity
|
||||||
import com.meloda.fast.databinding.ActivityMainBinding
|
import com.meloda.fast.databinding.ActivityMainBinding
|
||||||
import com.meloda.fast.fragment.LoginFragment
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class MainActivity : BaseActivity(R.layout.activity_main) {
|
class MainActivity : BaseActivity(R.layout.activity_main) {
|
||||||
|
|
||||||
private val binding: ActivityMainBinding by viewBinding()
|
private val binding: ActivityMainBinding by viewBinding()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.replace(R.id.fragmentContainer, LoginFragment())
|
|
||||||
.commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
sealed class Answer<out R> {
|
||||||
|
data class Success<out T>(val data: T) : Answer<T>()
|
||||||
|
data class Error(val errorString: String) : Answer<Nothing>()
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ object VKAuth {
|
|||||||
|
|
||||||
fun getDirectAuthUrl(login: String, password: String, captcha: String = ""): String {
|
fun getDirectAuthUrl(login: String, password: String, captcha: String = ""): String {
|
||||||
return "https://oauth.vk.com/token?grant_type=password&" +
|
return "https://oauth.vk.com/token?grant_type=password&" +
|
||||||
"client_id=6146827&" +
|
"client_id=${VKConstants.VK_APP_ID}&" +
|
||||||
"scope=$settings&" +
|
"scope=$settings&" +
|
||||||
"client_secret=qVxWRF1CwHERuIrKBnqe&" +
|
"client_secret=${VKConstants.VK_APP_SECRET}&" +
|
||||||
"username=$login&" +
|
"username=$login&" +
|
||||||
"password=$password" +
|
"password=$password" +
|
||||||
(if (captcha.isEmpty()) "" else "&$captcha") +
|
(if (captcha.isBlank()) "" else "&$captcha") +
|
||||||
"&v=${VKApi.API_VERSION}"
|
"&v=${VKApi.API_VERSION}"
|
||||||
// return "https://oauth.vk.com/token?grant_type=password&" +
|
// return "https://oauth.vk.com/token?grant_type=password&" +
|
||||||
// "client_id=2274003&" +
|
// "client_id=2274003&" +
|
||||||
@@ -61,20 +61,19 @@ object VKAuth {
|
|||||||
"v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
|
"v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
fun parseRedirectUrl(url: String): Pair<String, Int> {
|
||||||
fun parseRedirectUrl(url: String): Array<String> {
|
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&") ?: ""
|
||||||
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&")
|
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")?.toIntOrNull() ?: -1
|
||||||
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")
|
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.i(TAG, "access_token=$accessToken")
|
Log.i(TAG, "access_token=$accessToken")
|
||||||
Log.i(TAG, "user_id=$userId")
|
Log.i(TAG, "user_id=$userId")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId == null || userId.isEmpty() || accessToken == null || accessToken.isEmpty()) throw Exception(
|
if (accessToken.isEmpty() || userId == -1) throw Exception(
|
||||||
"Failed to parse redirect url $url"
|
"Failed to parse redirect url: $url"
|
||||||
)
|
)
|
||||||
|
|
||||||
return arrayOf(accessToken, userId)
|
return accessToken to userId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,12 @@ object VKConstants {
|
|||||||
const val USER_FIELDS =
|
const val USER_FIELDS =
|
||||||
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
|
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex"
|
||||||
|
|
||||||
|
const val VK_APP_ID = "2274003"
|
||||||
|
const val VK_APP_SECRET = "hHbZxrka2uZ6jB1inYsH"
|
||||||
|
|
||||||
|
const val VK_ME_ID = "6146827"
|
||||||
|
const val VK_ME_SECRET = "qVxWRF1CwHERuIrKBnqe"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
const val ACTION_CHAT_CREATE = "chat_create"
|
const val ACTION_CHAT_CREATE = "chat_create"
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.meloda.fast.api
|
||||||
|
|
||||||
|
interface VKRepo {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.meloda.fast.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseVM
|
||||||
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
|
||||||
|
abstract class BaseVMFragment<VM : BaseVM> : BaseFragment {
|
||||||
|
|
||||||
|
constructor() : super()
|
||||||
|
|
||||||
|
constructor(@LayoutRes resId: Int) : super(resId)
|
||||||
|
|
||||||
|
protected abstract val viewModel: VM
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
||||||
|
viewModel.tasksEvent.onEach { onEvent(it) }.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun onEvent(event: VKEvent) {
|
||||||
|
when (event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.meloda.fast.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.meloda.fast.api.Answer
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
abstract class BaseVM : ViewModel() {
|
||||||
|
|
||||||
|
protected val tasksEventChannel = Channel<VKEvent>()
|
||||||
|
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||||
|
|
||||||
|
protected fun <T> makeJob(
|
||||||
|
job: suspend () -> Answer<T>,
|
||||||
|
onAnswer: suspend (T) -> Unit = {},
|
||||||
|
onStart: (suspend () -> Unit)? = null,
|
||||||
|
onEnd: (suspend () -> Unit)? = null,
|
||||||
|
onError: (suspend (String) -> Unit)? = null
|
||||||
|
) = viewModelScope.launch {
|
||||||
|
onStart?.invoke()
|
||||||
|
when (val response = job()) {
|
||||||
|
is Answer.Success -> onAnswer(response.data)
|
||||||
|
is Answer.Error -> onError?.invoke(response.errorString) ?: tasksEventChannel.send(
|
||||||
|
ShowDialogInfoEvent(null, response.errorString)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.meloda.fast.base.viewmodel
|
||||||
|
|
||||||
|
data class ShowDialogInfoEvent(
|
||||||
|
val title: String? = null,
|
||||||
|
val message: String,
|
||||||
|
val positiveBtn: String? = null,
|
||||||
|
val negativeBtn: String? = null
|
||||||
|
) : VKEvent()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.meloda.fast.base.viewmodel
|
||||||
|
|
||||||
|
abstract class VKEvent
|
||||||
@@ -11,9 +11,9 @@ import androidx.core.content.pm.PackageInfoCompat
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.meloda.fast.BuildConfig
|
import com.meloda.fast.BuildConfig
|
||||||
import com.meloda.fast.R
|
import com.meloda.fast.R
|
||||||
import com.meloda.fast.UserConfig
|
|
||||||
import com.meloda.fast.database.DatabaseHelper
|
import com.meloda.fast.database.DatabaseHelper
|
||||||
import com.meloda.fast.util.AndroidUtils
|
import com.meloda.fast.util.AndroidUtils
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.ReportingInteractionMode
|
import org.acra.ReportingInteractionMode
|
||||||
import org.acra.annotation.ReportsCrashes
|
import org.acra.annotation.ReportsCrashes
|
||||||
@@ -29,6 +29,7 @@ import java.util.*
|
|||||||
resDialogPositiveButtonText = R.string.send_crash_report,
|
resDialogPositiveButtonText = R.string.send_crash_report,
|
||||||
resDialogNegativeButtonText = R.string.ok
|
resDialogNegativeButtonText = R.string.ok
|
||||||
)
|
)
|
||||||
|
@HiltAndroidApp
|
||||||
class AppGlobal : Application() {
|
class AppGlobal : Application() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -81,8 +82,6 @@ class AppGlobal : Application() {
|
|||||||
|
|
||||||
screenWidth = AndroidUtils.getDisplayWidth()
|
screenWidth = AndroidUtils.getDisplayWidth()
|
||||||
screenHeight = AndroidUtils.getDisplayHeight()
|
screenHeight = AndroidUtils.getDisplayHeight()
|
||||||
|
|
||||||
UserConfig.restore()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
package com.meloda.fast.extensions
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.SparseArray
|
||||||
|
import androidx.core.util.forEach
|
||||||
|
import androidx.core.util.set
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
import com.meloda.fast.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the various graphs needed for a [BottomNavigationView].
|
||||||
|
*
|
||||||
|
* This sample is a workaround until the Navigation Component supports multiple back stacks.
|
||||||
|
*/
|
||||||
|
object NavigationExtensions {
|
||||||
|
|
||||||
|
fun BottomNavigationView.setupWithNavController(
|
||||||
|
navGraphIds: List<Int>,
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
// dataManager: DataManager,
|
||||||
|
containerId: Int,
|
||||||
|
intent: Intent
|
||||||
|
): LiveData<NavController> {
|
||||||
|
|
||||||
|
// Map of tags
|
||||||
|
val graphIdToTagMap = SparseArray<String>()
|
||||||
|
// Result. Mutable live data with the selected controlled
|
||||||
|
val selectedNavController = MutableLiveData<NavController>()
|
||||||
|
|
||||||
|
var firstFragmentGraphId = 0
|
||||||
|
|
||||||
|
// First create a NavHostFragment for each NavGraph ID
|
||||||
|
navGraphIds.forEachIndexed { index, navGraphId ->
|
||||||
|
val fragmentTag = getFragmentTag(index)
|
||||||
|
|
||||||
|
// Find or create the Navigation host fragment
|
||||||
|
val navHostFragment = obtainNavHostFragment(
|
||||||
|
fragmentManager,
|
||||||
|
fragmentTag,
|
||||||
|
navGraphId,
|
||||||
|
containerId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Obtain its id
|
||||||
|
val graphId = navHostFragment.navController.graph.id
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
firstFragmentGraphId = graphId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to the map
|
||||||
|
graphIdToTagMap[graphId] = fragmentTag
|
||||||
|
|
||||||
|
// Attach or detach nav host fragment depending on whether it's the selected item.
|
||||||
|
if (this.selectedItemId == graphId) {
|
||||||
|
// Update livedata with the selected graph
|
||||||
|
selectedNavController.value = navHostFragment.navController
|
||||||
|
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
|
||||||
|
} else {
|
||||||
|
detachNavHostFragment(fragmentManager, navHostFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now connect selecting an item with swapping Fragments
|
||||||
|
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
|
||||||
|
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
|
||||||
|
var isOnFirstFragment = selectedItemTag == firstFragmentTag
|
||||||
|
|
||||||
|
// When a navigation item is selected
|
||||||
|
setOnNavigationItemSelectedListener { item ->
|
||||||
|
// Don't do anything if the state is state has already been saved.
|
||||||
|
if (fragmentManager.isStateSaved) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val navController =
|
||||||
|
(fragmentManager.findFragmentByTag(selectedItemTag) as NavHostFragment).navController
|
||||||
|
navController.popBackStack(navController.graph.startDestination, false)
|
||||||
|
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
|
||||||
|
// val listCheck = listOf(R.id.contacts, R.id.chats)
|
||||||
|
val newlySelectedItemTag =
|
||||||
|
//if (listCheck.contains(item.itemId) && dataManager.token.isBlank()) graphIdToTagMap[R.id.signIn] else
|
||||||
|
graphIdToTagMap[item.itemId]
|
||||||
|
|
||||||
|
// Pop everything above the first fragment (the "fixed start destination")
|
||||||
|
fragmentManager.popBackStack(
|
||||||
|
firstFragmentTag,
|
||||||
|
FragmentManager.POP_BACK_STACK_INCLUSIVE
|
||||||
|
)
|
||||||
|
val selectedFragment =
|
||||||
|
fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
|
||||||
|
|
||||||
|
// Exclude the first fragment tag because it's always in the back stack.
|
||||||
|
if (firstFragmentTag != newlySelectedItemTag) {
|
||||||
|
// Commit a transaction that cleans the back stack and adds the first fragment
|
||||||
|
// to it, creating the fixed started destination.
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(
|
||||||
|
R.anim.nav_default_enter_anim,
|
||||||
|
R.anim.nav_default_exit_anim,
|
||||||
|
R.anim.nav_default_pop_enter_anim,
|
||||||
|
R.anim.nav_default_pop_exit_anim
|
||||||
|
)
|
||||||
|
.attach(selectedFragment)
|
||||||
|
.setPrimaryNavigationFragment(selectedFragment)
|
||||||
|
.apply {
|
||||||
|
// Detach all other Fragments
|
||||||
|
graphIdToTagMap.forEach { _, fragmentTagIter ->
|
||||||
|
if (fragmentTagIter != newlySelectedItemTag) {
|
||||||
|
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addToBackStack(firstFragmentTag)
|
||||||
|
.setReorderingAllowed(true)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
selectedItemTag = newlySelectedItemTag
|
||||||
|
isOnFirstFragment = selectedItemTag == firstFragmentTag
|
||||||
|
selectedNavController.value = selectedFragment.navController
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnNavigationItemReselectedListener { item ->
|
||||||
|
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
|
||||||
|
val selectedFragment =
|
||||||
|
fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment
|
||||||
|
val navController = selectedFragment.navController
|
||||||
|
// Pop the back stack to the start destination of the current navController graph
|
||||||
|
if (selectedItemTag != graphIdToTagMap[item.itemId]) {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(
|
||||||
|
R.anim.nav_default_enter_anim,
|
||||||
|
R.anim.nav_default_exit_anim,
|
||||||
|
R.anim.nav_default_pop_enter_anim,
|
||||||
|
R.anim.nav_default_pop_exit_anim
|
||||||
|
)
|
||||||
|
.attach(selectedFragment)
|
||||||
|
.setPrimaryNavigationFragment(selectedFragment)
|
||||||
|
.apply {
|
||||||
|
// Detach all other Fragments
|
||||||
|
graphIdToTagMap.forEach { _, fragmentTagIter ->
|
||||||
|
if (fragmentTagIter != newlySelectedItemTag) {
|
||||||
|
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addToBackStack(firstFragmentTag)
|
||||||
|
.setReorderingAllowed(true)
|
||||||
|
.commit()
|
||||||
|
selectedItemTag = newlySelectedItemTag
|
||||||
|
isOnFirstFragment = selectedItemTag == firstFragmentTag
|
||||||
|
selectedNavController.value = selectedFragment.navController
|
||||||
|
} else navController.popBackStack(navController.graph.startDestination, false)
|
||||||
|
}
|
||||||
|
// Optional: on item reselected, pop back stack to the destination of the graph
|
||||||
|
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
|
||||||
|
|
||||||
|
// Finally, ensure that we update our BottomNavigationView when the back stack changes
|
||||||
|
fragmentManager.addOnBackStackChangedListener {
|
||||||
|
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
|
||||||
|
this.selectedItemId = firstFragmentGraphId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the graph if the currentDestination is not valid (happens when the back
|
||||||
|
// stack is popped after using the back button).
|
||||||
|
selectedNavController.value?.let { controller ->
|
||||||
|
if (controller.currentDestination == null) {
|
||||||
|
controller.navigate(controller.graph.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedNavController
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BottomNavigationView.setupDeepLinks(
|
||||||
|
navGraphIds: List<Int>,
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
containerId: Int,
|
||||||
|
intent: Intent
|
||||||
|
) {
|
||||||
|
navGraphIds.forEachIndexed { index, navGraphId ->
|
||||||
|
val fragmentTag = getFragmentTag(index)
|
||||||
|
|
||||||
|
// Find or create the Navigation host fragment
|
||||||
|
val navHostFragment = obtainNavHostFragment(
|
||||||
|
fragmentManager,
|
||||||
|
fragmentTag,
|
||||||
|
navGraphId,
|
||||||
|
containerId
|
||||||
|
)
|
||||||
|
// Handle Intent
|
||||||
|
if (navHostFragment.navController.handleDeepLink(intent) &&
|
||||||
|
selectedItemId != navHostFragment.navController.graph.id
|
||||||
|
) {
|
||||||
|
this.selectedItemId = navHostFragment.navController.graph.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detachNavHostFragment(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
navHostFragment: NavHostFragment
|
||||||
|
) {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.detach(navHostFragment)
|
||||||
|
.commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun attachNavHostFragment(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
navHostFragment: NavHostFragment,
|
||||||
|
isPrimaryNavFragment: Boolean
|
||||||
|
) {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.attach(navHostFragment)
|
||||||
|
.apply {
|
||||||
|
if (isPrimaryNavFragment) {
|
||||||
|
setPrimaryNavigationFragment(navHostFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.commitNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun obtainNavHostFragment(
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
fragmentTag: String,
|
||||||
|
navGraphId: Int,
|
||||||
|
containerId: Int,
|
||||||
|
): NavHostFragment {
|
||||||
|
// If the Nav Host fragment exists, return it
|
||||||
|
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
|
||||||
|
existingFragment?.let { return it }
|
||||||
|
|
||||||
|
// Otherwise, create it and return it.
|
||||||
|
val navHostFragment = NavHostFragment.create(navGraphId)
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.add(containerId, navHostFragment, fragmentTag)
|
||||||
|
.commitNow()
|
||||||
|
return navHostFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
|
||||||
|
val backStackCount = backStackEntryCount
|
||||||
|
for (index in 0 until backStackCount) {
|
||||||
|
if (getBackStackEntryAt(index).name == backStackName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package com.meloda.fast.fragment
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.viewbinding.library.fragment.viewBinding
|
|
||||||
import com.meloda.fast.R
|
|
||||||
import com.meloda.fast.base.BaseFragment
|
|
||||||
import com.meloda.fast.databinding.FragmentLoginBinding
|
|
||||||
|
|
||||||
class LoginFragment : BaseFragment(R.layout.fragment_login) {
|
|
||||||
|
|
||||||
private val binding: FragmentLoginBinding by viewBinding()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
package com.meloda.fast.fragment.login
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.fragment.app.setFragmentResultListener
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import coil.load
|
||||||
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.base.BaseVMFragment
|
||||||
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
|
import com.meloda.fast.databinding.FragmentLoginBinding
|
||||||
|
import com.meloda.fast.util.KeyboardUtils
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LoginFragment : BaseVMFragment<LoginVM>(R.layout.fragment_login) {
|
||||||
|
|
||||||
|
override val viewModel: LoginVM by viewModels()
|
||||||
|
private val binding: FragmentLoginBinding by viewBinding()
|
||||||
|
|
||||||
|
private var lastEmail: String = ""
|
||||||
|
private var lastPassword: String = ""
|
||||||
|
|
||||||
|
private var errorTimer: Timer? = null
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
viewModel.checkUserSession()
|
||||||
|
|
||||||
|
prepareViews()
|
||||||
|
|
||||||
|
setFragmentResultListener("validation") { _, bundle ->
|
||||||
|
lifecycleScope.launch { viewModel.getValidatedData(bundle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.loginInput.setText("danil_nikolaev_02@mail.ru")
|
||||||
|
binding.passwordInput.setText("iwanttofuck24")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: VKEvent) {
|
||||||
|
super.onEvent(event)
|
||||||
|
|
||||||
|
when (event) {
|
||||||
|
is ShowCaptchaDialog -> showCaptchaDialog(event.captchaImage, event.captchaSid)
|
||||||
|
is GoToValidationEvent -> goToValidation(event.redirectUrl)
|
||||||
|
GoToMainEvent -> goToMain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareViews() {
|
||||||
|
prepareEmailEditText()
|
||||||
|
preparePasswordEditText()
|
||||||
|
prepareAuthButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareEmailEditText() {
|
||||||
|
binding.loginInput.addTextChangedListener {
|
||||||
|
if (!binding.loginLayout.error.isNullOrBlank()) binding.loginLayout.error = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preparePasswordEditText() {
|
||||||
|
binding.passwordLayout.endIconMode = TextInputLayout.END_ICON_NONE
|
||||||
|
|
||||||
|
binding.passwordInput.addTextChangedListener {
|
||||||
|
if (!binding.passwordLayout.error.isNullOrBlank()) binding.passwordLayout.error = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.passwordInput.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
binding.passwordLayout.endIconMode =
|
||||||
|
if (hasFocus) TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||||
|
else TextInputLayout.END_ICON_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.passwordInput.setOnEditorActionListener { _, _, event ->
|
||||||
|
if (event == null) return@setOnEditorActionListener false
|
||||||
|
return@setOnEditorActionListener if (event.action == EditorInfo.IME_ACTION_GO ||
|
||||||
|
(event.action == KeyEvent.ACTION_DOWN && (event.keyCode == KeyEvent.KEYCODE_ENTER || event.keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
|
||||||
|
) {
|
||||||
|
KeyboardUtils.hideKeyboardFrom(binding.passwordInput)
|
||||||
|
binding.authorize.performClick()
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAuthButton() {
|
||||||
|
binding.authorize.setOnClickListener {
|
||||||
|
val loginString = binding.loginInput.text.toString().trim()
|
||||||
|
val passwordString = binding.passwordInput.text.toString().trim()
|
||||||
|
|
||||||
|
if (!validateInputData(loginString, passwordString)) return@setOnClickListener
|
||||||
|
|
||||||
|
viewModel.login(requireContext(), loginString, passwordString)
|
||||||
|
KeyboardUtils.hideKeyboardFrom(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateInputData(loginString: String, passwordString: String): Boolean {
|
||||||
|
var isValidated = true
|
||||||
|
|
||||||
|
if (loginString.isEmpty()) {
|
||||||
|
isValidated = false
|
||||||
|
setError("Input login", binding.loginLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordString.isEmpty()) {
|
||||||
|
isValidated = false
|
||||||
|
setError("Input password", binding.passwordLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidated
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setError(error: String, inputLayout: TextInputLayout) {
|
||||||
|
inputLayout.error = error
|
||||||
|
|
||||||
|
if (errorTimer != null) {
|
||||||
|
errorTimer?.cancel()
|
||||||
|
errorTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorTimer == null) {
|
||||||
|
errorTimer = Timer()
|
||||||
|
}
|
||||||
|
|
||||||
|
errorTimer?.schedule(2500) {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) { clearErrors() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearErrors() {
|
||||||
|
binding.loginLayout.error = ""
|
||||||
|
binding.passwordLayout.error = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 7/10/2021 extract layout to resources
|
||||||
|
private fun showCaptchaDialog(captchaImage: String, captchaSid: String) {
|
||||||
|
val metrics = resources.displayMetrics
|
||||||
|
|
||||||
|
val width = (metrics.widthPixels / 3.5).roundToInt()
|
||||||
|
val height = metrics.heightPixels / 7
|
||||||
|
|
||||||
|
val image = ShapeableImageView(requireContext()).also {
|
||||||
|
it.layoutParams = ViewGroup.LayoutParams(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
val shapeModel = image.shapeAppearanceModel
|
||||||
|
image.shapeAppearanceModel = shapeModel.withCornerSize { 12f }
|
||||||
|
|
||||||
|
image.load(captchaImage) { crossfade(100) }
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
viewModel.login(
|
||||||
|
requireContext(),
|
||||||
|
lastEmail,
|
||||||
|
lastPassword,
|
||||||
|
"&captcha_sid=$captchaSid&captcha_key=$captchaCode"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setTitle(R.string.input_captcha)
|
||||||
|
builder.show()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goToValidation(redirectUrl: String) {
|
||||||
|
findNavController().navigate(
|
||||||
|
R.id.toValidation,
|
||||||
|
bundleOf("redirectUrl" to redirectUrl)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goToMain() {
|
||||||
|
findNavController().navigate(R.id.toMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package com.meloda.fast.fragment.login
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.meloda.fast.UserConfig
|
||||||
|
import com.meloda.fast.api.VKAuth
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseVM
|
||||||
|
import com.meloda.fast.base.viewmodel.VKEvent
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
|
class LoginVM : BaseVM() {
|
||||||
|
|
||||||
|
fun login(
|
||||||
|
context: Context,
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
captcha: String = ""
|
||||||
|
) {
|
||||||
|
val urlToGo = VKAuth.getDirectAuthUrl(email, password, captcha)
|
||||||
|
|
||||||
|
// val builder = AlertDialog.Builder(context)
|
||||||
|
|
||||||
|
val webView = createWebView(context)
|
||||||
|
webView.layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
|
||||||
|
// builder.setTitle("Auth")
|
||||||
|
// builder.setView(webView)
|
||||||
|
// builder.show()
|
||||||
|
|
||||||
|
webView.addJavascriptInterface(WebViewHandlerInterface(), "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(urlToGo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MoveVariableDeclarationIntoWhen")
|
||||||
|
private fun checkResponse(response: JSONObject) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (response.has("error")) {
|
||||||
|
val errorString = response.optString("error")
|
||||||
|
|
||||||
|
when (errorString) {
|
||||||
|
"need_validation" -> {
|
||||||
|
val redirectUrl = response.optString("redirect_uri")
|
||||||
|
|
||||||
|
tasksEventChannel.send(GoToValidationEvent(redirectUrl))
|
||||||
|
|
||||||
|
// val bundle = Bundle()
|
||||||
|
// bundle.putString("url", redirectUrl)
|
||||||
|
|
||||||
|
/* 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")
|
||||||
|
|
||||||
|
tasksEventChannel.send(ShowCaptchaDialog(captchaImage, captchaSid))
|
||||||
|
// showCaptchaDialog(captchaImage, captchaSid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val userId = response.optInt("user_id", -1)
|
||||||
|
val accessToken = response.optString("access_token")
|
||||||
|
|
||||||
|
UserConfig.accessToken = accessToken
|
||||||
|
UserConfig.userId = userId
|
||||||
|
|
||||||
|
tasksEventChannel.send(GoToMainEvent)
|
||||||
|
|
||||||
|
// openMainScreen()
|
||||||
|
|
||||||
|
// onResponseListener?.onResponse(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getValidatedData(bundle: Bundle) {
|
||||||
|
val accessToken = bundle.getString("token") ?: ""
|
||||||
|
val userId = bundle.getInt("userId")
|
||||||
|
|
||||||
|
UserConfig.accessToken = accessToken
|
||||||
|
UserConfig.userId = userId
|
||||||
|
|
||||||
|
tasksEventChannel.send(GoToMainEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkUserSession() = viewModelScope.launch {
|
||||||
|
if (UserConfig.isLoggedIn()) tasksEventChannel.send(GoToMainEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class WebViewHandlerInterface {
|
||||||
|
@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() ?: ""
|
||||||
|
|
||||||
|
checkResponse(JSONObject(responseString))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ShowCaptchaDialog(val captchaImage: String, val captchaSid: String) : VKEvent()
|
||||||
|
data class GoToValidationEvent(val redirectUrl: String) : VKEvent()
|
||||||
|
object GoToMainEvent : VKEvent()
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.meloda.fast.fragment.login
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.api.VKAuth
|
||||||
|
import com.meloda.fast.base.BaseFragment
|
||||||
|
import com.meloda.fast.databinding.FragmentValidationBinding
|
||||||
|
|
||||||
|
class ValidationFragment : BaseFragment(R.layout.fragment_validation) {
|
||||||
|
|
||||||
|
private val binding: FragmentValidationBinding by viewBinding()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val redirectUrl = getRedirectUrl()
|
||||||
|
|
||||||
|
binding.webView.webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||||
|
Log.d("Fast::Validation", "onPageStarted: url: $url")
|
||||||
|
parseUrl(url ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.webView.settings.domStorageEnabled = true
|
||||||
|
binding.webView.clearCache(true)
|
||||||
|
binding.webView.layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
|
||||||
|
val manager = CookieManager.getInstance()
|
||||||
|
manager.removeAllCookies(null)
|
||||||
|
manager.flush()
|
||||||
|
manager.setAcceptCookie(true)
|
||||||
|
|
||||||
|
binding.webView.loadUrl(redirectUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRedirectUrl() = requireArguments().getString("redirectUrl", "")
|
||||||
|
|
||||||
|
private fun parseUrl(url: String) {
|
||||||
|
if (url.startsWith("https://oauth.vk.com/blank.html#success=1")) {
|
||||||
|
if (!url.contains("error=")) {
|
||||||
|
val data = VKAuth.parseRedirectUrl(url)
|
||||||
|
|
||||||
|
val accessToken = data.first
|
||||||
|
val userId = data.second
|
||||||
|
|
||||||
|
parentFragmentManager.setFragmentResult(
|
||||||
|
"validation",
|
||||||
|
bundleOf(
|
||||||
|
"accessToken" to accessToken,
|
||||||
|
"userId" to userId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
findNavController().navigate(R.id.toLogin)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("Fast::Validation", "parseUrl: $url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.meloda.fast.fragment.main
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.viewbinding.library.fragment.viewBinding
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.base.BaseVMFragment
|
||||||
|
import com.meloda.fast.databinding.FragmentMainBinding
|
||||||
|
import com.meloda.fast.extensions.NavigationExtensions.setupWithNavController
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MainFragment : BaseVMFragment<MainVM>(R.layout.fragment_main) {
|
||||||
|
|
||||||
|
override val viewModel: MainVM by viewModels()
|
||||||
|
private val binding: FragmentMainBinding by viewBinding()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) setupBottomBar()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBottomBar() {
|
||||||
|
val navGraphIds = listOf(R.id.messages, R.id.friends)
|
||||||
|
|
||||||
|
binding.bottomBar.setupWithNavController(
|
||||||
|
navGraphIds = listOf(),
|
||||||
|
fragmentManager = childFragmentManager,
|
||||||
|
containerId = R.id.fragmentContainer,
|
||||||
|
intent = requireActivity().intent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.meloda.fast.fragment.main
|
||||||
|
|
||||||
|
import com.meloda.fast.base.viewmodel.BaseVM
|
||||||
|
|
||||||
|
class MainVM : BaseVM()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.meloda.fast.fragment.messages
|
||||||
|
|
||||||
|
import com.meloda.fast.R
|
||||||
|
import com.meloda.fast.base.BaseFragment
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class ConversationsFragment : BaseFragment(R.layout.fragment_conversations) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?colorAccent" android:state_focused="true" />
|
||||||
|
<item android:alpha="0.87" android:color="?colorOnSurface" android:state_hovered="true" />
|
||||||
|
<item android:alpha="0.12" android:color="?colorOnSurface" android:state_enabled="false" />
|
||||||
|
<item android:alpha="0.38" android:color="?colorOnSurface" />
|
||||||
|
</selector>
|
||||||
@@ -1,43 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/drawerLayout"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/fragmentContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
android:id="@+id/bottomBar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="?colorPrimary"
|
|
||||||
app:elevation="0.5dp"
|
|
||||||
app:itemIconTint="@drawable/navigation_view_items_colors"
|
|
||||||
app:itemTextColor="@drawable/navigation_view_items_colors"
|
|
||||||
app:labelVisibilityMode="unlabeled"
|
|
||||||
app:menu="@menu/activity_main_bottom" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
|
||||||
android:id="@+id/navigationView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:orientation="vertical"
|
||||||
app:headerLayout="@layout/activity_main_drawer_header"
|
tools:context=".MainActivity">
|
||||||
app:itemBackground="@drawable/navigation_view_item_background_selector"
|
|
||||||
app:itemIconTint="@drawable/navigation_view_item_icon_colors"
|
|
||||||
app:itemTextColor="@drawable/navigation_view_item_text_colors"
|
|
||||||
app:menu="@menu/activity_main_drawer" />
|
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragmentContainer"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:defaultNavHost="true"
|
||||||
|
app:navGraph="@navigation/main" />
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
||||||
@@ -18,7 +18,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_scrollFlags="scroll|enterAlways">
|
app:layout_scrollFlags="scroll|enterAlways">
|
||||||
|
|
||||||
<include layout="@layout/toolbar" />
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
app:titleCentered="true" />
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/loginRoot"
|
android:id="@+id/loginRoot"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -11,125 +13,126 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fillViewport="true">
|
android:fillViewport="true">
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/loginCard"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:minWidth="380dp"
|
|
||||||
app:cardCornerRadius="9dp"
|
|
||||||
app:cardElevation="0dp"
|
|
||||||
app:strokeColor="?dividerHorizontal"
|
|
||||||
app:strokeWidth="2dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/loginCardLayout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/loginLogoText"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:fontFamily="@font/google_sans_regular"
|
|
||||||
android:text="@string/fast_messenger"
|
|
||||||
android:textColor="@color/login_secondary_color"
|
|
||||||
android:textSize="26sp" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/loginLogoContainer"
|
android:id="@+id/logoContainer"
|
||||||
android:layout_width="176dp"
|
android:layout_width="140dp"
|
||||||
android:layout_height="176dp"
|
android:layout_height="140dp"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:background="@drawable/ic_logo_fast_border">
|
android:background="@drawable/ic_logo_fast_border">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<com.meloda.fast.widget.CircleImageView
|
||||||
android:id="@+id/loginLogoImage"
|
android:id="@+id/logoImage"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:padding="42dp"
|
||||||
android:src="@drawable/ic_fast_lightning"
|
android:src="@drawable/ic_fast_lightning"
|
||||||
app:tint="?colorAccent" />
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/loginEmailContainer"
|
android:id="@+id/loginContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="48dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/loginEmailImage"
|
android:id="@+id/loginImage"
|
||||||
style="@style/AppTheme.Login.EditText.Icon"
|
style="@style/AppTheme.Login.EditText.Icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_email" />
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/ic_baseline_account_circle_24"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/loginEmailEditText"
|
android:id="@+id/loginLayout"
|
||||||
style="@style/AppTheme.Login.EditText"
|
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:hint="@string/email_login_hint"
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/loginInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:hint="@string/login_hint"
|
||||||
android:imeOptions="actionGo"
|
android:imeOptions="actionGo"
|
||||||
android:inputType="textEmailAddress" />
|
android:inputType="textEmailAddress"
|
||||||
|
android:textCursorDrawable="@null" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/loginPasswordContainer"
|
android:id="@+id/passwordContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/loginPasswordImage"
|
android:id="@+id/passwordImage"
|
||||||
style="@style/AppTheme.Login.EditText.Icon"
|
style="@style/AppTheme.Login.EditText.Icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_key" />
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/ic_key"
|
||||||
|
app:tint="?colorAccent" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/loginPasswordEditText"
|
android:id="@+id/passwordLayout"
|
||||||
style="@style/AppTheme.Login.EditText"
|
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:passwordToggleEnabled="true"
|
||||||
|
app:passwordToggleTint="?colorAccent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/passwordInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
android:hint="@string/password_login_hint"
|
android:hint="@string/password_login_hint"
|
||||||
android:inputType="textPassword" />
|
android:inputType="textPassword"
|
||||||
|
android:textCursorDrawable="@null" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/loginAuthorize"
|
android:id="@+id/authorize"
|
||||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
style="@style/Widget.MaterialButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:fontFamily="@font/google_sans_regular"
|
android:fontFamily="@font/google_sans_medium"
|
||||||
android:letterSpacing="0"
|
android:letterSpacing="0"
|
||||||
android:paddingStart="36dp"
|
android:paddingStart="24dp"
|
||||||
android:paddingEnd="36dp"
|
android:paddingEnd="16dp"
|
||||||
android:text="@string/log_in"
|
android:text="@string/log_in"
|
||||||
android:textColor="@color/login_button_text_color"
|
android:textColor="?colorAccent"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:backgroundTint="?colorAccent"
|
app:backgroundTint="?colorSurface"
|
||||||
app:cornerRadius="50dp" />
|
app:cornerRadius="50dp"
|
||||||
|
app:elevation="16dp"
|
||||||
|
app:icon="@drawable/ic_arrow_end"
|
||||||
|
app:iconGravity="end"
|
||||||
|
app:iconTint="?colorAccent"
|
||||||
|
app:rippleColor="?colorAccent" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
</layout>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragmentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="48dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
android:id="@+id/bottomBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
app:backgroundTint="?colorPrimary"
|
||||||
|
app:elevation="0.5dp"
|
||||||
|
app:itemIconTint="@drawable/navigation_view_items_colors"
|
||||||
|
app:itemTextColor="@drawable/navigation_view_items_colors"
|
||||||
|
app:labelVisibilityMode="unlabeled"
|
||||||
|
app:menu="@menu/activity_main_bottom" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</layout>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/friends">
|
||||||
|
|
||||||
|
</navigation>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
app:startDestination="@id/loginFragment">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/mainFragment"
|
||||||
|
android:name="com.meloda.fast.fragment.main.MainFragment"
|
||||||
|
android:label="MainFragment"
|
||||||
|
tools:layout="@layout/fragment_main" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/loginFragment"
|
||||||
|
android:name="com.meloda.fast.fragment.login.LoginFragment"
|
||||||
|
android:label="LoginFragment"
|
||||||
|
tools:layout="@layout/fragment_login">
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/toMain"
|
||||||
|
app:destination="@id/mainFragment"
|
||||||
|
app:popUpTo="@id/loginFragment"
|
||||||
|
app:popUpToInclusive="true" />
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/toValidation"
|
||||||
|
app:destination="@id/validationFragment" />
|
||||||
|
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/validationFragment"
|
||||||
|
android:name="com.meloda.fast.fragment.login.ValidationFragment"
|
||||||
|
android:label="ValidationFragment"
|
||||||
|
tools:layout="@layout/fragment_validation">
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/toLogin"
|
||||||
|
app:destination="@id/loginFragment"
|
||||||
|
app:popUpTo="@id/validationFragment"
|
||||||
|
app:popUpToInclusive="true" />
|
||||||
|
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
</navigation>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/messages">
|
||||||
|
|
||||||
|
</navigation>
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.NoActionBar.Bridge">
|
|
||||||
<item name="colorPrimary">@color/dark_primary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
|
|
||||||
<item name="colorAccent">@color/dark_accent</item>
|
|
||||||
<item name="toolbarStyle">@style/AppTheme.Toolbar</item>
|
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
|
|
||||||
<item name="dialogCornerRadius">12dp</item>
|
|
||||||
<item name="android:windowBackground">@color/dark_background</item>
|
|
||||||
<item name="itemTitleColor">@android:color/white</item>
|
|
||||||
<item name="dividerHorizontal">@color/dark_divider</item>
|
|
||||||
<item name="textColorSecondary">@color/dark_textSecondary</item>
|
|
||||||
<item name="editTextFilledBackgroundColor">@color/dark_edittext_filled_background</item>
|
|
||||||
<item name="messageInTextColor">@color/dark_message_in</item>
|
|
||||||
<item name="messageOutTextColor">@color/dark_message_out</item>
|
|
||||||
|
|
||||||
<item name="android:windowAnimationStyle">@style/AppTheme.ActivityAnimation</item>
|
|
||||||
|
|
||||||
<item name="android:navigationBarColor" tools:targetApi="M">@color/dark_navigationBar</item>
|
|
||||||
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">@android:color/transparent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.FullScreenDialog" parent="Theme.MaterialComponents.Dialog.Bridge">
|
|
||||||
<item name="colorPrimary">@color/dark_primary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
|
|
||||||
<item name="colorAccent">@color/dark_accent</item>
|
|
||||||
<item name="android:windowIsFloating">false</item>
|
|
||||||
<item name="android:windowBackground">@color/dark_background</item>
|
|
||||||
<item name="actionMenuTextColor">?colorAccent</item>
|
|
||||||
|
|
||||||
<item name="android:navigationBarColor" tools:targetApi="o_mr1">?colorPrimaryDark</item>
|
|
||||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
|
|
||||||
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">
|
|
||||||
@android:color/transparent
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item name="colorControlNormal">?colorAccent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.ProfileDialog" parent="Theme.MaterialComponents.BottomSheetDialog">
|
|
||||||
<item name="colorAccent">@color/dark_accent</item>
|
|
||||||
<item name="colorPrimary">@color/dark_primary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/dark_primaryDark</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -153,5 +153,6 @@
|
|||||||
<string name="captcha_hint">Captcha</string>
|
<string name="captcha_hint">Captcha</string>
|
||||||
<string name="input_captcha">Input code from picture</string>
|
<string name="input_captcha">Input code from picture</string>
|
||||||
|
|
||||||
|
<string name="login_hint">Login</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
<item name="colorPrimary">@color/primary</item>
|
<item name="colorPrimary">@color/primary</item>
|
||||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||||
<item name="colorAccent">@color/accent</item>
|
<item name="colorAccent">@color/accent</item>
|
||||||
|
|
||||||
<item name="toolbarStyle">@style/AppTheme.Toolbar</item>
|
<item name="toolbarStyle">@style/AppTheme.Toolbar</item>
|
||||||
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
|
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
|
||||||
<item name="dialogCornerRadius">12dp</item>
|
<item name="dialogCornerRadius">12dp</item>
|
||||||
@@ -26,14 +28,14 @@
|
|||||||
@android:color/transparent
|
@android:color/transparent
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
|
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
|
||||||
<item name="titleTextAppearance">@style/Toolbar.Title</item>
|
<item name="titleTextAppearance">@style/Toolbar.Title</item>
|
||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">24sp</item>
|
||||||
<item name="android:elevation">3dp</item>
|
<item name="android:elevation">3dp</item>
|
||||||
<item name="titleTextColor">?colorAccent</item>
|
<item name="titleTextColor">@color/accent</item>
|
||||||
<item name="android:background">?colorPrimary</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Toolbar.Title" parent="TextAppearance.MaterialComponents.Body1">
|
<style name="Toolbar.Title" parent="TextAppearance.MaterialComponents.Body1">
|
||||||
@@ -86,4 +88,21 @@
|
|||||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
<item name="errorEnabled">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.TextInputLayout.NoError.Dense" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense">
|
||||||
|
<item name="errorEnabled">true</item>
|
||||||
|
<item name="hintTextColor">?colorAccent</item>
|
||||||
|
<item name="boxStrokeColor">@drawable/edit_text_box_background</item>
|
||||||
|
<item name="boxStrokeErrorColor">?colorAccent</item>
|
||||||
|
<item name="boxStrokeWidth">2dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.MaterialButton" parent="Widget.MaterialComponents.Button">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
+5
-2
@@ -5,8 +5,11 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
|
||||||
classpath("com.android.tools.build:gradle:${Versions.gradlePlugin}")
|
classpath("com.android.tools.build:gradle:4.2.2")
|
||||||
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5")
|
||||||
|
|
||||||
|
classpath("com.google.dagger:hilt-android-gradle-plugin:2.37")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import org.gradle.kotlin.dsl.`kotlin-dsl`
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`kotlin-dsl`
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\BuildPlugins$android$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\BuildPlugins$kotlin$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\BuildPlugins.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\ConfigData.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$acra$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$appCompat$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$cardView$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$core$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$coroutineAndroid$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$coroutineCore$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$desugaring$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$fragment$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$gson$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$jsoup$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$kotlin$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$material$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$preferences$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$recyclerView$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$roomCompiler$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$roomRuntime$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps$swipeRefreshLayout$2.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Deps.class;D:\Workspace\Android\fast-messenger\buildSrc\build\classes\kotlin\main\Versions.class
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user