forked from melod1n/fast-messenger
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("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("dagger.hilt.android.plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion(ConfigData.compileSdkVersion)
|
||||
buildToolsVersion(ConfigData.buildToolsVersion)
|
||||
compileSdkVersion(30)
|
||||
buildToolsVersion("30.0.3")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.meloda.fast"
|
||||
minSdkVersion(ConfigData.minSdkVersion)
|
||||
targetSdkVersion(ConfigData.targetSdkVersion)
|
||||
versionCode = ConfigData.versionCode
|
||||
versionName = ConfigData.versionName
|
||||
minSdkVersion(23)
|
||||
targetSdkVersion(30)
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -43,53 +51,62 @@ android {
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
java {
|
||||
val kotlinSrcDir = "src/main/kotlin"
|
||||
println(sourceSets.names)
|
||||
// val mainJavaSourceSet: SourceDirectorySet = sourceSets.getByName("main").java
|
||||
// mainJavaSourceSet.srcDir(kotlinSrcDir)
|
||||
// println(mainJavaSourceSet.srcDirs)
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
|
||||
//use this shit if you don't want to have hilt errors
|
||||
javacOptions {
|
||||
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
|
||||
}
|
||||
}
|
||||
|
||||
//java.sourceSets.create("src/main/kotlin")
|
||||
|
||||
//sourceSets {
|
||||
// main.java.srcDirs += "src/main/kotlin"
|
||||
//}
|
||||
|
||||
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(Deps.material)
|
||||
implementation(Deps.core)
|
||||
implementation(Deps.preferences)
|
||||
implementation(Deps.swipeRefreshLayout)
|
||||
implementation(Deps.recyclerView)
|
||||
implementation(Deps.cardView)
|
||||
implementation(Deps.fragment)
|
||||
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
implementation("androidx.core:core-ktx:1.7.0-alpha01")
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.3.5")
|
||||
|
||||
implementation(Deps.coroutineCore)
|
||||
implementation(Deps.coroutineAndroid)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")
|
||||
|
||||
implementation(Deps.roomRuntime)
|
||||
kapt(Deps.roomCompiler)
|
||||
implementation("androidx.room:room-runtime:2.3.0")
|
||||
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("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}")
|
||||
implementation("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}")
|
||||
implementation("io.coil-kt:coil:1.2.2")
|
||||
|
||||
implementation("com.google.code.gson:gson:2.8.7")
|
||||
implementation("org.jsoup:jsoup:1.14.1")
|
||||
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"
|
||||
package="com.meloda.fast">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
|
||||
@@ -10,32 +10,26 @@ object UserConfig {
|
||||
|
||||
const val API_ID = "6964679"
|
||||
|
||||
var token = ""
|
||||
var userId = 0
|
||||
var userId: Int = -1
|
||||
get() = AppGlobal.preferences.getInt(USER_ID, -1)
|
||||
set(value) {
|
||||
field = value
|
||||
AppGlobal.preferences.edit().putInt(USER_ID, value).apply()
|
||||
}
|
||||
|
||||
fun save() {
|
||||
AppGlobal.preferences.edit()
|
||||
.putString(TOKEN, token)
|
||||
.putInt(USER_ID, userId)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun restore() {
|
||||
token = AppGlobal.preferences.getString(TOKEN, "") ?: ""
|
||||
userId = AppGlobal.preferences.getInt(USER_ID, -1)
|
||||
}
|
||||
var accessToken: String = ""
|
||||
get() = AppGlobal.preferences.getString(TOKEN, "") ?: ""
|
||||
set(value) {
|
||||
field = value
|
||||
AppGlobal.preferences.edit().putString(TOKEN, value).apply()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
token = ""
|
||||
accessToken = ""
|
||||
userId = -1
|
||||
|
||||
AppGlobal.preferences.edit()
|
||||
.remove(TOKEN)
|
||||
.remove(USER_ID)
|
||||
.apply()
|
||||
}
|
||||
|
||||
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.base.BaseActivity
|
||||
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) {
|
||||
|
||||
private val binding: ActivityMainBinding by viewBinding()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
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 {
|
||||
return "https://oauth.vk.com/token?grant_type=password&" +
|
||||
"client_id=6146827&" +
|
||||
"client_id=${VKConstants.VK_APP_ID}&" +
|
||||
"scope=$settings&" +
|
||||
"client_secret=qVxWRF1CwHERuIrKBnqe&" +
|
||||
"client_secret=${VKConstants.VK_APP_SECRET}&" +
|
||||
"username=$login&" +
|
||||
"password=$password" +
|
||||
(if (captcha.isEmpty()) "" else "&$captcha") +
|
||||
(if (captcha.isBlank()) "" else "&$captcha") +
|
||||
"&v=${VKApi.API_VERSION}"
|
||||
// return "https://oauth.vk.com/token?grant_type=password&" +
|
||||
// "client_id=2274003&" +
|
||||
@@ -61,20 +61,19 @@ object VKAuth {
|
||||
"v=${URLEncoder.encode(VKApi.API_VERSION, "utf-8")}"
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun parseRedirectUrl(url: String): Array<String> {
|
||||
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&")
|
||||
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")
|
||||
fun parseRedirectUrl(url: String): Pair<String, Int> {
|
||||
val accessToken = VKUtil.extractPattern(url, "access_token=(.*?)&") ?: ""
|
||||
val userId = VKUtil.extractPattern(url, "user_id=(\\d*)")?.toIntOrNull() ?: -1
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.i(TAG, "access_token=$accessToken")
|
||||
Log.i(TAG, "user_id=$userId")
|
||||
}
|
||||
|
||||
if (userId == null || userId.isEmpty() || accessToken == null || accessToken.isEmpty()) throw Exception(
|
||||
"Failed to parse redirect url $url"
|
||||
if (accessToken.isEmpty() || userId == -1) throw Exception(
|
||||
"Failed to parse redirect url: $url"
|
||||
)
|
||||
|
||||
return arrayOf(accessToken, userId)
|
||||
return accessToken to userId
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ object VKConstants {
|
||||
const val USER_FIELDS =
|
||||
"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"
|
||||
|
||||
@@ -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 com.meloda.fast.BuildConfig
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.UserConfig
|
||||
import com.meloda.fast.database.DatabaseHelper
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import org.acra.ACRA
|
||||
import org.acra.ReportingInteractionMode
|
||||
import org.acra.annotation.ReportsCrashes
|
||||
@@ -29,6 +29,7 @@ import java.util.*
|
||||
resDialogPositiveButtonText = R.string.send_crash_report,
|
||||
resDialogNegativeButtonText = R.string.ok
|
||||
)
|
||||
@HiltAndroidApp
|
||||
class AppGlobal : Application() {
|
||||
|
||||
companion object {
|
||||
@@ -81,8 +82,6 @@ class AppGlobal : Application() {
|
||||
|
||||
screenWidth = AndroidUtils.getDisplayWidth()
|
||||
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"?>
|
||||
<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"
|
||||
android:id="@+id/drawerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
<LinearLayout
|
||||
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_gravity="start"
|
||||
app:headerLayout="@layout/activity_main_drawer_header"
|
||||
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" />
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
</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"
|
||||
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.AppBarLayout>
|
||||
|
||||
@@ -1,135 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/loginRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/loginRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/loginCard"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:minWidth="380dp"
|
||||
app:cardCornerRadius="9dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="?dividerHorizontal"
|
||||
app:strokeWidth="2dp">
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/logoContainer"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/ic_logo_fast_border">
|
||||
|
||||
<com.meloda.fast.widget.CircleImageView
|
||||
android:id="@+id/logoImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:padding="42dp"
|
||||
android:src="@drawable/ic_fast_lightning"
|
||||
app:tint="?colorAccent" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loginCardLayout"
|
||||
android:id="@+id/loginContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:layout_marginTop="48dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginLogoText"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/loginImage"
|
||||
style="@style/AppTheme.Login.EditText.Icon"
|
||||
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" />
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/ic_baseline_account_circle_24"
|
||||
app:tint="?colorAccent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/loginLogoContainer"
|
||||
android:layout_width="176dp"
|
||||
android:layout_height="176dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/ic_logo_fast_border">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/loginLogoImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_fast_lightning"
|
||||
app:tint="?colorAccent" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loginEmailContainer"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/loginLayout"
|
||||
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/loginEmailImage"
|
||||
style="@style/AppTheme.Login.EditText.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_email" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/loginEmailEditText"
|
||||
style="@style/AppTheme.Login.EditText"
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/loginInput"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="@string/email_login_hint"
|
||||
android:layout_height="48dp"
|
||||
android:hint="@string/login_hint"
|
||||
android:imeOptions="actionGo"
|
||||
android:inputType="textEmailAddress" />
|
||||
android:inputType="textEmailAddress"
|
||||
android:textCursorDrawable="@null" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loginPasswordContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/loginPasswordImage"
|
||||
style="@style/AppTheme.Login.EditText.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_key" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/loginPasswordEditText"
|
||||
style="@style/AppTheme.Login.EditText"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="@string/password_login_hint"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/loginAuthorize"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="@font/google_sans_regular"
|
||||
android:letterSpacing="0"
|
||||
android:paddingStart="36dp"
|
||||
android:paddingEnd="36dp"
|
||||
android:text="@string/log_in"
|
||||
android:textColor="@color/login_button_text_color"
|
||||
android:textSize="14sp"
|
||||
app:backgroundTint="?colorAccent"
|
||||
app:cornerRadius="50dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<LinearLayout
|
||||
android:id="@+id/passwordContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:orientation="horizontal">
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/passwordImage"
|
||||
style="@style/AppTheme.Login.EditText.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/ic_key"
|
||||
app:tint="?colorAccent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/passwordLayout"
|
||||
style="@style/Widget.TextInputLayout.NoError.Dense"
|
||||
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:inputType="textPassword"
|
||||
android:textCursorDrawable="@null" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/authorize"
|
||||
style="@style/Widget.MaterialButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="@font/google_sans_medium"
|
||||
android:letterSpacing="0"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/log_in"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="14sp"
|
||||
app:backgroundTint="?colorSurface"
|
||||
app:cornerRadius="50dp"
|
||||
app:elevation="16dp"
|
||||
app:icon="@drawable/ic_arrow_end"
|
||||
app:iconGravity="end"
|
||||
app:iconTint="?colorAccent"
|
||||
app:rippleColor="?colorAccent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</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="input_captcha">Input code from picture</string>
|
||||
|
||||
<string name="login_hint">Login</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
|
||||
<item name="toolbarStyle">@style/AppTheme.Toolbar</item>
|
||||
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
|
||||
<item name="dialogCornerRadius">12dp</item>
|
||||
@@ -26,14 +28,14 @@
|
||||
@android:color/transparent
|
||||
</item>
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.PrimarySurface">
|
||||
<item name="titleTextAppearance">@style/Toolbar.Title</item>
|
||||
<item name="android:textSize">24sp</item>
|
||||
<item name="android:elevation">3dp</item>
|
||||
<item name="titleTextColor">?colorAccent</item>
|
||||
<item name="android:background">?colorPrimary</item>
|
||||
<item name="titleTextColor">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="Toolbar.Title" parent="TextAppearance.MaterialComponents.Body1">
|
||||
@@ -86,4 +88,21 @@
|
||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||
</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>
|
||||
Reference in New Issue
Block a user