diff --git a/Habitica/res/layout/dialog_achievement_detail.xml b/Habitica/res/layout/dialog_achievement_detail.xml
new file mode 100644
index 000000000..bb6805767
--- /dev/null
+++ b/Habitica/res/layout/dialog_achievement_detail.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
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 7ce40d956..eb998a571 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/HostConfig.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/api/HostConfig.kt
index 95c73cfed..7fdff9ccf 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/HostConfig.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/HostConfig.kt
@@ -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
@@ -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()
}
}
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 dcbc7e4b5..25154888f 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
@@ -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) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialog-Extensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialog-Extensions.kt
index d4d420c11..e507c5a08 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialog-Extensions.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialog-Extensions.kt
@@ -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)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Keyhelper.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Keyhelper.kt
new file mode 100644
index 000000000..4f2cd0b55
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Keyhelper.kt
@@ -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
+ }
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
index 1b6aa3d75..d5c942446 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
index c4a02f188..22f584285 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java
index 741e47e60..fb9582190 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java
@@ -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) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/prefs/scanner/IntentIntegrator.java b/Habitica/src/main/java/com/habitrpg/android/habitica/prefs/scanner/IntentIntegrator.java
index 28b07caef..88456e490 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/prefs/scanner/IntentIntegrator.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/prefs/scanner/IntentIntegrator.java
@@ -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;
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
index bea3c2952..41ed4202f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
index b13048821..fc7399041 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
index 14383c78d..85ab51190 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
index ce2abff05..a71672a03 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftIAPActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftIAPActivity.kt
index 9c2a7f529..1b52f7fbc 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftIAPActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GiftIAPActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
index 6fc758ec2..60c38dd1b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
@@ -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 {
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 {
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 {
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)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index 0c8eaa977..736e0ea58 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -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()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
index 478fab56e..ea1d2b3a0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
@@ -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()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
index 171a88292..96410db0d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt
index 661d2a643..f8ec1ebfe 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/AchievementsAdapter.kt
@@ -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() {
@@ -71,7 +72,7 @@ class AchievementsAdapter: RecyclerView.Adapter() {
}
}
- 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() {
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() {
}
achievementContainer?.clipToOutline = true
}
+
+ override fun onClick(v: View?) {
+ achievement?.let {
+ AchievementDetailDialog(it, itemView.context).show()
+ }
+ }
}
class QuestAchievementViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
index 41f311401..3eefa7c3f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt
index 63ef0f33b..a7a7fb7a5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/AchievementProfileAdapter.kt
@@ -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() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt
index 671a2f723..f19b3cf50 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AchievementsFragment.kt
@@ -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)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt
index df64a24e1..4ce6dc688 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/StatsFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/SubscriptionFragment.kt
index a447a4a1b..e82f0eab6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/SubscriptionFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/SubscriptionFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxFragment.kt
index 557972b0c..b94670444 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt
index e95fc3ebf..e53add645 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/QuestDetailFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt
index 9f6f9e662..ae88d67e0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt
@@ -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.*
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
index 549440ba2..340a3664c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
index 9015e8c82..d6a78435d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
index 4eb2032a1..786639b75 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
@@ -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.*
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDetailDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDetailDialog.kt
new file mode 100644
index 000000000..a1ba22e5f
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDetailDialog.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt
similarity index 99%
rename from Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt
rename to Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt
index e56e84559..a8fcd5bb4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaAlertDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/HabiticaAlertDialog.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientCurrencyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientCurrencyDialog.kt
index cfe0d6752..5dcbf629f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientCurrencyDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientCurrencyDialog.kt
@@ -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.
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
index 71af57414..27c32505d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
index c0f1e472d..28358a747 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
@@ -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
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
index 7762d872e..d594b75f2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
@@ -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