mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-17 03:22:04 +00:00
store API key encrypted
This commit is contained in:
parent
4e5f092869
commit
49c7795be5
37 changed files with 429 additions and 55 deletions
18
Habitica/res/layout/dialog_achievement_detail.xml
Normal file
18
Habitica/res/layout/dialog_achievement_detail.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal">
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/icon_view"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="52dp"
|
||||
android:scaleType="center"
|
||||
android:layout_marginBottom="@dimen/spacing_large"/>
|
||||
<TextView
|
||||
android:id="@+id/description_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Body2"
|
||||
android:gravity="center_horizontal"/>
|
||||
</LinearLayout>
|
||||
|
|
@ -244,7 +244,7 @@ abstract class HabiticaBaseApplication : MultiDexApplication() {
|
|||
}
|
||||
|
||||
fun checkUserAuthentication(context: Context, hostConfig: HostConfig?): Boolean {
|
||||
if (hostConfig?.api == null || hostConfig.api == "" || hostConfig.user == "") {
|
||||
if (hostConfig?.apiKey == null || hostConfig.apiKey == "" || hostConfig.userID == "") {
|
||||
startActivity(IntroActivity::class.java, context)
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.habitrpg.android.habitica.api
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
|
||||
import androidx.core.content.edit
|
||||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.helpers.KeyHelper
|
||||
|
||||
|
||||
/**
|
||||
* The configuration of the host<br></br>
|
||||
|
|
@ -16,11 +17,10 @@ import com.habitrpg.android.habitica.R
|
|||
class HostConfig {
|
||||
var address: String
|
||||
var port: String
|
||||
var api: String
|
||||
var user: String
|
||||
var apiKey: String
|
||||
var userID: String
|
||||
|
||||
constructor(sharedPreferences: SharedPreferences, context: Context) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
constructor(sharedPreferences: SharedPreferences, keyHelper: KeyHelper?, context: Context) {
|
||||
this.port = BuildConfig.PORT
|
||||
if (BuildConfig.DEBUG) {
|
||||
this.address = BuildConfig.BASE_URL
|
||||
|
|
@ -32,19 +32,41 @@ class HostConfig {
|
|||
this.address = context.getString(R.string.base_url)
|
||||
}
|
||||
}
|
||||
this.api = prefs.getString("APIToken", null) ?: ""
|
||||
this.user = prefs.getString(context.getString(R.string.SP_userID), null) ?: ""
|
||||
this.userID = sharedPreferences.getString(context.getString(R.string.SP_userID), null) ?: ""
|
||||
|
||||
this.apiKey = loadAPIKey(sharedPreferences, keyHelper)
|
||||
}
|
||||
|
||||
private fun loadAPIKey(sharedPreferences: SharedPreferences, keyHelper: KeyHelper?): String {
|
||||
return if (sharedPreferences.contains(userID)) {
|
||||
val encryptedKey = sharedPreferences.getString(userID, null)
|
||||
if (encryptedKey?.isNotBlank() == true) {
|
||||
keyHelper?.decrypt(encryptedKey)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
val key = sharedPreferences.getString("APIToken", null)
|
||||
if (key?.isNotBlank() == true) {
|
||||
val encryptedKey = keyHelper?.encrypt(key)
|
||||
sharedPreferences.edit {
|
||||
putString(userID, encryptedKey)
|
||||
remove("APIToken")
|
||||
}
|
||||
}
|
||||
key
|
||||
} ?: ""
|
||||
}
|
||||
|
||||
constructor(address: String, port: String, api: String, user: String) {
|
||||
this.address = address
|
||||
this.port = port
|
||||
this.api = api
|
||||
this.user = user
|
||||
this.apiKey = api
|
||||
this.userID = user
|
||||
}
|
||||
|
||||
fun hasAuthentication(): Boolean {
|
||||
return user.isNotEmpty() && api.isNotEmpty()
|
||||
return userID.isNotEmpty() && apiKey.isNotEmpty()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
|
|||
this.popupNotificationsManager.setApiClient(this)
|
||||
|
||||
HabiticaBaseApplication.component?.inject(this)
|
||||
crashlyticsProxy.setUserIdentifier(this.hostConfig.user)
|
||||
crashlyticsProxy.setUserName(this.hostConfig.user)
|
||||
Amplitude.getInstance().userId = this.hostConfig.user
|
||||
crashlyticsProxy.setUserIdentifier(this.hostConfig.userID)
|
||||
crashlyticsProxy.setUserName(this.hostConfig.userID)
|
||||
Amplitude.getInstance().userId = this.hostConfig.userID
|
||||
buildRetrofit()
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +115,8 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
|
|||
var builder: Request.Builder = original.newBuilder()
|
||||
if (this.hostConfig.hasAuthentication()) {
|
||||
builder = builder
|
||||
.header("x-api-key", this.hostConfig.api)
|
||||
.header("x-api-user", this.hostConfig.user)
|
||||
.header("x-api-key", this.hostConfig.apiKey)
|
||||
.header("x-api-user", this.hostConfig.userID)
|
||||
}
|
||||
builder = builder.header("x-client", "habitica-android")
|
||||
.header("x-user-timezoneOffset", timezoneOffset.toString())
|
||||
|
|
@ -256,7 +256,7 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
|
|||
}
|
||||
|
||||
override fun hasAuthenticationKeys(): Boolean {
|
||||
return this.hostConfig.user.isNotEmpty() && hostConfig.api.isNotEmpty()
|
||||
return this.hostConfig.userID.isNotEmpty() && hostConfig.apiKey.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun showConnectionProblemDialog(resourceMessageString: Int) {
|
||||
|
|
@ -282,11 +282,11 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
|
|||
}
|
||||
|
||||
override fun updateAuthenticationCredentials(userID: String?, apiToken: String?) {
|
||||
this.hostConfig.user = userID ?: ""
|
||||
this.hostConfig.api = apiToken ?: ""
|
||||
crashlyticsProxy.setUserIdentifier(this.hostConfig.user)
|
||||
crashlyticsProxy.setUserName(this.hostConfig.user)
|
||||
Amplitude.getInstance().userId = this.hostConfig.user
|
||||
this.hostConfig.userID = userID ?: ""
|
||||
this.hostConfig.apiKey = apiToken ?: ""
|
||||
crashlyticsProxy.setUserIdentifier(this.hostConfig.userID)
|
||||
crashlyticsProxy.setUserName(this.hostConfig.userID)
|
||||
Amplitude.getInstance().userId = this.hostConfig.userID
|
||||
}
|
||||
|
||||
override fun setLanguageCode(languageCode: String) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.habitrpg.android.habitica.extensions
|
|||
|
||||
import android.content.DialogInterface
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
|
||||
fun HabiticaAlertDialog.addOkButton(isPrimary: Boolean = true, listener: ((HabiticaAlertDialog, Int) -> Unit)? = null) {
|
||||
this.addButton(R.string.ok, isPrimary, listener)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
package com.habitrpg.android.habitica.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.security.KeyPairGeneratorSpec
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import androidx.core.content.edit
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.util.*
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import javax.security.cert.CertificateException
|
||||
|
||||
|
||||
class KeyHelper @Throws(NoSuchPaddingException::class, NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class)
|
||||
constructor(ctx: Context, var sharedPreferences: SharedPreferences, var keyStore: KeyStore) {
|
||||
|
||||
private val aesKeyFromKS: Key?
|
||||
@Throws(NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class, UnrecoverableKeyException::class)
|
||||
get() {
|
||||
keyStore.load(null)
|
||||
return keyStore.getKey(KEY_ALIAS, null) as? SecretKey
|
||||
}
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
this.generateEncryptKey(ctx)
|
||||
}
|
||||
this.generateRandomIV(sharedPreferences)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
try {
|
||||
this.generateAESKey()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(NoSuchProviderException::class, NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class, KeyStoreException::class, CertificateException::class, IOException::class)
|
||||
private fun generateEncryptKey(ctx: Context) {
|
||||
keyStore.load(null)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (!keyStore.containsAlias(KEY_ALIAS)) {
|
||||
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore)
|
||||
keyGenerator.init(
|
||||
KeyGenParameterSpec.Builder(KEY_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setRandomizedEncryptionRequired(false)
|
||||
.build())
|
||||
keyGenerator.generateKey()
|
||||
}
|
||||
} else {
|
||||
if (!keyStore.containsAlias(KEY_ALIAS)) {
|
||||
// Generate a key pair for encryption
|
||||
val start = Calendar.getInstance()
|
||||
val end = Calendar.getInstance()
|
||||
end.add(Calendar.YEAR, 30)
|
||||
val spec = KeyPairGeneratorSpec.Builder(ctx)
|
||||
.setAlias(KEY_ALIAS)
|
||||
.setSubject(X500Principal("CN=$KEY_ALIAS"))
|
||||
.setSerialNumber(BigInteger.TEN)
|
||||
.setStartDate(start.time)
|
||||
.setEndDate(end.time)
|
||||
.build()
|
||||
val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, AndroidKeyStore)
|
||||
kpg.initialize(spec)
|
||||
kpg.generateKeyPair()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun rsaEncrypt(secret: ByteArray): ByteArray {
|
||||
val privateKeyEntry = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
|
||||
// Encrypt the text
|
||||
val inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL")
|
||||
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry?.certificate?.publicKey)
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val cipherOutputStream = CipherOutputStream(outputStream, inputCipher)
|
||||
cipherOutputStream.write(secret)
|
||||
cipherOutputStream.close()
|
||||
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun rsaDecrypt(encrypted: ByteArray): ByteArray {
|
||||
val privateKeyEntry = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.PrivateKeyEntry
|
||||
val output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL")
|
||||
output.init(Cipher.DECRYPT_MODE, privateKeyEntry?.privateKey)
|
||||
val cipherInputStream = CipherInputStream(
|
||||
ByteArrayInputStream(encrypted), output)
|
||||
return cipherInputStream.readBytes()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun generateAESKey() {
|
||||
var enryptedKeyB64 = sharedPreferences.getString(ENCRYPTED_KEY, null)
|
||||
if (enryptedKeyB64 == null) {
|
||||
val key = ByteArray(16)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(key)
|
||||
val encryptedKey = rsaEncrypt(key)
|
||||
enryptedKeyB64 = Base64.encodeToString(encryptedKey, Base64.DEFAULT)
|
||||
sharedPreferences.edit {
|
||||
putString(ENCRYPTED_KEY, enryptedKeyB64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun getSecretKey(): Key {
|
||||
val enryptedKeyB64 = sharedPreferences.getString(ENCRYPTED_KEY, null)
|
||||
|
||||
val encryptedKey = Base64.decode(enryptedKeyB64, Base64.DEFAULT)
|
||||
val key = rsaDecrypt(encryptedKey)
|
||||
return SecretKeySpec(key, "AES")
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, NoSuchProviderException::class, BadPaddingException::class, IllegalBlockSizeException::class, UnsupportedEncodingException::class)
|
||||
fun encrypt(input: String): String {
|
||||
val c: Cipher
|
||||
val publicIV = sharedPreferences.getString(PUBLIC_IV, null)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
c = Cipher.getInstance(AES_MODE_M)
|
||||
try {
|
||||
c.init(Cipher.ENCRYPT_MODE, aesKeyFromKS, GCMParameterSpec(128, Base64.decode(publicIV, Base64.DEFAULT)))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
c = Cipher.getInstance(AES_MODE_M)
|
||||
try {
|
||||
c.init(Cipher.ENCRYPT_MODE, getSecretKey(), GCMParameterSpec(128, Base64.decode(publicIV, Base64.DEFAULT)))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
val encodedBytes = c.doFinal(input.toByteArray(charset("UTF-8")))
|
||||
return Base64.encodeToString(encodedBytes, Base64.DEFAULT)
|
||||
}
|
||||
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, NoSuchProviderException::class, BadPaddingException::class, IllegalBlockSizeException::class, UnsupportedEncodingException::class)
|
||||
fun decrypt(encrypted: String): String {
|
||||
val c: Cipher
|
||||
val publicIV = sharedPreferences.getString(PUBLIC_IV, null)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
c = Cipher.getInstance(AES_MODE_M)
|
||||
try {
|
||||
c.init(Cipher.DECRYPT_MODE, aesKeyFromKS, GCMParameterSpec(128, Base64.decode(publicIV, Base64.DEFAULT)))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
c = Cipher.getInstance(AES_MODE_M)
|
||||
try {
|
||||
c.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, Base64.decode(publicIV, Base64.DEFAULT)))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val decodedValue = Base64.decode(encrypted.toByteArray(charset("UTF-8")), Base64.DEFAULT)
|
||||
val decryptedVal = c.doFinal(decodedValue)
|
||||
return String(decryptedVal)
|
||||
}
|
||||
|
||||
private fun generateRandomIV(sharedPreferences: SharedPreferences) {
|
||||
val publicIV = sharedPreferences.getString(PUBLIC_IV, null)
|
||||
|
||||
if (publicIV == null) {
|
||||
val random = SecureRandom()
|
||||
val generated = random.generateSeed(12)
|
||||
val generatedIVstr = Base64.encodeToString(generated, Base64.DEFAULT)
|
||||
sharedPreferences.edit {
|
||||
putString(PUBLIC_IV, generatedIVstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStringFromSharedPrefs(key: String): String? {
|
||||
return sharedPreferences.getString(key, null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
private const val RSA_MODE = "RSA/ECB/PKCS1Padding"
|
||||
private const val AES_MODE_M = "AES/GCM/NoPadding"
|
||||
|
||||
private const val KEY_ALIAS = "KEY"
|
||||
private const val AndroidKeyStore = "AndroidKeyStore"
|
||||
const val ENCRYPTED_KEY = "ENCRYPTED_KEY"
|
||||
const val PUBLIC_IV = "PUBLIC_IV"
|
||||
private var keyHelper: KeyHelper? = null
|
||||
|
||||
fun getInstance(ctx: Context, sharedPreferences: SharedPreferences, keyStore: KeyStore): KeyHelper? {
|
||||
if (keyHelper == null) {
|
||||
try {
|
||||
keyHelper = KeyHelper(ctx, sharedPreferences, keyStore)
|
||||
} catch (e: NoSuchPaddingException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchProviderException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvalidAlgorithmParameterException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: KeyStoreException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: CertificateException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
return keyHelper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import com.habitrpg.android.habitica.helpers.SoundManager
|
|||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import com.habitrpg.android.habitica.api.HostConfig;
|
|||
import com.habitrpg.android.habitica.api.MaintenanceApiService;
|
||||
import com.habitrpg.android.habitica.data.ApiClient;
|
||||
import com.habitrpg.android.habitica.data.implementation.ApiClientImpl;
|
||||
import com.habitrpg.android.habitica.helpers.KeyHelper;
|
||||
import com.habitrpg.android.habitica.helpers.PopupNotificationsManager;
|
||||
import com.habitrpg.android.habitica.proxy.CrashlyticsProxy;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
|
|
@ -23,8 +25,8 @@ public class ApiModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
public HostConfig providesHostConfig(SharedPreferences sharedPreferences, Context context) {
|
||||
return new HostConfig(sharedPreferences, context);
|
||||
public HostConfig providesHostConfig(SharedPreferences sharedPreferences, @Nullable KeyHelper keyHelper, Context context) {
|
||||
return new HostConfig(sharedPreferences, keyHelper, context);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.app.Application;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.habitrpg.android.habitica.R;
|
||||
|
|
@ -14,12 +15,17 @@ import com.habitrpg.android.habitica.executors.PostExecutionThread;
|
|||
import com.habitrpg.android.habitica.executors.ThreadExecutor;
|
||||
import com.habitrpg.android.habitica.executors.UIThread;
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager;
|
||||
import com.habitrpg.android.habitica.helpers.KeyHelper;
|
||||
import com.habitrpg.android.habitica.helpers.SoundFileLoader;
|
||||
import com.habitrpg.android.habitica.helpers.SoundManager;
|
||||
import com.habitrpg.android.habitica.helpers.TaskAlarmManager;
|
||||
import com.habitrpg.android.habitica.helpers.TaskFilterHelper;
|
||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
|
|
@ -49,6 +55,26 @@ public class AppModule {
|
|||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Nullable
|
||||
KeyStore provideKeyStore() {
|
||||
try {
|
||||
return KeyStore.getInstance("AndroidKeyStore");
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Nullable
|
||||
KeyHelper provideKeyHelper(Context context, SharedPreferences sharedPreferences, @Nullable KeyStore keyStore) {
|
||||
if (keyStore == null) {
|
||||
return null;
|
||||
}
|
||||
return KeyHelper.Companion.getInstance(context, sharedPreferences, keyStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named(NAMED_USER_ID)
|
||||
public String providesUserID(SharedPreferences sharedPreferences) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import android.util.Log;
|
|||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog;
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
|
|||
import com.habitrpg.android.habitica.components.AppComponent
|
||||
import com.habitrpg.android.habitica.events.ShowConnectionProblemEvent
|
||||
import com.habitrpg.android.habitica.helpers.LanguageHelper
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import com.habitrpg.android.habitica.models.user.User
|
|||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
|||
import com.habitrpg.android.habitica.models.user.*
|
||||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import io.reactivex.functions.Consumer
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
|||
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
|
||||
import io.reactivex.Flowable
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import com.habitrpg.android.habitica.proxy.CrashlyticsProxy
|
|||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.social.UsernameLabel
|
||||
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
|
|
@ -44,12 +44,13 @@ import com.habitrpg.android.habitica.extensions.addCloseButton
|
|||
import com.habitrpg.android.habitica.extensions.addOkButton
|
||||
import com.habitrpg.android.habitica.extensions.notNull
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.KeyHelper
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.auth.UserAuthResponse
|
||||
import com.habitrpg.android.habitica.prefs.scanner.IntentIntegrator
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.login.LockableScrollView
|
||||
import com.habitrpg.android.habitica.ui.views.login.LoginBackgroundView
|
||||
import io.reactivex.Flowable
|
||||
|
|
@ -75,6 +76,9 @@ class LoginActivity : BaseActivity(), Consumer<UserAuthResponse> {
|
|||
lateinit var hostConfig: HostConfig
|
||||
@Inject
|
||||
internal lateinit var userRepository: UserRepository
|
||||
@Inject
|
||||
@JvmField
|
||||
var keyHelper: KeyHelper? = null
|
||||
|
||||
private var isRegistering: Boolean = false
|
||||
private var isShowingForm: Boolean = false
|
||||
|
|
@ -302,7 +306,11 @@ class LoginActivity : BaseActivity(), Consumer<UserAuthResponse> {
|
|||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
prefs.edit {
|
||||
putString(getString(R.string.SP_address), adr)
|
||||
putString(getString(R.string.SP_APIToken), key)
|
||||
if (keyHelper != null) {
|
||||
putString(user, keyHelper?.encrypt(key))
|
||||
} else {
|
||||
putString(getString(R.string.SP_APIToken), key)
|
||||
}
|
||||
putString(getString(R.string.SP_userID), user)
|
||||
}
|
||||
startMainActivity()
|
||||
|
|
@ -337,7 +345,11 @@ class LoginActivity : BaseActivity(), Consumer<UserAuthResponse> {
|
|||
private fun saveTokens(api: String, user: String) {
|
||||
this.apiClient.updateAuthenticationCredentials(user, api)
|
||||
sharedPrefs.edit {
|
||||
putString(getString(R.string.SP_APIToken), api)
|
||||
if (keyHelper != null) {
|
||||
putString(user, keyHelper?.encrypt(api))
|
||||
} else {
|
||||
putString(getString(R.string.SP_APIToken), api)
|
||||
}
|
||||
putString(getString(R.string.SP_userID), user)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
|||
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
|
||||
|
|
@ -206,7 +206,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
|
|||
val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics)
|
||||
avatarWithBars.setPadding(px.toInt(), statusBarHeight, px.toInt(), 0)
|
||||
|
||||
compositeSubscription.add(userRepository.getUser(hostConfig.user)
|
||||
compositeSubscription.add(userRepository.getUser(hostConfig.userID)
|
||||
.subscribe(Consumer { newUser ->
|
||||
this@MainActivity.user = newUser
|
||||
this@MainActivity.setUserData()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
compositeSubscription.add(userRepository.getUser(hostConfig.user)
|
||||
compositeSubscription.add(userRepository.getUser(hostConfig.userID)
|
||||
.subscribe(Consumer { this.onUserReceived(it) }, RxErrorHandler.handleEmptyError()))
|
||||
|
||||
val additionalData = HashMap<String, Any>()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import com.habitrpg.android.habitica.models.tasks.HabitResetOption
|
|||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.tasks.form.*
|
||||
import io.reactivex.functions.Consumer
|
||||
import io.realm.RealmList
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.models.QuestAchievement
|
|||
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.AchievementDetailDialog
|
||||
|
||||
class AchievementsAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ class AchievementsAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
}
|
||||
}
|
||||
|
||||
class AchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
class AchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
private var achievement: Achievement? = null
|
||||
|
||||
private val achievementContainer: ViewGroup? by bindOptionalView(R.id.achievement_container)
|
||||
|
|
@ -80,6 +81,10 @@ class AchievementsAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
private val achievementTitleView: TextView by bindView(R.id.achievement_title)
|
||||
private val achievementDescriptionView: TextView? by bindOptionalView(R.id.achievement_description)
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
fun bind(achievement: Achievement) {
|
||||
this.achievement = achievement
|
||||
val iconName = if (achievement.earned) {
|
||||
|
|
@ -98,6 +103,12 @@ class AchievementsAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
}
|
||||
achievementContainer?.clipToOutline = true
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
achievement?.let {
|
||||
AchievementDetailDialog(it, itemView.context).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QuestAchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.models.inventory.Customization
|
|||
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
|
||||
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import io.reactivex.BackpressureStrategy
|
||||
import io.reactivex.Flowable
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.ui.AvatarView
|
|||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
|
||||
class AchievementProfileAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class AchievementsFragment: BaseMainFragment(), SwipeRefreshLayout.OnRefreshList
|
|||
private var menuID: Int = 0
|
||||
private lateinit var adapter: AchievementsAdapter
|
||||
private val layoutManager = GridLayoutManager(activity, 2)
|
||||
private var useGridLayout = true
|
||||
private var useGridLayout = false
|
||||
set(value) {
|
||||
field = value
|
||||
adapter.useGridLayout = value
|
||||
|
|
@ -48,6 +48,9 @@ class AchievementsFragment: BaseMainFragment(), SwipeRefreshLayout.OnRefreshList
|
|||
hidesToolbar = true
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
adapter = AchievementsAdapter()
|
||||
|
||||
onRefresh()
|
||||
|
||||
return inflater.inflate(R.layout.fragment_refresh_recyclerview, container, false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.helpers.UserStatComputer
|
|||
import com.habitrpg.android.habitica.models.user.Stats
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.stats.BulkAllocateStatsDialog
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.habitrpg.android.habitica.ui.activities.GiftIAPActivity
|
|||
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionDetailsView
|
||||
import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.modules.AppModule
|
|||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.social.UsernameLabel
|
||||
import io.reactivex.functions.Consumer
|
||||
import io.realm.RealmResults
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
|||
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
|
||||
import com.habitrpg.android.habitica.ui.helpers.resetViews
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.functions.Consumer
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.habitrpg.android.habitica.models.social.Group
|
|||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.social.UsernameLabel
|
||||
import io.reactivex.functions.Consumer
|
||||
import kotlinx.android.synthetic.main.fragment_tavern_detail.*
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
|
|||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaEmojiTextView
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.android.habitica.ui.views.social.UsernameLabel
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
|||
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemRecyclerFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.*
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.social.OldQuestProgressView
|
||||
import io.reactivex.functions.Consumer
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.habitrpg.android.habitica.ui.helpers.bindView
|
|||
import com.habitrpg.android.habitica.ui.helpers.resetViews
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.functions.Consumer
|
||||
import java.util.*
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.habitrpg.android.habitica.ui.views.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.extensions.addCloseButton
|
||||
import com.habitrpg.android.habitica.models.Achievement
|
||||
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
||||
|
||||
class AchievementDetailDialog(val achievement: Achievement, context: Context): HabiticaAlertDialog(context) {
|
||||
|
||||
private var iconView: SimpleDraweeView?
|
||||
private var descriptionView: TextView?
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
|
||||
val view = inflater?.inflate(R.layout.dialog_achievement_detail, null)
|
||||
iconView = view?.findViewById(R.id.icon_view)
|
||||
descriptionView = view?.findViewById(R.id.description_view)
|
||||
setAdditionalContentView(view)
|
||||
setTitle(achievement.title)
|
||||
descriptionView?.text = achievement.text
|
||||
val iconName = if (achievement.earned) {
|
||||
achievement.icon + "2x"
|
||||
} else {
|
||||
"achievement-unearned2x"
|
||||
}
|
||||
DataBindingUtils.loadImage(iconView, iconName)
|
||||
addCloseButton(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
package com.habitrpg.android.habitica.ui.views.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -5,7 +5,7 @@ import android.view.LayoutInflater
|
|||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
|
||||
/**
|
||||
* Created by phillip on 27.09.17.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.models.shops.ShopItem
|
|||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.*
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
|
||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGoldDialog
|
||||
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
|||
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindView
|
||||
import com.habitrpg.android.habitica.ui.views.*
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
|||
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.ui.helpers.bindColor
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaAlertDialog
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaEmojiTextView
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Consumer
|
||||
|
|
|
|||
Loading…
Reference in a new issue