mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
Implement basic authntication flow
This commit is contained in:
parent
02e74aa452
commit
430e49e6be
20 changed files with 417 additions and 55 deletions
|
|
@ -37,21 +37,6 @@
|
|||
<string name="preference_push_your_quest_has_begun">Your Quest has Begun</string>
|
||||
<string name="preference_push_invited_to_quest">Invited to Quest</string>
|
||||
|
||||
<!-- Adding tasks -->
|
||||
<string name="action_edit">Edit</string>
|
||||
<string name="action_cancel">Cancel</string>
|
||||
<string name="login_btn">Login</string>
|
||||
<string name="register_btn">Register</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="email_username">Email or Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="emailAddress">Email address</string>
|
||||
<string name="confirmpassword">Confirm password</string>
|
||||
|
||||
<string name="logout">Logout</string>
|
||||
<string name="logout_description">Log out of your account</string>
|
||||
<string name="LoginActivityName">Welcome</string>
|
||||
<string name="about_title">About</string>
|
||||
<string name="about_libraries">Libraries</string>
|
||||
<string name="about_habitica_open_source">Habitica is available as open source software on Github</string>
|
||||
<string name="about_rate_our_app">Rate our App</string>
|
||||
|
|
@ -66,10 +51,6 @@
|
|||
|
||||
<string name="authentication_error_title">Authentication Error</string>
|
||||
<string name="authentication_error_body">Your Username and/or Password was incorrect.</string>
|
||||
|
||||
<string name="login_validation_error_title">Validation Error</string>
|
||||
<string name="login_validation_error_fieldsmissing">You have to fill out all fields.</string>
|
||||
|
||||
<string name="save_changes">Save</string>
|
||||
<string name="copy">Copy</string>
|
||||
<string name="notes">Notes</string>
|
||||
|
|
@ -470,7 +451,6 @@
|
|||
<string name="owned">Owned</string>
|
||||
<string name="not_owned">Not owned</string>
|
||||
<string name="login_btn_fb">Login with Facebook</string>
|
||||
<string name="login_btn_google">Login with Google</string>
|
||||
<string name="login_btn_apple">Sign in with Apple</string>
|
||||
<string name="register_btn_fb">Sign up with Facebook</string>
|
||||
<string name="register_btn_google">Sign up with Google</string>
|
||||
|
|
|
|||
|
|
@ -18,13 +18,7 @@ import android.util.Log
|
|||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.transition.CrossfadeTransition
|
||||
import coil.util.DebugLogger
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.Identify
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
|
|
@ -42,6 +36,7 @@ import com.habitrpg.android.habitica.modules.UserRepositoryModule
|
|||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.activities.BaseActivity
|
||||
import com.habitrpg.android.habitica.ui.activities.LoginActivity
|
||||
import com.habitrpg.common.habitica.extensions.setupCoil
|
||||
import com.habitrpg.common.habitica.helpers.LanguageHelper
|
||||
import com.habitrpg.common.habitica.helpers.MarkdownParser
|
||||
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
|
||||
|
|
@ -90,20 +85,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
} catch (ignored: Resources.NotFoundException) {
|
||||
}
|
||||
}
|
||||
var builder = ImageLoader.Builder(this)
|
||||
.transition(CrossfadeTransition())
|
||||
.allowHardware(false)
|
||||
.componentRegistry {
|
||||
if (SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder(this@HabiticaBaseApplication))
|
||||
} else {
|
||||
add(GifDecoder())
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
builder = builder.logger(DebugLogger())
|
||||
}
|
||||
Coil.setImageLoader(builder.build())
|
||||
setupCoil()
|
||||
|
||||
RxErrorHandler.init(analyticsManager)
|
||||
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class ApiClientImpl(
|
|||
|
||||
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
|
||||
return
|
||||
}
|
||||
}∂
|
||||
if (error.response()?.raw()?.request?.url?.toString()?.endsWith("/user/push-devices") == true) {
|
||||
// workaround for an error that sometimes displays that the user already has this push device
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.habitrpg.common.habitica.extensions
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.util.DebugLogger
|
||||
import com.habitrpg.common.habitica.BuildConfig
|
||||
|
||||
fun Application.setupCoil() {
|
||||
var builder = ImageLoader.Builder(this)
|
||||
.allowHardware(false)
|
||||
.componentRegistry {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(ImageDecoderDecoder(this@setupCoil))
|
||||
} else {
|
||||
add(GifDecoder())
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
builder = builder.logger(DebugLogger())
|
||||
}
|
||||
Coil.setImageLoader(builder.build())
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.habitrpg.common.habitica.models.auth
|
||||
|
||||
class UserAuth {
|
||||
var username: String? = null
|
||||
var password: String? = null
|
||||
var confirmPassword: String? = null
|
||||
var email: String? = null
|
||||
class UserAuth(var username: String? = null, var password: String? = null,
|
||||
var confirmPassword: String? = null, var email: String? = null
|
||||
) {
|
||||
}
|
||||
|
|
@ -166,6 +166,7 @@ class AvatarView : FrameLayout {
|
|||
}
|
||||
|
||||
override fun onSuccess(result: Drawable) {
|
||||
result.isFilterBitmap = false
|
||||
super.onSuccess(result)
|
||||
val bounds = getLayerBounds(layerKey, layerName, result)
|
||||
imageView.imageMatrix = avatarMatrix
|
||||
|
|
@ -384,7 +385,7 @@ class AvatarView : FrameLayout {
|
|||
} else {
|
||||
PointF(24.0f, 0f)
|
||||
}
|
||||
hasPet -> PointF(24.0f, 24.5f)
|
||||
hasPet -> PointF(24.0f, 24f)
|
||||
else -> PointF(24.0f, 28.0f)
|
||||
}
|
||||
} else if (showBackground) {
|
||||
|
|
|
|||
|
|
@ -23,4 +23,24 @@
|
|||
<string name="settings">Settings</string>
|
||||
<string name="new_task">New Task</string>
|
||||
<string name="avatar">Avatar</string>
|
||||
|
||||
<string name="action_edit">Edit</string>
|
||||
<string name="action_cancel">Cancel</string>
|
||||
<string name="login_btn">Login</string>
|
||||
<string name="register_btn">Register</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="email_username">Email or Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="emailAddress">Email address</string>
|
||||
<string name="confirmpassword">Confirm password</string>
|
||||
<string name="login_btn_google">Login with Google</string>
|
||||
|
||||
<string name="logout">Logout</string>
|
||||
<string name="logout_description">Log out of your account</string>
|
||||
<string name="LoginActivityName">Welcome</string>
|
||||
<string name="about_title">About</string>
|
||||
|
||||
<string name="login_validation_error_title">Validation Error</string>
|
||||
<string name="login_validation_error_fieldsmissing">You have to fill out all fields.</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -53,7 +53,7 @@ dependencies {
|
|||
//Analytics
|
||||
implementation 'com.amplitude:android-sdk:3.35.1'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
|
||||
|
|
@ -63,13 +63,15 @@ dependencies {
|
|||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
|
||||
implementation 'com.google.android.gms:play-services-auth:20.2.0'
|
||||
|
||||
implementation project(':common')
|
||||
implementation project(':shared')
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
|
||||
implementation "com.google.dagger:hilt-android:2.41"
|
||||
kapt "com.google.dagger:hilt-compiler:2.41"
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "androidx.core:core-ktx:1.8.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
repositories {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ui.activities.LoginActivity" />
|
||||
|
||||
<activity android:name=".ui.activities.TaskListActivity" />
|
||||
<activity android:name=".ui.activities.TaskFormActivity" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.habitrpg.wearos.habitica
|
||||
|
||||
import android.app.Application
|
||||
import com.habitrpg.common.habitica.extensions.setupCoil
|
||||
import com.habitrpg.common.habitica.helpers.MarkdownParser
|
||||
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
|
@ -11,5 +12,6 @@ class MainApplication : Application() {
|
|||
super.onCreate()
|
||||
HabiticaIconsHelper.init(this)
|
||||
MarkdownParser.setup(this)
|
||||
setupCoil()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.habitrpg.wearos.habitica.data
|
||||
|
||||
import android.content.Context
|
||||
import com.amplitude.api.Amplitude
|
||||
import com.habitrpg.common.habitica.BuildConfig
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.common.habitica.api.Server
|
||||
|
|
@ -85,6 +86,12 @@ class ApiClient @Inject constructor(
|
|||
this.apiService = retrofitAdapter.create(ApiService::class.java)
|
||||
}
|
||||
|
||||
fun updateAuthenticationCredentials(userID: String?, apiToken: String?) {
|
||||
this.hostConfig.userID = userID ?: ""
|
||||
this.hostConfig.apiKey = apiToken ?: ""
|
||||
Amplitude.getInstance().userId = this.hostConfig.userID
|
||||
}
|
||||
|
||||
private fun <T> process(response: WearableHabitResponse<T>): T? {
|
||||
return response.data
|
||||
}
|
||||
|
|
@ -104,4 +111,5 @@ class ApiClient @Inject constructor(
|
|||
|
||||
suspend fun getTasks() = process(apiService.getTasks())
|
||||
suspend fun scoreTask(id: String, direction: String) = process(apiService.scoreTask(id, direction))
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import androidx.preference.PreferenceManager
|
|||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.common.habitica.helpers.KeyHelper
|
||||
import com.habitrpg.shared.habitica.HLogger
|
||||
import com.habitrpg.wearos.habitica.BuildConfig
|
||||
import com.habitrpg.wearos.habitica.data.ApiClient
|
||||
import com.habitrpg.wearos.habitica.data.AttributeAdapter
|
||||
import com.habitrpg.wearos.habitica.data.FrequencyAdapter
|
||||
|
|
@ -39,8 +38,7 @@ class AppModule {
|
|||
keyHelper: KeyHelper?,
|
||||
@ApplicationContext context: Context
|
||||
): HostConfig {
|
||||
return HostConfig(BuildConfig.DEBUG_USER_ID,
|
||||
BuildConfig.DEBUG_API_KEY)
|
||||
return HostConfig(sharedPreferences, keyHelper, context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -28,16 +28,23 @@ class AvatarActivity: BaseActivity<ActivityAvatarBinding, AvatarViewModel>() {
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
scaleAvatar()
|
||||
}
|
||||
|
||||
private fun scaleAvatar() {
|
||||
val params = binding.root.layoutParams as FrameLayout.LayoutParams
|
||||
val maxSize = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
max(windowManager.currentWindowMetrics.bounds.bottom, windowManager.currentWindowMetrics.bounds.right)
|
||||
max(
|
||||
windowManager.currentWindowMetrics.bounds.bottom,
|
||||
windowManager.currentWindowMetrics.bounds.right
|
||||
)
|
||||
} else {
|
||||
max(windowManager.defaultDisplay.width, windowManager.defaultDisplay.height)
|
||||
}
|
||||
var factor = (maxSize / 46f) / 3f
|
||||
var viewSize = 138 * factor.roundToInt()
|
||||
if (maxSize - viewSize > 20.dpToPx(this)) {
|
||||
viewSize += 45
|
||||
viewSize += 46
|
||||
factor += 1
|
||||
}
|
||||
params.width = viewSize
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package com.habitrpg.wearos.habitica.ui.activities
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
|
||||
import com.habitrpg.wearos.habitica.R
|
||||
import com.habitrpg.wearos.habitica.databinding.ActivityLoginBinding
|
||||
import com.habitrpg.wearos.habitica.ui.viewmodels.LoginViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LoginActivity: BaseActivity<ActivityLoginBinding, LoginViewModel>() {
|
||||
override val viewModel: LoginViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding.loginButton.setOnClickListener { loginLocal() }
|
||||
binding.googleLoginButton.setOnClickListener { loginGoogle() }
|
||||
binding.registerButton.setOnClickListener { openRegisterOnPhone() }
|
||||
}
|
||||
|
||||
private fun openRegisterOnPhone() {
|
||||
|
||||
}
|
||||
|
||||
private fun loginLocal() {
|
||||
val username: String = binding.usernameEditText.text.toString().trim { it <= ' ' }
|
||||
val password: String = binding.passwordEditText.text.toString()
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
showValidationError(getString(R.string.login_validation_error_fieldsmissing))
|
||||
return
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val result = viewModel.login(username, password)
|
||||
if (result != null) {
|
||||
handleAuthResponse(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginGoogle() {
|
||||
viewModel.handleGoogleLogin(this, pickAccountResult)
|
||||
}
|
||||
|
||||
private fun handleAuthResponse(response: UserAuthResponse) {
|
||||
viewModel.handleAuthResponse(response)
|
||||
lifecycleScope.launch {
|
||||
viewModel.retrieveUser()
|
||||
startMainActivity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showValidationError(message: String) {
|
||||
val alert = AlertDialog.Builder(this).create()
|
||||
alert.setTitle(R.string.login_validation_error_title)
|
||||
alert.setMessage(message)
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.ok)) { alert, _ ->
|
||||
alert.dismiss()
|
||||
}
|
||||
alert.show()
|
||||
}
|
||||
|
||||
private val pickAccountResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
viewModel.googleEmail = it?.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
|
||||
viewModel.handleGoogleLoginResult(this, recoverFromPlayServicesErrorResult) { response ->
|
||||
if (response != null) {
|
||||
handleAuthResponse(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val recoverFromPlayServicesErrorResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode != Activity.RESULT_CANCELED) {
|
||||
viewModel.handleGoogleLoginResult(this, null) { response ->
|
||||
if (response != null) {
|
||||
handleAuthResponse(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startMainActivity() {
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,9 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
|
|||
WearableLinearLayoutManager(this@MainActivity, HabiticaScrollingLayoutCallback())
|
||||
adapter = this@MainActivity.adapter
|
||||
}
|
||||
if (!viewModel.isAuthenticated) {
|
||||
openLoginActivity()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
@ -123,6 +126,12 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
|
|||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
private fun openLoginActivity() {
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun openTasklist(type: TaskType) {
|
||||
val intent = Intent(this, TaskListActivity::class.java).apply {
|
||||
putExtra("task_type", type.value)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
|
||||
import com.habitrpg.wearos.habitica.models.DisplayedError
|
||||
import com.habitrpg.wearos.habitica.models.User
|
||||
import com.habitrpg.wearos.habitica.util.ErrorPresenter
|
||||
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
|
||||
|
||||
|
|
@ -11,5 +12,9 @@ open class BaseViewModel(
|
|||
val userRepository: UserRepository,
|
||||
val exceptionBuilder: ExceptionHandlerBuilder
|
||||
): ViewModel(), ErrorPresenter {
|
||||
suspend fun retrieveUser(): User? {
|
||||
return userRepository.retrieveUser()
|
||||
}
|
||||
|
||||
override val errorValues = MutableLiveData<DisplayedError>()
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
package com.habitrpg.wearos.habitica.ui.viewmodels
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.gms.auth.GoogleAuthException
|
||||
import com.google.android.gms.auth.GoogleAuthUtil
|
||||
import com.google.android.gms.auth.GooglePlayServicesAvailabilityException
|
||||
import com.google.android.gms.auth.UserRecoverableAuthException
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.common.GooglePlayServicesUtil
|
||||
import com.google.android.gms.common.Scopes
|
||||
import com.google.android.gms.common.UserRecoverableException
|
||||
import com.habitrpg.common.habitica.helpers.KeyHelper
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuth
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
|
||||
import com.habitrpg.common.habitica.models.auth.UserAuthSocial
|
||||
import com.habitrpg.wearos.habitica.data.ApiClient
|
||||
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
|
||||
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(userRepository: UserRepository,
|
||||
exceptionBuilder: ExceptionHandlerBuilder,
|
||||
val keyHelper: KeyHelper?,
|
||||
val sharedPreferences: SharedPreferences,
|
||||
val apiClient: ApiClient
|
||||
) : BaseViewModel(userRepository, exceptionBuilder) {
|
||||
var googleEmail: String? = null
|
||||
|
||||
fun handleGoogleLogin(
|
||||
activity: Activity,
|
||||
pickAccountResult: ActivityResultLauncher<Intent>
|
||||
) {
|
||||
if (!checkPlayServices(activity)) {
|
||||
return
|
||||
}
|
||||
val accountTypes = arrayOf("com.google")
|
||||
val intent = AccountManager.newChooseAccountIntent(
|
||||
null, null,
|
||||
accountTypes, true, null, null, null, null
|
||||
)
|
||||
try {
|
||||
pickAccountResult.launch(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
/*val alert = AlertDialog.Builder(activity).create()
|
||||
alert.setTitle(R.string.authentication_error_title)
|
||||
alert.setMessage(R.string.google_services_missing)
|
||||
alert.addCloseButton()
|
||||
alert.show()*/
|
||||
}
|
||||
}
|
||||
|
||||
fun handleGoogleLoginResult(
|
||||
activity: Activity,
|
||||
recoverFromPlayServicesErrorResult: ActivityResultLauncher<Intent>?,
|
||||
onSuccess: (UserAuthResponse?) -> Unit
|
||||
) {
|
||||
val scopesString = Scopes.PROFILE + " " + Scopes.EMAIL
|
||||
val scopes = "oauth2:$scopesString"
|
||||
viewModelScope.launch {
|
||||
val token = try {
|
||||
GoogleAuthUtil.getToken(activity, googleEmail ?: "", scopes)
|
||||
} catch (e: IOException) {
|
||||
return@launch
|
||||
} catch (e: GoogleAuthException) {
|
||||
if (recoverFromPlayServicesErrorResult != null) {
|
||||
handleGoogleAuthException(e, activity, recoverFromPlayServicesErrorResult)
|
||||
}
|
||||
return@launch
|
||||
} catch (e: UserRecoverableException) {
|
||||
return@launch
|
||||
}
|
||||
val response = apiClient.loginSocial(UserAuthSocial())
|
||||
onSuccess(response)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGoogleAuthException(
|
||||
e: Exception,
|
||||
activity: Activity,
|
||||
recoverFromPlayServicesErrorResult: ActivityResultLauncher<Intent>
|
||||
) {
|
||||
if (e is GooglePlayServicesAvailabilityException) {
|
||||
GoogleApiAvailability.getInstance()
|
||||
GooglePlayServicesUtil.showErrorDialogFragment(
|
||||
e.connectionStatusCode,
|
||||
activity,
|
||||
null,
|
||||
REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR
|
||||
) {
|
||||
}
|
||||
return
|
||||
} else if (e is UserRecoverableAuthException) {
|
||||
// Unable to authenticate, such as when the user has not yet granted
|
||||
// the app access to the account, but the user can fix this.
|
||||
// Forward the user to an activity in Google Play services.
|
||||
val intent = e.intent
|
||||
recoverFromPlayServicesErrorResult.launch(intent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPlayServices(activity: Activity): Boolean {
|
||||
val googleAPI = GoogleApiAvailability.getInstance()
|
||||
val result = googleAPI.isGooglePlayServicesAvailable(activity)
|
||||
if (result != ConnectionResult.SUCCESS) {
|
||||
if (googleAPI.isUserResolvableError(result)) {
|
||||
googleAPI.getErrorDialog(
|
||||
activity, result,
|
||||
PLAY_SERVICES_RESOLUTION_REQUEST
|
||||
)?.show()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun handleAuthResponse(userAuthResponse: UserAuthResponse) {
|
||||
try {
|
||||
saveTokens(userAuthResponse.apiToken, userAuthResponse.id)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun saveTokens(api: String, user: String) {
|
||||
this.apiClient.updateAuthenticationCredentials(user, api)
|
||||
sharedPreferences.edit {
|
||||
putString("UserID", user)
|
||||
val encryptedKey =
|
||||
try {
|
||||
keyHelper?.encrypt(api)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
if ((encryptedKey?.length ?: 0) > 5) {
|
||||
putString(user, encryptedKey)
|
||||
} else {
|
||||
// Something might have gone wrong with encryption, so fall back to this.
|
||||
putString("APIToken", api)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun login(username: String, password: String): UserAuthResponse? {
|
||||
return apiClient.loginLocal(UserAuth(username, password))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001
|
||||
private const val PLAY_SERVICES_RESOLUTION_REQUEST = 9000
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.habitrpg.wearos.habitica.ui.viewmodels
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.habitrpg.common.habitica.api.HostConfig
|
||||
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
|
||||
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
|
||||
import com.habitrpg.wearos.habitica.models.User
|
||||
|
|
@ -13,10 +14,15 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
val hostConfig: HostConfig,
|
||||
userRepository: UserRepository,
|
||||
val taskRepository: TaskRepository,
|
||||
exceptionBuilder: ExceptionHandlerBuilder
|
||||
) : BaseViewModel(userRepository, exceptionBuilder) {
|
||||
val isAuthenticated: Boolean
|
||||
get() {
|
||||
return hostConfig.hasAuthentication()
|
||||
}
|
||||
var user: LiveData<User>
|
||||
|
||||
init {
|
||||
|
|
|
|||
48
wearos/src/main/res/layout/activity_login.xml
Normal file
48
wearos/src/main/res/layout/activity_login.xml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.wear.widget.BoxInsetLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_boxedEdges="all">
|
||||
<EditText
|
||||
android:id="@+id/username_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textEmailAddress"
|
||||
android:autofillHints="username" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/password_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:autofillHints="password"/>
|
||||
<Button
|
||||
android:id="@+id/login_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_btn"/>
|
||||
<Button
|
||||
android:id="@+id/google_login_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_btn_google"
|
||||
android:layout_marginTop="@dimen/spacing_small"/>
|
||||
<Button
|
||||
android:id="@+id/register_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/register_btn"
|
||||
android:layout_marginTop="@dimen/spacing_large"/>
|
||||
</LinearLayout>
|
||||
</androidx.wear.widget.BoxInsetLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
<resources>
|
||||
<string name="app_name">Habitica</string>
|
||||
<string name="ok">OK</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue