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 @@ + + + + + + + +