diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index f598bc9f6..10e95789d 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -37,21 +37,6 @@
Your Quest has Begun
Invited to Quest
-
- Edit
- Cancel
- Login
- Register
- Username
- Email or Username
- Password
- Email address
- Confirm password
-
- Logout
- Log out of your account
- Welcome
- About
Libraries
Habitica is available as open source software on Github
Rate our App
@@ -66,10 +51,6 @@
Authentication Error
Your Username and/or Password was incorrect.
-
- Validation Error
- You have to fill out all fields.
-
Save
Copy
Notes
@@ -470,7 +451,6 @@
Owned
Not owned
Login with Facebook
- Login with Google
Sign in with Apple
Sign up with Facebook
Sign up with Google
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
index 6619e1632..84e9a66da 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -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)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
index a99038881..3a95056f0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
@@ -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
diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt
new file mode 100644
index 000000000..8e0f89bbb
--- /dev/null
+++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/Application-Extensions.kt
@@ -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())
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/habitrpg/common/habitica/models/auth/UserAuth.kt b/common/src/main/java/com/habitrpg/common/habitica/models/auth/UserAuth.kt
index 5a5507373..2c8a2fa74 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/models/auth/UserAuth.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/models/auth/UserAuth.kt
@@ -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
+) {
}
\ No newline at end of file
diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
index 7d0ee5570..9c1e2ad1f 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
@@ -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) {
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 2549edd4e..4567eeb63 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -23,4 +23,24 @@
Settings
New Task
Avatar
+
+ Edit
+ Cancel
+ Login
+ Register
+ Username
+ Email or Username
+ Password
+ Email address
+ Confirm password
+ Login with Google
+
+ Logout
+ Log out of your account
+ Welcome
+ About
+
+ Validation Error
+ You have to fill out all fields.
+
\ No newline at end of file
diff --git a/wearos/build.gradle b/wearos/build.gradle
index 05a13943c..2c869020d 100644
--- a/wearos/build.gradle
+++ b/wearos/build.gradle
@@ -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 {
diff --git a/wearos/src/main/AndroidManifest.xml b/wearos/src/main/AndroidManifest.xml
index da04116cc..f9ca176b6 100644
--- a/wearos/src/main/AndroidManifest.xml
+++ b/wearos/src/main/AndroidManifest.xml
@@ -35,6 +35,8 @@
+
+
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/MainApplication.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/MainApplication.kt
index db13fc1cb..2a1938abd 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/MainApplication.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/MainApplication.kt
@@ -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()
}
}
\ No newline at end of file
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/data/ApiClient.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/data/ApiClient.kt
index c41a84c8b..8791076d0 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/data/ApiClient.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/data/ApiClient.kt
@@ -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 process(response: WearableHabitResponse): 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))
+
}
\ No newline at end of file
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/modules/AppModule.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/modules/AppModule.kt
index 3693535b8..0ce45f8fd 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/modules/AppModule.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/modules/AppModule.kt
@@ -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
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/AvatarActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/AvatarActivity.kt
index 273bbf910..552da78d9 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/AvatarActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/AvatarActivity.kt
@@ -28,16 +28,23 @@ class AvatarActivity: BaseActivity() {
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
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt
new file mode 100644
index 000000000..fa2a06dcd
--- /dev/null
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt
@@ -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() {
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
index c2175a0b8..b6151d24f 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
@@ -29,6 +29,9 @@ class MainActivity : BaseActivity() {
WearableLinearLayoutManager(this@MainActivity, HabiticaScrollingLayoutCallback())
adapter = this@MainActivity.adapter
}
+ if (!viewModel.isAuthenticated) {
+ openLoginActivity()
+ }
}
override fun onStart() {
@@ -123,6 +126,12 @@ class MainActivity : BaseActivity() {
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)
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/BaseViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/BaseViewModel.kt
index 2130e1980..3ed83df52 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/BaseViewModel.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/BaseViewModel.kt
@@ -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()
}
\ No newline at end of file
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt
new file mode 100644
index 000000000..78a8f7170
--- /dev/null
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt
@@ -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
+ ) {
+ 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?,
+ 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
+ ) {
+ 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
+ }
+}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/MainViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/MainViewModel.kt
index ae7f66e65..82ab2d183 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/MainViewModel.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/MainViewModel.kt
@@ -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
init {
diff --git a/wearos/src/main/res/layout/activity_login.xml b/wearos/src/main/res/layout/activity_login.xml
new file mode 100644
index 000000000..d5d7f8c1e
--- /dev/null
+++ b/wearos/src/main/res/layout/activity_login.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wearos/src/main/res/values/strings.xml b/wearos/src/main/res/values/strings.xml
index 596a90594..2b8f5ad0d 100644
--- a/wearos/src/main/res/values/strings.xml
+++ b/wearos/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
Habitica
+ OK
\ No newline at end of file