From 4c1e3fb1ed60582012f27b3f3d231756630760f3 Mon Sep 17 00:00:00 2001 From: Mohammed Alaa Date: Mon, 9 Jun 2025 22:21:50 +0300 Subject: [PATCH 01/57] Enhance analytics logic --- .../android/habitica/helpers/Analytics.kt | 31 +++++++++++++------ .../habitica/ui/activities/MainActivity.kt | 6 ++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt index f992a2a30..79a31a57c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt @@ -56,12 +56,12 @@ object Analytics { data.putAll(additionalData) } if (eventAction != null) { - if (this::amplitude.isInitialized) { + executeLambda(AnalyticsTarget.AMPLITUDE) { if (target == null || target == AnalyticsTarget.AMPLITUDE) { amplitude.track(eventAction, data) } } - if (this::firebase.isInitialized) { + executeLambda(AnalyticsTarget.FIREBASE) { if (target == null || target == AnalyticsTarget.FIREBASE) { firebase.logEvent(eventAction, bundleOf(*data.toList().toTypedArray())) } @@ -94,17 +94,17 @@ object Analytics { sharedPrefs.getString("launch_screen", "")?.let { identify.set("launch_screen", it) } - if (this::amplitude.isInitialized) { + executeLambda(AnalyticsTarget.AMPLITUDE) { amplitude.identify(identify) } } fun setUserID(userID: String) { - if (this::amplitude.isInitialized) { + executeLambda(AnalyticsTarget.AMPLITUDE) { amplitude.setUserId(userID) } FirebaseCrashlytics.getInstance().setUserId(userID) - if (this::firebase.isInitialized) { + executeLambda(AnalyticsTarget.FIREBASE) { firebase.setUserId(userID) } } @@ -113,10 +113,10 @@ object Analytics { identifier: String, value: Any? ) { - if (this::amplitude.isInitialized) { + executeLambda(AnalyticsTarget.AMPLITUDE) { amplitude.identify(mapOf(identifier to value)) } - if (this::firebase.isInitialized) { + executeLambda(AnalyticsTarget.FIREBASE) { firebase.setUserProperty(identifier, value?.toString()) } } @@ -131,8 +131,21 @@ object Analytics { fun setAnalyticsConsent(consents: Boolean?) { val isEnabled = consents == true - firebase.setAnalyticsCollectionEnabled(isEnabled) + executeLambda(AnalyticsTarget.FIREBASE) { + firebase.setAnalyticsCollectionEnabled(isEnabled) + } FirebasePerformance.getInstance().isPerformanceCollectionEnabled = isEnabled - amplitude.configuration.optOut = !isEnabled + executeLambda(AnalyticsTarget.AMPLITUDE) { + amplitude.configuration.optOut = !isEnabled + } + } + + + private fun executeLambda(analyticsTarget: AnalyticsTarget, action: () -> Unit) { + when (analyticsTarget) { + AnalyticsTarget.AMPLITUDE -> if (!::amplitude.isInitialized) return + AnalyticsTarget.FIREBASE -> if (!::firebase.isInitialized) return + } + action() } } 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 55a4e8b1c..8dcba1265 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 @@ -186,7 +186,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { viewModel.updateAllowPushNotifications(false) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.sharedPreferences.getBoolean("prompted_exact_scheduling", false)) { - val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as? AlarmManager ?: return@registerForActivityResult + val alarmManager = this.getSystemService(ALARM_SERVICE) as? AlarmManager ?: return@registerForActivityResult if (!alarmManager.canScheduleExactAlarms()) { val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM) intent.setData(Uri.fromParts("package", applicationContext?.packageName, null)) @@ -645,8 +645,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity { } preferences?.sound?.let { soundManager.soundTheme = it } - CrashReporter.setCustomKey("day_start", user.preferences?.dayStart ?: 0) - CrashReporter.setCustomKey("timezone_offset", user.preferences?.timezoneOffset ?: 0) + CrashReporter.setCustomKey("day_start", "${user.preferences?.dayStart ?: 0}") + CrashReporter.setCustomKey("timezone_offset", "${user.preferences?.timezoneOffset ?: 0}") Analytics.setAnalyticsConsent(user.preferences?.analyticsConsent) displayDeathDialogIfNeeded() From e7e28ff44c51519bd2ce749de8f83fcb101b921d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 9 Jun 2025 13:54:12 -0500 Subject: [PATCH 02/57] Fix chat box sliding under keyboard when long pressing text Slide ChatBarView under IME instead of padding to preserve text selection handles --- .../habitica/ui/views/social/ChatBarView.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt index 4d8fcce21..07013644c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt @@ -164,19 +164,15 @@ class ChatBarView : LinearLayout, OnImeVisibilityChangedListener { override fun onImeVisibilityChanged(visible: Boolean, height: Int, safeInsets: Insets) { this.safeInsets = safeInsets - imeHeight = if (visible) { - height - } else { - 0 - } - applyAllPadding() - } + imeHeight = if (visible) height else 0 - private fun applyAllPadding() { - Log.e("ChatBarView", "applyAllPadding: safeInsets = $safeInsets, imeHeight = $imeHeight") updatePadding( left = safeInsets.left, right = safeInsets.right, - bottom = if (imeHeight > 0) imeHeight else safeInsets.bottom) + bottom = safeInsets.bottom + ) + + // slide the bar up under the keyboard + translationY = if (imeHeight > 0) -imeHeight.toFloat() else 0f } } From 6732eaee20beb6a642a420dcdaca7e1efb641d50 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 9 Jun 2025 13:38:12 -0500 Subject: [PATCH 03/57] Fix crash that occurs when tapping GroupPlanMemberList Constrain GroupPlanMemberList bottom sheet height to prevent infinite-measure crash --- .../habitica/ui/activities/MainActivity.kt | 138 ++++++++++-------- 1 file changed, 78 insertions(+), 60 deletions(-) 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 8dcba1265..7cc5bec89 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 @@ -22,9 +22,14 @@ import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.ActionBarDrawerToggle import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -32,6 +37,7 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.view.ViewCompat @@ -344,69 +350,76 @@ open class MainActivity : BaseActivity(), SnackbarActivity { isMyProfile = true, onAvatarClicked = { showAsBottomSheet { dismiss -> - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(22.dp) + Box( + Modifier + .fillMaxWidth() + .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f) + .padding(22.dp) ) { - ComposableAvatarView( - avatar = user, - configManager = appConfigManager - ) Column( horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(15.dp) + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.verticalScroll(rememberScrollState()) ) { - HabiticaButton( - background = HabiticaTheme.colors.tintedUiSub, - color = Color.White, - modifier = Modifier.height(48.dp), - onClick = { - dismiss() - MainNavigationController.navigate( - MainNavDirections.openProfileActivity( - user?.id ?: "" - ) - ) - } + ComposableAvatarView( + avatar = user, + configManager = appConfigManager + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(15.dp) ) { - Text(stringResource(id = R.string.open_profile)) - } - - HabiticaButton( - background = HabiticaTheme.colors.tintedUiSub, - color = Color.White, - modifier = Modifier.height(48.dp), - onClick = { - dismiss() - MainNavigationController.navigate(R.id.avatarOverviewFragment) - } - ) { - Text(stringResource(id = R.string.customize_avatar)) - } - - HabiticaButton( - background = HabiticaTheme.colors.tintedUiSub, - color = Color.White, - modifier = Modifier.height(48.dp), - onClick = { - dismiss() - user?.let { - val usecase = ShareAvatarUseCase() - lifecycleScope.launchCatching { - usecase.callInteractor( - ShareAvatarUseCase.RequestValues( - this@MainActivity, - it, - "Check out my avatar on Habitica!", - "avatar_bottomsheet" - ) + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + modifier = Modifier.height(48.dp), + onClick = { + dismiss() + MainNavigationController.navigate( + MainNavDirections.openProfileActivity( + user?.id ?: "" ) + ) + } + ) { + Text(stringResource(id = R.string.open_profile)) + } + + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + modifier = Modifier.height(48.dp), + onClick = { + dismiss() + MainNavigationController.navigate(R.id.avatarOverviewFragment) + } + ) { + Text(stringResource(id = R.string.customize_avatar)) + } + + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + modifier = Modifier.height(48.dp), + onClick = { + dismiss() + user?.let { + val usecase = ShareAvatarUseCase() + lifecycleScope.launchCatching { + usecase.callInteractor( + ShareAvatarUseCase.RequestValues( + this@MainActivity, + it, + "Check out my avatar on Habitica!", + "avatar_bottomsheet" + ) + ) + } } } + ) { + Text(stringResource(id = R.string.share_avatar)) } - ) { - Text(stringResource(id = R.string.share_avatar)) } } } @@ -414,13 +427,18 @@ open class MainActivity : BaseActivity(), SnackbarActivity { }, onMemberRowClicked = { showAsBottomSheet { onClose -> - val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState( - null - ) + val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(null) val members by viewModel.userViewModel.currentTeamPlanMembers.observeAsState() - GroupPlanMemberList(members, group, appConfigManager) { - onClose() - FullProfileActivity.open(it) + Box( + Modifier + .fillMaxWidth() + .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + GroupPlanMemberList(members, group, appConfigManager) { member -> + onClose() + FullProfileActivity.open(member) + } } } }, From 9633025778cfdb463ff4a5c6c23c67fa03962c0b Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 11 Jun 2025 13:06:05 +0200 Subject: [PATCH 04/57] convert emoji map to kotlin and remove duplicate map entries --- .../common/habitica/helpers/EmojiMap.java | 1702 ----------------- .../common/habitica/helpers/EmojiMap.kt | 1676 ++++++++++++++++ 2 files changed, 1676 insertions(+), 1702 deletions(-) delete mode 100644 common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.java create mode 100644 common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.java b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.java deleted file mode 100644 index efa48f642..000000000 --- a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.java +++ /dev/null @@ -1,1702 +0,0 @@ -package com.habitrpg.common.habitica.helpers; - -import java.util.HashMap; -import java.util.Map; - -public class EmojiMap { - public static final Map emojiMap = new HashMap<>(); - public static final Map invertedEmojiMap = new HashMap<>(); - static { - emojiMap.put(128077, ":+1:"); - emojiMap.put(128078, ":-1:"); - emojiMap.put(128175, ":100:"); - emojiMap.put(128290, ":1234:"); - emojiMap.put(127921, ":8ball:"); - emojiMap.put(127344, ":a:"); - emojiMap.put(127374, ":ab:"); - emojiMap.put(128292, ":abc:"); - emojiMap.put(128289, ":abcd:"); - emojiMap.put(127569, ":accept:"); - emojiMap.put(128673, ":aerial_tramway:"); - emojiMap.put(9992, ":airplane:"); - emojiMap.put(9200, ":alarm_clock:"); - emojiMap.put(128125, ":alien:"); - emojiMap.put(128657, ":ambulance:"); - emojiMap.put(9875, ":anchor:"); - emojiMap.put(128124, ":angel:"); - emojiMap.put(128162, ":anger:"); - emojiMap.put(128544, ":angry:"); - emojiMap.put(128551, ":anguished:"); - emojiMap.put(128028, ":ant:"); - emojiMap.put(127822, ":apple:"); - emojiMap.put(9810, ":aquarius:"); - emojiMap.put(9800, ":aries:"); - emojiMap.put(9664, ":arrow_backward:"); - emojiMap.put(9196, ":arrow_double_down:"); - emojiMap.put(9195, ":arrow_double_up:"); - emojiMap.put(11015, ":arrow_down:"); - emojiMap.put(128317, ":arrow_down_small:"); - emojiMap.put(9654, ":arrow_forward:"); - emojiMap.put(10549, ":arrow_heading_down:"); - emojiMap.put(10548, ":arrow_heading_up:"); - emojiMap.put(11013, ":arrow_left:"); - emojiMap.put(8601, ":arrow_lower_left:"); - emojiMap.put(8600, ":arrow_lower_right:"); - emojiMap.put(10145, ":arrow_right:"); - emojiMap.put(8618, ":arrow_right_hook:"); - emojiMap.put(11014, ":arrow_up:"); - emojiMap.put(8597, ":arrow_up_down:"); - emojiMap.put(128316, ":arrow_up_small:"); - emojiMap.put(8598, ":arrow_upper_left:"); - emojiMap.put(8599, ":arrow_upper_right:"); - emojiMap.put(128259, ":arrows_clockwise:"); - emojiMap.put(128260, ":arrows_counterclockwise:"); - emojiMap.put(127912, ":art:"); - emojiMap.put(128667, ":articulated_lorry:"); - emojiMap.put(128562, ":astonished:"); - emojiMap.put(128095, ":athletic_shoe:"); - emojiMap.put(127975, ":atm:"); - emojiMap.put(127345, ":b:"); - emojiMap.put(128118, ":baby:"); - emojiMap.put(127868, ":baby_bottle:"); - emojiMap.put(128036, ":baby_chick:"); - emojiMap.put(128700, ":baby_symbol:"); - emojiMap.put(128281, ":back:"); - emojiMap.put(128708, ":baggage_claim:"); - emojiMap.put(127880, ":balloon:"); - emojiMap.put(9745, ":ballot_box_with_check:"); - emojiMap.put(127885, ":bamboo:"); - emojiMap.put(127820, ":banana:"); - emojiMap.put(8252, ":bangbang:"); - emojiMap.put(127974, ":bank:"); - emojiMap.put(128202, ":bar_chart:"); - emojiMap.put(128136, ":barber:"); - emojiMap.put(9918, ":baseball:"); - emojiMap.put(127936, ":basketball:"); - emojiMap.put(128704, ":bath:"); - emojiMap.put(128705, ":bathtub:"); - emojiMap.put(128267, ":battery:"); - emojiMap.put(128059, ":bear:"); - emojiMap.put(128029, ":bee:"); - emojiMap.put(127866, ":beer:"); - emojiMap.put(127867, ":beers:"); - emojiMap.put(128030, ":beetle:"); - emojiMap.put(128304, ":beginner:"); - emojiMap.put(128276, ":bell:"); - emojiMap.put(127857, ":bento:"); - emojiMap.put(128692, ":bicyclist:"); - emojiMap.put(128690, ":bike:"); - emojiMap.put(128089, ":bikini:"); - emojiMap.put(128038, ":bird:"); - emojiMap.put(127874, ":birthday:"); - emojiMap.put(9899, ":black_circle:"); - emojiMap.put(127183, ":black_joker:"); - emojiMap.put(11035, ":black_large_square:"); - emojiMap.put(9726, ":black_medium_small_square:"); - emojiMap.put(9724, ":black_medium_square:"); - emojiMap.put(10002, ":black_nib:"); - emojiMap.put(9642, ":black_small_square:"); - emojiMap.put(128306, ":black_square_button:"); - emojiMap.put(127804, ":blossom:"); - emojiMap.put(128033, ":blowfish:"); - emojiMap.put(128216, ":blue_book:"); - emojiMap.put(128665, ":blue_car:"); - emojiMap.put(128153, ":blue_heart:"); - emojiMap.put(128522, ":blush:"); - emojiMap.put(128023, ":boar:"); - emojiMap.put(9973, ":boat:"); - emojiMap.put(128163, ":bomb:"); - emojiMap.put(128214, ":book:"); - emojiMap.put(128278, ":bookmark:"); - emojiMap.put(128209, ":bookmark_tabs:"); - emojiMap.put(128218, ":books:"); - emojiMap.put(128165, ":boom:"); - emojiMap.put(128098, ":boot:"); - emojiMap.put(128144, ":bouquet:"); - emojiMap.put(128583, ":bow:"); - emojiMap.put(127923, ":bowling:"); - emojiMap.put(128102, ":boy:"); - emojiMap.put(127838, ":bread:"); - emojiMap.put(128112, ":bride_with_veil:"); - emojiMap.put(127753, ":bridge_at_night:"); - emojiMap.put(128188, ":briefcase:"); - emojiMap.put(128148, ":broken_heart:"); - emojiMap.put(128027, ":bug:"); - emojiMap.put(128161, ":bulb:"); - emojiMap.put(128645, ":bullettrain_front:"); - emojiMap.put(128644, ":bullettrain_side:"); - emojiMap.put(128652, ":bus:"); - emojiMap.put(128655, ":busstop:"); - emojiMap.put(128100, ":bust_in_silhouette:"); - emojiMap.put(128101, ":busts_in_silhouette:"); - emojiMap.put(127797, ":cactus:"); - emojiMap.put(127856, ":cake:"); - emojiMap.put(128198, ":calendar:"); - emojiMap.put(128242, ":calling:"); - emojiMap.put(128043, ":camel:"); - emojiMap.put(128247, ":camera:"); - emojiMap.put(9803, ":cancer:"); - emojiMap.put(127852, ":candy:"); - emojiMap.put(128288, ":capital_abcd:"); - emojiMap.put(9809, ":capricorn:"); - emojiMap.put(128663, ":car:"); - emojiMap.put(128199, ":card_index:"); - emojiMap.put(127904, ":carousel_horse:"); - emojiMap.put(128049, ":cat:"); - emojiMap.put(128008, ":cat2:"); - emojiMap.put(128191, ":cd:"); - emojiMap.put(128185, ":chart:"); - emojiMap.put(128201, ":chart_with_downwards_trend:"); - emojiMap.put(128200, ":chart_with_upwards_trend:"); - emojiMap.put(127937, ":checkered_flag:"); - emojiMap.put(127826, ":cherries:"); - emojiMap.put(127800, ":cherry_blossom:"); - emojiMap.put(127792, ":chestnut:"); - emojiMap.put(128020, ":chicken:"); - emojiMap.put(128696, ":children_crossing:"); - emojiMap.put(127851, ":chocolate_bar:"); - emojiMap.put(127876, ":christmas_tree:"); - emojiMap.put(9962, ":church:"); - emojiMap.put(127910, ":cinema:"); - emojiMap.put(127914, ":circus_tent:"); - emojiMap.put(127751, ":city_sunrise:"); - emojiMap.put(127750, ":city_sunset:"); - emojiMap.put(127377, ":cl:"); - emojiMap.put(128079, ":clap:"); - emojiMap.put(127916, ":clapper:"); - emojiMap.put(128203, ":clipboard:"); - emojiMap.put(128336, ":clock1:"); - emojiMap.put(128345, ":clock10:"); - emojiMap.put(128357, ":clock1030:"); - emojiMap.put(128346, ":clock11:"); - emojiMap.put(128358, ":clock1130:"); - emojiMap.put(128347, ":clock12:"); - emojiMap.put(128359, ":clock1230:"); - emojiMap.put(128348, ":clock130:"); - emojiMap.put(128337, ":clock2:"); - emojiMap.put(128349, ":clock230:"); - emojiMap.put(128338, ":clock3:"); - emojiMap.put(128350, ":clock330:"); - emojiMap.put(128339, ":clock4:"); - emojiMap.put(128351, ":clock430:"); - emojiMap.put(128340, ":clock5:"); - emojiMap.put(128352, ":clock530:"); - emojiMap.put(128341, ":clock6:"); - emojiMap.put(128353, ":clock630:"); - emojiMap.put(128342, ":clock7:"); - emojiMap.put(128354, ":clock730:"); - emojiMap.put(128343, ":clock8:"); - emojiMap.put(128355, ":clock830:"); - emojiMap.put(128344, ":clock9:"); - emojiMap.put(128356, ":clock930:"); - emojiMap.put(128213, ":closed_book:"); - emojiMap.put(128272, ":closed_lock_with_key:"); - emojiMap.put(127746, ":closed_umbrella:"); - emojiMap.put(9729, ":cloud:"); - emojiMap.put(9827, ":clubs:"); - emojiMap.put(127864, ":cocktail:"); - emojiMap.put(9749, ":coffee:"); - emojiMap.put(128560, ":cold_sweat:"); - emojiMap.put(128165, ":collision:"); - emojiMap.put(128187, ":computer:"); - emojiMap.put(127882, ":confetti_ball:"); - emojiMap.put(128534, ":confounded:"); - emojiMap.put(128533, ":confused:"); - emojiMap.put(12951, ":congratulations:"); - emojiMap.put(128679, ":construction:"); - emojiMap.put(128119, ":construction_worker:"); - emojiMap.put(127978, ":convenience_store:"); - emojiMap.put(127850, ":cookie:"); - emojiMap.put(127378, ":cool:"); - emojiMap.put(128110, ":cop:"); - emojiMap.put(169, ":copyright:"); - emojiMap.put(127805, ":corn:"); - emojiMap.put(128107, ":couple:"); - emojiMap.put(128145, ":couple_with_heart:"); - emojiMap.put(128143, ":couplekiss:"); - emojiMap.put(128046, ":cow:"); - emojiMap.put(128004, ":cow2:"); - emojiMap.put(128179, ":credit_card:"); - emojiMap.put(127769, ":crescent_moon:"); - emojiMap.put(128010, ":crocodile:"); - emojiMap.put(127884, ":crossed_flags:"); - emojiMap.put(128081, ":crown:"); - emojiMap.put(128546, ":cry:"); - emojiMap.put(128575, ":crying_cat_face:"); - emojiMap.put(128302, ":crystal_ball:"); - emojiMap.put(128152, ":cupid:"); - emojiMap.put(10160, ":curly_loop:"); - emojiMap.put(128177, ":currency_exchange:"); - emojiMap.put(127835, ":curry:"); - emojiMap.put(127854, ":custard:"); - emojiMap.put(128707, ":customs:"); - emojiMap.put(127744, ":cyclone:"); - emojiMap.put(128131, ":dancer:"); - emojiMap.put(128111, ":dancers:"); - emojiMap.put(127841, ":dango:"); - emojiMap.put(127919, ":dart:"); - emojiMap.put(128168, ":dash:"); - emojiMap.put(128197, ":date:"); - emojiMap.put(127795, ":deciduous_tree:"); - emojiMap.put(127980, ":department_store:"); - emojiMap.put(128160, ":diamond_shape_with_a_dot_inside:"); - emojiMap.put(9830, ":diamonds:"); - emojiMap.put(128542, ":disappointed:"); - emojiMap.put(128549, ":disappointed_relieved:"); - emojiMap.put(128171, ":dizzy:"); - emojiMap.put(128565, ":dizzy_face:"); - emojiMap.put(128687, ":do_not_litter:"); - emojiMap.put(128054, ":dog:"); - emojiMap.put(128021, ":dog2:"); - emojiMap.put(128181, ":dollar:"); - emojiMap.put(127886, ":dolls:"); - emojiMap.put(128044, ":dolphin:"); - emojiMap.put(128682, ":door:"); - emojiMap.put(127849, ":doughnut:"); - emojiMap.put(128009, ":dragon:"); - emojiMap.put(128050, ":dragon_face:"); - emojiMap.put(128087, ":dress:"); - emojiMap.put(128042, ":dromedary_camel:"); - emojiMap.put(128167, ":droplet:"); - emojiMap.put(128192, ":dvd:"); - emojiMap.put(128231, ":e-mail:"); - emojiMap.put(128066, ":ear:"); - emojiMap.put(127806, ":ear_of_rice:"); - emojiMap.put(127757, ":earth_africa:"); - emojiMap.put(127758, ":earth_americas:"); - emojiMap.put(127759, ":earth_asia:"); - emojiMap.put(127859, ":egg:"); - emojiMap.put(127814, ":eggplant:"); - emojiMap.put(10036, ":eight_pointed_black_star:"); - emojiMap.put(10035, ":eight_spoked_asterisk:"); - emojiMap.put(128268, ":electric_plug:"); - emojiMap.put(128024, ":elephant:"); - emojiMap.put(9993, ":email:"); - emojiMap.put(128282, ":end:"); - emojiMap.put(9993, ":envelope:"); - emojiMap.put(128233, ":envelope_with_arrow:"); - emojiMap.put(128182, ":euro:"); - emojiMap.put(127984, ":european_castle:"); - emojiMap.put(127972, ":european_post_office:"); - emojiMap.put(127794, ":evergreen_tree:"); - emojiMap.put(10071, ":exclamation:"); - emojiMap.put(128529, ":expressionless:"); - emojiMap.put(128083, ":eyeglasses:"); - emojiMap.put(128064, ":eyes:"); - emojiMap.put(128074, ":facepunch:"); - emojiMap.put(127981, ":factory:"); - emojiMap.put(127810, ":fallen_leaf:"); - emojiMap.put(128106, ":family:"); - emojiMap.put(9193, ":fast_forward:"); - emojiMap.put(128224, ":fax:"); - emojiMap.put(128552, ":fearful:"); - emojiMap.put(128062, ":feet:"); - emojiMap.put(127905, ":ferris_wheel:"); - emojiMap.put(128193, ":file_folder:"); - emojiMap.put(128293, ":fire:"); - emojiMap.put(128658, ":fire_engine:"); - emojiMap.put(127878, ":fireworks:"); - emojiMap.put(127763, ":first_quarter_moon:"); - emojiMap.put(127771, ":first_quarter_moon_with_face:"); - emojiMap.put(128031, ":fish:"); - emojiMap.put(127845, ":fish_cake:"); - emojiMap.put(127907, ":fishing_pole_and_fish:"); - emojiMap.put(9994, ":fist:"); - emojiMap.put(127887, ":flags:"); - emojiMap.put(128294, ":flashlight:"); - emojiMap.put(128044, ":flipper:"); - emojiMap.put(128190, ":floppy_disk:"); - emojiMap.put(127924, ":flower_playing_cards:"); - emojiMap.put(128563, ":flushed:"); - emojiMap.put(127745, ":foggy:"); - emojiMap.put(127944, ":football:"); - emojiMap.put(128099, ":footprints:"); - emojiMap.put(127860, ":fork_and_knife:"); - emojiMap.put(9970, ":fountain:"); - emojiMap.put(127808, ":four_leaf_clover:"); - emojiMap.put(127379, ":free:"); - emojiMap.put(127844, ":fried_shrimp:"); - emojiMap.put(127839, ":fries:"); - emojiMap.put(128056, ":frog:"); - emojiMap.put(128550, ":frowning:"); - emojiMap.put(9981, ":fuelpump:"); - emojiMap.put(127765, ":full_moon:"); - emojiMap.put(127773, ":full_moon_with_face:"); - emojiMap.put(127922, ":game_die:"); - emojiMap.put(128142, ":gem:"); - emojiMap.put(9802, ":gemini:"); - emojiMap.put(128123, ":ghost:"); - emojiMap.put(127873, ":gift:"); - emojiMap.put(128157, ":gift_heart:"); - emojiMap.put(128103, ":girl:"); - emojiMap.put(127760, ":globe_with_meridians:"); - emojiMap.put(128016, ":goat:"); - emojiMap.put(9971, ":golf:"); - emojiMap.put(127815, ":grapes:"); - emojiMap.put(127823, ":green_apple:"); - emojiMap.put(128215, ":green_book:"); - emojiMap.put(128154, ":green_heart:"); - emojiMap.put(10069, ":grey_exclamation:"); - emojiMap.put(10068, ":grey_question:"); - emojiMap.put(128556, ":grimacing:"); - emojiMap.put(128513, ":grin:"); - emojiMap.put(128512, ":grinning:"); - emojiMap.put(128130, ":guardsman:"); - emojiMap.put(127928, ":guitar:"); - emojiMap.put(128299, ":gun:"); - emojiMap.put(128135, ":haircut:"); - emojiMap.put(127828, ":hamburger:"); - emojiMap.put(128296, ":hammer:"); - emojiMap.put(128057, ":hamster:"); - emojiMap.put(9995, ":hand:"); - emojiMap.put(128092, ":handbag:"); - emojiMap.put(128169, ":hankey:"); - emojiMap.put(128037, ":hatched_chick:"); - emojiMap.put(128035, ":hatching_chick:"); - emojiMap.put(127911, ":headphones:"); - emojiMap.put(128585, ":hear_no_evil:"); - emojiMap.put(10084, ":heart:"); - emojiMap.put(128159, ":heart_decoration:"); - emojiMap.put(128525, ":heart_eyes:"); - emojiMap.put(128571, ":heart_eyes_cat:"); - emojiMap.put(128147, ":heartbeat:"); - emojiMap.put(128151, ":heartpulse:"); - emojiMap.put(9829, ":hearts:"); - emojiMap.put(10004, ":heavy_check_mark:"); - emojiMap.put(10135, ":heavy_division_sign:"); - emojiMap.put(128178, ":heavy_dollar_sign:"); - emojiMap.put(10071, ":heavy_exclamation_mark:"); - emojiMap.put(10134, ":heavy_minus_sign:"); - emojiMap.put(10006, ":heavy_multiplication_x:"); - emojiMap.put(10133, ":heavy_plus_sign:"); - emojiMap.put(128641, ":helicopter:"); - emojiMap.put(127807, ":herb:"); - emojiMap.put(127802, ":hibiscus:"); - emojiMap.put(128262, ":high_brightness:"); - emojiMap.put(128096, ":high_heel:"); - emojiMap.put(128298, ":hocho:"); - emojiMap.put(127855, ":honey_pot:"); - emojiMap.put(128029, ":honeybee:"); - emojiMap.put(128052, ":horse:"); - emojiMap.put(127943, ":horse_racing:"); - emojiMap.put(127973, ":hospital:"); - emojiMap.put(127976, ":hotel:"); - emojiMap.put(9832, ":hotsprings:"); - emojiMap.put(8987, ":hourglass:"); - emojiMap.put(9203, ":hourglass_flowing_sand:"); - emojiMap.put(127968, ":house:"); - emojiMap.put(127969, ":house_with_garden:"); - emojiMap.put(128559, ":hushed:"); - emojiMap.put(127848, ":ice_cream:"); - emojiMap.put(127846, ":icecream:"); - emojiMap.put(127380, ":id:"); - emojiMap.put(127568, ":ideograph_advantage:"); - emojiMap.put(128127, ":imp:"); - emojiMap.put(128229, ":inbox_tray:"); - emojiMap.put(128232, ":incoming_envelope:"); - emojiMap.put(128129, ":information_desk_person:"); - emojiMap.put(8505, ":information_source:"); - emojiMap.put(128519, ":innocent:"); - emojiMap.put(8265, ":interrobang:"); - emojiMap.put(128241, ":iphone:"); - emojiMap.put(127982, ":izakaya_lantern:"); - emojiMap.put(127875, ":jack_o_lantern:"); - emojiMap.put(128510, ":japan:"); - emojiMap.put(127983, ":japanese_castle:"); - emojiMap.put(128122, ":japanese_goblin:"); - emojiMap.put(128121, ":japanese_ogre:"); - emojiMap.put(128086, ":jeans:"); - emojiMap.put(128514, ":joy:"); - emojiMap.put(128569, ":joy_cat:"); - emojiMap.put(128273, ":key:"); - emojiMap.put(128287, ":keycap_ten:"); - emojiMap.put(128088, ":kimono:"); - emojiMap.put(128139, ":kiss:"); - emojiMap.put(128535, ":kissing:"); - emojiMap.put(128573, ":kissing_cat:"); - emojiMap.put(128538, ":kissing_closed_eyes:"); - emojiMap.put(128536, ":kissing_heart:"); - emojiMap.put(128537, ":kissing_smiling_eyes:"); - emojiMap.put(128040, ":koala:"); - emojiMap.put(127489, ":koko:"); - emojiMap.put(127982, ":lantern:"); - emojiMap.put(128309, ":large_blue_circle:"); - emojiMap.put(128311, ":large_blue_diamond:"); - emojiMap.put(128310, ":large_orange_diamond:"); - emojiMap.put(127767, ":last_quarter_moon:"); - emojiMap.put(127772, ":last_quarter_moon_with_face:"); - emojiMap.put(128518, ":laughing:"); - emojiMap.put(127811, ":leaves:"); - emojiMap.put(128210, ":ledger:"); - emojiMap.put(128709, ":left_luggage:"); - emojiMap.put(8596, ":left_right_arrow:"); - emojiMap.put(8617, ":leftwards_arrow_with_hook:"); - emojiMap.put(127819, ":lemon:"); - emojiMap.put(9804, ":leo:"); - emojiMap.put(128006, ":leopard:"); - emojiMap.put(9806, ":libra:"); - emojiMap.put(128648, ":light_rail:"); - emojiMap.put(128279, ":link:"); - emojiMap.put(128068, ":lips:"); - emojiMap.put(128132, ":lipstick:"); - emojiMap.put(128274, ":lock:"); - emojiMap.put(128271, ":lock_with_ink_pen:"); - emojiMap.put(127853, ":lollipop:"); - emojiMap.put(10175, ":loop:"); - emojiMap.put(128226, ":loudspeaker:"); - emojiMap.put(127977, ":love_hotel:"); - emojiMap.put(128140, ":love_letter:"); - emojiMap.put(128261, ":low_brightness:"); - emojiMap.put(9410, ":m:"); - emojiMap.put(128269, ":mag:"); - emojiMap.put(128270, ":mag_right:"); - emojiMap.put(126980, ":mahjong:"); - emojiMap.put(128235, ":mailbox:"); - emojiMap.put(128234, ":mailbox_closed:"); - emojiMap.put(128236, ":mailbox_with_mail:"); - emojiMap.put(128237, ":mailbox_with_no_mail:"); - emojiMap.put(128104, ":man:"); - emojiMap.put(128114, ":man_with_gua_pi_mao:"); - emojiMap.put(128115, ":man_with_turban:"); - emojiMap.put(128094, ":mans_shoe:"); - emojiMap.put(127809, ":maple_leaf:"); - emojiMap.put(128567, ":mask:"); - emojiMap.put(128134, ":massage:"); - emojiMap.put(127830, ":meat_on_bone:"); - emojiMap.put(128227, ":mega:"); - emojiMap.put(127816, ":melon:"); - emojiMap.put(128221, ":memo:"); - emojiMap.put(128697, ":mens:"); - emojiMap.put(128647, ":metro:"); - emojiMap.put(127908, ":microphone:"); - emojiMap.put(128300, ":microscope:"); - emojiMap.put(127756, ":milky_way:"); - emojiMap.put(128656, ":minibus:"); - emojiMap.put(128189, ":minidisc:"); - emojiMap.put(128244, ":mobile_phone_off:"); - emojiMap.put(128184, ":money_with_wings:"); - emojiMap.put(128176, ":moneybag:"); - emojiMap.put(128018, ":monkey:"); - emojiMap.put(128053, ":monkey_face:"); - emojiMap.put(128669, ":monorail:"); - emojiMap.put(127764, ":moon:"); - emojiMap.put(127891, ":mortar_board:"); - emojiMap.put(128507, ":mount_fuji:"); - emojiMap.put(128693, ":mountain_bicyclist:"); - emojiMap.put(128672, ":mountain_cableway:"); - emojiMap.put(128670, ":mountain_railway:"); - emojiMap.put(128045, ":mouse:"); - emojiMap.put(128001, ":mouse2:"); - emojiMap.put(127909, ":movie_camera:"); - emojiMap.put(128511, ":moyai:"); - emojiMap.put(128170, ":muscle:"); - emojiMap.put(127812, ":mushroom:"); - emojiMap.put(127929, ":musical_keyboard:"); - emojiMap.put(127925, ":musical_note:"); - emojiMap.put(127932, ":musical_score:"); - emojiMap.put(128263, ":mute:"); - emojiMap.put(128133, ":nail_care:"); - emojiMap.put(128219, ":name_badge:"); - emojiMap.put(128084, ":necktie:"); - emojiMap.put(10062, ":negative_squared_cross_mark:"); - emojiMap.put(128528, ":neutral_face:"); - emojiMap.put(127381, ":new:"); - emojiMap.put(127761, ":new_moon:"); - emojiMap.put(127770, ":new_moon_with_face:"); - emojiMap.put(128240, ":newspaper:"); - emojiMap.put(127382, ":ng:"); - emojiMap.put(128277, ":no_bell:"); - emojiMap.put(128691, ":no_bicycles:"); - emojiMap.put(9940, ":no_entry:"); - emojiMap.put(128683, ":no_entry_sign:"); - emojiMap.put(128581, ":no_good:"); - emojiMap.put(128245, ":no_mobile_phones:"); - emojiMap.put(128566, ":no_mouth:"); - emojiMap.put(128695, ":no_pedestrians:"); - emojiMap.put(128685, ":no_smoking:"); - emojiMap.put(128689, ":non-potable_water:"); - emojiMap.put(128067, ":nose:"); - emojiMap.put(128211, ":notebook:"); - emojiMap.put(128212, ":notebook_with_decorative_cover:"); - emojiMap.put(127926, ":notes:"); - emojiMap.put(128297, ":nut_and_bolt:"); - emojiMap.put(11093, ":o:"); - emojiMap.put(127358, ":o2:"); - emojiMap.put(127754, ":ocean:"); - emojiMap.put(128025, ":octopus:"); - emojiMap.put(127842, ":oden:"); - emojiMap.put(127970, ":office:"); - emojiMap.put(127383, ":ok:"); - emojiMap.put(128076, ":ok_hand:"); - emojiMap.put(128582, ":ok_woman:"); - emojiMap.put(128116, ":older_man:"); - emojiMap.put(128117, ":older_woman:"); - emojiMap.put(128283, ":on:"); - emojiMap.put(128664, ":oncoming_automobile:"); - emojiMap.put(128653, ":oncoming_bus:"); - emojiMap.put(128660, ":oncoming_police_car:"); - emojiMap.put(128662, ":oncoming_taxi:"); - emojiMap.put(128214, ":open_book:"); - emojiMap.put(128194, ":open_file_folder:"); - emojiMap.put(128080, ":open_hands:"); - emojiMap.put(128558, ":open_mouth:"); - emojiMap.put(9934, ":ophiuchus:"); - emojiMap.put(128217, ":orange_book:"); - emojiMap.put(128228, ":outbox_tray:"); - emojiMap.put(128002, ":ox:"); - emojiMap.put(128230, ":package:"); - emojiMap.put(128196, ":page_facing_up:"); - emojiMap.put(128195, ":page_with_curl:"); - emojiMap.put(128223, ":pager:"); - emojiMap.put(127796, ":palm_tree:"); - emojiMap.put(128060, ":panda_face:"); - emojiMap.put(128206, ":paperclip:"); - emojiMap.put(127359, ":parking:"); - emojiMap.put(12349, ":part_alternation_mark:"); - emojiMap.put(9925, ":partly_sunny:"); - emojiMap.put(128706, ":passport_control:"); - emojiMap.put(128062, ":paw_prints:"); - emojiMap.put(127825, ":peach:"); - emojiMap.put(127824, ":pear:"); - emojiMap.put(128221, ":pencil:"); - emojiMap.put(9999, ":pencil2:"); - emojiMap.put(128039, ":penguin:"); - emojiMap.put(128532, ":pensive:"); - emojiMap.put(127917, ":performing_arts:"); - emojiMap.put(128547, ":persevere:"); - emojiMap.put(128589, ":person_frowning:"); - emojiMap.put(128113, ":person_with_blond_hair:"); - emojiMap.put(128590, ":person_with_pouting_face:"); - emojiMap.put(9742, ":phone:"); - emojiMap.put(128055, ":pig:"); - emojiMap.put(128022, ":pig2:"); - emojiMap.put(128061, ":pig_nose:"); - emojiMap.put(128138, ":pill:"); - emojiMap.put(127821, ":pineapple:"); - emojiMap.put(9811, ":pisces:"); - emojiMap.put(127829, ":pizza:"); - emojiMap.put(128071, ":point_down:"); - emojiMap.put(128072, ":point_left:"); - emojiMap.put(128073, ":point_right:"); - emojiMap.put(9757, ":point_up:"); - emojiMap.put(128070, ":point_up_2:"); - emojiMap.put(128659, ":police_car:"); - emojiMap.put(128041, ":poodle:"); - emojiMap.put(128169, ":poop:"); - emojiMap.put(127971, ":post_office:"); - emojiMap.put(128239, ":postal_horn:"); - emojiMap.put(128238, ":postbox:"); - emojiMap.put(128688, ":potable_water:"); - emojiMap.put(128093, ":pouch:"); - emojiMap.put(127831, ":poultry_leg:"); - emojiMap.put(128183, ":pound:"); - emojiMap.put(128574, ":pouting_cat:"); - emojiMap.put(128591, ":pray:"); - emojiMap.put(128120, ":princess:"); - emojiMap.put(128074, ":punch:"); - emojiMap.put(128156, ":purple_heart:"); - emojiMap.put(128091, ":purse:"); - emojiMap.put(128204, ":pushpin:"); - emojiMap.put(128686, ":put_litter_in_its_place:"); - emojiMap.put(10067, ":question:"); - emojiMap.put(128048, ":rabbit:"); - emojiMap.put(128007, ":rabbit2:"); - emojiMap.put(128014, ":racehorse:"); - emojiMap.put(128251, ":radio:"); - emojiMap.put(128280, ":radio_button:"); - emojiMap.put(128545, ":rage:"); - emojiMap.put(128643, ":railway_car:"); - emojiMap.put(127752, ":rainbow:"); - emojiMap.put(9995, ":raised_hand:"); - emojiMap.put(128588, ":raised_hands:"); - emojiMap.put(128587, ":raising_hand:"); - emojiMap.put(128015, ":ram:"); - emojiMap.put(127836, ":ramen:"); - emojiMap.put(128000, ":rat:"); - emojiMap.put(9851, ":recycle:"); - emojiMap.put(128663, ":red_car:"); - emojiMap.put(128308, ":red_circle:"); - emojiMap.put(174, ":registered:"); - emojiMap.put(9786, ":relaxed:"); - emojiMap.put(128524, ":relieved:"); - emojiMap.put(128257, ":repeat:"); - emojiMap.put(128258, ":repeat_one:"); - emojiMap.put(128699, ":restroom:"); - emojiMap.put(128158, ":revolving_hearts:"); - emojiMap.put(9194, ":rewind:"); - emojiMap.put(127872, ":ribbon:"); - emojiMap.put(127834, ":rice:"); - emojiMap.put(127833, ":rice_ball:"); - emojiMap.put(127832, ":rice_cracker:"); - emojiMap.put(127889, ":rice_scene:"); - emojiMap.put(128141, ":ring:"); - emojiMap.put(128640, ":rocket:"); - emojiMap.put(127906, ":roller_coaster:"); - emojiMap.put(128019, ":rooster:"); - emojiMap.put(127801, ":rose:"); - emojiMap.put(128680, ":rotating_light:"); - emojiMap.put(128205, ":round_pushpin:"); - emojiMap.put(128675, ":rowboat:"); - emojiMap.put(127945, ":rugby_football:"); - emojiMap.put(127939, ":runner:"); - emojiMap.put(127939, ":running:"); - emojiMap.put(127933, ":running_shirt_with_sash:"); - emojiMap.put(127490, ":sa:"); - emojiMap.put(9808, ":sagittarius:"); - emojiMap.put(9973, ":sailboat:"); - emojiMap.put(127862, ":sake:"); - emojiMap.put(128097, ":sandal:"); - emojiMap.put(127877, ":santa:"); - emojiMap.put(128225, ":satellite:"); - emojiMap.put(128518, ":satisfied:"); - emojiMap.put(127927, ":saxophone:"); - emojiMap.put(127979, ":school:"); - emojiMap.put(127890, ":school_satchel:"); - emojiMap.put(9986, ":scissors:"); - emojiMap.put(9807, ":scorpius:"); - emojiMap.put(128561, ":scream:"); - emojiMap.put(128576, ":scream_cat:"); - emojiMap.put(128220, ":scroll:"); - emojiMap.put(128186, ":seat:"); - emojiMap.put(12953, ":secret:"); - emojiMap.put(128584, ":see_no_evil:"); - emojiMap.put(127793, ":seedling:"); - emojiMap.put(127847, ":shaved_ice:"); - emojiMap.put(128017, ":sheep:"); - emojiMap.put(128026, ":shell:"); - emojiMap.put(128674, ":ship:"); - emojiMap.put(128085, ":shirt:"); - emojiMap.put(128169, ":shit:"); - emojiMap.put(128094, ":shoe:"); - emojiMap.put(128703, ":shower:"); - emojiMap.put(128246, ":signal_strength:"); - emojiMap.put(128303, ":six_pointed_star:"); - emojiMap.put(127935, ":ski:"); - emojiMap.put(128128, ":skull:"); - emojiMap.put(128564, ":sleeping:"); - emojiMap.put(128554, ":sleepy:"); - emojiMap.put(127920, ":slot_machine:"); - emojiMap.put(128313, ":small_blue_diamond:"); - emojiMap.put(128312, ":small_orange_diamond:"); - emojiMap.put(128314, ":small_red_triangle:"); - emojiMap.put(128315, ":small_red_triangle_down:"); - emojiMap.put(128516, ":smile:"); - emojiMap.put(128568, ":smile_cat:"); - emojiMap.put(128515, ":smiley:"); - emojiMap.put(128570, ":smiley_cat:"); - emojiMap.put(128520, ":smiling_imp:"); - emojiMap.put(128527, ":smirk:"); - emojiMap.put(128572, ":smirk_cat:"); - emojiMap.put(128684, ":smoking:"); - emojiMap.put(128012, ":snail:"); - emojiMap.put(128013, ":snake:"); - emojiMap.put(127938, ":snowboarder:"); - emojiMap.put(10052, ":snowflake:"); - emojiMap.put(9924, ":snowman:"); - emojiMap.put(128557, ":sob:"); - emojiMap.put(9917, ":soccer:"); - emojiMap.put(128284, ":soon:"); - emojiMap.put(127384, ":sos:"); - emojiMap.put(128265, ":sound:"); - emojiMap.put(128126, ":space_invader:"); - emojiMap.put(9824, ":spades:"); - emojiMap.put(127837, ":spaghetti:"); - emojiMap.put(10055, ":sparkle:"); - emojiMap.put(127879, ":sparkler:"); - emojiMap.put(10024, ":sparkles:"); - emojiMap.put(128150, ":sparkling_heart:"); - emojiMap.put(128586, ":speak_no_evil:"); - emojiMap.put(128266, ":speaker:"); - emojiMap.put(128172, ":speech_balloon:"); - emojiMap.put(128676, ":speedboat:"); - emojiMap.put(11088, ":star:"); - emojiMap.put(127775, ":star2:"); - emojiMap.put(127747, ":stars:"); - emojiMap.put(128649, ":station:"); - emojiMap.put(128509, ":statue_of_liberty:"); - emojiMap.put(128642, ":steam_locomotive:"); - emojiMap.put(127858, ":stew:"); - emojiMap.put(128207, ":straight_ruler:"); - emojiMap.put(127827, ":strawberry:"); - emojiMap.put(128539, ":stuck_out_tongue:"); - emojiMap.put(128541, ":stuck_out_tongue_closed_eyes:"); - emojiMap.put(128540, ":stuck_out_tongue_winking_eye:"); - emojiMap.put(127774, ":sun_with_face:"); - emojiMap.put(127803, ":sunflower:"); - emojiMap.put(128526, ":sunglasses:"); - emojiMap.put(9728, ":sunny:"); - emojiMap.put(127749, ":sunrise:"); - emojiMap.put(127748, ":sunrise_over_mountains:"); - emojiMap.put(127940, ":surfer:"); - emojiMap.put(127843, ":sushi:"); - emojiMap.put(128671, ":suspension_railway:"); - emojiMap.put(128531, ":sweat:"); - emojiMap.put(128166, ":sweat_drops:"); - emojiMap.put(128517, ":sweat_smile:"); - emojiMap.put(127840, ":sweet_potato:"); - emojiMap.put(127946, ":swimmer:"); - emojiMap.put(128291, ":symbols:"); - emojiMap.put(128137, ":syringe:"); - emojiMap.put(127881, ":tada:"); - emojiMap.put(127883, ":tanabata_tree:"); - emojiMap.put(127818, ":tangerine:"); - emojiMap.put(9801, ":taurus:"); - emojiMap.put(128661, ":taxi:"); - emojiMap.put(127861, ":tea:"); - emojiMap.put(9742, ":telephone:"); - emojiMap.put(128222, ":telephone_receiver:"); - emojiMap.put(128301, ":telescope:"); - emojiMap.put(127934, ":tennis:"); - emojiMap.put(9978, ":tent:"); - emojiMap.put(128173, ":thought_balloon:"); - emojiMap.put(128078, ":thumbsdown:"); - emojiMap.put(128077, ":thumbsup:"); - emojiMap.put(127915, ":ticket:"); - emojiMap.put(128047, ":tiger:"); - emojiMap.put(128005, ":tiger2:"); - emojiMap.put(128555, ":tired_face:"); - emojiMap.put(8482, ":tm:"); - emojiMap.put(128701, ":toilet:"); - emojiMap.put(128508, ":tokyo_tower:"); - emojiMap.put(127813, ":tomato:"); - emojiMap.put(128069, ":tongue:"); - emojiMap.put(128285, ":top:"); - emojiMap.put(127913, ":tophat:"); - emojiMap.put(128668, ":tractor:"); - emojiMap.put(128677, ":traffic_light:"); - emojiMap.put(128643, ":train:"); - emojiMap.put(128646, ":train2:"); - emojiMap.put(128650, ":tram:"); - emojiMap.put(128681, ":triangular_flag_on_post:"); - emojiMap.put(128208, ":triangular_ruler:"); - emojiMap.put(128305, ":trident:"); - emojiMap.put(128548, ":triumph:"); - emojiMap.put(128654, ":trolleybus:"); - emojiMap.put(127942, ":trophy:"); - emojiMap.put(127865, ":tropical_drink:"); - emojiMap.put(128032, ":tropical_fish:"); - emojiMap.put(128666, ":truck:"); - emojiMap.put(127930, ":trumpet:"); - emojiMap.put(128085, ":tshirt:"); - emojiMap.put(127799, ":tulip:"); - emojiMap.put(128034, ":turtle:"); - emojiMap.put(128250, ":tv:"); - emojiMap.put(128256, ":twisted_rightwards_arrows:"); - emojiMap.put(128149, ":two_hearts:"); - emojiMap.put(128108, ":two_men_holding_hands:"); - emojiMap.put(128109, ":two_women_holding_hands:"); - emojiMap.put(127545, ":u5272:"); - emojiMap.put(127540, ":u5408:"); - emojiMap.put(127546, ":u55b6:"); - emojiMap.put(127535, ":u6307:"); - emojiMap.put(127543, ":u6708:"); - emojiMap.put(127542, ":u6709:"); - emojiMap.put(127541, ":u6e80:"); - emojiMap.put(127514, ":u7121:"); - emojiMap.put(127544, ":u7533:"); - emojiMap.put(127538, ":u7981:"); - emojiMap.put(127539, ":u7a7a:"); - emojiMap.put(9748, ":umbrella:"); - emojiMap.put(128530, ":unamused:"); - emojiMap.put(128286, ":underage:"); - emojiMap.put(128275, ":unlock:"); - emojiMap.put(127385, ":up:"); - emojiMap.put(9996, ":v:"); - emojiMap.put(128678, ":vertical_traffic_light:"); - emojiMap.put(128252, ":vhs:"); - emojiMap.put(128243, ":vibration_mode:"); - emojiMap.put(128249, ":video_camera:"); - emojiMap.put(127918, ":video_game:"); - emojiMap.put(127931, ":violin:"); - emojiMap.put(9805, ":virgo:"); - emojiMap.put(127755, ":volcano:"); - emojiMap.put(127386, ":vs:"); - emojiMap.put(128694, ":walking:"); - emojiMap.put(127768, ":waning_crescent_moon:"); - emojiMap.put(127766, ":waning_gibbous_moon:"); - emojiMap.put(9888, ":warning:"); - emojiMap.put(8986, ":watch:"); - emojiMap.put(128003, ":water_buffalo:"); - emojiMap.put(127817, ":watermelon:"); - emojiMap.put(128075, ":wave:"); - emojiMap.put(12336, ":wavy_dash:"); - emojiMap.put(127762, ":waxing_crescent_moon:"); - emojiMap.put(127764, ":waxing_gibbous_moon:"); - emojiMap.put(128702, ":wc:"); - emojiMap.put(128553, ":weary:"); - emojiMap.put(128146, ":wedding:"); - emojiMap.put(128051, ":whale:"); - emojiMap.put(128011, ":whale2:"); - emojiMap.put(9855, ":wheelchair:"); - emojiMap.put(9989, ":white_check_mark:"); - emojiMap.put(9898, ":white_circle:"); - emojiMap.put(128174, ":white_flower:"); - emojiMap.put(11036, ":white_large_square:"); - emojiMap.put(9725, ":white_medium_small_square:"); - emojiMap.put(9723, ":white_medium_square:"); - emojiMap.put(9643, ":white_small_square:"); - emojiMap.put(128307, ":white_square_button:"); - emojiMap.put(127888, ":wind_chime:"); - emojiMap.put(127863, ":wine_glass:"); - emojiMap.put(128521, ":wink:"); - emojiMap.put(128058, ":wolf:"); - emojiMap.put(128105, ":woman:"); - emojiMap.put(128090, ":womans_clothes:"); - emojiMap.put(128082, ":womans_hat:"); - emojiMap.put(128698, ":womens:"); - emojiMap.put(128543, ":worried:"); - emojiMap.put(128295, ":wrench:"); - emojiMap.put(10060, ":x:"); - emojiMap.put(128155, ":yellow_heart:"); - emojiMap.put(128180, ":yen:"); - emojiMap.put(128523, ":yum:"); - emojiMap.put(9889, ":zap:"); - emojiMap.put(128164, ":zzz:"); - - invertedEmojiMap.put(":+1:", 128077); - invertedEmojiMap.put(":-1:", 128078); - invertedEmojiMap.put(":100:", 128175); - invertedEmojiMap.put(":1234:", 128290); - invertedEmojiMap.put(":8ball:", 127921); - invertedEmojiMap.put(":a:", 127344); - invertedEmojiMap.put(":ab:", 127374); - invertedEmojiMap.put(":abc:", 128292); - invertedEmojiMap.put(":abcd:", 128289); - invertedEmojiMap.put(":accept:", 127569); - invertedEmojiMap.put(":aerial_tramway:", 128673); - invertedEmojiMap.put(":airplane:", 9992); - invertedEmojiMap.put(":alarm_clock:", 9200); - invertedEmojiMap.put(":alien:", 128125); - invertedEmojiMap.put(":ambulance:", 128657); - invertedEmojiMap.put(":anchor:", 9875); - invertedEmojiMap.put(":angel:", 128124); - invertedEmojiMap.put(":anger:", 128162); - invertedEmojiMap.put(":angry:", 128544); - invertedEmojiMap.put(":anguished:", 128551); - invertedEmojiMap.put(":ant:", 128028); - invertedEmojiMap.put(":apple:", 127822); - invertedEmojiMap.put(":aquarius:", 9810); - invertedEmojiMap.put(":aries:", 9800); - invertedEmojiMap.put(":arrow_backward:", 9664); - invertedEmojiMap.put(":arrow_double_down:", 9196); - invertedEmojiMap.put(":arrow_double_up:", 9195); - invertedEmojiMap.put(":arrow_down:", 11015); - invertedEmojiMap.put(":arrow_down_small:", 128317); - invertedEmojiMap.put(":arrow_forward:", 9654); - invertedEmojiMap.put(":arrow_heading_down:", 10549); - invertedEmojiMap.put(":arrow_heading_up:", 10548); - invertedEmojiMap.put(":arrow_left:", 11013); - invertedEmojiMap.put(":arrow_lower_left:", 8601); - invertedEmojiMap.put(":arrow_lower_right:", 8600); - invertedEmojiMap.put(":arrow_right:", 10145); - invertedEmojiMap.put(":arrow_right_hook:", 8618); - invertedEmojiMap.put(":arrow_up:", 11014); - invertedEmojiMap.put(":arrow_up_down:", 8597); - invertedEmojiMap.put(":arrow_up_small:", 128316); - invertedEmojiMap.put(":arrow_upper_left:", 8598); - invertedEmojiMap.put(":arrow_upper_right:", 8599); - invertedEmojiMap.put(":arrows_clockwise:", 128259); - invertedEmojiMap.put(":arrows_counterclockwise:", 128260); - invertedEmojiMap.put(":art:", 127912); - invertedEmojiMap.put(":articulated_lorry:", 128667); - invertedEmojiMap.put(":astonished:", 128562); - invertedEmojiMap.put(":athletic_shoe:", 128095); - invertedEmojiMap.put(":atm:", 127975); - invertedEmojiMap.put(":b:", 127345); - invertedEmojiMap.put(":baby:", 128118); - invertedEmojiMap.put(":baby_bottle:", 127868); - invertedEmojiMap.put(":baby_chick:", 128036); - invertedEmojiMap.put(":baby_symbol:", 128700); - invertedEmojiMap.put(":back:", 128281); - invertedEmojiMap.put(":baggage_claim:", 128708); - invertedEmojiMap.put(":balloon:", 127880); - invertedEmojiMap.put(":ballot_box_with_check:", 9745); - invertedEmojiMap.put(":bamboo:", 127885); - invertedEmojiMap.put(":banana:", 127820); - invertedEmojiMap.put(":bangbang:", 8252); - invertedEmojiMap.put(":bank:", 127974); - invertedEmojiMap.put(":bar_chart:", 128202); - invertedEmojiMap.put(":barber:", 128136); - invertedEmojiMap.put(":baseball:", 9918); - invertedEmojiMap.put(":basketball:", 127936); - invertedEmojiMap.put(":bath:", 128704); - invertedEmojiMap.put(":bathtub:", 128705); - invertedEmojiMap.put(":battery:", 128267); - invertedEmojiMap.put(":bear:", 128059); - invertedEmojiMap.put(":bee:", 128029); - invertedEmojiMap.put(":beer:", 127866); - invertedEmojiMap.put(":beers:", 127867); - invertedEmojiMap.put(":beetle:", 128030); - invertedEmojiMap.put(":beginner:", 128304); - invertedEmojiMap.put(":bell:", 128276); - invertedEmojiMap.put(":bento:", 127857); - invertedEmojiMap.put(":bicyclist:", 128692); - invertedEmojiMap.put(":bike:", 128690); - invertedEmojiMap.put(":bikini:", 128089); - invertedEmojiMap.put(":bird:", 128038); - invertedEmojiMap.put(":birthday:", 127874); - invertedEmojiMap.put(":black_circle:", 9899); - invertedEmojiMap.put(":black_joker:", 127183); - invertedEmojiMap.put(":black_large_square:", 11035); - invertedEmojiMap.put(":black_medium_small_square:", 9726); - invertedEmojiMap.put(":black_medium_square:", 9724); - invertedEmojiMap.put(":black_nib:", 10002); - invertedEmojiMap.put(":black_small_square:", 9642); - invertedEmojiMap.put(":black_square_button:", 128306); - invertedEmojiMap.put(":blossom:", 127804); - invertedEmojiMap.put(":blowfish:", 128033); - invertedEmojiMap.put(":blue_book:", 128216); - invertedEmojiMap.put(":blue_car:", 128665); - invertedEmojiMap.put(":blue_heart:", 128153); - invertedEmojiMap.put(":blush:", 128522); - invertedEmojiMap.put(":boar:", 128023); - invertedEmojiMap.put(":boat:", 9973); - invertedEmojiMap.put(":bomb:", 128163); - invertedEmojiMap.put(":book:", 128214); - invertedEmojiMap.put(":bookmark:", 128278); - invertedEmojiMap.put(":bookmark_tabs:", 128209); - invertedEmojiMap.put(":books:", 128218); - invertedEmojiMap.put(":boom:", 128165); - invertedEmojiMap.put(":boot:", 128098); - invertedEmojiMap.put(":bouquet:", 128144); - invertedEmojiMap.put(":bow:", 128583); - invertedEmojiMap.put(":bowling:", 127923); - invertedEmojiMap.put(":boy:", 128102); - invertedEmojiMap.put(":bread:", 127838); - invertedEmojiMap.put(":bride_with_veil:", 128112); - invertedEmojiMap.put(":bridge_at_night:", 127753); - invertedEmojiMap.put(":briefcase:", 128188); - invertedEmojiMap.put(":broken_heart:", 128148); - invertedEmojiMap.put(":bug:", 128027); - invertedEmojiMap.put(":bulb:", 128161); - invertedEmojiMap.put(":bullettrain_front:", 128645); - invertedEmojiMap.put(":bullettrain_side:", 128644); - invertedEmojiMap.put(":bus:", 128652); - invertedEmojiMap.put(":busstop:", 128655); - invertedEmojiMap.put(":bust_in_silhouette:", 128100); - invertedEmojiMap.put(":busts_in_silhouette:", 128101); - invertedEmojiMap.put(":cactus:", 127797); - invertedEmojiMap.put(":cake:", 127856); - invertedEmojiMap.put(":calendar:", 128198); - invertedEmojiMap.put(":calling:", 128242); - invertedEmojiMap.put(":camel:", 128043); - invertedEmojiMap.put(":camera:", 128247); - invertedEmojiMap.put(":cancer:", 9803); - invertedEmojiMap.put(":candy:", 127852); - invertedEmojiMap.put(":capital_abcd:", 128288); - invertedEmojiMap.put(":capricorn:", 9809); - invertedEmojiMap.put(":car:", 128663); - invertedEmojiMap.put(":card_index:", 128199); - invertedEmojiMap.put(":carousel_horse:", 127904); - invertedEmojiMap.put(":cat:", 128049); - invertedEmojiMap.put(":cat2:", 128008); - invertedEmojiMap.put(":cd:", 128191); - invertedEmojiMap.put(":chart:", 128185); - invertedEmojiMap.put(":chart_with_downwards_trend:", 128201); - invertedEmojiMap.put(":chart_with_upwards_trend:", 128200); - invertedEmojiMap.put(":checkered_flag:", 127937); - invertedEmojiMap.put(":cherries:", 127826); - invertedEmojiMap.put(":cherry_blossom:", 127800); - invertedEmojiMap.put(":chestnut:", 127792); - invertedEmojiMap.put(":chicken:", 128020); - invertedEmojiMap.put(":children_crossing:", 128696); - invertedEmojiMap.put(":chocolate_bar:", 127851); - invertedEmojiMap.put(":christmas_tree:", 127876); - invertedEmojiMap.put(":church:", 9962); - invertedEmojiMap.put(":cinema:", 127910); - invertedEmojiMap.put(":circus_tent:", 127914); - invertedEmojiMap.put(":city_sunrise:", 127751); - invertedEmojiMap.put(":city_sunset:", 127750); - invertedEmojiMap.put(":cl:", 127377); - invertedEmojiMap.put(":clap:", 128079); - invertedEmojiMap.put(":clapper:", 127916); - invertedEmojiMap.put(":clipboard:", 128203); - invertedEmojiMap.put(":clock1:", 128336); - invertedEmojiMap.put(":clock10:", 128345); - invertedEmojiMap.put(":clock1030:", 128357); - invertedEmojiMap.put(":clock11:", 128346); - invertedEmojiMap.put(":clock1130:", 128358); - invertedEmojiMap.put(":clock12:", 128347); - invertedEmojiMap.put(":clock1230:", 128359); - invertedEmojiMap.put(":clock130:", 128348); - invertedEmojiMap.put(":clock2:", 128337); - invertedEmojiMap.put(":clock230:", 128349); - invertedEmojiMap.put(":clock3:", 128338); - invertedEmojiMap.put(":clock330:", 128350); - invertedEmojiMap.put(":clock4:", 128339); - invertedEmojiMap.put(":clock430:", 128351); - invertedEmojiMap.put(":clock5:", 128340); - invertedEmojiMap.put(":clock530:", 128352); - invertedEmojiMap.put(":clock6:", 128341); - invertedEmojiMap.put(":clock630:", 128353); - invertedEmojiMap.put(":clock7:", 128342); - invertedEmojiMap.put(":clock730:", 128354); - invertedEmojiMap.put(":clock8:", 128343); - invertedEmojiMap.put(":clock830:", 128355); - invertedEmojiMap.put(":clock9:", 128344); - invertedEmojiMap.put(":clock930:", 128356); - invertedEmojiMap.put(":closed_book:", 128213); - invertedEmojiMap.put(":closed_lock_with_key:", 128272); - invertedEmojiMap.put(":closed_umbrella:", 127746); - invertedEmojiMap.put(":cloud:", 9729); - invertedEmojiMap.put(":clubs:", 9827); - invertedEmojiMap.put(":cocktail:", 127864); - invertedEmojiMap.put(":coffee:", 9749); - invertedEmojiMap.put(":cold_sweat:", 128560); - invertedEmojiMap.put(":collision:", 128165); - invertedEmojiMap.put(":computer:", 128187); - invertedEmojiMap.put(":confetti_ball:", 127882); - invertedEmojiMap.put(":confounded:", 128534); - invertedEmojiMap.put(":confused:", 128533); - invertedEmojiMap.put(":congratulations:", 12951); - invertedEmojiMap.put(":construction:", 128679); - invertedEmojiMap.put(":construction_worker:", 128119); - invertedEmojiMap.put(":convenience_store:", 127978); - invertedEmojiMap.put(":cookie:", 127850); - invertedEmojiMap.put(":cool:", 127378); - invertedEmojiMap.put(":cop:", 128110); - invertedEmojiMap.put(":copyright:", 169); - invertedEmojiMap.put(":corn:", 127805); - invertedEmojiMap.put(":couple:", 128107); - invertedEmojiMap.put(":couple_with_heart:", 128145); - invertedEmojiMap.put(":couplekiss:", 128143); - invertedEmojiMap.put(":cow:", 128046); - invertedEmojiMap.put(":cow2:", 128004); - invertedEmojiMap.put(":credit_card:", 128179); - invertedEmojiMap.put(":crescent_moon:", 127769); - invertedEmojiMap.put(":crocodile:", 128010); - invertedEmojiMap.put(":crossed_flags:", 127884); - invertedEmojiMap.put(":crown:", 128081); - invertedEmojiMap.put(":cry:", 128546); - invertedEmojiMap.put(":crying_cat_face:", 128575); - invertedEmojiMap.put(":crystal_ball:", 128302); - invertedEmojiMap.put(":cupid:", 128152); - invertedEmojiMap.put(":curly_loop:", 10160); - invertedEmojiMap.put(":currency_exchange:", 128177); - invertedEmojiMap.put(":curry:", 127835); - invertedEmojiMap.put(":custard:", 127854); - invertedEmojiMap.put(":customs:", 128707); - invertedEmojiMap.put(":cyclone:", 127744); - invertedEmojiMap.put(":dancer:", 128131); - invertedEmojiMap.put(":dancers:", 128111); - invertedEmojiMap.put(":dango:", 127841); - invertedEmojiMap.put(":dart:", 127919); - invertedEmojiMap.put(":dash:", 128168); - invertedEmojiMap.put(":date:", 128197); - invertedEmojiMap.put(":deciduous_tree:", 127795); - invertedEmojiMap.put(":department_store:", 127980); - invertedEmojiMap.put(":diamond_shape_with_a_dot_inside:", 128160); - invertedEmojiMap.put(":diamonds:", 9830); - invertedEmojiMap.put(":disappointed:", 128542); - invertedEmojiMap.put(":disappointed_relieved:", 128549); - invertedEmojiMap.put(":dizzy:", 128171); - invertedEmojiMap.put(":dizzy_face:", 128565); - invertedEmojiMap.put(":do_not_litter:", 128687); - invertedEmojiMap.put(":dog:", 128054); - invertedEmojiMap.put(":dog2:", 128021); - invertedEmojiMap.put(":dollar:", 128181); - invertedEmojiMap.put(":dolls:", 127886); - invertedEmojiMap.put(":dolphin:", 128044); - invertedEmojiMap.put(":door:", 128682); - invertedEmojiMap.put(":doughnut:", 127849); - invertedEmojiMap.put(":dragon:", 128009); - invertedEmojiMap.put(":dragon_face:", 128050); - invertedEmojiMap.put(":dress:", 128087); - invertedEmojiMap.put(":dromedary_camel:", 128042); - invertedEmojiMap.put(":droplet:", 128167); - invertedEmojiMap.put(":dvd:", 128192); - invertedEmojiMap.put(":e-mail:", 128231); - invertedEmojiMap.put(":ear:", 128066); - invertedEmojiMap.put(":ear_of_rice:", 127806); - invertedEmojiMap.put(":earth_africa:", 127757); - invertedEmojiMap.put(":earth_americas:", 127758); - invertedEmojiMap.put(":earth_asia:", 127759); - invertedEmojiMap.put(":egg:", 127859); - invertedEmojiMap.put(":eggplant:", 127814); - invertedEmojiMap.put(":eight_pointed_black_star:", 10036); - invertedEmojiMap.put(":eight_spoked_asterisk:", 10035); - invertedEmojiMap.put(":electric_plug:", 128268); - invertedEmojiMap.put(":elephant:", 128024); - invertedEmojiMap.put(":email:", 9993); - invertedEmojiMap.put(":end:", 128282); - invertedEmojiMap.put(":envelope:", 9993); - invertedEmojiMap.put(":envelope_with_arrow:", 128233); - invertedEmojiMap.put(":euro:", 128182); - invertedEmojiMap.put(":european_castle:", 127984); - invertedEmojiMap.put(":european_post_office:", 127972); - invertedEmojiMap.put(":evergreen_tree:", 127794); - invertedEmojiMap.put(":exclamation:", 10071); - invertedEmojiMap.put(":expressionless:", 128529); - invertedEmojiMap.put(":eyeglasses:", 128083); - invertedEmojiMap.put(":eyes:", 128064); - invertedEmojiMap.put(":facepunch:", 128074); - invertedEmojiMap.put(":factory:", 127981); - invertedEmojiMap.put(":fallen_leaf:", 127810); - invertedEmojiMap.put(":family:", 128106); - invertedEmojiMap.put(":fast_forward:", 9193); - invertedEmojiMap.put(":fax:", 128224); - invertedEmojiMap.put(":fearful:", 128552); - invertedEmojiMap.put(":feet:", 128062); - invertedEmojiMap.put(":ferris_wheel:", 127905); - invertedEmojiMap.put(":file_folder:", 128193); - invertedEmojiMap.put(":fire:", 128293); - invertedEmojiMap.put(":fire_engine:", 128658); - invertedEmojiMap.put(":fireworks:", 127878); - invertedEmojiMap.put(":first_quarter_moon:", 127763); - invertedEmojiMap.put(":first_quarter_moon_with_face:", 127771); - invertedEmojiMap.put(":fish:", 128031); - invertedEmojiMap.put(":fish_cake:", 127845); - invertedEmojiMap.put(":fishing_pole_and_fish:", 127907); - invertedEmojiMap.put(":fist:", 9994); - invertedEmojiMap.put(":flags:", 127887); - invertedEmojiMap.put(":flashlight:", 128294); - invertedEmojiMap.put(":flipper:", 128044); - invertedEmojiMap.put(":floppy_disk:", 128190); - invertedEmojiMap.put(":flower_playing_cards:", 127924); - invertedEmojiMap.put(":flushed:", 128563); - invertedEmojiMap.put(":foggy:", 127745); - invertedEmojiMap.put(":football:", 127944); - invertedEmojiMap.put(":footprints:", 128099); - invertedEmojiMap.put(":fork_and_knife:", 127860); - invertedEmojiMap.put(":fountain:", 9970); - invertedEmojiMap.put(":four_leaf_clover:", 127808); - invertedEmojiMap.put(":free:", 127379); - invertedEmojiMap.put(":fried_shrimp:", 127844); - invertedEmojiMap.put(":fries:", 127839); - invertedEmojiMap.put(":frog:", 128056); - invertedEmojiMap.put(":frowning:", 128550); - invertedEmojiMap.put(":fuelpump:", 9981); - invertedEmojiMap.put(":full_moon:", 127765); - invertedEmojiMap.put(":full_moon_with_face:", 127773); - invertedEmojiMap.put(":game_die:", 127922); - invertedEmojiMap.put(":gem:", 128142); - invertedEmojiMap.put(":gemini:", 9802); - invertedEmojiMap.put(":ghost:", 128123); - invertedEmojiMap.put(":gift:", 127873); - invertedEmojiMap.put(":gift_heart:", 128157); - invertedEmojiMap.put(":girl:", 128103); - invertedEmojiMap.put(":globe_with_meridians:", 127760); - invertedEmojiMap.put(":goat:", 128016); - invertedEmojiMap.put(":golf:", 9971); - invertedEmojiMap.put(":grapes:", 127815); - invertedEmojiMap.put(":green_apple:", 127823); - invertedEmojiMap.put(":green_book:", 128215); - invertedEmojiMap.put(":green_heart:", 128154); - invertedEmojiMap.put(":grey_exclamation:", 10069); - invertedEmojiMap.put(":grey_question:", 10068); - invertedEmojiMap.put(":grimacing:", 128556); - invertedEmojiMap.put(":grin:", 128513); - invertedEmojiMap.put(":grinning:", 128512); - invertedEmojiMap.put(":guardsman:", 128130); - invertedEmojiMap.put(":guitar:", 127928); - invertedEmojiMap.put(":gun:", 128299); - invertedEmojiMap.put(":haircut:", 128135); - invertedEmojiMap.put(":hamburger:", 127828); - invertedEmojiMap.put(":hammer:", 128296); - invertedEmojiMap.put(":hamster:", 128057); - invertedEmojiMap.put(":hand:", 9995); - invertedEmojiMap.put(":handbag:", 128092); - invertedEmojiMap.put(":hankey:", 128169); - invertedEmojiMap.put(":hatched_chick:", 128037); - invertedEmojiMap.put(":hatching_chick:", 128035); - invertedEmojiMap.put(":headphones:", 127911); - invertedEmojiMap.put(":hear_no_evil:", 128585); - invertedEmojiMap.put(":heart:", 10084); - invertedEmojiMap.put(":heart_decoration:", 128159); - invertedEmojiMap.put(":heart_eyes:", 128525); - invertedEmojiMap.put(":heart_eyes_cat:", 128571); - invertedEmojiMap.put(":heartbeat:", 128147); - invertedEmojiMap.put(":heartpulse:", 128151); - invertedEmojiMap.put(":hearts:", 9829); - invertedEmojiMap.put(":heavy_check_mark:", 10004); - invertedEmojiMap.put(":heavy_division_sign:", 10135); - invertedEmojiMap.put(":heavy_dollar_sign:", 128178); - invertedEmojiMap.put(":heavy_exclamation_mark:", 10071); - invertedEmojiMap.put(":heavy_minus_sign:", 10134); - invertedEmojiMap.put(":heavy_multiplication_x:", 10006); - invertedEmojiMap.put(":heavy_plus_sign:", 10133); - invertedEmojiMap.put(":helicopter:", 128641); - invertedEmojiMap.put(":herb:", 127807); - invertedEmojiMap.put(":hibiscus:", 127802); - invertedEmojiMap.put(":high_brightness:", 128262); - invertedEmojiMap.put(":high_heel:", 128096); - invertedEmojiMap.put(":hocho:", 128298); - invertedEmojiMap.put(":honey_pot:", 127855); - invertedEmojiMap.put(":honeybee:", 128029); - invertedEmojiMap.put(":horse:", 128052); - invertedEmojiMap.put(":horse_racing:", 127943); - invertedEmojiMap.put(":hospital:", 127973); - invertedEmojiMap.put(":hotel:", 127976); - invertedEmojiMap.put(":hotsprings:", 9832); - invertedEmojiMap.put(":hourglass:", 8987); - invertedEmojiMap.put(":hourglass_flowing_sand:", 9203); - invertedEmojiMap.put(":house:", 127968); - invertedEmojiMap.put(":house_with_garden:", 127969); - invertedEmojiMap.put(":hushed:", 128559); - invertedEmojiMap.put(":ice_cream:", 127848); - invertedEmojiMap.put(":icecream:", 127846); - invertedEmojiMap.put(":id:", 127380); - invertedEmojiMap.put(":ideograph_advantage:", 127568); - invertedEmojiMap.put(":imp:", 128127); - invertedEmojiMap.put(":inbox_tray:", 128229); - invertedEmojiMap.put(":incoming_envelope:", 128232); - invertedEmojiMap.put(":information_desk_person:", 128129); - invertedEmojiMap.put(":information_source:", 8505); - invertedEmojiMap.put(":innocent:", 128519); - invertedEmojiMap.put(":interrobang:", 8265); - invertedEmojiMap.put(":iphone:", 128241); - invertedEmojiMap.put(":izakaya_lantern:", 127982); - invertedEmojiMap.put(":jack_o_lantern:", 127875); - invertedEmojiMap.put(":japan:", 128510); - invertedEmojiMap.put(":japanese_castle:", 127983); - invertedEmojiMap.put(":japanese_goblin:", 128122); - invertedEmojiMap.put(":japanese_ogre:", 128121); - invertedEmojiMap.put(":jeans:", 128086); - invertedEmojiMap.put(":joy:", 128514); - invertedEmojiMap.put(":joy_cat:", 128569); - invertedEmojiMap.put(":key:", 128273); - invertedEmojiMap.put(":keycap_ten:", 128287); - invertedEmojiMap.put(":kimono:", 128088); - invertedEmojiMap.put(":kiss:", 128139); - invertedEmojiMap.put(":kissing:", 128535); - invertedEmojiMap.put(":kissing_cat:", 128573); - invertedEmojiMap.put(":kissing_closed_eyes:", 128538); - invertedEmojiMap.put(":kissing_heart:", 128536); - invertedEmojiMap.put(":kissing_smiling_eyes:", 128537); - invertedEmojiMap.put(":koala:", 128040); - invertedEmojiMap.put(":koko:", 127489); - invertedEmojiMap.put(":lantern:", 127982); - invertedEmojiMap.put(":large_blue_circle:", 128309); - invertedEmojiMap.put(":large_blue_diamond:", 128311); - invertedEmojiMap.put(":large_orange_diamond:", 128310); - invertedEmojiMap.put(":last_quarter_moon:", 127767); - invertedEmojiMap.put(":last_quarter_moon_with_face:", 127772); - invertedEmojiMap.put(":laughing:", 128518); - invertedEmojiMap.put(":leaves:", 127811); - invertedEmojiMap.put(":ledger:", 128210); - invertedEmojiMap.put(":left_luggage:", 128709); - invertedEmojiMap.put(":left_right_arrow:", 8596); - invertedEmojiMap.put(":leftwards_arrow_with_hook:", 8617); - invertedEmojiMap.put(":lemon:", 127819); - invertedEmojiMap.put(":leo:", 9804); - invertedEmojiMap.put(":leopard:", 128006); - invertedEmojiMap.put(":libra:", 9806); - invertedEmojiMap.put(":light_rail:", 128648); - invertedEmojiMap.put(":link:", 128279); - invertedEmojiMap.put(":lips:", 128068); - invertedEmojiMap.put(":lipstick:", 128132); - invertedEmojiMap.put(":lock:", 128274); - invertedEmojiMap.put(":lock_with_ink_pen:", 128271); - invertedEmojiMap.put(":lollipop:", 127853); - invertedEmojiMap.put(":loop:", 10175); - invertedEmojiMap.put(":loudspeaker:", 128226); - invertedEmojiMap.put(":love_hotel:", 127977); - invertedEmojiMap.put(":love_letter:", 128140); - invertedEmojiMap.put(":low_brightness:", 128261); - invertedEmojiMap.put(":m:", 9410); - invertedEmojiMap.put(":mag:", 128269); - invertedEmojiMap.put(":mag_right:", 128270); - invertedEmojiMap.put(":mahjong:", 126980); - invertedEmojiMap.put(":mailbox:", 128235); - invertedEmojiMap.put(":mailbox_closed:", 128234); - invertedEmojiMap.put(":mailbox_with_mail:", 128236); - invertedEmojiMap.put(":mailbox_with_no_mail:", 128237); - invertedEmojiMap.put(":man:", 128104); - invertedEmojiMap.put(":man_with_gua_pi_mao:", 128114); - invertedEmojiMap.put(":man_with_turban:", 128115); - invertedEmojiMap.put(":mans_shoe:", 128094); - invertedEmojiMap.put(":maple_leaf:", 127809); - invertedEmojiMap.put(":mask:", 128567); - invertedEmojiMap.put(":massage:", 128134); - invertedEmojiMap.put(":meat_on_bone:", 127830); - invertedEmojiMap.put(":mega:", 128227); - invertedEmojiMap.put(":melon:", 127816); - invertedEmojiMap.put(":memo:", 128221); - invertedEmojiMap.put(":mens:", 128697); - invertedEmojiMap.put(":metro:", 128647); - invertedEmojiMap.put(":microphone:", 127908); - invertedEmojiMap.put(":microscope:", 128300); - invertedEmojiMap.put(":milky_way:", 127756); - invertedEmojiMap.put(":minibus:", 128656); - invertedEmojiMap.put(":minidisc:", 128189); - invertedEmojiMap.put(":mobile_phone_off:", 128244); - invertedEmojiMap.put(":money_with_wings:", 128184); - invertedEmojiMap.put(":moneybag:", 128176); - invertedEmojiMap.put(":monkey:", 128018); - invertedEmojiMap.put(":monkey_face:", 128053); - invertedEmojiMap.put(":monorail:", 128669); - invertedEmojiMap.put(":moon:", 127764); - invertedEmojiMap.put(":mortar_board:", 127891); - invertedEmojiMap.put(":mount_fuji:", 128507); - invertedEmojiMap.put(":mountain_bicyclist:", 128693); - invertedEmojiMap.put(":mountain_cableway:", 128672); - invertedEmojiMap.put(":mountain_railway:", 128670); - invertedEmojiMap.put(":mouse:", 128045); - invertedEmojiMap.put(":mouse2:", 128001); - invertedEmojiMap.put(":movie_camera:", 127909); - invertedEmojiMap.put(":moyai:", 128511); - invertedEmojiMap.put(":muscle:", 128170); - invertedEmojiMap.put(":mushroom:", 127812); - invertedEmojiMap.put(":musical_keyboard:", 127929); - invertedEmojiMap.put(":musical_note:", 127925); - invertedEmojiMap.put(":musical_score:", 127932); - invertedEmojiMap.put(":mute:", 128263); - invertedEmojiMap.put(":nail_care:", 128133); - invertedEmojiMap.put(":name_badge:", 128219); - invertedEmojiMap.put(":necktie:", 128084); - invertedEmojiMap.put(":negative_squared_cross_mark:", 10062); - invertedEmojiMap.put(":neutral_face:", 128528); - invertedEmojiMap.put(":new:", 127381); - invertedEmojiMap.put(":new_moon:", 127761); - invertedEmojiMap.put(":new_moon_with_face:", 127770); - invertedEmojiMap.put(":newspaper:", 128240); - invertedEmojiMap.put(":ng:", 127382); - invertedEmojiMap.put(":no_bell:", 128277); - invertedEmojiMap.put(":no_bicycles:", 128691); - invertedEmojiMap.put(":no_entry:", 9940); - invertedEmojiMap.put(":no_entry_sign:", 128683); - invertedEmojiMap.put(":no_good:", 128581); - invertedEmojiMap.put(":no_mobile_phones:", 128245); - invertedEmojiMap.put(":no_mouth:", 128566); - invertedEmojiMap.put(":no_pedestrians:", 128695); - invertedEmojiMap.put(":no_smoking:", 128685); - invertedEmojiMap.put(":non-potable_water:", 128689); - invertedEmojiMap.put(":nose:", 128067); - invertedEmojiMap.put(":notebook:", 128211); - invertedEmojiMap.put(":notebook_with_decorative_cover:", 128212); - invertedEmojiMap.put(":notes:", 127926); - invertedEmojiMap.put(":nut_and_bolt:", 128297); - invertedEmojiMap.put(":o:", 11093); - invertedEmojiMap.put(":o2:", 127358); - invertedEmojiMap.put(":ocean:", 127754); - invertedEmojiMap.put(":octopus:", 128025); - invertedEmojiMap.put(":oden:", 127842); - invertedEmojiMap.put(":office:", 127970); - invertedEmojiMap.put(":ok:", 127383); - invertedEmojiMap.put(":ok_hand:", 128076); - invertedEmojiMap.put(":ok_woman:", 128582); - invertedEmojiMap.put(":older_man:", 128116); - invertedEmojiMap.put(":older_woman:", 128117); - invertedEmojiMap.put(":on:", 128283); - invertedEmojiMap.put(":oncoming_automobile:", 128664); - invertedEmojiMap.put(":oncoming_bus:", 128653); - invertedEmojiMap.put(":oncoming_police_car:", 128660); - invertedEmojiMap.put(":oncoming_taxi:", 128662); - invertedEmojiMap.put(":open_book:", 128214); - invertedEmojiMap.put(":open_file_folder:", 128194); - invertedEmojiMap.put(":open_hands:", 128080); - invertedEmojiMap.put(":open_mouth:", 128558); - invertedEmojiMap.put(":ophiuchus:", 9934); - invertedEmojiMap.put(":orange_book:", 128217); - invertedEmojiMap.put(":outbox_tray:", 128228); - invertedEmojiMap.put(":ox:", 128002); - invertedEmojiMap.put(":package:", 128230); - invertedEmojiMap.put(":page_facing_up:", 128196); - invertedEmojiMap.put(":page_with_curl:", 128195); - invertedEmojiMap.put(":pager:", 128223); - invertedEmojiMap.put(":palm_tree:", 127796); - invertedEmojiMap.put(":panda_face:", 128060); - invertedEmojiMap.put(":paperclip:", 128206); - invertedEmojiMap.put(":parking:", 127359); - invertedEmojiMap.put(":part_alternation_mark:", 12349); - invertedEmojiMap.put(":partly_sunny:", 9925); - invertedEmojiMap.put(":passport_control:", 128706); - invertedEmojiMap.put(":paw_prints:", 128062); - invertedEmojiMap.put(":peach:", 127825); - invertedEmojiMap.put(":pear:", 127824); - invertedEmojiMap.put(":pencil:", 128221); - invertedEmojiMap.put(":pencil2:", 9999); - invertedEmojiMap.put(":penguin:", 128039); - invertedEmojiMap.put(":pensive:", 128532); - invertedEmojiMap.put(":performing_arts:", 127917); - invertedEmojiMap.put(":persevere:", 128547); - invertedEmojiMap.put(":person_frowning:", 128589); - invertedEmojiMap.put(":person_with_blond_hair:", 128113); - invertedEmojiMap.put(":person_with_pouting_face:", 128590); - invertedEmojiMap.put(":phone:", 9742); - invertedEmojiMap.put(":pig:", 128055); - invertedEmojiMap.put(":pig2:", 128022); - invertedEmojiMap.put(":pig_nose:", 128061); - invertedEmojiMap.put(":pill:", 128138); - invertedEmojiMap.put(":pineapple:", 127821); - invertedEmojiMap.put(":pisces:", 9811); - invertedEmojiMap.put(":pizza:", 127829); - invertedEmojiMap.put(":point_down:", 128071); - invertedEmojiMap.put(":point_left:", 128072); - invertedEmojiMap.put(":point_right:", 128073); - invertedEmojiMap.put(":point_up:", 9757); - invertedEmojiMap.put(":point_up_2:", 128070); - invertedEmojiMap.put(":police_car:", 128659); - invertedEmojiMap.put(":poodle:", 128041); - invertedEmojiMap.put(":poop:", 128169); - invertedEmojiMap.put(":post_office:", 127971); - invertedEmojiMap.put(":postal_horn:", 128239); - invertedEmojiMap.put(":postbox:", 128238); - invertedEmojiMap.put(":potable_water:", 128688); - invertedEmojiMap.put(":pouch:", 128093); - invertedEmojiMap.put(":poultry_leg:", 127831); - invertedEmojiMap.put(":pound:", 128183); - invertedEmojiMap.put(":pouting_cat:", 128574); - invertedEmojiMap.put(":pray:", 128591); - invertedEmojiMap.put(":princess:", 128120); - invertedEmojiMap.put(":punch:", 128074); - invertedEmojiMap.put(":purple_heart:", 128156); - invertedEmojiMap.put(":purse:", 128091); - invertedEmojiMap.put(":pushpin:", 128204); - invertedEmojiMap.put(":put_litter_in_its_place:", 128686); - invertedEmojiMap.put(":question:", 10067); - invertedEmojiMap.put(":rabbit:", 128048); - invertedEmojiMap.put(":rabbit2:", 128007); - invertedEmojiMap.put(":racehorse:", 128014); - invertedEmojiMap.put(":radio:", 128251); - invertedEmojiMap.put(":radio_button:", 128280); - invertedEmojiMap.put(":rage:", 128545); - invertedEmojiMap.put(":railway_car:", 128643); - invertedEmojiMap.put(":rainbow:", 127752); - invertedEmojiMap.put(":raised_hand:", 9995); - invertedEmojiMap.put(":raised_hands:", 128588); - invertedEmojiMap.put(":raising_hand:", 128587); - invertedEmojiMap.put(":ram:", 128015); - invertedEmojiMap.put(":ramen:", 127836); - invertedEmojiMap.put(":rat:", 128000); - invertedEmojiMap.put(":recycle:", 9851); - invertedEmojiMap.put(":red_car:", 128663); - invertedEmojiMap.put(":red_circle:", 128308); - invertedEmojiMap.put(":registered:", 174); - invertedEmojiMap.put(":relaxed:", 9786); - invertedEmojiMap.put(":relieved:", 128524); - invertedEmojiMap.put(":repeat:", 128257); - invertedEmojiMap.put(":repeat_one:", 128258); - invertedEmojiMap.put(":restroom:", 128699); - invertedEmojiMap.put(":revolving_hearts:", 128158); - invertedEmojiMap.put(":rewind:", 9194); - invertedEmojiMap.put(":ribbon:", 127872); - invertedEmojiMap.put(":rice:", 127834); - invertedEmojiMap.put(":rice_ball:", 127833); - invertedEmojiMap.put(":rice_cracker:", 127832); - invertedEmojiMap.put(":rice_scene:", 127889); - invertedEmojiMap.put(":ring:", 128141); - invertedEmojiMap.put(":rocket:", 128640); - invertedEmojiMap.put(":roller_coaster:", 127906); - invertedEmojiMap.put(":rooster:", 128019); - invertedEmojiMap.put(":rose:", 127801); - invertedEmojiMap.put(":rotating_light:", 128680); - invertedEmojiMap.put(":round_pushpin:", 128205); - invertedEmojiMap.put(":rowboat:", 128675); - invertedEmojiMap.put(":rugby_football:", 127945); - invertedEmojiMap.put(":runner:", 127939); - invertedEmojiMap.put(":running:", 127939); - invertedEmojiMap.put(":running_shirt_with_sash:", 127933); - invertedEmojiMap.put(":sa:", 127490); - invertedEmojiMap.put(":sagittarius:", 9808); - invertedEmojiMap.put(":sailboat:", 9973); - invertedEmojiMap.put(":sake:", 127862); - invertedEmojiMap.put(":sandal:", 128097); - invertedEmojiMap.put(":santa:", 127877); - invertedEmojiMap.put(":satellite:", 128225); - invertedEmojiMap.put(":satisfied:", 128518); - invertedEmojiMap.put(":saxophone:", 127927); - invertedEmojiMap.put(":school:", 127979); - invertedEmojiMap.put(":school_satchel:", 127890); - invertedEmojiMap.put(":scissors:", 9986); - invertedEmojiMap.put(":scorpius:", 9807); - invertedEmojiMap.put(":scream:", 128561); - invertedEmojiMap.put(":scream_cat:", 128576); - invertedEmojiMap.put(":scroll:", 128220); - invertedEmojiMap.put(":seat:", 128186); - invertedEmojiMap.put(":secret:", 12953); - invertedEmojiMap.put(":see_no_evil:", 128584); - invertedEmojiMap.put(":seedling:", 127793); - invertedEmojiMap.put(":shaved_ice:", 127847); - invertedEmojiMap.put(":sheep:", 128017); - invertedEmojiMap.put(":shell:", 128026); - invertedEmojiMap.put(":ship:", 128674); - invertedEmojiMap.put(":shirt:", 128085); - invertedEmojiMap.put(":shit:", 128169); - invertedEmojiMap.put(":shoe:", 128094); - invertedEmojiMap.put(":shower:", 128703); - invertedEmojiMap.put(":signal_strength:", 128246); - invertedEmojiMap.put(":six_pointed_star:", 128303); - invertedEmojiMap.put(":ski:", 127935); - invertedEmojiMap.put(":skull:", 128128); - invertedEmojiMap.put(":sleeping:", 128564); - invertedEmojiMap.put(":sleepy:", 128554); - invertedEmojiMap.put(":slot_machine:", 127920); - invertedEmojiMap.put(":small_blue_diamond:", 128313); - invertedEmojiMap.put(":small_orange_diamond:", 128312); - invertedEmojiMap.put(":small_red_triangle:", 128314); - invertedEmojiMap.put(":small_red_triangle_down:", 128315); - invertedEmojiMap.put(":smile:", 128516); - invertedEmojiMap.put(":smile_cat:", 128568); - invertedEmojiMap.put(":smiley:", 128515); - invertedEmojiMap.put(":smiley_cat:", 128570); - invertedEmojiMap.put(":smiling_imp:", 128520); - invertedEmojiMap.put(":smirk:", 128527); - invertedEmojiMap.put(":smirk_cat:", 128572); - invertedEmojiMap.put(":smoking:", 128684); - invertedEmojiMap.put(":snail:", 128012); - invertedEmojiMap.put(":snake:", 128013); - invertedEmojiMap.put(":snowboarder:", 127938); - invertedEmojiMap.put(":snowflake:", 10052); - invertedEmojiMap.put(":snowman:", 9924); - invertedEmojiMap.put(":sob:", 128557); - invertedEmojiMap.put(":soccer:", 9917); - invertedEmojiMap.put(":soon:", 128284); - invertedEmojiMap.put(":sos:", 127384); - invertedEmojiMap.put(":sound:", 128265); - invertedEmojiMap.put(":space_invader:", 128126); - invertedEmojiMap.put(":spades:", 9824); - invertedEmojiMap.put(":spaghetti:", 127837); - invertedEmojiMap.put(":sparkle:", 10055); - invertedEmojiMap.put(":sparkler:", 127879); - invertedEmojiMap.put(":sparkles:", 10024); - invertedEmojiMap.put(":sparkling_heart:", 128150); - invertedEmojiMap.put(":speak_no_evil:", 128586); - invertedEmojiMap.put(":speaker:", 128266); - invertedEmojiMap.put(":speech_balloon:", 128172); - invertedEmojiMap.put(":speedboat:", 128676); - invertedEmojiMap.put(":star:", 11088); - invertedEmojiMap.put(":star2:", 127775); - invertedEmojiMap.put(":stars:", 127747); - invertedEmojiMap.put(":station:", 128649); - invertedEmojiMap.put(":statue_of_liberty:", 128509); - invertedEmojiMap.put(":steam_locomotive:", 128642); - invertedEmojiMap.put(":stew:", 127858); - invertedEmojiMap.put(":straight_ruler:", 128207); - invertedEmojiMap.put(":strawberry:", 127827); - invertedEmojiMap.put(":stuck_out_tongue:", 128539); - invertedEmojiMap.put(":stuck_out_tongue_closed_eyes:", 128541); - invertedEmojiMap.put(":stuck_out_tongue_winking_eye:", 128540); - invertedEmojiMap.put(":sun_with_face:", 127774); - invertedEmojiMap.put(":sunflower:", 127803); - invertedEmojiMap.put(":sunglasses:", 128526); - invertedEmojiMap.put(":sunny:", 9728); - invertedEmojiMap.put(":sunrise:", 127749); - invertedEmojiMap.put(":sunrise_over_mountains:", 127748); - invertedEmojiMap.put(":surfer:", 127940); - invertedEmojiMap.put(":sushi:", 127843); - invertedEmojiMap.put(":suspension_railway:", 128671); - invertedEmojiMap.put(":sweat:", 128531); - invertedEmojiMap.put(":sweat_drops:", 128166); - invertedEmojiMap.put(":sweat_smile:", 128517); - invertedEmojiMap.put(":sweet_potato:", 127840); - invertedEmojiMap.put(":swimmer:", 127946); - invertedEmojiMap.put(":symbols:", 128291); - invertedEmojiMap.put(":syringe:", 128137); - invertedEmojiMap.put(":tada:", 127881); - invertedEmojiMap.put(":tanabata_tree:", 127883); - invertedEmojiMap.put(":tangerine:", 127818); - invertedEmojiMap.put(":taurus:", 9801); - invertedEmojiMap.put(":taxi:", 128661); - invertedEmojiMap.put(":tea:", 127861); - invertedEmojiMap.put(":telephone:", 9742); - invertedEmojiMap.put(":telephone_receiver:", 128222); - invertedEmojiMap.put(":telescope:", 128301); - invertedEmojiMap.put(":tennis:", 127934); - invertedEmojiMap.put(":tent:", 9978); - invertedEmojiMap.put(":thought_balloon:", 128173); - invertedEmojiMap.put(":thumbsdown:", 128078); - invertedEmojiMap.put(":thumbsup:", 128077); - invertedEmojiMap.put(":ticket:", 127915); - invertedEmojiMap.put(":tiger:", 128047); - invertedEmojiMap.put(":tiger2:", 128005); - invertedEmojiMap.put(":tired_face:", 128555); - invertedEmojiMap.put(":tm:", 8482); - invertedEmojiMap.put(":toilet:", 128701); - invertedEmojiMap.put(":tokyo_tower:", 128508); - invertedEmojiMap.put(":tomato:", 127813); - invertedEmojiMap.put(":tongue:", 128069); - invertedEmojiMap.put(":top:", 128285); - invertedEmojiMap.put(":tophat:", 127913); - invertedEmojiMap.put(":tractor:", 128668); - invertedEmojiMap.put(":traffic_light:", 128677); - invertedEmojiMap.put(":train:", 128643); - invertedEmojiMap.put(":train2:", 128646); - invertedEmojiMap.put(":tram:", 128650); - invertedEmojiMap.put(":triangular_flag_on_post:", 128681); - invertedEmojiMap.put(":triangular_ruler:", 128208); - invertedEmojiMap.put(":trident:", 128305); - invertedEmojiMap.put(":triumph:", 128548); - invertedEmojiMap.put(":trolleybus:", 128654); - invertedEmojiMap.put(":trophy:", 127942); - invertedEmojiMap.put(":tropical_drink:", 127865); - invertedEmojiMap.put(":tropical_fish:", 128032); - invertedEmojiMap.put(":truck:", 128666); - invertedEmojiMap.put(":trumpet:", 127930); - invertedEmojiMap.put(":tshirt:", 128085); - invertedEmojiMap.put(":tulip:", 127799); - invertedEmojiMap.put(":turtle:", 128034); - invertedEmojiMap.put(":tv:", 128250); - invertedEmojiMap.put(":twisted_rightwards_arrows:", 128256); - invertedEmojiMap.put(":two_hearts:", 128149); - invertedEmojiMap.put(":two_men_holding_hands:", 128108); - invertedEmojiMap.put(":two_women_holding_hands:", 128109); - invertedEmojiMap.put(":u5272:", 127545); - invertedEmojiMap.put(":u5408:", 127540); - invertedEmojiMap.put(":u55b6:", 127546); - invertedEmojiMap.put(":u6307:", 127535); - invertedEmojiMap.put(":u6708:", 127543); - invertedEmojiMap.put(":u6709:", 127542); - invertedEmojiMap.put(":u6e80:", 127541); - invertedEmojiMap.put(":u7121:", 127514); - invertedEmojiMap.put(":u7533:", 127544); - invertedEmojiMap.put(":u7981:", 127538); - invertedEmojiMap.put(":u7a7a:", 127539); - invertedEmojiMap.put(":umbrella:", 9748); - invertedEmojiMap.put(":unamused:", 128530); - invertedEmojiMap.put(":underage:", 128286); - invertedEmojiMap.put(":unlock:", 128275); - invertedEmojiMap.put(":up:", 127385); - invertedEmojiMap.put(":v:", 9996); - invertedEmojiMap.put(":vertical_traffic_light:", 128678); - invertedEmojiMap.put(":vhs:", 128252); - invertedEmojiMap.put(":vibration_mode:", 128243); - invertedEmojiMap.put(":video_camera:", 128249); - invertedEmojiMap.put(":video_game:", 127918); - invertedEmojiMap.put(":violin:", 127931); - invertedEmojiMap.put(":virgo:", 9805); - invertedEmojiMap.put(":volcano:", 127755); - invertedEmojiMap.put(":vs:", 127386); - invertedEmojiMap.put(":walking:", 128694); - invertedEmojiMap.put(":waning_crescent_moon:", 127768); - invertedEmojiMap.put(":waning_gibbous_moon:", 127766); - invertedEmojiMap.put(":warning:", 9888); - invertedEmojiMap.put(":watch:", 8986); - invertedEmojiMap.put(":water_buffalo:", 128003); - invertedEmojiMap.put(":watermelon:", 127817); - invertedEmojiMap.put(":wave:", 128075); - invertedEmojiMap.put(":wavy_dash:", 12336); - invertedEmojiMap.put(":waxing_crescent_moon:", 127762); - invertedEmojiMap.put(":waxing_gibbous_moon:", 127764); - invertedEmojiMap.put(":wc:", 128702); - invertedEmojiMap.put(":weary:", 128553); - invertedEmojiMap.put(":wedding:", 128146); - invertedEmojiMap.put(":whale:", 128051); - invertedEmojiMap.put(":whale2:", 128011); - invertedEmojiMap.put(":wheelchair:", 9855); - invertedEmojiMap.put(":white_check_mark:", 9989); - invertedEmojiMap.put(":white_circle:", 9898); - invertedEmojiMap.put(":white_flower:", 128174); - invertedEmojiMap.put(":white_large_square:", 11036); - invertedEmojiMap.put(":white_medium_small_square:", 9725); - invertedEmojiMap.put(":white_medium_square:", 9723); - invertedEmojiMap.put(":white_small_square:", 9643); - invertedEmojiMap.put(":white_square_button:", 128307); - invertedEmojiMap.put(":wind_chime:", 127888); - invertedEmojiMap.put(":wine_glass:", 127863); - invertedEmojiMap.put(":wink:", 128521); - invertedEmojiMap.put(":wolf:", 128058); - invertedEmojiMap.put(":woman:", 128105); - invertedEmojiMap.put(":womans_clothes:", 128090); - invertedEmojiMap.put(":womans_hat:", 128082); - invertedEmojiMap.put(":womens:", 128698); - invertedEmojiMap.put(":worried:", 128543); - invertedEmojiMap.put(":wrench:", 128295); - invertedEmojiMap.put(":x:", 10060); - invertedEmojiMap.put(":yellow_heart:", 128155); - invertedEmojiMap.put(":yen:", 128180); - invertedEmojiMap.put(":yum:", 128523); - invertedEmojiMap.put(":zap:", 9889); - invertedEmojiMap.put(":zzz:", 128164); - } -} diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt new file mode 100644 index 000000000..0c65a48bb --- /dev/null +++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt @@ -0,0 +1,1676 @@ +package com.habitrpg.common.habitica.helpers + +object EmojiMap { + val emojiMap: MutableMap = HashMap() + val invertedEmojiMap: MutableMap = HashMap() + + init { + emojiMap.put(128077, ":+1:") + emojiMap.put(128078, ":-1:") + emojiMap.put(128175, ":100:") + emojiMap.put(128290, ":1234:") + emojiMap.put(127921, ":8ball:") + emojiMap.put(127344, ":a:") + emojiMap.put(127374, ":ab:") + emojiMap.put(128292, ":abc:") + emojiMap.put(128289, ":abcd:") + emojiMap.put(127569, ":accept:") + emojiMap.put(128673, ":aerial_tramway:") + emojiMap.put(9992, ":airplane:") + emojiMap.put(9200, ":alarm_clock:") + emojiMap.put(128125, ":alien:") + emojiMap.put(128657, ":ambulance:") + emojiMap.put(9875, ":anchor:") + emojiMap.put(128124, ":angel:") + emojiMap.put(128162, ":anger:") + emojiMap.put(128544, ":angry:") + emojiMap.put(128551, ":anguished:") + emojiMap.put(128028, ":ant:") + emojiMap.put(127822, ":apple:") + emojiMap.put(9810, ":aquarius:") + emojiMap.put(9800, ":aries:") + emojiMap.put(9664, ":arrow_backward:") + emojiMap.put(9196, ":arrow_double_down:") + emojiMap.put(9195, ":arrow_double_up:") + emojiMap.put(11015, ":arrow_down:") + emojiMap.put(128317, ":arrow_down_small:") + emojiMap.put(9654, ":arrow_forward:") + emojiMap.put(10549, ":arrow_heading_down:") + emojiMap.put(10548, ":arrow_heading_up:") + emojiMap.put(11013, ":arrow_left:") + emojiMap.put(8601, ":arrow_lower_left:") + emojiMap.put(8600, ":arrow_lower_right:") + emojiMap.put(10145, ":arrow_right:") + emojiMap.put(8618, ":arrow_right_hook:") + emojiMap.put(11014, ":arrow_up:") + emojiMap.put(8597, ":arrow_up_down:") + emojiMap.put(128316, ":arrow_up_small:") + emojiMap.put(8598, ":arrow_upper_left:") + emojiMap.put(8599, ":arrow_upper_right:") + emojiMap.put(128259, ":arrows_clockwise:") + emojiMap.put(128260, ":arrows_counterclockwise:") + emojiMap.put(127912, ":art:") + emojiMap.put(128667, ":articulated_lorry:") + emojiMap.put(128562, ":astonished:") + emojiMap.put(128095, ":athletic_shoe:") + emojiMap.put(127975, ":atm:") + emojiMap.put(127345, ":b:") + emojiMap.put(128118, ":baby:") + emojiMap.put(127868, ":baby_bottle:") + emojiMap.put(128036, ":baby_chick:") + emojiMap.put(128700, ":baby_symbol:") + emojiMap.put(128281, ":back:") + emojiMap.put(128708, ":baggage_claim:") + emojiMap.put(127880, ":balloon:") + emojiMap.put(9745, ":ballot_box_with_check:") + emojiMap.put(127885, ":bamboo:") + emojiMap.put(127820, ":banana:") + emojiMap.put(8252, ":bangbang:") + emojiMap.put(127974, ":bank:") + emojiMap.put(128202, ":bar_chart:") + emojiMap.put(128136, ":barber:") + emojiMap.put(9918, ":baseball:") + emojiMap.put(127936, ":basketball:") + emojiMap.put(128704, ":bath:") + emojiMap.put(128705, ":bathtub:") + emojiMap.put(128267, ":battery:") + emojiMap.put(128059, ":bear:") + emojiMap.put(128029, ":bee:") + emojiMap.put(127866, ":beer:") + emojiMap.put(127867, ":beers:") + emojiMap.put(128030, ":beetle:") + emojiMap.put(128304, ":beginner:") + emojiMap.put(128276, ":bell:") + emojiMap.put(127857, ":bento:") + emojiMap.put(128692, ":bicyclist:") + emojiMap.put(128690, ":bike:") + emojiMap.put(128089, ":bikini:") + emojiMap.put(128038, ":bird:") + emojiMap.put(127874, ":birthday:") + emojiMap.put(9899, ":black_circle:") + emojiMap.put(127183, ":black_joker:") + emojiMap.put(11035, ":black_large_square:") + emojiMap.put(9726, ":black_medium_small_square:") + emojiMap.put(9724, ":black_medium_square:") + emojiMap.put(10002, ":black_nib:") + emojiMap.put(9642, ":black_small_square:") + emojiMap.put(128306, ":black_square_button:") + emojiMap.put(127804, ":blossom:") + emojiMap.put(128033, ":blowfish:") + emojiMap.put(128216, ":blue_book:") + emojiMap.put(128665, ":blue_car:") + emojiMap.put(128153, ":blue_heart:") + emojiMap.put(128522, ":blush:") + emojiMap.put(128023, ":boar:") + emojiMap.put(9973, ":boat:") + emojiMap.put(128163, ":bomb:") + emojiMap.put(128214, ":book:") + emojiMap.put(128278, ":bookmark:") + emojiMap.put(128209, ":bookmark_tabs:") + emojiMap.put(128218, ":books:") + emojiMap.put(128165, ":boom:") + emojiMap.put(128098, ":boot:") + emojiMap.put(128144, ":bouquet:") + emojiMap.put(128583, ":bow:") + emojiMap.put(127923, ":bowling:") + emojiMap.put(128102, ":boy:") + emojiMap.put(127838, ":bread:") + emojiMap.put(128112, ":bride_with_veil:") + emojiMap.put(127753, ":bridge_at_night:") + emojiMap.put(128188, ":briefcase:") + emojiMap.put(128148, ":broken_heart:") + emojiMap.put(128027, ":bug:") + emojiMap.put(128161, ":bulb:") + emojiMap.put(128645, ":bullettrain_front:") + emojiMap.put(128644, ":bullettrain_side:") + emojiMap.put(128652, ":bus:") + emojiMap.put(128655, ":busstop:") + emojiMap.put(128100, ":bust_in_silhouette:") + emojiMap.put(128101, ":busts_in_silhouette:") + emojiMap.put(127797, ":cactus:") + emojiMap.put(127856, ":cake:") + emojiMap.put(128198, ":calendar:") + emojiMap.put(128242, ":calling:") + emojiMap.put(128043, ":camel:") + emojiMap.put(128247, ":camera:") + emojiMap.put(9803, ":cancer:") + emojiMap.put(127852, ":candy:") + emojiMap.put(128288, ":capital_abcd:") + emojiMap.put(9809, ":capricorn:") + emojiMap.put(128663, ":car:") + emojiMap.put(128199, ":card_index:") + emojiMap.put(127904, ":carousel_horse:") + emojiMap.put(128049, ":cat:") + emojiMap.put(128008, ":cat2:") + emojiMap.put(128191, ":cd:") + emojiMap.put(128185, ":chart:") + emojiMap.put(128201, ":chart_with_downwards_trend:") + emojiMap.put(128200, ":chart_with_upwards_trend:") + emojiMap.put(127937, ":checkered_flag:") + emojiMap.put(127826, ":cherries:") + emojiMap.put(127800, ":cherry_blossom:") + emojiMap.put(127792, ":chestnut:") + emojiMap.put(128020, ":chicken:") + emojiMap.put(128696, ":children_crossing:") + emojiMap.put(127851, ":chocolate_bar:") + emojiMap.put(127876, ":christmas_tree:") + emojiMap.put(9962, ":church:") + emojiMap.put(127910, ":cinema:") + emojiMap.put(127914, ":circus_tent:") + emojiMap.put(127751, ":city_sunrise:") + emojiMap.put(127750, ":city_sunset:") + emojiMap.put(127377, ":cl:") + emojiMap.put(128079, ":clap:") + emojiMap.put(127916, ":clapper:") + emojiMap.put(128203, ":clipboard:") + emojiMap.put(128336, ":clock1:") + emojiMap.put(128345, ":clock10:") + emojiMap.put(128357, ":clock1030:") + emojiMap.put(128346, ":clock11:") + emojiMap.put(128358, ":clock1130:") + emojiMap.put(128347, ":clock12:") + emojiMap.put(128359, ":clock1230:") + emojiMap.put(128348, ":clock130:") + emojiMap.put(128337, ":clock2:") + emojiMap.put(128349, ":clock230:") + emojiMap.put(128338, ":clock3:") + emojiMap.put(128350, ":clock330:") + emojiMap.put(128339, ":clock4:") + emojiMap.put(128351, ":clock430:") + emojiMap.put(128340, ":clock5:") + emojiMap.put(128352, ":clock530:") + emojiMap.put(128341, ":clock6:") + emojiMap.put(128353, ":clock630:") + emojiMap.put(128342, ":clock7:") + emojiMap.put(128354, ":clock730:") + emojiMap.put(128343, ":clock8:") + emojiMap.put(128355, ":clock830:") + emojiMap.put(128344, ":clock9:") + emojiMap.put(128356, ":clock930:") + emojiMap.put(128213, ":closed_book:") + emojiMap.put(128272, ":closed_lock_with_key:") + emojiMap.put(127746, ":closed_umbrella:") + emojiMap.put(9729, ":cloud:") + emojiMap.put(9827, ":clubs:") + emojiMap.put(127864, ":cocktail:") + emojiMap.put(9749, ":coffee:") + emojiMap.put(128560, ":cold_sweat:") + emojiMap.put(128187, ":computer:") + emojiMap.put(127882, ":confetti_ball:") + emojiMap.put(128534, ":confounded:") + emojiMap.put(128533, ":confused:") + emojiMap.put(12951, ":congratulations:") + emojiMap.put(128679, ":construction:") + emojiMap.put(128119, ":construction_worker:") + emojiMap.put(127978, ":convenience_store:") + emojiMap.put(127850, ":cookie:") + emojiMap.put(127378, ":cool:") + emojiMap.put(128110, ":cop:") + emojiMap.put(169, ":copyright:") + emojiMap.put(127805, ":corn:") + emojiMap.put(128107, ":couple:") + emojiMap.put(128145, ":couple_with_heart:") + emojiMap.put(128143, ":couplekiss:") + emojiMap.put(128046, ":cow:") + emojiMap.put(128004, ":cow2:") + emojiMap.put(128179, ":credit_card:") + emojiMap.put(127769, ":crescent_moon:") + emojiMap.put(128010, ":crocodile:") + emojiMap.put(127884, ":crossed_flags:") + emojiMap.put(128081, ":crown:") + emojiMap.put(128546, ":cry:") + emojiMap.put(128575, ":crying_cat_face:") + emojiMap.put(128302, ":crystal_ball:") + emojiMap.put(128152, ":cupid:") + emojiMap.put(10160, ":curly_loop:") + emojiMap.put(128177, ":currency_exchange:") + emojiMap.put(127835, ":curry:") + emojiMap.put(127854, ":custard:") + emojiMap.put(128707, ":customs:") + emojiMap.put(127744, ":cyclone:") + emojiMap.put(128131, ":dancer:") + emojiMap.put(128111, ":dancers:") + emojiMap.put(127841, ":dango:") + emojiMap.put(127919, ":dart:") + emojiMap.put(128168, ":dash:") + emojiMap.put(128197, ":date:") + emojiMap.put(127795, ":deciduous_tree:") + emojiMap.put(127980, ":department_store:") + emojiMap.put(128160, ":diamond_shape_with_a_dot_inside:") + emojiMap.put(9830, ":diamonds:") + emojiMap.put(128542, ":disappointed:") + emojiMap.put(128549, ":disappointed_relieved:") + emojiMap.put(128171, ":dizzy:") + emojiMap.put(128565, ":dizzy_face:") + emojiMap.put(128687, ":do_not_litter:") + emojiMap.put(128054, ":dog:") + emojiMap.put(128021, ":dog2:") + emojiMap.put(128181, ":dollar:") + emojiMap.put(127886, ":dolls:") + emojiMap.put(128044, ":dolphin:") + emojiMap.put(128682, ":door:") + emojiMap.put(127849, ":doughnut:") + emojiMap.put(128009, ":dragon:") + emojiMap.put(128050, ":dragon_face:") + emojiMap.put(128087, ":dress:") + emojiMap.put(128042, ":dromedary_camel:") + emojiMap.put(128167, ":droplet:") + emojiMap.put(128192, ":dvd:") + emojiMap.put(128231, ":e-mail:") + emojiMap.put(128066, ":ear:") + emojiMap.put(127806, ":ear_of_rice:") + emojiMap.put(127757, ":earth_africa:") + emojiMap.put(127758, ":earth_americas:") + emojiMap.put(127759, ":earth_asia:") + emojiMap.put(127859, ":egg:") + emojiMap.put(127814, ":eggplant:") + emojiMap.put(10036, ":eight_pointed_black_star:") + emojiMap.put(10035, ":eight_spoked_asterisk:") + emojiMap.put(128268, ":electric_plug:") + emojiMap.put(128024, ":elephant:") + emojiMap.put(128282, ":end:") + emojiMap.put(9993, ":envelope:") + emojiMap.put(128233, ":envelope_with_arrow:") + emojiMap.put(128182, ":euro:") + emojiMap.put(127984, ":european_castle:") + emojiMap.put(127972, ":european_post_office:") + emojiMap.put(127794, ":evergreen_tree:") + emojiMap.put(128529, ":expressionless:") + emojiMap.put(128083, ":eyeglasses:") + emojiMap.put(128064, ":eyes:") + emojiMap.put(127981, ":factory:") + emojiMap.put(127810, ":fallen_leaf:") + emojiMap.put(128106, ":family:") + emojiMap.put(9193, ":fast_forward:") + emojiMap.put(128224, ":fax:") + emojiMap.put(128552, ":fearful:") + emojiMap.put(128062, ":feet:") + emojiMap.put(127905, ":ferris_wheel:") + emojiMap.put(128193, ":file_folder:") + emojiMap.put(128293, ":fire:") + emojiMap.put(128658, ":fire_engine:") + emojiMap.put(127878, ":fireworks:") + emojiMap.put(127763, ":first_quarter_moon:") + emojiMap.put(127771, ":first_quarter_moon_with_face:") + emojiMap.put(128031, ":fish:") + emojiMap.put(127845, ":fish_cake:") + emojiMap.put(127907, ":fishing_pole_and_fish:") + emojiMap.put(9994, ":fist:") + emojiMap.put(127887, ":flags:") + emojiMap.put(128294, ":flashlight:") + emojiMap.put(128190, ":floppy_disk:") + emojiMap.put(127924, ":flower_playing_cards:") + emojiMap.put(128563, ":flushed:") + emojiMap.put(127745, ":foggy:") + emojiMap.put(127944, ":football:") + emojiMap.put(128099, ":footprints:") + emojiMap.put(127860, ":fork_and_knife:") + emojiMap.put(9970, ":fountain:") + emojiMap.put(127808, ":four_leaf_clover:") + emojiMap.put(127379, ":free:") + emojiMap.put(127844, ":fried_shrimp:") + emojiMap.put(127839, ":fries:") + emojiMap.put(128056, ":frog:") + emojiMap.put(128550, ":frowning:") + emojiMap.put(9981, ":fuelpump:") + emojiMap.put(127765, ":full_moon:") + emojiMap.put(127773, ":full_moon_with_face:") + emojiMap.put(127922, ":game_die:") + emojiMap.put(128142, ":gem:") + emojiMap.put(9802, ":gemini:") + emojiMap.put(128123, ":ghost:") + emojiMap.put(127873, ":gift:") + emojiMap.put(128157, ":gift_heart:") + emojiMap.put(128103, ":girl:") + emojiMap.put(127760, ":globe_with_meridians:") + emojiMap.put(128016, ":goat:") + emojiMap.put(9971, ":golf:") + emojiMap.put(127815, ":grapes:") + emojiMap.put(127823, ":green_apple:") + emojiMap.put(128215, ":green_book:") + emojiMap.put(128154, ":green_heart:") + emojiMap.put(10069, ":grey_exclamation:") + emojiMap.put(10068, ":grey_question:") + emojiMap.put(128556, ":grimacing:") + emojiMap.put(128513, ":grin:") + emojiMap.put(128512, ":grinning:") + emojiMap.put(128130, ":guardsman:") + emojiMap.put(127928, ":guitar:") + emojiMap.put(128299, ":gun:") + emojiMap.put(128135, ":haircut:") + emojiMap.put(127828, ":hamburger:") + emojiMap.put(128296, ":hammer:") + emojiMap.put(128057, ":hamster:") + emojiMap.put(128092, ":handbag:") + emojiMap.put(128037, ":hatched_chick:") + emojiMap.put(128035, ":hatching_chick:") + emojiMap.put(127911, ":headphones:") + emojiMap.put(128585, ":hear_no_evil:") + emojiMap.put(10084, ":heart:") + emojiMap.put(128159, ":heart_decoration:") + emojiMap.put(128525, ":heart_eyes:") + emojiMap.put(128571, ":heart_eyes_cat:") + emojiMap.put(128147, ":heartbeat:") + emojiMap.put(128151, ":heartpulse:") + emojiMap.put(9829, ":hearts:") + emojiMap.put(10004, ":heavy_check_mark:") + emojiMap.put(10135, ":heavy_division_sign:") + emojiMap.put(128178, ":heavy_dollar_sign:") + emojiMap.put(10071, ":heavy_exclamation_mark:") + emojiMap.put(10134, ":heavy_minus_sign:") + emojiMap.put(10006, ":heavy_multiplication_x:") + emojiMap.put(10133, ":heavy_plus_sign:") + emojiMap.put(128641, ":helicopter:") + emojiMap.put(127807, ":herb:") + emojiMap.put(127802, ":hibiscus:") + emojiMap.put(128262, ":high_brightness:") + emojiMap.put(128096, ":high_heel:") + emojiMap.put(128298, ":hocho:") + emojiMap.put(127855, ":honey_pot:") + emojiMap.put(128052, ":horse:") + emojiMap.put(127943, ":horse_racing:") + emojiMap.put(127973, ":hospital:") + emojiMap.put(127976, ":hotel:") + emojiMap.put(9832, ":hotsprings:") + emojiMap.put(8987, ":hourglass:") + emojiMap.put(9203, ":hourglass_flowing_sand:") + emojiMap.put(127968, ":house:") + emojiMap.put(127969, ":house_with_garden:") + emojiMap.put(128559, ":hushed:") + emojiMap.put(127848, ":ice_cream:") + emojiMap.put(127846, ":icecream:") + emojiMap.put(127380, ":id:") + emojiMap.put(127568, ":ideograph_advantage:") + emojiMap.put(128127, ":imp:") + emojiMap.put(128229, ":inbox_tray:") + emojiMap.put(128232, ":incoming_envelope:") + emojiMap.put(128129, ":information_desk_person:") + emojiMap.put(8505, ":information_source:") + emojiMap.put(128519, ":innocent:") + emojiMap.put(8265, ":interrobang:") + emojiMap.put(128241, ":iphone:") + emojiMap.put(127875, ":jack_o_lantern:") + emojiMap.put(128510, ":japan:") + emojiMap.put(127983, ":japanese_castle:") + emojiMap.put(128122, ":japanese_goblin:") + emojiMap.put(128121, ":japanese_ogre:") + emojiMap.put(128086, ":jeans:") + emojiMap.put(128514, ":joy:") + emojiMap.put(128569, ":joy_cat:") + emojiMap.put(128273, ":key:") + emojiMap.put(128287, ":keycap_ten:") + emojiMap.put(128088, ":kimono:") + emojiMap.put(128139, ":kiss:") + emojiMap.put(128535, ":kissing:") + emojiMap.put(128573, ":kissing_cat:") + emojiMap.put(128538, ":kissing_closed_eyes:") + emojiMap.put(128536, ":kissing_heart:") + emojiMap.put(128537, ":kissing_smiling_eyes:") + emojiMap.put(128040, ":koala:") + emojiMap.put(127489, ":koko:") + emojiMap.put(127982, ":lantern:") + emojiMap.put(128309, ":large_blue_circle:") + emojiMap.put(128311, ":large_blue_diamond:") + emojiMap.put(128310, ":large_orange_diamond:") + emojiMap.put(127767, ":last_quarter_moon:") + emojiMap.put(127772, ":last_quarter_moon_with_face:") + emojiMap.put(128518, ":laughing:") + emojiMap.put(127811, ":leaves:") + emojiMap.put(128210, ":ledger:") + emojiMap.put(128709, ":left_luggage:") + emojiMap.put(8596, ":left_right_arrow:") + emojiMap.put(8617, ":leftwards_arrow_with_hook:") + emojiMap.put(127819, ":lemon:") + emojiMap.put(9804, ":leo:") + emojiMap.put(128006, ":leopard:") + emojiMap.put(9806, ":libra:") + emojiMap.put(128648, ":light_rail:") + emojiMap.put(128279, ":link:") + emojiMap.put(128068, ":lips:") + emojiMap.put(128132, ":lipstick:") + emojiMap.put(128274, ":lock:") + emojiMap.put(128271, ":lock_with_ink_pen:") + emojiMap.put(127853, ":lollipop:") + emojiMap.put(10175, ":loop:") + emojiMap.put(128226, ":loudspeaker:") + emojiMap.put(127977, ":love_hotel:") + emojiMap.put(128140, ":love_letter:") + emojiMap.put(128261, ":low_brightness:") + emojiMap.put(9410, ":m:") + emojiMap.put(128269, ":mag:") + emojiMap.put(128270, ":mag_right:") + emojiMap.put(126980, ":mahjong:") + emojiMap.put(128235, ":mailbox:") + emojiMap.put(128234, ":mailbox_closed:") + emojiMap.put(128236, ":mailbox_with_mail:") + emojiMap.put(128237, ":mailbox_with_no_mail:") + emojiMap.put(128104, ":man:") + emojiMap.put(128114, ":man_with_gua_pi_mao:") + emojiMap.put(128115, ":man_with_turban:") + emojiMap.put(127809, ":maple_leaf:") + emojiMap.put(128567, ":mask:") + emojiMap.put(128134, ":massage:") + emojiMap.put(127830, ":meat_on_bone:") + emojiMap.put(128227, ":mega:") + emojiMap.put(127816, ":melon:") + emojiMap.put(128697, ":mens:") + emojiMap.put(128647, ":metro:") + emojiMap.put(127908, ":microphone:") + emojiMap.put(128300, ":microscope:") + emojiMap.put(127756, ":milky_way:") + emojiMap.put(128656, ":minibus:") + emojiMap.put(128189, ":minidisc:") + emojiMap.put(128244, ":mobile_phone_off:") + emojiMap.put(128184, ":money_with_wings:") + emojiMap.put(128176, ":moneybag:") + emojiMap.put(128018, ":monkey:") + emojiMap.put(128053, ":monkey_face:") + emojiMap.put(128669, ":monorail:") + emojiMap.put(127764, ":moon:") + emojiMap.put(127891, ":mortar_board:") + emojiMap.put(128507, ":mount_fuji:") + emojiMap.put(128693, ":mountain_bicyclist:") + emojiMap.put(128672, ":mountain_cableway:") + emojiMap.put(128670, ":mountain_railway:") + emojiMap.put(128045, ":mouse:") + emojiMap.put(128001, ":mouse2:") + emojiMap.put(127909, ":movie_camera:") + emojiMap.put(128511, ":moyai:") + emojiMap.put(128170, ":muscle:") + emojiMap.put(127812, ":mushroom:") + emojiMap.put(127929, ":musical_keyboard:") + emojiMap.put(127925, ":musical_note:") + emojiMap.put(127932, ":musical_score:") + emojiMap.put(128263, ":mute:") + emojiMap.put(128133, ":nail_care:") + emojiMap.put(128219, ":name_badge:") + emojiMap.put(128084, ":necktie:") + emojiMap.put(10062, ":negative_squared_cross_mark:") + emojiMap.put(128528, ":neutral_face:") + emojiMap.put(127381, ":new:") + emojiMap.put(127761, ":new_moon:") + emojiMap.put(127770, ":new_moon_with_face:") + emojiMap.put(128240, ":newspaper:") + emojiMap.put(127382, ":ng:") + emojiMap.put(128277, ":no_bell:") + emojiMap.put(128691, ":no_bicycles:") + emojiMap.put(9940, ":no_entry:") + emojiMap.put(128683, ":no_entry_sign:") + emojiMap.put(128581, ":no_good:") + emojiMap.put(128245, ":no_mobile_phones:") + emojiMap.put(128566, ":no_mouth:") + emojiMap.put(128695, ":no_pedestrians:") + emojiMap.put(128685, ":no_smoking:") + emojiMap.put(128689, ":non-potable_water:") + emojiMap.put(128067, ":nose:") + emojiMap.put(128211, ":notebook:") + emojiMap.put(128212, ":notebook_with_decorative_cover:") + emojiMap.put(127926, ":notes:") + emojiMap.put(128297, ":nut_and_bolt:") + emojiMap.put(11093, ":o:") + emojiMap.put(127358, ":o2:") + emojiMap.put(127754, ":ocean:") + emojiMap.put(128025, ":octopus:") + emojiMap.put(127842, ":oden:") + emojiMap.put(127970, ":office:") + emojiMap.put(127383, ":ok:") + emojiMap.put(128076, ":ok_hand:") + emojiMap.put(128582, ":ok_woman:") + emojiMap.put(128116, ":older_man:") + emojiMap.put(128117, ":older_woman:") + emojiMap.put(128283, ":on:") + emojiMap.put(128664, ":oncoming_automobile:") + emojiMap.put(128653, ":oncoming_bus:") + emojiMap.put(128660, ":oncoming_police_car:") + emojiMap.put(128662, ":oncoming_taxi:") + emojiMap.put(128194, ":open_file_folder:") + emojiMap.put(128080, ":open_hands:") + emojiMap.put(128558, ":open_mouth:") + emojiMap.put(9934, ":ophiuchus:") + emojiMap.put(128217, ":orange_book:") + emojiMap.put(128228, ":outbox_tray:") + emojiMap.put(128002, ":ox:") + emojiMap.put(128230, ":package:") + emojiMap.put(128196, ":page_facing_up:") + emojiMap.put(128195, ":page_with_curl:") + emojiMap.put(128223, ":pager:") + emojiMap.put(127796, ":palm_tree:") + emojiMap.put(128060, ":panda_face:") + emojiMap.put(128206, ":paperclip:") + emojiMap.put(127359, ":parking:") + emojiMap.put(12349, ":part_alternation_mark:") + emojiMap.put(9925, ":partly_sunny:") + emojiMap.put(128706, ":passport_control:") + emojiMap.put(127825, ":peach:") + emojiMap.put(127824, ":pear:") + emojiMap.put(128221, ":pencil:") + emojiMap.put(9999, ":pencil2:") + emojiMap.put(128039, ":penguin:") + emojiMap.put(128532, ":pensive:") + emojiMap.put(127917, ":performing_arts:") + emojiMap.put(128547, ":persevere:") + emojiMap.put(128589, ":person_frowning:") + emojiMap.put(128113, ":person_with_blond_hair:") + emojiMap.put(128590, ":person_with_pouting_face:") + emojiMap.put(9742, ":phone:") + emojiMap.put(128055, ":pig:") + emojiMap.put(128022, ":pig2:") + emojiMap.put(128061, ":pig_nose:") + emojiMap.put(128138, ":pill:") + emojiMap.put(127821, ":pineapple:") + emojiMap.put(9811, ":pisces:") + emojiMap.put(127829, ":pizza:") + emojiMap.put(128071, ":point_down:") + emojiMap.put(128072, ":point_left:") + emojiMap.put(128073, ":point_right:") + emojiMap.put(9757, ":point_up:") + emojiMap.put(128070, ":point_up_2:") + emojiMap.put(128659, ":police_car:") + emojiMap.put(128041, ":poodle:") + emojiMap.put(128169, ":poop:") + emojiMap.put(127971, ":post_office:") + emojiMap.put(128239, ":postal_horn:") + emojiMap.put(128238, ":postbox:") + emojiMap.put(128688, ":potable_water:") + emojiMap.put(128093, ":pouch:") + emojiMap.put(127831, ":poultry_leg:") + emojiMap.put(128183, ":pound:") + emojiMap.put(128574, ":pouting_cat:") + emojiMap.put(128591, ":pray:") + emojiMap.put(128120, ":princess:") + emojiMap.put(128074, ":punch:") + emojiMap.put(128156, ":purple_heart:") + emojiMap.put(128091, ":purse:") + emojiMap.put(128204, ":pushpin:") + emojiMap.put(128686, ":put_litter_in_its_place:") + emojiMap.put(10067, ":question:") + emojiMap.put(128048, ":rabbit:") + emojiMap.put(128007, ":rabbit2:") + emojiMap.put(128014, ":racehorse:") + emojiMap.put(128251, ":radio:") + emojiMap.put(128280, ":radio_button:") + emojiMap.put(128545, ":rage:") + emojiMap.put(127752, ":rainbow:") + emojiMap.put(9995, ":raised_hand:") + emojiMap.put(128588, ":raised_hands:") + emojiMap.put(128587, ":raising_hand:") + emojiMap.put(128015, ":ram:") + emojiMap.put(127836, ":ramen:") + emojiMap.put(128000, ":rat:") + emojiMap.put(9851, ":recycle:") + emojiMap.put(128308, ":red_circle:") + emojiMap.put(174, ":registered:") + emojiMap.put(9786, ":relaxed:") + emojiMap.put(128524, ":relieved:") + emojiMap.put(128257, ":repeat:") + emojiMap.put(128258, ":repeat_one:") + emojiMap.put(128699, ":restroom:") + emojiMap.put(128158, ":revolving_hearts:") + emojiMap.put(9194, ":rewind:") + emojiMap.put(127872, ":ribbon:") + emojiMap.put(127834, ":rice:") + emojiMap.put(127833, ":rice_ball:") + emojiMap.put(127832, ":rice_cracker:") + emojiMap.put(127889, ":rice_scene:") + emojiMap.put(128141, ":ring:") + emojiMap.put(128640, ":rocket:") + emojiMap.put(127906, ":roller_coaster:") + emojiMap.put(128019, ":rooster:") + emojiMap.put(127801, ":rose:") + emojiMap.put(128680, ":rotating_light:") + emojiMap.put(128205, ":round_pushpin:") + emojiMap.put(128675, ":rowboat:") + emojiMap.put(127945, ":rugby_football:") + emojiMap.put(127939, ":runner:") + emojiMap.put(127933, ":running_shirt_with_sash:") + emojiMap.put(127490, ":sa:") + emojiMap.put(9808, ":sagittarius:") + emojiMap.put(127862, ":sake:") + emojiMap.put(128097, ":sandal:") + emojiMap.put(127877, ":santa:") + emojiMap.put(128225, ":satellite:") + emojiMap.put(127927, ":saxophone:") + emojiMap.put(127979, ":school:") + emojiMap.put(127890, ":school_satchel:") + emojiMap.put(9986, ":scissors:") + emojiMap.put(9807, ":scorpius:") + emojiMap.put(128561, ":scream:") + emojiMap.put(128576, ":scream_cat:") + emojiMap.put(128220, ":scroll:") + emojiMap.put(128186, ":seat:") + emojiMap.put(12953, ":secret:") + emojiMap.put(128584, ":see_no_evil:") + emojiMap.put(127793, ":seedling:") + emojiMap.put(127847, ":shaved_ice:") + emojiMap.put(128017, ":sheep:") + emojiMap.put(128026, ":shell:") + emojiMap.put(128674, ":ship:") + emojiMap.put(128094, ":shoe:") + emojiMap.put(128703, ":shower:") + emojiMap.put(128246, ":signal_strength:") + emojiMap.put(128303, ":six_pointed_star:") + emojiMap.put(127935, ":ski:") + emojiMap.put(128128, ":skull:") + emojiMap.put(128564, ":sleeping:") + emojiMap.put(128554, ":sleepy:") + emojiMap.put(127920, ":slot_machine:") + emojiMap.put(128313, ":small_blue_diamond:") + emojiMap.put(128312, ":small_orange_diamond:") + emojiMap.put(128314, ":small_red_triangle:") + emojiMap.put(128315, ":small_red_triangle_down:") + emojiMap.put(128516, ":smile:") + emojiMap.put(128568, ":smile_cat:") + emojiMap.put(128515, ":smiley:") + emojiMap.put(128570, ":smiley_cat:") + emojiMap.put(128520, ":smiling_imp:") + emojiMap.put(128527, ":smirk:") + emojiMap.put(128572, ":smirk_cat:") + emojiMap.put(128684, ":smoking:") + emojiMap.put(128012, ":snail:") + emojiMap.put(128013, ":snake:") + emojiMap.put(127938, ":snowboarder:") + emojiMap.put(10052, ":snowflake:") + emojiMap.put(9924, ":snowman:") + emojiMap.put(128557, ":sob:") + emojiMap.put(9917, ":soccer:") + emojiMap.put(128284, ":soon:") + emojiMap.put(127384, ":sos:") + emojiMap.put(128265, ":sound:") + emojiMap.put(128126, ":space_invader:") + emojiMap.put(9824, ":spades:") + emojiMap.put(127837, ":spaghetti:") + emojiMap.put(10055, ":sparkle:") + emojiMap.put(127879, ":sparkler:") + emojiMap.put(10024, ":sparkles:") + emojiMap.put(128150, ":sparkling_heart:") + emojiMap.put(128586, ":speak_no_evil:") + emojiMap.put(128266, ":speaker:") + emojiMap.put(128172, ":speech_balloon:") + emojiMap.put(128676, ":speedboat:") + emojiMap.put(11088, ":star:") + emojiMap.put(127775, ":star2:") + emojiMap.put(127747, ":stars:") + emojiMap.put(128649, ":station:") + emojiMap.put(128509, ":statue_of_liberty:") + emojiMap.put(128642, ":steam_locomotive:") + emojiMap.put(127858, ":stew:") + emojiMap.put(128207, ":straight_ruler:") + emojiMap.put(127827, ":strawberry:") + emojiMap.put(128539, ":stuck_out_tongue:") + emojiMap.put(128541, ":stuck_out_tongue_closed_eyes:") + emojiMap.put(128540, ":stuck_out_tongue_winking_eye:") + emojiMap.put(127774, ":sun_with_face:") + emojiMap.put(127803, ":sunflower:") + emojiMap.put(128526, ":sunglasses:") + emojiMap.put(9728, ":sunny:") + emojiMap.put(127749, ":sunrise:") + emojiMap.put(127748, ":sunrise_over_mountains:") + emojiMap.put(127940, ":surfer:") + emojiMap.put(127843, ":sushi:") + emojiMap.put(128671, ":suspension_railway:") + emojiMap.put(128531, ":sweat:") + emojiMap.put(128166, ":sweat_drops:") + emojiMap.put(128517, ":sweat_smile:") + emojiMap.put(127840, ":sweet_potato:") + emojiMap.put(127946, ":swimmer:") + emojiMap.put(128291, ":symbols:") + emojiMap.put(128137, ":syringe:") + emojiMap.put(127881, ":tada:") + emojiMap.put(127883, ":tanabata_tree:") + emojiMap.put(127818, ":tangerine:") + emojiMap.put(9801, ":taurus:") + emojiMap.put(128661, ":taxi:") + emojiMap.put(127861, ":tea:") + emojiMap.put(128222, ":telephone_receiver:") + emojiMap.put(128301, ":telescope:") + emojiMap.put(127934, ":tennis:") + emojiMap.put(9978, ":tent:") + emojiMap.put(128173, ":thought_balloon:") + emojiMap.put(127915, ":ticket:") + emojiMap.put(128047, ":tiger:") + emojiMap.put(128005, ":tiger2:") + emojiMap.put(128555, ":tired_face:") + emojiMap.put(8482, ":tm:") + emojiMap.put(128701, ":toilet:") + emojiMap.put(128508, ":tokyo_tower:") + emojiMap.put(127813, ":tomato:") + emojiMap.put(128069, ":tongue:") + emojiMap.put(128285, ":top:") + emojiMap.put(127913, ":tophat:") + emojiMap.put(128668, ":tractor:") + emojiMap.put(128677, ":traffic_light:") + emojiMap.put(128643, ":train:") + emojiMap.put(128646, ":train2:") + emojiMap.put(128650, ":tram:") + emojiMap.put(128681, ":triangular_flag_on_post:") + emojiMap.put(128208, ":triangular_ruler:") + emojiMap.put(128305, ":trident:") + emojiMap.put(128548, ":triumph:") + emojiMap.put(128654, ":trolleybus:") + emojiMap.put(127942, ":trophy:") + emojiMap.put(127865, ":tropical_drink:") + emojiMap.put(128032, ":tropical_fish:") + emojiMap.put(128666, ":truck:") + emojiMap.put(127930, ":trumpet:") + emojiMap.put(128085, ":tshirt:") + emojiMap.put(127799, ":tulip:") + emojiMap.put(128034, ":turtle:") + emojiMap.put(128250, ":tv:") + emojiMap.put(128256, ":twisted_rightwards_arrows:") + emojiMap.put(128149, ":two_hearts:") + emojiMap.put(128108, ":two_men_holding_hands:") + emojiMap.put(128109, ":two_women_holding_hands:") + emojiMap.put(127545, ":u5272:") + emojiMap.put(127540, ":u5408:") + emojiMap.put(127546, ":u55b6:") + emojiMap.put(127535, ":u6307:") + emojiMap.put(127543, ":u6708:") + emojiMap.put(127542, ":u6709:") + emojiMap.put(127541, ":u6e80:") + emojiMap.put(127514, ":u7121:") + emojiMap.put(127544, ":u7533:") + emojiMap.put(127538, ":u7981:") + emojiMap.put(127539, ":u7a7a:") + emojiMap.put(9748, ":umbrella:") + emojiMap.put(128530, ":unamused:") + emojiMap.put(128286, ":underage:") + emojiMap.put(128275, ":unlock:") + emojiMap.put(127385, ":up:") + emojiMap.put(9996, ":v:") + emojiMap.put(128678, ":vertical_traffic_light:") + emojiMap.put(128252, ":vhs:") + emojiMap.put(128243, ":vibration_mode:") + emojiMap.put(128249, ":video_camera:") + emojiMap.put(127918, ":video_game:") + emojiMap.put(127931, ":violin:") + emojiMap.put(9805, ":virgo:") + emojiMap.put(127755, ":volcano:") + emojiMap.put(127386, ":vs:") + emojiMap.put(128694, ":walking:") + emojiMap.put(127768, ":waning_crescent_moon:") + emojiMap.put(127766, ":waning_gibbous_moon:") + emojiMap.put(9888, ":warning:") + emojiMap.put(8986, ":watch:") + emojiMap.put(128003, ":water_buffalo:") + emojiMap.put(127817, ":watermelon:") + emojiMap.put(128075, ":wave:") + emojiMap.put(12336, ":wavy_dash:") + emojiMap.put(127762, ":waxing_crescent_moon:") + emojiMap.put(128702, ":wc:") + emojiMap.put(128553, ":weary:") + emojiMap.put(128146, ":wedding:") + emojiMap.put(128051, ":whale:") + emojiMap.put(128011, ":whale2:") + emojiMap.put(9855, ":wheelchair:") + emojiMap.put(9989, ":white_check_mark:") + emojiMap.put(9898, ":white_circle:") + emojiMap.put(128174, ":white_flower:") + emojiMap.put(11036, ":white_large_square:") + emojiMap.put(9725, ":white_medium_small_square:") + emojiMap.put(9723, ":white_medium_square:") + emojiMap.put(9643, ":white_small_square:") + emojiMap.put(128307, ":white_square_button:") + emojiMap.put(127888, ":wind_chime:") + emojiMap.put(127863, ":wine_glass:") + emojiMap.put(128521, ":wink:") + emojiMap.put(128058, ":wolf:") + emojiMap.put(128105, ":woman:") + emojiMap.put(128090, ":womans_clothes:") + emojiMap.put(128082, ":womans_hat:") + emojiMap.put(128698, ":womens:") + emojiMap.put(128543, ":worried:") + emojiMap.put(128295, ":wrench:") + emojiMap.put(10060, ":x:") + emojiMap.put(128155, ":yellow_heart:") + emojiMap.put(128180, ":yen:") + emojiMap.put(128523, ":yum:") + emojiMap.put(9889, ":zap:") + emojiMap.put(128164, ":zzz:") + + invertedEmojiMap.put(":+1:", 128077) + invertedEmojiMap.put(":-1:", 128078) + invertedEmojiMap.put(":100:", 128175) + invertedEmojiMap.put(":1234:", 128290) + invertedEmojiMap.put(":8ball:", 127921) + invertedEmojiMap.put(":a:", 127344) + invertedEmojiMap.put(":ab:", 127374) + invertedEmojiMap.put(":abc:", 128292) + invertedEmojiMap.put(":abcd:", 128289) + invertedEmojiMap.put(":accept:", 127569) + invertedEmojiMap.put(":aerial_tramway:", 128673) + invertedEmojiMap.put(":airplane:", 9992) + invertedEmojiMap.put(":alarm_clock:", 9200) + invertedEmojiMap.put(":alien:", 128125) + invertedEmojiMap.put(":ambulance:", 128657) + invertedEmojiMap.put(":anchor:", 9875) + invertedEmojiMap.put(":angel:", 128124) + invertedEmojiMap.put(":anger:", 128162) + invertedEmojiMap.put(":angry:", 128544) + invertedEmojiMap.put(":anguished:", 128551) + invertedEmojiMap.put(":ant:", 128028) + invertedEmojiMap.put(":apple:", 127822) + invertedEmojiMap.put(":aquarius:", 9810) + invertedEmojiMap.put(":aries:", 9800) + invertedEmojiMap.put(":arrow_backward:", 9664) + invertedEmojiMap.put(":arrow_double_down:", 9196) + invertedEmojiMap.put(":arrow_double_up:", 9195) + invertedEmojiMap.put(":arrow_down:", 11015) + invertedEmojiMap.put(":arrow_down_small:", 128317) + invertedEmojiMap.put(":arrow_forward:", 9654) + invertedEmojiMap.put(":arrow_heading_down:", 10549) + invertedEmojiMap.put(":arrow_heading_up:", 10548) + invertedEmojiMap.put(":arrow_left:", 11013) + invertedEmojiMap.put(":arrow_lower_left:", 8601) + invertedEmojiMap.put(":arrow_lower_right:", 8600) + invertedEmojiMap.put(":arrow_right:", 10145) + invertedEmojiMap.put(":arrow_right_hook:", 8618) + invertedEmojiMap.put(":arrow_up:", 11014) + invertedEmojiMap.put(":arrow_up_down:", 8597) + invertedEmojiMap.put(":arrow_up_small:", 128316) + invertedEmojiMap.put(":arrow_upper_left:", 8598) + invertedEmojiMap.put(":arrow_upper_right:", 8599) + invertedEmojiMap.put(":arrows_clockwise:", 128259) + invertedEmojiMap.put(":arrows_counterclockwise:", 128260) + invertedEmojiMap.put(":art:", 127912) + invertedEmojiMap.put(":articulated_lorry:", 128667) + invertedEmojiMap.put(":astonished:", 128562) + invertedEmojiMap.put(":athletic_shoe:", 128095) + invertedEmojiMap.put(":atm:", 127975) + invertedEmojiMap.put(":b:", 127345) + invertedEmojiMap.put(":baby:", 128118) + invertedEmojiMap.put(":baby_bottle:", 127868) + invertedEmojiMap.put(":baby_chick:", 128036) + invertedEmojiMap.put(":baby_symbol:", 128700) + invertedEmojiMap.put(":back:", 128281) + invertedEmojiMap.put(":baggage_claim:", 128708) + invertedEmojiMap.put(":balloon:", 127880) + invertedEmojiMap.put(":ballot_box_with_check:", 9745) + invertedEmojiMap.put(":bamboo:", 127885) + invertedEmojiMap.put(":banana:", 127820) + invertedEmojiMap.put(":bangbang:", 8252) + invertedEmojiMap.put(":bank:", 127974) + invertedEmojiMap.put(":bar_chart:", 128202) + invertedEmojiMap.put(":barber:", 128136) + invertedEmojiMap.put(":baseball:", 9918) + invertedEmojiMap.put(":basketball:", 127936) + invertedEmojiMap.put(":bath:", 128704) + invertedEmojiMap.put(":bathtub:", 128705) + invertedEmojiMap.put(":battery:", 128267) + invertedEmojiMap.put(":bear:", 128059) + invertedEmojiMap.put(":bee:", 128029) + invertedEmojiMap.put(":beer:", 127866) + invertedEmojiMap.put(":beers:", 127867) + invertedEmojiMap.put(":beetle:", 128030) + invertedEmojiMap.put(":beginner:", 128304) + invertedEmojiMap.put(":bell:", 128276) + invertedEmojiMap.put(":bento:", 127857) + invertedEmojiMap.put(":bicyclist:", 128692) + invertedEmojiMap.put(":bike:", 128690) + invertedEmojiMap.put(":bikini:", 128089) + invertedEmojiMap.put(":bird:", 128038) + invertedEmojiMap.put(":birthday:", 127874) + invertedEmojiMap.put(":black_circle:", 9899) + invertedEmojiMap.put(":black_joker:", 127183) + invertedEmojiMap.put(":black_large_square:", 11035) + invertedEmojiMap.put(":black_medium_small_square:", 9726) + invertedEmojiMap.put(":black_medium_square:", 9724) + invertedEmojiMap.put(":black_nib:", 10002) + invertedEmojiMap.put(":black_small_square:", 9642) + invertedEmojiMap.put(":black_square_button:", 128306) + invertedEmojiMap.put(":blossom:", 127804) + invertedEmojiMap.put(":blowfish:", 128033) + invertedEmojiMap.put(":blue_book:", 128216) + invertedEmojiMap.put(":blue_car:", 128665) + invertedEmojiMap.put(":blue_heart:", 128153) + invertedEmojiMap.put(":blush:", 128522) + invertedEmojiMap.put(":boar:", 128023) + invertedEmojiMap.put(":boat:", 9973) + invertedEmojiMap.put(":bomb:", 128163) + invertedEmojiMap.put(":book:", 128214) + invertedEmojiMap.put(":bookmark:", 128278) + invertedEmojiMap.put(":bookmark_tabs:", 128209) + invertedEmojiMap.put(":books:", 128218) + invertedEmojiMap.put(":boom:", 128165) + invertedEmojiMap.put(":boot:", 128098) + invertedEmojiMap.put(":bouquet:", 128144) + invertedEmojiMap.put(":bow:", 128583) + invertedEmojiMap.put(":bowling:", 127923) + invertedEmojiMap.put(":boy:", 128102) + invertedEmojiMap.put(":bread:", 127838) + invertedEmojiMap.put(":bride_with_veil:", 128112) + invertedEmojiMap.put(":bridge_at_night:", 127753) + invertedEmojiMap.put(":briefcase:", 128188) + invertedEmojiMap.put(":broken_heart:", 128148) + invertedEmojiMap.put(":bug:", 128027) + invertedEmojiMap.put(":bulb:", 128161) + invertedEmojiMap.put(":bullettrain_front:", 128645) + invertedEmojiMap.put(":bullettrain_side:", 128644) + invertedEmojiMap.put(":bus:", 128652) + invertedEmojiMap.put(":busstop:", 128655) + invertedEmojiMap.put(":bust_in_silhouette:", 128100) + invertedEmojiMap.put(":busts_in_silhouette:", 128101) + invertedEmojiMap.put(":cactus:", 127797) + invertedEmojiMap.put(":cake:", 127856) + invertedEmojiMap.put(":calendar:", 128198) + invertedEmojiMap.put(":calling:", 128242) + invertedEmojiMap.put(":camel:", 128043) + invertedEmojiMap.put(":camera:", 128247) + invertedEmojiMap.put(":cancer:", 9803) + invertedEmojiMap.put(":candy:", 127852) + invertedEmojiMap.put(":capital_abcd:", 128288) + invertedEmojiMap.put(":capricorn:", 9809) + invertedEmojiMap.put(":car:", 128663) + invertedEmojiMap.put(":card_index:", 128199) + invertedEmojiMap.put(":carousel_horse:", 127904) + invertedEmojiMap.put(":cat:", 128049) + invertedEmojiMap.put(":cat2:", 128008) + invertedEmojiMap.put(":cd:", 128191) + invertedEmojiMap.put(":chart:", 128185) + invertedEmojiMap.put(":chart_with_downwards_trend:", 128201) + invertedEmojiMap.put(":chart_with_upwards_trend:", 128200) + invertedEmojiMap.put(":checkered_flag:", 127937) + invertedEmojiMap.put(":cherries:", 127826) + invertedEmojiMap.put(":cherry_blossom:", 127800) + invertedEmojiMap.put(":chestnut:", 127792) + invertedEmojiMap.put(":chicken:", 128020) + invertedEmojiMap.put(":children_crossing:", 128696) + invertedEmojiMap.put(":chocolate_bar:", 127851) + invertedEmojiMap.put(":christmas_tree:", 127876) + invertedEmojiMap.put(":church:", 9962) + invertedEmojiMap.put(":cinema:", 127910) + invertedEmojiMap.put(":circus_tent:", 127914) + invertedEmojiMap.put(":city_sunrise:", 127751) + invertedEmojiMap.put(":city_sunset:", 127750) + invertedEmojiMap.put(":cl:", 127377) + invertedEmojiMap.put(":clap:", 128079) + invertedEmojiMap.put(":clapper:", 127916) + invertedEmojiMap.put(":clipboard:", 128203) + invertedEmojiMap.put(":clock1:", 128336) + invertedEmojiMap.put(":clock10:", 128345) + invertedEmojiMap.put(":clock1030:", 128357) + invertedEmojiMap.put(":clock11:", 128346) + invertedEmojiMap.put(":clock1130:", 128358) + invertedEmojiMap.put(":clock12:", 128347) + invertedEmojiMap.put(":clock1230:", 128359) + invertedEmojiMap.put(":clock130:", 128348) + invertedEmojiMap.put(":clock2:", 128337) + invertedEmojiMap.put(":clock230:", 128349) + invertedEmojiMap.put(":clock3:", 128338) + invertedEmojiMap.put(":clock330:", 128350) + invertedEmojiMap.put(":clock4:", 128339) + invertedEmojiMap.put(":clock430:", 128351) + invertedEmojiMap.put(":clock5:", 128340) + invertedEmojiMap.put(":clock530:", 128352) + invertedEmojiMap.put(":clock6:", 128341) + invertedEmojiMap.put(":clock630:", 128353) + invertedEmojiMap.put(":clock7:", 128342) + invertedEmojiMap.put(":clock730:", 128354) + invertedEmojiMap.put(":clock8:", 128343) + invertedEmojiMap.put(":clock830:", 128355) + invertedEmojiMap.put(":clock9:", 128344) + invertedEmojiMap.put(":clock930:", 128356) + invertedEmojiMap.put(":closed_book:", 128213) + invertedEmojiMap.put(":closed_lock_with_key:", 128272) + invertedEmojiMap.put(":closed_umbrella:", 127746) + invertedEmojiMap.put(":cloud:", 9729) + invertedEmojiMap.put(":clubs:", 9827) + invertedEmojiMap.put(":cocktail:", 127864) + invertedEmojiMap.put(":coffee:", 9749) + invertedEmojiMap.put(":cold_sweat:", 128560) + invertedEmojiMap.put(":collision:", 128165) + invertedEmojiMap.put(":computer:", 128187) + invertedEmojiMap.put(":confetti_ball:", 127882) + invertedEmojiMap.put(":confounded:", 128534) + invertedEmojiMap.put(":confused:", 128533) + invertedEmojiMap.put(":congratulations:", 12951) + invertedEmojiMap.put(":construction:", 128679) + invertedEmojiMap.put(":construction_worker:", 128119) + invertedEmojiMap.put(":convenience_store:", 127978) + invertedEmojiMap.put(":cookie:", 127850) + invertedEmojiMap.put(":cool:", 127378) + invertedEmojiMap.put(":cop:", 128110) + invertedEmojiMap.put(":copyright:", 169) + invertedEmojiMap.put(":corn:", 127805) + invertedEmojiMap.put(":couple:", 128107) + invertedEmojiMap.put(":couple_with_heart:", 128145) + invertedEmojiMap.put(":couplekiss:", 128143) + invertedEmojiMap.put(":cow:", 128046) + invertedEmojiMap.put(":cow2:", 128004) + invertedEmojiMap.put(":credit_card:", 128179) + invertedEmojiMap.put(":crescent_moon:", 127769) + invertedEmojiMap.put(":crocodile:", 128010) + invertedEmojiMap.put(":crossed_flags:", 127884) + invertedEmojiMap.put(":crown:", 128081) + invertedEmojiMap.put(":cry:", 128546) + invertedEmojiMap.put(":crying_cat_face:", 128575) + invertedEmojiMap.put(":crystal_ball:", 128302) + invertedEmojiMap.put(":cupid:", 128152) + invertedEmojiMap.put(":curly_loop:", 10160) + invertedEmojiMap.put(":currency_exchange:", 128177) + invertedEmojiMap.put(":curry:", 127835) + invertedEmojiMap.put(":custard:", 127854) + invertedEmojiMap.put(":customs:", 128707) + invertedEmojiMap.put(":cyclone:", 127744) + invertedEmojiMap.put(":dancer:", 128131) + invertedEmojiMap.put(":dancers:", 128111) + invertedEmojiMap.put(":dango:", 127841) + invertedEmojiMap.put(":dart:", 127919) + invertedEmojiMap.put(":dash:", 128168) + invertedEmojiMap.put(":date:", 128197) + invertedEmojiMap.put(":deciduous_tree:", 127795) + invertedEmojiMap.put(":department_store:", 127980) + invertedEmojiMap.put(":diamond_shape_with_a_dot_inside:", 128160) + invertedEmojiMap.put(":diamonds:", 9830) + invertedEmojiMap.put(":disappointed:", 128542) + invertedEmojiMap.put(":disappointed_relieved:", 128549) + invertedEmojiMap.put(":dizzy:", 128171) + invertedEmojiMap.put(":dizzy_face:", 128565) + invertedEmojiMap.put(":do_not_litter:", 128687) + invertedEmojiMap.put(":dog:", 128054) + invertedEmojiMap.put(":dog2:", 128021) + invertedEmojiMap.put(":dollar:", 128181) + invertedEmojiMap.put(":dolls:", 127886) + invertedEmojiMap.put(":dolphin:", 128044) + invertedEmojiMap.put(":door:", 128682) + invertedEmojiMap.put(":doughnut:", 127849) + invertedEmojiMap.put(":dragon:", 128009) + invertedEmojiMap.put(":dragon_face:", 128050) + invertedEmojiMap.put(":dress:", 128087) + invertedEmojiMap.put(":dromedary_camel:", 128042) + invertedEmojiMap.put(":droplet:", 128167) + invertedEmojiMap.put(":dvd:", 128192) + invertedEmojiMap.put(":e-mail:", 128231) + invertedEmojiMap.put(":ear:", 128066) + invertedEmojiMap.put(":ear_of_rice:", 127806) + invertedEmojiMap.put(":earth_africa:", 127757) + invertedEmojiMap.put(":earth_americas:", 127758) + invertedEmojiMap.put(":earth_asia:", 127759) + invertedEmojiMap.put(":egg:", 127859) + invertedEmojiMap.put(":eggplant:", 127814) + invertedEmojiMap.put(":eight_pointed_black_star:", 10036) + invertedEmojiMap.put(":eight_spoked_asterisk:", 10035) + invertedEmojiMap.put(":electric_plug:", 128268) + invertedEmojiMap.put(":elephant:", 128024) + invertedEmojiMap.put(":email:", 9993) + invertedEmojiMap.put(":end:", 128282) + invertedEmojiMap.put(":envelope:", 9993) + invertedEmojiMap.put(":envelope_with_arrow:", 128233) + invertedEmojiMap.put(":euro:", 128182) + invertedEmojiMap.put(":european_castle:", 127984) + invertedEmojiMap.put(":european_post_office:", 127972) + invertedEmojiMap.put(":evergreen_tree:", 127794) + invertedEmojiMap.put(":exclamation:", 10071) + invertedEmojiMap.put(":expressionless:", 128529) + invertedEmojiMap.put(":eyeglasses:", 128083) + invertedEmojiMap.put(":eyes:", 128064) + invertedEmojiMap.put(":facepunch:", 128074) + invertedEmojiMap.put(":factory:", 127981) + invertedEmojiMap.put(":fallen_leaf:", 127810) + invertedEmojiMap.put(":family:", 128106) + invertedEmojiMap.put(":fast_forward:", 9193) + invertedEmojiMap.put(":fax:", 128224) + invertedEmojiMap.put(":fearful:", 128552) + invertedEmojiMap.put(":feet:", 128062) + invertedEmojiMap.put(":ferris_wheel:", 127905) + invertedEmojiMap.put(":file_folder:", 128193) + invertedEmojiMap.put(":fire:", 128293) + invertedEmojiMap.put(":fire_engine:", 128658) + invertedEmojiMap.put(":fireworks:", 127878) + invertedEmojiMap.put(":first_quarter_moon:", 127763) + invertedEmojiMap.put(":first_quarter_moon_with_face:", 127771) + invertedEmojiMap.put(":fish:", 128031) + invertedEmojiMap.put(":fish_cake:", 127845) + invertedEmojiMap.put(":fishing_pole_and_fish:", 127907) + invertedEmojiMap.put(":fist:", 9994) + invertedEmojiMap.put(":flags:", 127887) + invertedEmojiMap.put(":flashlight:", 128294) + invertedEmojiMap.put(":flipper:", 128044) + invertedEmojiMap.put(":floppy_disk:", 128190) + invertedEmojiMap.put(":flower_playing_cards:", 127924) + invertedEmojiMap.put(":flushed:", 128563) + invertedEmojiMap.put(":foggy:", 127745) + invertedEmojiMap.put(":football:", 127944) + invertedEmojiMap.put(":footprints:", 128099) + invertedEmojiMap.put(":fork_and_knife:", 127860) + invertedEmojiMap.put(":fountain:", 9970) + invertedEmojiMap.put(":four_leaf_clover:", 127808) + invertedEmojiMap.put(":free:", 127379) + invertedEmojiMap.put(":fried_shrimp:", 127844) + invertedEmojiMap.put(":fries:", 127839) + invertedEmojiMap.put(":frog:", 128056) + invertedEmojiMap.put(":frowning:", 128550) + invertedEmojiMap.put(":fuelpump:", 9981) + invertedEmojiMap.put(":full_moon:", 127765) + invertedEmojiMap.put(":full_moon_with_face:", 127773) + invertedEmojiMap.put(":game_die:", 127922) + invertedEmojiMap.put(":gem:", 128142) + invertedEmojiMap.put(":gemini:", 9802) + invertedEmojiMap.put(":ghost:", 128123) + invertedEmojiMap.put(":gift:", 127873) + invertedEmojiMap.put(":gift_heart:", 128157) + invertedEmojiMap.put(":girl:", 128103) + invertedEmojiMap.put(":globe_with_meridians:", 127760) + invertedEmojiMap.put(":goat:", 128016) + invertedEmojiMap.put(":golf:", 9971) + invertedEmojiMap.put(":grapes:", 127815) + invertedEmojiMap.put(":green_apple:", 127823) + invertedEmojiMap.put(":green_book:", 128215) + invertedEmojiMap.put(":green_heart:", 128154) + invertedEmojiMap.put(":grey_exclamation:", 10069) + invertedEmojiMap.put(":grey_question:", 10068) + invertedEmojiMap.put(":grimacing:", 128556) + invertedEmojiMap.put(":grin:", 128513) + invertedEmojiMap.put(":grinning:", 128512) + invertedEmojiMap.put(":guardsman:", 128130) + invertedEmojiMap.put(":guitar:", 127928) + invertedEmojiMap.put(":gun:", 128299) + invertedEmojiMap.put(":haircut:", 128135) + invertedEmojiMap.put(":hamburger:", 127828) + invertedEmojiMap.put(":hammer:", 128296) + invertedEmojiMap.put(":hamster:", 128057) + invertedEmojiMap.put(":hand:", 9995) + invertedEmojiMap.put(":handbag:", 128092) + invertedEmojiMap.put(":hankey:", 128169) + invertedEmojiMap.put(":hatched_chick:", 128037) + invertedEmojiMap.put(":hatching_chick:", 128035) + invertedEmojiMap.put(":headphones:", 127911) + invertedEmojiMap.put(":hear_no_evil:", 128585) + invertedEmojiMap.put(":heart:", 10084) + invertedEmojiMap.put(":heart_decoration:", 128159) + invertedEmojiMap.put(":heart_eyes:", 128525) + invertedEmojiMap.put(":heart_eyes_cat:", 128571) + invertedEmojiMap.put(":heartbeat:", 128147) + invertedEmojiMap.put(":heartpulse:", 128151) + invertedEmojiMap.put(":hearts:", 9829) + invertedEmojiMap.put(":heavy_check_mark:", 10004) + invertedEmojiMap.put(":heavy_division_sign:", 10135) + invertedEmojiMap.put(":heavy_dollar_sign:", 128178) + invertedEmojiMap.put(":heavy_exclamation_mark:", 10071) + invertedEmojiMap.put(":heavy_minus_sign:", 10134) + invertedEmojiMap.put(":heavy_multiplication_x:", 10006) + invertedEmojiMap.put(":heavy_plus_sign:", 10133) + invertedEmojiMap.put(":helicopter:", 128641) + invertedEmojiMap.put(":herb:", 127807) + invertedEmojiMap.put(":hibiscus:", 127802) + invertedEmojiMap.put(":high_brightness:", 128262) + invertedEmojiMap.put(":high_heel:", 128096) + invertedEmojiMap.put(":hocho:", 128298) + invertedEmojiMap.put(":honey_pot:", 127855) + invertedEmojiMap.put(":honeybee:", 128029) + invertedEmojiMap.put(":horse:", 128052) + invertedEmojiMap.put(":horse_racing:", 127943) + invertedEmojiMap.put(":hospital:", 127973) + invertedEmojiMap.put(":hotel:", 127976) + invertedEmojiMap.put(":hotsprings:", 9832) + invertedEmojiMap.put(":hourglass:", 8987) + invertedEmojiMap.put(":hourglass_flowing_sand:", 9203) + invertedEmojiMap.put(":house:", 127968) + invertedEmojiMap.put(":house_with_garden:", 127969) + invertedEmojiMap.put(":hushed:", 128559) + invertedEmojiMap.put(":ice_cream:", 127848) + invertedEmojiMap.put(":icecream:", 127846) + invertedEmojiMap.put(":id:", 127380) + invertedEmojiMap.put(":ideograph_advantage:", 127568) + invertedEmojiMap.put(":imp:", 128127) + invertedEmojiMap.put(":inbox_tray:", 128229) + invertedEmojiMap.put(":incoming_envelope:", 128232) + invertedEmojiMap.put(":information_desk_person:", 128129) + invertedEmojiMap.put(":information_source:", 8505) + invertedEmojiMap.put(":innocent:", 128519) + invertedEmojiMap.put(":interrobang:", 8265) + invertedEmojiMap.put(":iphone:", 128241) + invertedEmojiMap.put(":izakaya_lantern:", 127982) + invertedEmojiMap.put(":jack_o_lantern:", 127875) + invertedEmojiMap.put(":japan:", 128510) + invertedEmojiMap.put(":japanese_castle:", 127983) + invertedEmojiMap.put(":japanese_goblin:", 128122) + invertedEmojiMap.put(":japanese_ogre:", 128121) + invertedEmojiMap.put(":jeans:", 128086) + invertedEmojiMap.put(":joy:", 128514) + invertedEmojiMap.put(":joy_cat:", 128569) + invertedEmojiMap.put(":key:", 128273) + invertedEmojiMap.put(":keycap_ten:", 128287) + invertedEmojiMap.put(":kimono:", 128088) + invertedEmojiMap.put(":kiss:", 128139) + invertedEmojiMap.put(":kissing:", 128535) + invertedEmojiMap.put(":kissing_cat:", 128573) + invertedEmojiMap.put(":kissing_closed_eyes:", 128538) + invertedEmojiMap.put(":kissing_heart:", 128536) + invertedEmojiMap.put(":kissing_smiling_eyes:", 128537) + invertedEmojiMap.put(":koala:", 128040) + invertedEmojiMap.put(":koko:", 127489) + invertedEmojiMap.put(":lantern:", 127982) + invertedEmojiMap.put(":large_blue_circle:", 128309) + invertedEmojiMap.put(":large_blue_diamond:", 128311) + invertedEmojiMap.put(":large_orange_diamond:", 128310) + invertedEmojiMap.put(":last_quarter_moon:", 127767) + invertedEmojiMap.put(":last_quarter_moon_with_face:", 127772) + invertedEmojiMap.put(":laughing:", 128518) + invertedEmojiMap.put(":leaves:", 127811) + invertedEmojiMap.put(":ledger:", 128210) + invertedEmojiMap.put(":left_luggage:", 128709) + invertedEmojiMap.put(":left_right_arrow:", 8596) + invertedEmojiMap.put(":leftwards_arrow_with_hook:", 8617) + invertedEmojiMap.put(":lemon:", 127819) + invertedEmojiMap.put(":leo:", 9804) + invertedEmojiMap.put(":leopard:", 128006) + invertedEmojiMap.put(":libra:", 9806) + invertedEmojiMap.put(":light_rail:", 128648) + invertedEmojiMap.put(":link:", 128279) + invertedEmojiMap.put(":lips:", 128068) + invertedEmojiMap.put(":lipstick:", 128132) + invertedEmojiMap.put(":lock:", 128274) + invertedEmojiMap.put(":lock_with_ink_pen:", 128271) + invertedEmojiMap.put(":lollipop:", 127853) + invertedEmojiMap.put(":loop:", 10175) + invertedEmojiMap.put(":loudspeaker:", 128226) + invertedEmojiMap.put(":love_hotel:", 127977) + invertedEmojiMap.put(":love_letter:", 128140) + invertedEmojiMap.put(":low_brightness:", 128261) + invertedEmojiMap.put(":m:", 9410) + invertedEmojiMap.put(":mag:", 128269) + invertedEmojiMap.put(":mag_right:", 128270) + invertedEmojiMap.put(":mahjong:", 126980) + invertedEmojiMap.put(":mailbox:", 128235) + invertedEmojiMap.put(":mailbox_closed:", 128234) + invertedEmojiMap.put(":mailbox_with_mail:", 128236) + invertedEmojiMap.put(":mailbox_with_no_mail:", 128237) + invertedEmojiMap.put(":man:", 128104) + invertedEmojiMap.put(":man_with_gua_pi_mao:", 128114) + invertedEmojiMap.put(":man_with_turban:", 128115) + invertedEmojiMap.put(":mans_shoe:", 128094) + invertedEmojiMap.put(":maple_leaf:", 127809) + invertedEmojiMap.put(":mask:", 128567) + invertedEmojiMap.put(":massage:", 128134) + invertedEmojiMap.put(":meat_on_bone:", 127830) + invertedEmojiMap.put(":mega:", 128227) + invertedEmojiMap.put(":melon:", 127816) + invertedEmojiMap.put(":memo:", 128221) + invertedEmojiMap.put(":mens:", 128697) + invertedEmojiMap.put(":metro:", 128647) + invertedEmojiMap.put(":microphone:", 127908) + invertedEmojiMap.put(":microscope:", 128300) + invertedEmojiMap.put(":milky_way:", 127756) + invertedEmojiMap.put(":minibus:", 128656) + invertedEmojiMap.put(":minidisc:", 128189) + invertedEmojiMap.put(":mobile_phone_off:", 128244) + invertedEmojiMap.put(":money_with_wings:", 128184) + invertedEmojiMap.put(":moneybag:", 128176) + invertedEmojiMap.put(":monkey:", 128018) + invertedEmojiMap.put(":monkey_face:", 128053) + invertedEmojiMap.put(":monorail:", 128669) + invertedEmojiMap.put(":moon:", 127764) + invertedEmojiMap.put(":mortar_board:", 127891) + invertedEmojiMap.put(":mount_fuji:", 128507) + invertedEmojiMap.put(":mountain_bicyclist:", 128693) + invertedEmojiMap.put(":mountain_cableway:", 128672) + invertedEmojiMap.put(":mountain_railway:", 128670) + invertedEmojiMap.put(":mouse:", 128045) + invertedEmojiMap.put(":mouse2:", 128001) + invertedEmojiMap.put(":movie_camera:", 127909) + invertedEmojiMap.put(":moyai:", 128511) + invertedEmojiMap.put(":muscle:", 128170) + invertedEmojiMap.put(":mushroom:", 127812) + invertedEmojiMap.put(":musical_keyboard:", 127929) + invertedEmojiMap.put(":musical_note:", 127925) + invertedEmojiMap.put(":musical_score:", 127932) + invertedEmojiMap.put(":mute:", 128263) + invertedEmojiMap.put(":nail_care:", 128133) + invertedEmojiMap.put(":name_badge:", 128219) + invertedEmojiMap.put(":necktie:", 128084) + invertedEmojiMap.put(":negative_squared_cross_mark:", 10062) + invertedEmojiMap.put(":neutral_face:", 128528) + invertedEmojiMap.put(":new:", 127381) + invertedEmojiMap.put(":new_moon:", 127761) + invertedEmojiMap.put(":new_moon_with_face:", 127770) + invertedEmojiMap.put(":newspaper:", 128240) + invertedEmojiMap.put(":ng:", 127382) + invertedEmojiMap.put(":no_bell:", 128277) + invertedEmojiMap.put(":no_bicycles:", 128691) + invertedEmojiMap.put(":no_entry:", 9940) + invertedEmojiMap.put(":no_entry_sign:", 128683) + invertedEmojiMap.put(":no_good:", 128581) + invertedEmojiMap.put(":no_mobile_phones:", 128245) + invertedEmojiMap.put(":no_mouth:", 128566) + invertedEmojiMap.put(":no_pedestrians:", 128695) + invertedEmojiMap.put(":no_smoking:", 128685) + invertedEmojiMap.put(":non-potable_water:", 128689) + invertedEmojiMap.put(":nose:", 128067) + invertedEmojiMap.put(":notebook:", 128211) + invertedEmojiMap.put(":notebook_with_decorative_cover:", 128212) + invertedEmojiMap.put(":notes:", 127926) + invertedEmojiMap.put(":nut_and_bolt:", 128297) + invertedEmojiMap.put(":o:", 11093) + invertedEmojiMap.put(":o2:", 127358) + invertedEmojiMap.put(":ocean:", 127754) + invertedEmojiMap.put(":octopus:", 128025) + invertedEmojiMap.put(":oden:", 127842) + invertedEmojiMap.put(":office:", 127970) + invertedEmojiMap.put(":ok:", 127383) + invertedEmojiMap.put(":ok_hand:", 128076) + invertedEmojiMap.put(":ok_woman:", 128582) + invertedEmojiMap.put(":older_man:", 128116) + invertedEmojiMap.put(":older_woman:", 128117) + invertedEmojiMap.put(":on:", 128283) + invertedEmojiMap.put(":oncoming_automobile:", 128664) + invertedEmojiMap.put(":oncoming_bus:", 128653) + invertedEmojiMap.put(":oncoming_police_car:", 128660) + invertedEmojiMap.put(":oncoming_taxi:", 128662) + invertedEmojiMap.put(":open_book:", 128214) + invertedEmojiMap.put(":open_file_folder:", 128194) + invertedEmojiMap.put(":open_hands:", 128080) + invertedEmojiMap.put(":open_mouth:", 128558) + invertedEmojiMap.put(":ophiuchus:", 9934) + invertedEmojiMap.put(":orange_book:", 128217) + invertedEmojiMap.put(":outbox_tray:", 128228) + invertedEmojiMap.put(":ox:", 128002) + invertedEmojiMap.put(":package:", 128230) + invertedEmojiMap.put(":page_facing_up:", 128196) + invertedEmojiMap.put(":page_with_curl:", 128195) + invertedEmojiMap.put(":pager:", 128223) + invertedEmojiMap.put(":palm_tree:", 127796) + invertedEmojiMap.put(":panda_face:", 128060) + invertedEmojiMap.put(":paperclip:", 128206) + invertedEmojiMap.put(":parking:", 127359) + invertedEmojiMap.put(":part_alternation_mark:", 12349) + invertedEmojiMap.put(":partly_sunny:", 9925) + invertedEmojiMap.put(":passport_control:", 128706) + invertedEmojiMap.put(":paw_prints:", 128062) + invertedEmojiMap.put(":peach:", 127825) + invertedEmojiMap.put(":pear:", 127824) + invertedEmojiMap.put(":pencil:", 128221) + invertedEmojiMap.put(":pencil2:", 9999) + invertedEmojiMap.put(":penguin:", 128039) + invertedEmojiMap.put(":pensive:", 128532) + invertedEmojiMap.put(":performing_arts:", 127917) + invertedEmojiMap.put(":persevere:", 128547) + invertedEmojiMap.put(":person_frowning:", 128589) + invertedEmojiMap.put(":person_with_blond_hair:", 128113) + invertedEmojiMap.put(":person_with_pouting_face:", 128590) + invertedEmojiMap.put(":phone:", 9742) + invertedEmojiMap.put(":pig:", 128055) + invertedEmojiMap.put(":pig2:", 128022) + invertedEmojiMap.put(":pig_nose:", 128061) + invertedEmojiMap.put(":pill:", 128138) + invertedEmojiMap.put(":pineapple:", 127821) + invertedEmojiMap.put(":pisces:", 9811) + invertedEmojiMap.put(":pizza:", 127829) + invertedEmojiMap.put(":point_down:", 128071) + invertedEmojiMap.put(":point_left:", 128072) + invertedEmojiMap.put(":point_right:", 128073) + invertedEmojiMap.put(":point_up:", 9757) + invertedEmojiMap.put(":point_up_2:", 128070) + invertedEmojiMap.put(":police_car:", 128659) + invertedEmojiMap.put(":poodle:", 128041) + invertedEmojiMap.put(":poop:", 128169) + invertedEmojiMap.put(":post_office:", 127971) + invertedEmojiMap.put(":postal_horn:", 128239) + invertedEmojiMap.put(":postbox:", 128238) + invertedEmojiMap.put(":potable_water:", 128688) + invertedEmojiMap.put(":pouch:", 128093) + invertedEmojiMap.put(":poultry_leg:", 127831) + invertedEmojiMap.put(":pound:", 128183) + invertedEmojiMap.put(":pouting_cat:", 128574) + invertedEmojiMap.put(":pray:", 128591) + invertedEmojiMap.put(":princess:", 128120) + invertedEmojiMap.put(":punch:", 128074) + invertedEmojiMap.put(":purple_heart:", 128156) + invertedEmojiMap.put(":purse:", 128091) + invertedEmojiMap.put(":pushpin:", 128204) + invertedEmojiMap.put(":put_litter_in_its_place:", 128686) + invertedEmojiMap.put(":question:", 10067) + invertedEmojiMap.put(":rabbit:", 128048) + invertedEmojiMap.put(":rabbit2:", 128007) + invertedEmojiMap.put(":racehorse:", 128014) + invertedEmojiMap.put(":radio:", 128251) + invertedEmojiMap.put(":radio_button:", 128280) + invertedEmojiMap.put(":rage:", 128545) + invertedEmojiMap.put(":railway_car:", 128643) + invertedEmojiMap.put(":rainbow:", 127752) + invertedEmojiMap.put(":raised_hand:", 9995) + invertedEmojiMap.put(":raised_hands:", 128588) + invertedEmojiMap.put(":raising_hand:", 128587) + invertedEmojiMap.put(":ram:", 128015) + invertedEmojiMap.put(":ramen:", 127836) + invertedEmojiMap.put(":rat:", 128000) + invertedEmojiMap.put(":recycle:", 9851) + invertedEmojiMap.put(":red_car:", 128663) + invertedEmojiMap.put(":red_circle:", 128308) + invertedEmojiMap.put(":registered:", 174) + invertedEmojiMap.put(":relaxed:", 9786) + invertedEmojiMap.put(":relieved:", 128524) + invertedEmojiMap.put(":repeat:", 128257) + invertedEmojiMap.put(":repeat_one:", 128258) + invertedEmojiMap.put(":restroom:", 128699) + invertedEmojiMap.put(":revolving_hearts:", 128158) + invertedEmojiMap.put(":rewind:", 9194) + invertedEmojiMap.put(":ribbon:", 127872) + invertedEmojiMap.put(":rice:", 127834) + invertedEmojiMap.put(":rice_ball:", 127833) + invertedEmojiMap.put(":rice_cracker:", 127832) + invertedEmojiMap.put(":rice_scene:", 127889) + invertedEmojiMap.put(":ring:", 128141) + invertedEmojiMap.put(":rocket:", 128640) + invertedEmojiMap.put(":roller_coaster:", 127906) + invertedEmojiMap.put(":rooster:", 128019) + invertedEmojiMap.put(":rose:", 127801) + invertedEmojiMap.put(":rotating_light:", 128680) + invertedEmojiMap.put(":round_pushpin:", 128205) + invertedEmojiMap.put(":rowboat:", 128675) + invertedEmojiMap.put(":rugby_football:", 127945) + invertedEmojiMap.put(":runner:", 127939) + invertedEmojiMap.put(":running:", 127939) + invertedEmojiMap.put(":running_shirt_with_sash:", 127933) + invertedEmojiMap.put(":sa:", 127490) + invertedEmojiMap.put(":sagittarius:", 9808) + invertedEmojiMap.put(":sailboat:", 9973) + invertedEmojiMap.put(":sake:", 127862) + invertedEmojiMap.put(":sandal:", 128097) + invertedEmojiMap.put(":santa:", 127877) + invertedEmojiMap.put(":satellite:", 128225) + invertedEmojiMap.put(":satisfied:", 128518) + invertedEmojiMap.put(":saxophone:", 127927) + invertedEmojiMap.put(":school:", 127979) + invertedEmojiMap.put(":school_satchel:", 127890) + invertedEmojiMap.put(":scissors:", 9986) + invertedEmojiMap.put(":scorpius:", 9807) + invertedEmojiMap.put(":scream:", 128561) + invertedEmojiMap.put(":scream_cat:", 128576) + invertedEmojiMap.put(":scroll:", 128220) + invertedEmojiMap.put(":seat:", 128186) + invertedEmojiMap.put(":secret:", 12953) + invertedEmojiMap.put(":see_no_evil:", 128584) + invertedEmojiMap.put(":seedling:", 127793) + invertedEmojiMap.put(":shaved_ice:", 127847) + invertedEmojiMap.put(":sheep:", 128017) + invertedEmojiMap.put(":shell:", 128026) + invertedEmojiMap.put(":ship:", 128674) + invertedEmojiMap.put(":shirt:", 128085) + invertedEmojiMap.put(":shit:", 128169) + invertedEmojiMap.put(":shoe:", 128094) + invertedEmojiMap.put(":shower:", 128703) + invertedEmojiMap.put(":signal_strength:", 128246) + invertedEmojiMap.put(":six_pointed_star:", 128303) + invertedEmojiMap.put(":ski:", 127935) + invertedEmojiMap.put(":skull:", 128128) + invertedEmojiMap.put(":sleeping:", 128564) + invertedEmojiMap.put(":sleepy:", 128554) + invertedEmojiMap.put(":slot_machine:", 127920) + invertedEmojiMap.put(":small_blue_diamond:", 128313) + invertedEmojiMap.put(":small_orange_diamond:", 128312) + invertedEmojiMap.put(":small_red_triangle:", 128314) + invertedEmojiMap.put(":small_red_triangle_down:", 128315) + invertedEmojiMap.put(":smile:", 128516) + invertedEmojiMap.put(":smile_cat:", 128568) + invertedEmojiMap.put(":smiley:", 128515) + invertedEmojiMap.put(":smiley_cat:", 128570) + invertedEmojiMap.put(":smiling_imp:", 128520) + invertedEmojiMap.put(":smirk:", 128527) + invertedEmojiMap.put(":smirk_cat:", 128572) + invertedEmojiMap.put(":smoking:", 128684) + invertedEmojiMap.put(":snail:", 128012) + invertedEmojiMap.put(":snake:", 128013) + invertedEmojiMap.put(":snowboarder:", 127938) + invertedEmojiMap.put(":snowflake:", 10052) + invertedEmojiMap.put(":snowman:", 9924) + invertedEmojiMap.put(":sob:", 128557) + invertedEmojiMap.put(":soccer:", 9917) + invertedEmojiMap.put(":soon:", 128284) + invertedEmojiMap.put(":sos:", 127384) + invertedEmojiMap.put(":sound:", 128265) + invertedEmojiMap.put(":space_invader:", 128126) + invertedEmojiMap.put(":spades:", 9824) + invertedEmojiMap.put(":spaghetti:", 127837) + invertedEmojiMap.put(":sparkle:", 10055) + invertedEmojiMap.put(":sparkler:", 127879) + invertedEmojiMap.put(":sparkles:", 10024) + invertedEmojiMap.put(":sparkling_heart:", 128150) + invertedEmojiMap.put(":speak_no_evil:", 128586) + invertedEmojiMap.put(":speaker:", 128266) + invertedEmojiMap.put(":speech_balloon:", 128172) + invertedEmojiMap.put(":speedboat:", 128676) + invertedEmojiMap.put(":star:", 11088) + invertedEmojiMap.put(":star2:", 127775) + invertedEmojiMap.put(":stars:", 127747) + invertedEmojiMap.put(":station:", 128649) + invertedEmojiMap.put(":statue_of_liberty:", 128509) + invertedEmojiMap.put(":steam_locomotive:", 128642) + invertedEmojiMap.put(":stew:", 127858) + invertedEmojiMap.put(":straight_ruler:", 128207) + invertedEmojiMap.put(":strawberry:", 127827) + invertedEmojiMap.put(":stuck_out_tongue:", 128539) + invertedEmojiMap.put(":stuck_out_tongue_closed_eyes:", 128541) + invertedEmojiMap.put(":stuck_out_tongue_winking_eye:", 128540) + invertedEmojiMap.put(":sun_with_face:", 127774) + invertedEmojiMap.put(":sunflower:", 127803) + invertedEmojiMap.put(":sunglasses:", 128526) + invertedEmojiMap.put(":sunny:", 9728) + invertedEmojiMap.put(":sunrise:", 127749) + invertedEmojiMap.put(":sunrise_over_mountains:", 127748) + invertedEmojiMap.put(":surfer:", 127940) + invertedEmojiMap.put(":sushi:", 127843) + invertedEmojiMap.put(":suspension_railway:", 128671) + invertedEmojiMap.put(":sweat:", 128531) + invertedEmojiMap.put(":sweat_drops:", 128166) + invertedEmojiMap.put(":sweat_smile:", 128517) + invertedEmojiMap.put(":sweet_potato:", 127840) + invertedEmojiMap.put(":swimmer:", 127946) + invertedEmojiMap.put(":symbols:", 128291) + invertedEmojiMap.put(":syringe:", 128137) + invertedEmojiMap.put(":tada:", 127881) + invertedEmojiMap.put(":tanabata_tree:", 127883) + invertedEmojiMap.put(":tangerine:", 127818) + invertedEmojiMap.put(":taurus:", 9801) + invertedEmojiMap.put(":taxi:", 128661) + invertedEmojiMap.put(":tea:", 127861) + invertedEmojiMap.put(":telephone:", 9742) + invertedEmojiMap.put(":telephone_receiver:", 128222) + invertedEmojiMap.put(":telescope:", 128301) + invertedEmojiMap.put(":tennis:", 127934) + invertedEmojiMap.put(":tent:", 9978) + invertedEmojiMap.put(":thought_balloon:", 128173) + invertedEmojiMap.put(":thumbsdown:", 128078) + invertedEmojiMap.put(":thumbsup:", 128077) + invertedEmojiMap.put(":ticket:", 127915) + invertedEmojiMap.put(":tiger:", 128047) + invertedEmojiMap.put(":tiger2:", 128005) + invertedEmojiMap.put(":tired_face:", 128555) + invertedEmojiMap.put(":tm:", 8482) + invertedEmojiMap.put(":toilet:", 128701) + invertedEmojiMap.put(":tokyo_tower:", 128508) + invertedEmojiMap.put(":tomato:", 127813) + invertedEmojiMap.put(":tongue:", 128069) + invertedEmojiMap.put(":top:", 128285) + invertedEmojiMap.put(":tophat:", 127913) + invertedEmojiMap.put(":tractor:", 128668) + invertedEmojiMap.put(":traffic_light:", 128677) + invertedEmojiMap.put(":train:", 128643) + invertedEmojiMap.put(":train2:", 128646) + invertedEmojiMap.put(":tram:", 128650) + invertedEmojiMap.put(":triangular_flag_on_post:", 128681) + invertedEmojiMap.put(":triangular_ruler:", 128208) + invertedEmojiMap.put(":trident:", 128305) + invertedEmojiMap.put(":triumph:", 128548) + invertedEmojiMap.put(":trolleybus:", 128654) + invertedEmojiMap.put(":trophy:", 127942) + invertedEmojiMap.put(":tropical_drink:", 127865) + invertedEmojiMap.put(":tropical_fish:", 128032) + invertedEmojiMap.put(":truck:", 128666) + invertedEmojiMap.put(":trumpet:", 127930) + invertedEmojiMap.put(":tshirt:", 128085) + invertedEmojiMap.put(":tulip:", 127799) + invertedEmojiMap.put(":turtle:", 128034) + invertedEmojiMap.put(":tv:", 128250) + invertedEmojiMap.put(":twisted_rightwards_arrows:", 128256) + invertedEmojiMap.put(":two_hearts:", 128149) + invertedEmojiMap.put(":two_men_holding_hands:", 128108) + invertedEmojiMap.put(":two_women_holding_hands:", 128109) + invertedEmojiMap.put(":u5272:", 127545) + invertedEmojiMap.put(":u5408:", 127540) + invertedEmojiMap.put(":u55b6:", 127546) + invertedEmojiMap.put(":u6307:", 127535) + invertedEmojiMap.put(":u6708:", 127543) + invertedEmojiMap.put(":u6709:", 127542) + invertedEmojiMap.put(":u6e80:", 127541) + invertedEmojiMap.put(":u7121:", 127514) + invertedEmojiMap.put(":u7533:", 127544) + invertedEmojiMap.put(":u7981:", 127538) + invertedEmojiMap.put(":u7a7a:", 127539) + invertedEmojiMap.put(":umbrella:", 9748) + invertedEmojiMap.put(":unamused:", 128530) + invertedEmojiMap.put(":underage:", 128286) + invertedEmojiMap.put(":unlock:", 128275) + invertedEmojiMap.put(":up:", 127385) + invertedEmojiMap.put(":v:", 9996) + invertedEmojiMap.put(":vertical_traffic_light:", 128678) + invertedEmojiMap.put(":vhs:", 128252) + invertedEmojiMap.put(":vibration_mode:", 128243) + invertedEmojiMap.put(":video_camera:", 128249) + invertedEmojiMap.put(":video_game:", 127918) + invertedEmojiMap.put(":violin:", 127931) + invertedEmojiMap.put(":virgo:", 9805) + invertedEmojiMap.put(":volcano:", 127755) + invertedEmojiMap.put(":vs:", 127386) + invertedEmojiMap.put(":walking:", 128694) + invertedEmojiMap.put(":waning_crescent_moon:", 127768) + invertedEmojiMap.put(":waning_gibbous_moon:", 127766) + invertedEmojiMap.put(":warning:", 9888) + invertedEmojiMap.put(":watch:", 8986) + invertedEmojiMap.put(":water_buffalo:", 128003) + invertedEmojiMap.put(":watermelon:", 127817) + invertedEmojiMap.put(":wave:", 128075) + invertedEmojiMap.put(":wavy_dash:", 12336) + invertedEmojiMap.put(":waxing_crescent_moon:", 127762) + invertedEmojiMap.put(":waxing_gibbous_moon:", 127764) + invertedEmojiMap.put(":wc:", 128702) + invertedEmojiMap.put(":weary:", 128553) + invertedEmojiMap.put(":wedding:", 128146) + invertedEmojiMap.put(":whale:", 128051) + invertedEmojiMap.put(":whale2:", 128011) + invertedEmojiMap.put(":wheelchair:", 9855) + invertedEmojiMap.put(":white_check_mark:", 9989) + invertedEmojiMap.put(":white_circle:", 9898) + invertedEmojiMap.put(":white_flower:", 128174) + invertedEmojiMap.put(":white_large_square:", 11036) + invertedEmojiMap.put(":white_medium_small_square:", 9725) + invertedEmojiMap.put(":white_medium_square:", 9723) + invertedEmojiMap.put(":white_small_square:", 9643) + invertedEmojiMap.put(":white_square_button:", 128307) + invertedEmojiMap.put(":wind_chime:", 127888) + invertedEmojiMap.put(":wine_glass:", 127863) + invertedEmojiMap.put(":wink:", 128521) + invertedEmojiMap.put(":wolf:", 128058) + invertedEmojiMap.put(":woman:", 128105) + invertedEmojiMap.put(":womans_clothes:", 128090) + invertedEmojiMap.put(":womans_hat:", 128082) + invertedEmojiMap.put(":womens:", 128698) + invertedEmojiMap.put(":worried:", 128543) + invertedEmojiMap.put(":wrench:", 128295) + invertedEmojiMap.put(":x:", 10060) + invertedEmojiMap.put(":yellow_heart:", 128155) + invertedEmojiMap.put(":yen:", 128180) + invertedEmojiMap.put(":yum:", 128523) + invertedEmojiMap.put(":zap:", 9889) + invertedEmojiMap.put(":zzz:", 128164) + } +} From 2e3bde0735d74812c0f75beb28c25e8a5cba8141 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 11 Jun 2025 13:06:21 +0200 Subject: [PATCH 05/57] Fix emoji parsing --- .../habitrpg/common/habitica/helpers/EmojiParser.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiParser.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiParser.kt index fa78c2fd9..4e231fa18 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiParser.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiParser.kt @@ -3,6 +3,8 @@ package com.habitrpg.common.habitica.helpers import java.util.regex.Pattern object EmojiParser { + private val pattern: Pattern = Pattern.compile("(:[^:\\s]+:)") + /** * Converts Cheat Sheet emoji-codes into unicode characters * @@ -14,12 +16,10 @@ object EmojiParser { return text } var returnString: String = text - val pattern = Pattern.compile("(:[^:]+:)") val matcher = pattern.matcher(text) while (matcher.find()) { val found = matcher.group() - if (EmojiMap.invertedEmojiMap[found] == null) continue - val hexInt = EmojiMap.invertedEmojiMap[found]!! + val hexInt = EmojiMap.invertedEmojiMap[found] ?: continue val replacement = String(Character.toChars(hexInt)) returnString = returnString.replace(found, replacement) } @@ -41,9 +41,8 @@ object EmojiParser { for (i in 0..charArray.size - 2) { val testString = String(charArray.copyOfRange(i, i + 2)) val test = testString.codePointAt(0) - if (EmojiMap.emojiMap.containsKey(test)) { - returnString = returnString.replace(testString, EmojiMap.emojiMap[test]!!) - } + val cheatCode = EmojiMap.emojiMap[test] ?: continue + returnString = returnString.replace(testString, cheatCode) } return returnString } From a1a53852125abaa3f30bb1edc44fef54d31815a0 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 10:13:12 -0500 Subject: [PATCH 06/57] Revert "Skip redundant markdown parses to reduce flash" This reverts commit 1bfb5b3ff34af2716c82662721fdcf371347796d. --- .../viewHolders/tasks/BaseTaskViewHolder.kt | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index b969cb57b..e8881f9df 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -1,7 +1,6 @@ package com.habitrpg.android.habitica.ui.viewHolders.tasks import android.content.Context -import android.text.Spanned import android.text.method.LinkMovementMethod import android.view.MotionEvent import android.view.View @@ -38,8 +37,6 @@ abstract class BaseTaskViewHolder( private val scope = MainScope() var task: Task? = null - var existingMarkdownText: Spanned? = null - var existingMarkdownNotes: Spanned? = null var movingFromPosition: Int? = null var errorButtonClicked: (() -> Unit)? = null var userID: String? = null @@ -172,41 +169,24 @@ abstract class BaseTaskViewHolder( notesTextView?.visibility = View.GONE } - val text = data.text ?: "" - if (!MarkdownParser.containsMarkdown(text)) { - titleTextView.text = text - existingMarkdownText = null - } else { - scope.launch(Dispatchers.IO) { - if (text.isNotEmpty() && MarkdownParser.containsMarkdown(text)) { - val parsedText = MarkdownParser.parseMarkdown(text) - if (existingMarkdownText != null && existingMarkdownText == parsedText) { - return@launch - } - existingMarkdownText = parsedText - withContext(Dispatchers.Main) { - data.parsedText = parsedText - titleTextView.setParsedMarkdown(parsedText) - } + titleTextView.text = data.text + scope.launch(Dispatchers.IO) { + if (data.text.isNotEmpty() && MarkdownParser.containsMarkdown(data.text)) { + val parsedText = MarkdownParser.parseMarkdown(data.text) + withContext(Dispatchers.Main) { + data.parsedText = parsedText + titleTextView.setParsedMarkdown(parsedText) } } } - if (displayMode != "minimal") { - val notes = data.notes ?: "" - if (!MarkdownParser.containsMarkdown(notes)) { - notesTextView?.text = data.notes - existingMarkdownNotes = null - } else { + notesTextView?.text = data.notes + data.notes?.let { notes -> scope.launch(Dispatchers.IO) { if (notes.isEmpty() || !MarkdownParser.containsMarkdown(notes)) { return@launch } val parsedNotes = MarkdownParser.parseMarkdown(notes) - if (existingMarkdownNotes != null && existingMarkdownNotes == parsedNotes) { - return@launch - } - existingMarkdownNotes = parsedNotes withContext(Dispatchers.Main) { data.parsedNotes = parsedNotes notesTextView?.setParsedMarkdown(parsedNotes) From 6a5d13575673b08773f9142b086db0efd4a4c366 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 12:41:57 -0500 Subject: [PATCH 07/57] Remove extra bottom spacing showing on chat --- .../habitica/ui/views/social/ChatBarView.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt index 07013644c..be29ccb7f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt @@ -163,16 +163,16 @@ class ChatBarView : LinearLayout, OnImeVisibilityChangedListener { } override fun onImeVisibilityChanged(visible: Boolean, height: Int, safeInsets: Insets) { - this.safeInsets = safeInsets - imeHeight = if (visible) height else 0 + val navInset = safeInsets.bottom + val imeOffset = if (visible) (height - navInset).coerceAtLeast(0) else 0 updatePadding( - left = safeInsets.left, - right = safeInsets.right, - bottom = safeInsets.bottom + left = safeInsets.left, + right = safeInsets.right, + bottom = navInset ) - - // slide the bar up under the keyboard - translationY = if (imeHeight > 0) -imeHeight.toFloat() else 0f + + translationY = -imeOffset.toFloat() } + } From d69b300bf35577df59213b0472acf4c39651e11d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 12:53:33 -0500 Subject: [PATCH 08/57] small title/description/spacing tweak to empty chat text chat empty state: split into title and description TextViews with spacing --- Habitica/res/layout/fragment_chat.xml | 34 ++++++++++++++----- Habitica/res/values/strings.xml | 3 +- .../ui/fragments/social/ChatFragment.kt | 4 +-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Habitica/res/layout/fragment_chat.xml b/Habitica/res/layout/fragment_chat.xml index 05274489b..58728f472 100644 --- a/Habitica/res/layout/fragment_chat.xml +++ b/Habitica/res/layout/fragment_chat.xml @@ -4,18 +4,36 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:paddingHorizontal="@dimen/spacing_large" + android:paddingVertical="@dimen/spacing_medium"> + + + + + Error getting credentials for authentication. Received invalid credentials. Unknown error during authentication. - Start chatting!\nRemember to be friendly and follow the Community Guidelines. + Start chatting! + Remember to be friendly and follow the Community Guidelines. diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 9b38bc0b2..5a38eef19 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -224,10 +224,10 @@ open class ChatFragment : BaseFragment() { if (chatMessages.isEmpty()) { binding?.recyclerView?.state = RecyclerViewState.EMPTY - binding?.chatEmptyTextview?.fadeInAnimation() + binding?.chatEmptyContainer?.fadeInAnimation() } else { binding?.recyclerView?.state = RecyclerViewState.DISPLAYING_DATA - binding?.chatEmptyTextview?.isGone = true + binding?.chatEmptyContainer?.isGone = true } viewModel.gotNewMessages = true From 5f1648f2ef9ed27dc875d3f25215ad0648724edd Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 12:54:47 -0500 Subject: [PATCH 09/57] Set chat empty state text view invisible initially --- Habitica/res/layout/fragment_chat.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Habitica/res/layout/fragment_chat.xml b/Habitica/res/layout/fragment_chat.xml index 58728f472..439bdadae 100644 --- a/Habitica/res/layout/fragment_chat.xml +++ b/Habitica/res/layout/fragment_chat.xml @@ -10,6 +10,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" + android:visibility="invisible" android:layout_weight="1" android:paddingHorizontal="@dimen/spacing_large" android:paddingVertical="@dimen/spacing_medium"> From 86edc4ac7e3b34667b23941f22d896364c991bfa Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 13:11:22 -0500 Subject: [PATCH 10/57] Skill dialog tweaks - Set MP cost text to medium weight - Update skill description to 16sp regular - Fix text size/weight in pause damage bottom sheet - Adjust 'Use Skill' button for consistency (16sp medium, 12dp radius, 43dp height) - Reduce horizontal padding on MP cost chip by ~6-7dp per side - (Also tweak to text for "Pause/Resume Damage" text) --- .../com/habitrpg/android/habitica/ui/views/SkillDialog.kt | 8 ++++---- .../ui/views/preferences/PauseResumeDamageView.kt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt index c0b6bf21c..05e74776c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt @@ -106,7 +106,7 @@ fun SkillDialog( modifier = Modifier .clip(RoundedCornerShape(20.dp)) .background(chipBg) - .padding(horizontal = 20.dp, vertical = 8.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) ) { Icon( painter = resourceIconPainter, @@ -118,7 +118,7 @@ fun SkillDialog( Text( text = mpCost, color = chipTextColor, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Medium, fontSize = 16.sp ) } @@ -127,14 +127,14 @@ fun SkillDialog( Button( onClick = onUseSkill, - shape = RoundedCornerShape(8.dp), + shape = RoundedCornerShape(12.dp), colors = ButtonDefaults.buttonColors( containerColor = colorResource(R.color.brand_400), contentColor = Color.White ), modifier = Modifier .fillMaxWidth() - .height(48.dp) + .height(43.dp) ) { Text( text = stringResource(R.string.use_skill), diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt index b1f2f7afe..7afbf0c5f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt @@ -88,6 +88,7 @@ fun PauseResumeDamageView( color = HabiticaTheme.colors.textSecondary, fontSize = 16.sp, textAlign = TextAlign.Center, + fontWeight = FontWeight.Medium, modifier = Modifier .padding(bottom = 18.dp) From 0981e78e232ccfa3a5c90263ca56106cf4fafc04 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jun 2025 09:32:34 -0500 Subject: [PATCH 11/57] Task ViewHolder binding Markdown parsing check & DiffUtil - Check if text contains markdown before setting title/notes - Override getDiffCallback to enable RecyclerView DiffUtil updates --- .../RealmBaseTasksRecyclerViewAdapter.kt | 8 +++++ .../viewHolders/tasks/BaseTaskViewHolder.kt | 36 ++++++++----------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt index 21123dc36..e7258f314 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt @@ -13,6 +13,7 @@ import com.habitrpg.android.habitica.models.tasks.ChecklistItem import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter +import com.habitrpg.android.habitica.ui.adapter.DiffCallback import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -88,6 +89,13 @@ abstract class RealmBaseTasksRecyclerViewAdapter( } } + override fun getDiffCallback( + oldList: List, + newList: List + ): DiffCallback? { + return object : DiffCallback(oldList, newList) {} + } + override fun getItemCount(): Int { return data.size + if (showAdventureGuide) 1 else 0 } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt index e8881f9df..d61fe4bf8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt @@ -169,29 +169,23 @@ abstract class BaseTaskViewHolder( notesTextView?.visibility = View.GONE } - titleTextView.text = data.text - scope.launch(Dispatchers.IO) { - if (data.text.isNotEmpty() && MarkdownParser.containsMarkdown(data.text)) { - val parsedText = MarkdownParser.parseMarkdown(data.text) - withContext(Dispatchers.Main) { - data.parsedText = parsedText - titleTextView.setParsedMarkdown(parsedText) - } - } + val titleText = data.text ?: "" + if (!MarkdownParser.containsMarkdown(titleText)) { + titleTextView.text = titleText + } else { + val parsedText = MarkdownParser.parseMarkdown(titleText) + data.parsedText = parsedText + titleTextView.setParsedMarkdown(parsedText) } + if (displayMode != "minimal") { - notesTextView?.text = data.notes - data.notes?.let { notes -> - scope.launch(Dispatchers.IO) { - if (notes.isEmpty() || !MarkdownParser.containsMarkdown(notes)) { - return@launch - } - val parsedNotes = MarkdownParser.parseMarkdown(notes) - withContext(Dispatchers.Main) { - data.parsedNotes = parsedNotes - notesTextView?.setParsedMarkdown(parsedNotes) - } - } + val notes = data.notes ?: "" + if (!MarkdownParser.containsMarkdown(notes)) { + notesTextView?.text = notes + } else { + val parsedNotes = MarkdownParser.parseMarkdown(notes) + data.parsedNotes = parsedNotes + notesTextView?.setParsedMarkdown(parsedNotes) } } else { notesTextView?.visibility = View.GONE From 1773907fb216e4b952760db1d2469a6dacc42dd8 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jun 2025 13:26:41 -0500 Subject: [PATCH 12/57] Add transformation item to SkillDialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add isTransformationItem flag to SkillDialog: hide MP chip & spacer and update button label to “Use on Party” when true. --- Habitica/res/values/strings.xml | 1 + .../skills/SkillDialogBottomSheetFragment.kt | 4 + .../ui/fragments/skills/SkillsFragment.kt | 2 + .../android/habitica/ui/views/SkillDialog.kt | 75 ++++++++++--------- 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 0f2dafdd8..98ee7ac2a 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1040,6 +1040,7 @@ Stat Allocation All Habitica characters have four stats that affect the gameplay aspects of Habitica.\n\n**Strength (STR)** affects critical hits and raises damage done to a Quest Boss. Warriors and Rogues gain STR from their class equipment.\n\n**Constitution (CON)** raises your HP and makes you take less damage. Healers and Warriors gain CON from their class equipment.\n\n**Intelligence (INT)** raises the amount of EXP you earn and gives you more Mana. Mages and Healers gain INT from their class equipment.\n\n**Perception (PER)** increases the gold you earn and the rate of finding dropped items. Rogues and Mages gain PER from their class equipment.\n\nAfter level 10, you earn 1 Stat Point every level you gain that you can put into any stat you’d like. You can also equip gear that has different combinations of stat boosts. Use Skill + Use on Party Standard Premium Currency diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt index f158347fa..3b1343bfa 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt @@ -27,6 +27,7 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { skillPath: String, skillKey: String, resourceIcon: Drawable, + isTransformationItem: Boolean = false, onUseSkill: () -> Unit ): SkillDialogBottomSheetFragment { return SkillDialogBottomSheetFragment().apply { @@ -39,6 +40,7 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { this.skillKey = skillKey this.skillPath = skillPath this.onUseSkill = onUseSkill + this.isTransformationItem = isTransformationItem } } } @@ -47,6 +49,7 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { private var resourceIcon: Drawable? = null var skillKey = "" var skillPath = "" + var isTransformationItem: Boolean = false override fun onCreateView( inflater: LayoutInflater, @@ -62,6 +65,7 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { title = requireArguments().getString(ARG_SKILL_TITLE) ?: "", description = requireArguments().getString(ARG_SKILL_DESCRIPTION) ?: "", mpCost = requireArguments().getString(ARG_SKILL_MP_COST) ?: "", + isTransformationItem = isTransformationItem, onUseSkill = { onUseSkill?.invoke() dismiss() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index de23fbd1d..c18788da3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -106,6 +106,7 @@ class SkillsFragment : BaseMainFragment() { val context = context ?: return val resourceIconDrawable: Drawable = HabiticaIconsHelper.imageOfMagic().toDrawable(context.resources) val skillIdentifier = "shop_" + val isTransformationItem = skill.habitClass == "special" val bottomSheet = SkillDialogBottomSheetFragment.newInstance( skillTitle = skill.text, @@ -114,6 +115,7 @@ class SkillsFragment : BaseMainFragment() { skillPath = skillIdentifier, skillMpCost = "${skill.mana?.toInt() ?: 0} MP", resourceIcon = resourceIconDrawable, + isTransformationItem = isTransformationItem, onUseSkill = { when { "special" == skill.habitClass -> { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt index 05e74776c..64f4a91a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt @@ -43,6 +43,7 @@ fun SkillDialog( title: String, description: String, mpCost: String, + isTransformationItem: Boolean = false, onUseSkill: () -> Unit, ) { val colors = HabiticaTheme.colors @@ -61,11 +62,10 @@ fun SkillDialog( modifier = Modifier.fillMaxWidth() ) { Box( - modifier = - Modifier - .padding(bottom = 16.dp) - .background(colorResource(R.color.content_background_offset)) - .size(24.dp, 3.dp) + modifier = Modifier + .padding(bottom = 16.dp) + .background(colorResource(R.color.content_background_offset)) + .size(24.dp, 3.dp) ) Box( @@ -75,14 +75,12 @@ fun SkillDialog( .background(colors.pixelArtBackground(hasIcon = true)), contentAlignment = Alignment.Center ) { - PixelArtView ( - imageName = skillPath + skillKey, + PixelArtView( + imageName = "$skillPath$skillKey", modifier = Modifier.size(62.dp) ) } - Spacer(Modifier.height(16.dp)) - Text( text = title, fontSize = 16.sp, @@ -98,33 +96,33 @@ fun SkillDialog( modifier = Modifier.padding(top = 8.dp), textAlign = TextAlign.Center ) - Spacer(Modifier.height(18.dp)) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clip(RoundedCornerShape(20.dp)) - .background(chipBg) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) { - Icon( - painter = resourceIconPainter, - contentDescription = null, - tint = Color.Unspecified, - modifier = Modifier.size(22.dp) - ) - Spacer(Modifier.width(8.dp)) - Text( - text = mpCost, - color = chipTextColor, - fontWeight = FontWeight.Medium, - fontSize = 16.sp - ) + if (!isTransformationItem) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(20.dp)) + .background(chipBg) + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Icon( + painter = resourceIconPainter, + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(22.dp) + ) + Spacer(Modifier.width(8.dp)) + Text( + text = mpCost, + color = chipTextColor, + fontWeight = FontWeight.Medium, + fontSize = 16.sp + ) + } + Spacer(Modifier.height(28.dp)) + } else { + Spacer(Modifier.height(18.dp)) } - - Spacer(Modifier.height(28.dp)) - Button( onClick = onUseSkill, shape = RoundedCornerShape(12.dp), @@ -136,18 +134,22 @@ fun SkillDialog( .fillMaxWidth() .height(43.dp) ) { + val label = if (isTransformationItem) + stringResource(R.string.use_on_party) + else + stringResource(R.string.use_skill) Text( - text = stringResource(R.string.use_skill), + text = label, fontWeight = FontWeight.Normal, fontSize = 17.sp ) } - } } } + @Preview(showBackground = true, backgroundColor = 0xFF232136) @Composable fun PreviewSkillDialog() { @@ -158,6 +160,7 @@ fun PreviewSkillDialog() { title = "Title Skill", description = "Skill Description", mpCost = "10 MP", + isTransformationItem = true, onUseSkill = { } From b41ef037aeec7b8e42b64405d3315a40601373f1 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jun 2025 14:08:28 -0500 Subject: [PATCH 13/57] Use skilldialog bottomsheet with mystery items, also add typesafety --- .../adapter/inventory/ItemRecyclerAdapter.kt | 30 ++++++++----- .../inventory/items/ItemRecyclerFragment.kt | 43 ++++++++++++++++++- .../skills/SkillDialogBottomSheetFragment.kt | 16 +++---- .../ui/fragments/skills/SkillsFragment.kt | 2 - .../android/habitica/ui/views/SkillDialog.kt | 6 +-- 5 files changed, 72 insertions(+), 25 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt index 0394ab471..d01f68383 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt @@ -1,9 +1,13 @@ package com.habitrpg.android.habitica.ui.adapter.inventory import android.content.Context +import android.content.Intent import android.content.res.Resources +import android.graphics.drawable.Drawable import android.view.View import android.view.ViewGroup +import androidx.compose.ui.res.painterResource +import androidx.core.graphics.drawable.toDrawable import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.habitrpg.android.habitica.R @@ -20,10 +24,15 @@ import com.habitrpg.android.habitica.models.inventory.SpecialItem import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity +import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter +import com.habitrpg.android.habitica.ui.fragments.skills.SkillDialogBottomSheetFragment import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem +import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog +import com.habitrpg.common.habitica.extensions.asPainter import com.habitrpg.common.habitica.extensions.layoutInflater import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.extensions.localizedCapitalizeWithSpaces @@ -258,20 +267,19 @@ class ItemRecyclerAdapter(val context: Context) : } } else if (ownedItem?.itemType == "special") { if ((ownedItem?.numberOwned ?: 0) > 0) { - menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item))) + if (item == null && ownedItem != null) { + // Special items that are not Mystery Item + val specialItem = SpecialItem() + ownedItem?.key?.let { key -> + specialItem.key = key + specialItem.text = key.localizedCapitalizeWithSpaces() + } + onUseSpecialItem?.invoke(specialItem) + } } + return } menu.setSelectionRunnable { index -> - if (item == null && ownedItem != null) { - // Special items that are not Mystery Item - val specialItem = SpecialItem() - ownedItem?.key?.let { key -> - specialItem.key = key - specialItem.text = key.localizedCapitalizeWithSpaces() - } - onUseSpecialItem?.invoke(specialItem) - return@setSelectionRunnable - } item?.let { selectedItem -> if (!(selectedItem is QuestContent || selectedItem is SpecialItem || ownedItem?.itemType == "special") && index == 0) { ownedItem?.let { selectedOwnedItem -> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index e66e32cf3..f3da189cd 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.helpers.EventCategory import com.habitrpg.android.habitica.helpers.HitType import com.habitrpg.android.habitica.helpers.ReviewManager import com.habitrpg.android.habitica.interactors.HatchPetUseCase +import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.HatchingPotion @@ -38,18 +39,22 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment +import com.habitrpg.android.habitica.ui.fragments.skills.SkillDialogBottomSheetFragment import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator +import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog import com.habitrpg.common.habitica.extensions.loadImage +import com.habitrpg.common.habitica.extensions.localizedCapitalizeWithSpaces import com.habitrpg.common.habitica.extensions.observeOnce import com.habitrpg.common.habitica.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.ExceptionHandler import com.habitrpg.common.habitica.helpers.MainNavigationController import com.habitrpg.common.habitica.helpers.launchCatching import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -87,6 +92,7 @@ class ItemRecyclerFragment : var transformationItems: MutableList = mutableListOf() var itemTypeText: String? = null private var selectedSpecialItem: SpecialItem? = null + private var specialSkills: MutableList = mutableListOf() internal var layoutManager: androidx.recyclerview.widget.LinearLayoutManager? = null override var binding: FragmentItemsBinding? = null @@ -113,6 +119,7 @@ class ItemRecyclerFragment : this.itemType = savedInstanceState.getString(ITEM_TYPE_KEY, "") this.itemTypeText = savedInstanceState.getString(ITEM_TYPE_TEXT_KEY, "") } + getSpecialSkills() binding?.refreshLayout?.setOnRefreshListener(this) val buttonMethod = { @@ -170,6 +177,24 @@ class ItemRecyclerFragment : this.loadItems() } + private fun getSpecialSkills() { + // Get special skills for description of special items + lifecycleScope.launchCatching { + val user = userViewModel.user.value ?: return@launchCatching + userRepository.getSkills(user) + .combine(userRepository.getSpecialItems(user)) { skills, items -> + val allEntries = mutableListOf() + for (skill in skills) { + allEntries.add(skill) + } + for (item in items) { + allEntries.add(item) + } + return@combine allEntries + }.collect { skills -> specialSkills = skills } + } + } + private fun setAdapter() { val context = activity @@ -180,7 +205,23 @@ class ItemRecyclerFragment : } binding?.recyclerView?.adapter = adapter } - adapter?.onUseSpecialItem = { onSpecialItemSelected(it) } + adapter?.onUseSpecialItem = { specialItem -> + val specialSkill = specialSkills.find { it.key == specialItem.key } + if (specialSkill != null) { + val skillIdentifier = "shop_" + val bottomSheet = SkillDialogBottomSheetFragment.newInstance( + skillTitle = specialSkill.text, + skillDescription = specialSkill.notes ?: "", + skillKey = specialSkill.key, + skillPath = skillIdentifier, + isTransformationItem = true, + onUseSkill = { + onSpecialItemSelected(specialItem) + } + ) + bottomSheet.show(childFragmentManager, "SkillDialogBottomSheet") + } + } adapter?.onSellItem = { item, ownedItem -> showSellItemConfirmation(item, ownedItem) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt index 3b1343bfa..1c13c6a0a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillDialogBottomSheetFragment.kt @@ -21,12 +21,12 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { private const val ARG_SKILL_MP_COST = "skill_mp_cost" fun newInstance( - skillTitle: String, - skillDescription: String, - skillMpCost: String, - skillPath: String, - skillKey: String, - resourceIcon: Drawable, + skillTitle: String? = "", + skillDescription: String? = "", + skillMpCost: String? = "", + skillPath: String? = "", + skillKey: String? = "", + resourceIcon: Drawable? = null, isTransformationItem: Boolean = false, onUseSkill: () -> Unit ): SkillDialogBottomSheetFragment { @@ -37,8 +37,8 @@ class SkillDialogBottomSheetFragment : BottomSheetDialogFragment() { putString(ARG_SKILL_MP_COST, skillMpCost) } this.resourceIcon = resourceIcon - this.skillKey = skillKey - this.skillPath = skillPath + this.skillKey = skillKey ?: "" + this.skillPath = skillPath ?: "" this.onUseSkill = onUseSkill this.isTransformationItem = isTransformationItem } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index c18788da3..1a3413a4c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -135,8 +135,6 @@ class SkillsFragment : BaseMainFragment() { } ) bottomSheet.show(childFragmentManager, "SkillDialogBottomSheet") - - } private fun displaySkillResult( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt index 64f4a91a0..e34ab8805 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt @@ -40,9 +40,9 @@ fun SkillDialog( skillPath: String = "", skillKey: String = "", resourceIconPainter: Painter, - title: String, - description: String, - mpCost: String, + title: String = "", + description: String = "", + mpCost: String = "", isTransformationItem: Boolean = false, onUseSkill: () -> Unit, ) { From 51953283f1d36246a69898be8ef08d6b0fa870f6 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jun 2025 14:19:46 -0500 Subject: [PATCH 14/57] Wait until viewpager finished laying out page for checking active filter --- .../android/habitica/ui/fragments/tasks/TasksFragment.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt index 6394ead06..766314f38 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt @@ -249,7 +249,9 @@ class TasksFragment : override fun onPageSelected(position: Int) { super.onPageSelected(position) bottomNavigation?.selectedPosition = position - updateFilterIcon(getTaskTypeFromTabPosition(position)) + binding?.viewPager?.post { + updateFilterIcon(getTaskTypeFromTabPosition(position)) + } } } ) From 8ab3546696657312ee658ab64df8c750cc45e42d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 12 Jun 2025 16:43:09 -0500 Subject: [PATCH 15/57] remove DiffCallback implementation --- .../ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt index e7258f314..95f1e8d66 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RealmBaseTasksRecyclerViewAdapter.kt @@ -89,13 +89,6 @@ abstract class RealmBaseTasksRecyclerViewAdapter( } } - override fun getDiffCallback( - oldList: List, - newList: List - ): DiffCallback? { - return object : DiffCallback(oldList, newList) {} - } - override fun getItemCount(): Int { return data.size + if (showAdventureGuide) 1 else 0 } From 64f2942e7ae0082347e9f9668804503bf6864b94 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 13 Jun 2025 09:07:48 -0500 Subject: [PATCH 16/57] Reduces bottom margin for empty state title --- Habitica/res/layout/fragment_chat.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/res/layout/fragment_chat.xml b/Habitica/res/layout/fragment_chat.xml index 439bdadae..b175bd558 100644 --- a/Habitica/res/layout/fragment_chat.xml +++ b/Habitica/res/layout/fragment_chat.xml @@ -20,7 +20,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:layout_marginBottom="8dp" + android:layout_marginBottom="4dp" android:text="@string/chat_empty_state_title" android:textColor="@color/gray100_gray400" android:textSize="17sp" From f374acad5c472ffcd698243c3107321cde43bace Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 13 Jun 2025 09:09:30 -0500 Subject: [PATCH 17/57] set chat empty state gray700_gray10 --- Habitica/res/layout/fragment_chat.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/res/layout/fragment_chat.xml b/Habitica/res/layout/fragment_chat.xml index b175bd558..c0ac49cf4 100644 --- a/Habitica/res/layout/fragment_chat.xml +++ b/Habitica/res/layout/fragment_chat.xml @@ -32,7 +32,7 @@ android:layout_height="wrap_content" android:gravity="center" android:text="@string/chat_empty_state_description" - android:textColor="@color/gray200_gray400" + android:textColor="@color/gray700_gray10" android:textSize="16sp" /> From 7ca86538e37d0cd50345f2374cc869b4daa708ef Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 13 Jun 2025 09:27:44 -0500 Subject: [PATCH 18/57] Use WindowInsetsListener to adjust chat layout Remove old applyScrollContentWindowInsets helper and IME listeners Add ViewCompat.setOnApplyWindowInsetsListener on root view Translate chatBarView and recyclerView by IME height and pad for nav-bar insets Force initial inset dispatch with ViewCompat.requestApplyInsets() --- .../ui/fragments/social/ChatFragment.kt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 5a38eef19..221622f33 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -8,6 +8,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isGone import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle @@ -140,7 +142,36 @@ open class ChatFragment : BaseFragment() { } } } - binding?.chatBarView?.let { applyScrollContentWindowInsets(it) } + + binding?.root.apply { + ViewCompat.setOnApplyWindowInsetsListener(this!!) { _, insets -> + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom + val nav = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom + + binding?.chatBarView?.translationY = -ime.toFloat() + binding?.chatBarView?.setPadding( + binding?.chatBarView!!.paddingLeft, + binding?.chatBarView!!.paddingTop, + binding?.chatBarView!!.paddingRight, + nav + ) + + + binding?.recyclerView?.translationY = -ime.toFloat() + + + binding?.recyclerView?.setPadding( + binding?.recyclerView!!.paddingLeft, + binding?.recyclerView!!.paddingTop, + binding?.recyclerView!!.paddingRight, + ime + nav + ) + + insets + } + ViewCompat.requestApplyInsets(this) + } + } override fun onResume() { From 650c1049baf803ad27b8d3c22e1d28efc1895730 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 13 Jun 2025 09:31:33 -0500 Subject: [PATCH 19/57] Skill Description font fixes Set skill description to use FontWeight.Normal Set Button fontweight to use FontWeight.Medium --- .../java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt index e34ab8805..b6445d670 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SkillDialog.kt @@ -94,6 +94,7 @@ fun SkillDialog( fontSize = 14.sp, color = colors.textSecondary, modifier = Modifier.padding(top = 8.dp), + fontWeight = FontWeight.Normal, textAlign = TextAlign.Center ) Spacer(Modifier.height(18.dp)) @@ -140,7 +141,7 @@ fun SkillDialog( stringResource(R.string.use_skill) Text( text = label, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Medium, fontSize = 17.sp ) } From 85b374a354d6a36491b1aab473df4be7fe740939 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 12 Jun 2025 18:40:06 +0200 Subject: [PATCH 20/57] fix type definition --- .../java/com/habitrpg/common/habitica/helpers/EmojiMap.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt index 0c65a48bb..7a106333b 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/EmojiMap.kt @@ -1,8 +1,8 @@ package com.habitrpg.common.habitica.helpers object EmojiMap { - val emojiMap: MutableMap = HashMap() - val invertedEmojiMap: MutableMap = HashMap() + val emojiMap: MutableMap = HashMap() + val invertedEmojiMap: MutableMap = HashMap() init { emojiMap.put(128077, ":+1:") From 2210ecae63e22791fbca6c8fbb094786e4fecf9b Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 16 Jun 2025 09:37:10 -0500 Subject: [PATCH 21/57] Requests insets on resume for chat fragment This makes sure that the chatbox/views are back to default --- .../android/habitica/ui/fragments/social/ChatFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 221622f33..3e29d5a38 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -176,6 +176,7 @@ open class ChatFragment : BaseFragment() { override fun onResume() { super.onResume() + binding?.root?.let { ViewCompat.requestApplyInsets(it) } setNavigatedToFragment() } From 3084532f5bd0ae62c85f56bb0108360aeff6e51c Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 16 Jun 2025 09:39:21 -0500 Subject: [PATCH 22/57] Fix 2nd line of text ("remember to be friendly.. to gray200_gray400 --- Habitica/res/layout/fragment_chat.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/res/layout/fragment_chat.xml b/Habitica/res/layout/fragment_chat.xml index c0ac49cf4..b175bd558 100644 --- a/Habitica/res/layout/fragment_chat.xml +++ b/Habitica/res/layout/fragment_chat.xml @@ -32,7 +32,7 @@ android:layout_height="wrap_content" android:gravity="center" android:text="@string/chat_empty_state_description" - android:textColor="@color/gray700_gray10" + android:textColor="@color/gray200_gray400" android:textSize="16sp" /> From 58637eb906629fc756d4d51f7c085b0f75806f80 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 16 Jun 2025 09:58:47 -0500 Subject: [PATCH 23/57] Add skill_transformation_list_item and adapter usage for transformation skill items --- .../ui/adapter/SkillsRecyclerViewAdapter.kt | 224 ++++++++---------- .../layout/skill_transformation_list_item.xml | 73 ++++++ 2 files changed, 176 insertions(+), 121 deletions(-) create mode 100644 Habitica/src/main/res/layout/skill_transformation_list_item.xml diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt index 236a29581..f24ced658 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt @@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.SkillListItemBinding +import com.habitrpg.android.habitica.databinding.SkillTransformationListItemBinding import com.habitrpg.android.habitica.models.Skill import com.habitrpg.android.habitica.models.user.OwnedItem import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper @@ -18,7 +19,8 @@ import com.habitrpg.common.habitica.extensions.loadImage import io.realm.RealmList class SkillsRecyclerViewAdapter : - RecyclerView.Adapter() { + RecyclerView.Adapter() { + var onUseSkill: ((Skill) -> Unit)? = null var mana: Double = 0.0 @@ -26,65 +28,73 @@ class SkillsRecyclerViewAdapter : field = value notifyDataSetChanged() } + var level: Int = 0 set(value) { field = value notifyDataSetChanged() } + var specialItems: RealmList? = null set(value) { field = value notifyDataSetChanged() } + private var skillList: List = emptyList() - fun setSkillList(skillList: List) { - this.skillList = skillList - this.notifyDataSetChanged() + companion object { + private const val TYPE_NORMAL = 0 + private const val TYPE_SPECIAL = 1 } - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): SkillViewHolder { - return SkillViewHolder(parent.inflate(R.layout.skill_list_item)) + fun setSkillList(list: List) { + skillList = list + notifyDataSetChanged() } - override fun onBindViewHolder( - holder: SkillViewHolder, - position: Int - ) { - holder.bind(skillList[position]) + override fun getItemViewType(position: Int): Int { + return if (skillList[position].habitClass == "special") TYPE_SPECIAL + else TYPE_NORMAL } - override fun getItemCount(): Int { - return skillList.size + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == TYPE_SPECIAL) { + val view = parent.inflate(R.layout.skill_transformation_list_item) + SpecialViewHolder(view) + } else { + val view = parent.inflate(R.layout.skill_list_item) + NormalViewHolder(view) + } } - inner class SkillViewHolder(itemView: View) : - RecyclerView.ViewHolder(itemView), - View.OnClickListener { + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is SpecialViewHolder -> holder.bind(skillList[position]) + is NormalViewHolder -> holder.bind(skillList[position]) + } + } + + override fun getItemCount(): Int = skillList.size + + private inner class NormalViewHolder(itemView: View) : + RecyclerView.ViewHolder(itemView), View.OnClickListener { + private val binding = SkillListItemBinding.bind(itemView) - private val magicDrawable: Drawable - private val lockDrawable: Drawable - - var skill: Skill? = null - - var context: Context = itemView.context + private val context = itemView.context + private val magicDrawable: Drawable = + BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfMagic()) + private val lockDrawable: Drawable = + BitmapDrawable( + context.resources, + HabiticaIconsHelper.imageOfLocked( + ContextCompat.getColor(context, R.color.text_dimmed) + ) + ) + private var skill: Skill? = null init { binding.buttonWrapper.setOnClickListener(this) - magicDrawable = BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfMagic()) - lockDrawable = - BitmapDrawable( - context.resources, - HabiticaIconsHelper.imageOfLocked( - ContextCompat.getColor( - context, - R.color.text_dimmed - ) - ) - ) } fun bind(skill: Skill) { @@ -97,103 +107,75 @@ class SkillsRecyclerViewAdapter : binding.skillNotes.visibility = View.VISIBLE binding.priceLabel.visibility = View.VISIBLE - if ("special" == skill.habitClass) { - binding.countLabel.visibility = View.VISIBLE - binding.countLabel.text = getOwnedCount(skill.key).toString() - binding.priceLabel.setText(R.string.skill_transformation_use) - if (context.isUsingNightModeResources()) { - binding.priceLabel.setTextColor( - ContextCompat.getColor( - context, - R.color.brand_500 - ) - ) - } else { - binding.priceLabel.setTextColor( - ContextCompat.getColor( - context, - R.color.color_accent - ) - ) - } - binding.buttonIconView.setImageDrawable(null) + binding.countLabel.visibility = View.GONE + binding.priceLabel.text = skill.mana?.toString() + + val manaColor = if (context.isUsingNightModeResources()) + R.color.blue_500 else R.color.blue_10 + binding.priceLabel.setTextColor(ContextCompat.getColor(context, manaColor)) + + binding.buttonIconView.setImageDrawable(magicDrawable) + + if ((skill.mana ?: 0) > mana) { binding.buttonWrapper.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.offset_background - ) + ContextCompat.getColor(context, R.color.offset_background) + ) + binding.buttonIconView.alpha = 0.3f + binding.priceLabel.alpha = 0.3f + } else { + binding.buttonWrapper.setBackgroundColor( + ContextCompat.getColor(context, R.color.blue_500_24) ) binding.buttonIconView.alpha = 1.0f binding.priceLabel.alpha = 1.0f - } else { - binding.countLabel.visibility = View.GONE - binding.priceLabel.text = skill.mana?.toString() - if (context.isUsingNightModeResources()) { - binding.priceLabel.setTextColor( - ContextCompat.getColor( - context, - R.color.blue_500 - ) - ) - } else { - binding.priceLabel.setTextColor( - ContextCompat.getColor( - context, - R.color.blue_10 - ) - ) - } - binding.buttonIconView.setImageDrawable(magicDrawable) - - if ((skill.mana ?: 0) > mana) { - binding.buttonWrapper.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.offset_background - ) - ) - binding.buttonIconView.alpha = 0.3f - binding.priceLabel.alpha = 0.3f - } else { - binding.buttonWrapper.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.blue_500_24 - ) - ) - binding.buttonIconView.alpha = 1.0f - binding.priceLabel.alpha = 1.0f - } - if ((skill.lvl ?: 0) > level) { - binding.buttonWrapper.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.offset_background - ) - ) - binding.skillText.setTextColor( - ContextCompat.getColor( - context, - R.color.text_dimmed - ) - ) - binding.skillText.text = context.getString(R.string.skill_unlocks_at, skill.lvl) - binding.skillNotes.visibility = View.GONE - binding.buttonIconView.setImageDrawable(lockDrawable) - binding.priceLabel.visibility = View.GONE - } } + + if ((skill.lvl ?: 0) > level) { + binding.buttonWrapper.setBackgroundColor( + ContextCompat.getColor(context, R.color.offset_background) + ) + binding.skillText.setTextColor( + ContextCompat.getColor(context, R.color.text_dimmed) + ) + binding.skillText.text = context.getString( + R.string.skill_unlocks_at, skill.lvl + ) + binding.skillNotes.visibility = View.GONE + binding.buttonIconView.setImageDrawable(lockDrawable) + binding.priceLabel.visibility = View.GONE + } + binding.skillImage.loadImage("shop_" + skill.key) } override fun onClick(v: View) { - if ((skill?.lvl ?: 0) <= level) { - skill?.let { onUseSkill?.invoke(it) } + skill?.takeIf { (it.lvl ?: 0) <= level }?.also { + onUseSkill?.invoke(it) } } + } - private fun getOwnedCount(key: String): Int { - return specialItems?.firstOrNull { it.key == key }?.numberOwned ?: 0 + private inner class SpecialViewHolder(itemView: View) : + RecyclerView.ViewHolder(itemView), View.OnClickListener { + + private val binding = SkillTransformationListItemBinding.bind(itemView) + + init { + itemView.setOnClickListener(this) + } + + fun bind(skill: Skill) { + binding.skillText.text = skill.text + binding.skillNotes.text = skill.notes + binding.countLabel.text = getOwnedCount(skill.key).toString() + binding.skillImage.loadImage("shop_" + skill.key) + } + + override fun onClick(v: View) { + onUseSkill?.invoke(skillList[bindingAdapterPosition]) } } + + private fun getOwnedCount(key: String): Int = + specialItems?.firstOrNull { it.key == key }?.numberOwned ?: 0 } diff --git a/Habitica/src/main/res/layout/skill_transformation_list_item.xml b/Habitica/src/main/res/layout/skill_transformation_list_item.xml new file mode 100644 index 000000000..24236cf5a --- /dev/null +++ b/Habitica/src/main/res/layout/skill_transformation_list_item.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + From e68b34400210032a2ed0370380686d1ffc6a70e9 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 16 Jun 2025 10:05:16 -0500 Subject: [PATCH 24/57] Make entire special skill item clickable --- .../android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt index f24ced658..ccb0166b9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt @@ -161,7 +161,7 @@ class SkillsRecyclerViewAdapter : private val binding = SkillTransformationListItemBinding.bind(itemView) init { - itemView.setOnClickListener(this) + binding.specialSkillContainer.setOnClickListener(this) } fun bind(skill: Skill) { From c1d290784436d39182774f17f2e28b2b8f766283 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 16 Jun 2025 10:44:48 -0500 Subject: [PATCH 25/57] Set special skill text/note & fix margin --- .../habitica/ui/adapter/SkillsRecyclerViewAdapter.kt | 4 ++++ .../src/main/res/layout/skill_transformation_list_item.xml | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt index ccb0166b9..61cce912c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt @@ -159,6 +159,7 @@ class SkillsRecyclerViewAdapter : RecyclerView.ViewHolder(itemView), View.OnClickListener { private val binding = SkillTransformationListItemBinding.bind(itemView) + private val context = itemView.context init { binding.specialSkillContainer.setOnClickListener(this) @@ -167,6 +168,9 @@ class SkillsRecyclerViewAdapter : fun bind(skill: Skill) { binding.skillText.text = skill.text binding.skillNotes.text = skill.notes + binding.skillText.setTextColor(ContextCompat.getColor(context, R.color.text_primary)) + binding.skillNotes.setTextColor(ContextCompat.getColor(context, R.color.text_ternary)) + binding.countLabel.text = getOwnedCount(skill.key).toString() binding.skillImage.loadImage("shop_" + skill.key) } diff --git a/Habitica/src/main/res/layout/skill_transformation_list_item.xml b/Habitica/src/main/res/layout/skill_transformation_list_item.xml index 24236cf5a..4d719412a 100644 --- a/Habitica/src/main/res/layout/skill_transformation_list_item.xml +++ b/Habitica/src/main/res/layout/skill_transformation_list_item.xml @@ -25,13 +25,12 @@ android:id="@+id/skill_image" android:layout_width="40dp" android:layout_height="40dp" - android:layout_gravity="center_vertical" - android:layout_marginStart="8dp" /> + android:layout_gravity="center_vertical" /> Date: Mon, 16 Jun 2025 10:48:18 -0500 Subject: [PATCH 26/57] Reduces left margin in transformation list item Reduces the left margin of the transformation list item --- Habitica/src/main/res/layout/skill_transformation_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/res/layout/skill_transformation_list_item.xml b/Habitica/src/main/res/layout/skill_transformation_list_item.xml index 4d719412a..dd391a6ac 100644 --- a/Habitica/src/main/res/layout/skill_transformation_list_item.xml +++ b/Habitica/src/main/res/layout/skill_transformation_list_item.xml @@ -30,7 +30,7 @@ Date: Tue, 17 Jun 2025 09:05:48 -0500 Subject: [PATCH 27/57] Update skill item click listener to container Changes the skill item click listener from the button wrapper to the skill item container - makes the entire skill item is clickable, --- Habitica/res/layout/skill_list_item.xml | 1 + .../android/habitica/ui/adapter/SkillsRecyclerViewAdapter.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Habitica/res/layout/skill_list_item.xml b/Habitica/res/layout/skill_list_item.xml index 353ceb064..8ef8a24e1 100644 --- a/Habitica/res/layout/skill_list_item.xml +++ b/Habitica/res/layout/skill_list_item.xml @@ -6,6 +6,7 @@ android:orientation="horizontal"> Date: Tue, 17 Jun 2025 16:29:15 +0200 Subject: [PATCH 28/57] finish 4.7.6 --- fastlane/changelog.txt | 19 +++++++++++-------- version.properties | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt index 5a23887aa..1d808859d 100644 --- a/fastlane/changelog.txt +++ b/fastlane/changelog.txt @@ -1,8 +1,11 @@ -New in 4.7.4 -- Hungarian language support -- Upgraded to the latest Google Sign In authentication standards -- Implemented full edge-to-edge display functionality on Android 11+ devices -- Fixed some issues where the text box in chat wasn't adjusting properly -- More support for landscape mode -- Various other bug fixes and improvements -- Support for future events +New in 4.7.6 +- Monthly Dailies should repeat more accurately +- Reminders update correctly after being deleted +- More accessible designs for the Skill section +- Scheduled To Do filter will stay applied on app reopen +- Party description box no longer gets hidden by keyboard +- Fixed a crash with certain languages in Avatar Customization +- Fixed a crash with Group Plan member lists +- Animated backgrounds move again +- Markdown formatting no longer flashes when refreshing +- Various other bug fixes diff --git a/version.properties b/version.properties index c575e554a..e0343baf1 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -NAME=4.7.5 -CODE=12981 \ No newline at end of file +NAME=4.7.6 +CODE=13381 From 6d705325fee5fb42d85142ef430b4a2ba43a2c3c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 17 Jun 2025 16:52:01 +0200 Subject: [PATCH 29/57] Version bumped to v4.7.6 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index e0343baf1..6c0fbd9d7 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.7.6 -CODE=13381 +CODE=13391 \ No newline at end of file From 903a4164e98a07cabd9f03a65ecf969c2226273e Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 20:24:08 -0500 Subject: [PATCH 30/57] Detect negative HP as user fainted - Change isUserFainted to use <= 0.0 so negative HP values also count as fainted - Death dialog is triggered if HP falls at/below zero --- .../android/habitica/ui/viewmodels/MainUserViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt index 80eafd782..e6449fecb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainUserViewModel.kt @@ -40,7 +40,7 @@ constructor( val partyID: String? get() = validatedUser?.party?.id val isUserFainted: Boolean - get() = (validatedUser?.stats?.hp ?: 1.0) == 0.0 + get() = (validatedUser?.stats?.hp ?: 1.0) <= 0.0 val isUserInParty: Boolean get() = validatedUser?.hasParty == true val mirrorGroupTasks: List From 08bcaeb5038079c3908433cdb7cc6dad4db683f8 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 20:06:34 -0500 Subject: [PATCH 31/57] Pin currently equipped item to the top of equipment list --- .../equipment/EquipmentDetailFragment.kt | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt index dd810adbf..594644d39 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt @@ -40,6 +40,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.databinding.FragmentEquipmentDetailBinding import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.ReviewManager +import com.habitrpg.android.habitica.models.inventory.Equipment import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil @@ -80,6 +81,8 @@ class EquipmentDetailFragment : @Inject lateinit var configManager: AppConfigManager + private var pinnedGearKey: String? = null + override fun createBinding( inflater: LayoutInflater, container: ViewGroup? @@ -133,6 +136,7 @@ class EquipmentDetailFragment : type = args.type isCostume = args.isCostume equippedGear = args.equippedGear + pinnedGearKey = equippedGear } binding?.refreshLayout?.setOnRefreshListener(this) binding?.recyclerView?.onRefresh = { onRefresh() } @@ -175,26 +179,29 @@ class EquipmentDetailFragment : binding?.recyclerView?.layoutManager = LinearLayoutManager(mainActivity) binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator() - type?.let { type -> + type?.let { gearType -> lifecycleScope.launchCatching { - inventoryRepository.getOwnedEquipment(type) + inventoryRepository.getOwnedEquipment(gearType) .combine(searchedText) { equipment, query -> - if (query.isNullOrBlank()) { - return@combine equipment - } - val tokens = query.split(" ") - val tokenCount = tokens.size - equipment.filter { - var matchCount = 0 - for (token in tokens) { - if (it.text.contains(token, true) || it.notes.contains(token, true)) { - matchCount += 1 + if (query.isNullOrBlank()) equipment + else { + val tokens = query.split(" ") + equipment.filter { gear -> + tokens.all { token -> + gear.text.contains(token, true) || gear.notes.contains(token, true) } } - return@filter matchCount == tokenCount } } - .map { it.sortedBy { equipment -> equipment.text } } + .map { list -> + val sorted = list.sortedBy { it.text } + pinnedGearKey?.let { key -> + sorted.sortedWith( + compareBy { it.key != key } + .thenBy { it.text } + ) + } ?: sorted + } .collect { adapter.data = it } } } From 8f8bb1a5de3a7ca518abedc5ffa4cae61d6cefc2 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 19:43:47 -0500 Subject: [PATCH 32/57] Unregister and clear push device on logout - In logout(), set PushNotificationManager.user and refreshedToken before calling removePushDeviceUsingStoredToken() - Add clearUser() to null out in-memory user and remove saved DEVICE_TOKEN_PREFERENCE_KEY - Invoke clearUser() after database reset to prevent stray notifications post-logout --- .../habitica/HabiticaBaseApplication.kt | 25 ++++++++++++++----- .../notifications/PushNotificationManager.kt | 10 +++++++- .../preferences/AccountPreferenceFragment.kt | 5 +++- .../preferences/PreferencesFragment.kt | 2 +- 4 files changed, 33 insertions(+), 9 deletions(-) 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 551585175..526f147b5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -11,8 +11,6 @@ import android.content.res.Configuration import android.content.res.Resources import android.database.DatabaseErrorHandler import android.database.sqlite.SQLiteDatabase -import android.os.Build -import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatDelegate @@ -33,6 +31,8 @@ import com.habitrpg.android.habitica.extensions.DateUtils import com.habitrpg.android.habitica.helpers.AdHandler import com.habitrpg.android.habitica.helpers.Analytics import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager +import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager.Companion.DEVICE_TOKEN_PREFERENCE_KEY +import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.modules.AuthenticationHandler import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.activities.LoginActivity @@ -318,15 +318,26 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife realm.close() } - fun logout(context: Context) { + fun logout(context: Context, user: User? = null) { MainScope().launchCatching { - getInstance(context)?.pushNotificationManager?.removePushDeviceUsingStoredToken() - deleteDatabase(context) val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val instance = getInstance(context) + val pushManager = instance?.pushNotificationManager + val deviceToken = preferences.getString(DEVICE_TOKEN_PREFERENCE_KEY, "") ?: "" + val useReminder = preferences.getBoolean("use_reminder", false) val reminderTime = preferences.getString("reminder_time", "19:00") val lightMode = preferences.getString("theme_mode", "system") val launchScreen = preferences.getString("launch_screen", "") + + // set the user and refreshed token in the push manager, so we can remove the push device + if (deviceToken.isNotEmpty() && user != null) { + pushManager?.setUser(user) + pushManager?.refreshedToken = deviceToken + pushManager?.removePushDeviceUsingStoredToken() + } + + deleteDatabase(context) preferences.edit { clear() putBoolean("use_reminder", useReminder) @@ -334,7 +345,9 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife putString("theme_mode", lightMode) putString("launch_screen", launchScreen) } - getInstance(context)?.lazyApiHelper?.updateAuthenticationCredentials(null, null) + pushManager?.clearUser() + + instance?.lazyApiHelper?.updateAuthenticationCredentials(null, null) Wearable.getCapabilityClient(context).removeLocalCapability("provide_auth") startActivity(LoginActivity::class.java, context) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt index 63e9e7f50..7d1129e55 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt @@ -37,6 +37,14 @@ class PushNotificationManager( this.user = user } + fun clearUser() { + this.user = null + this.refreshedToken = "" + sharedPreferences.edit { + remove(DEVICE_TOKEN_PREFERENCE_KEY) + } + } + /** * New installs on Android 13 require * Notification permissions be approved. @@ -131,7 +139,7 @@ class PushNotificationManager( const val GROUP_ACTIVITY_NOTIFICATION_KEY = "groupActivity" const val CONTENT_RELEASE_NOTIFICATION_KEY = "contentRelease" const val G1G1_PROMO_KEY = "g1g1Promo" - private const val DEVICE_TOKEN_PREFERENCE_KEY = "device-token-preference" + const val DEVICE_TOKEN_PREFERENCE_KEY = "device-token-preference" fun displayNotification( remoteMessage: RemoteMessage, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index 3186f812c..ba327967d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -486,7 +486,10 @@ class AccountPreferenceFragment : userRepository.deleteAccount(password) dialog?.dismiss() accountDialog.dismiss() - context?.let { HabiticaBaseApplication.logout(it) } + context?.let { + val user = userViewModel.user.value + HabiticaBaseApplication.logout(it, user) + } activity?.finish() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index 543eb2cbf..49ac98764 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -251,7 +251,7 @@ class PreferencesFragment : val dialog = HabiticaAlertDialog(context) dialog.setTitle(R.string.are_you_sure) dialog.addButton(R.string.logout, true) { _, _ -> - HabiticaBaseApplication.logout(context) + HabiticaBaseApplication.logout(context, user) activity?.finish() } dialog.addCancelButton() From ab8015b4877ee1a166f40533e87e378a1d7eb34f Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 19:18:35 -0500 Subject: [PATCH 33/57] Update InviteButton to handle InviteResponse list - Treat sendInvites() result as List - Branch on InviteResponse.UserInvite vs EmailInvite - Use isNullOrEmpty() to drive success/failure state transitions - InviteResponseDeserializer added - Retain loading > success/failure > content state flow with delays --- .../habitica/api/GSonFactoryCreator.java | 3 ++ .../models/invitations/InviteResponse.kt | 12 ++++++- .../social/party/PartyInviteFragment.kt | 21 +++++++++--- .../utils/InviteResponseDeserializer.kt | 32 +++++++++++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/utils/InviteResponseDeserializer.kt diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java index 2271f8a9d..fa8433d57 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/GSonFactoryCreator.java @@ -15,6 +15,7 @@ import com.habitrpg.android.habitica.models.inventory.Equipment; import com.habitrpg.android.habitica.models.inventory.Quest; import com.habitrpg.android.habitica.models.inventory.QuestCollect; import com.habitrpg.android.habitica.models.inventory.QuestDropItem; +import com.habitrpg.android.habitica.models.invitations.InviteResponse; import com.habitrpg.android.habitica.models.members.Member; import com.habitrpg.android.habitica.models.social.Challenge; import com.habitrpg.android.habitica.models.social.ChatMessage; @@ -43,6 +44,7 @@ import com.habitrpg.android.habitica.utils.FAQArticleListDeserilializer; import com.habitrpg.android.habitica.utils.FeedResponseDeserializer; import com.habitrpg.android.habitica.utils.FindUsernameResultDeserializer; import com.habitrpg.android.habitica.utils.GroupSerialization; +import com.habitrpg.android.habitica.utils.InviteResponseDeserializer; import com.habitrpg.android.habitica.utils.MemberSerialization; import com.habitrpg.android.habitica.utils.NotificationDeserializer; import com.habitrpg.android.habitica.utils.OwnedItemListDeserializer; @@ -136,6 +138,7 @@ public class GSonFactoryCreator { .registerTypeAdapter(assignedDetailsListType, new AssignedDetailsDeserializer()) .registerTypeAdapter(Quest.class, new QuestDeserializer()) .registerTypeAdapter(Member.class, new MemberSerialization()) + .registerTypeAdapter(InviteResponse.class, new InviteResponseDeserializer()) .registerTypeAdapter(WorldState.class, new WorldStateSerialization()) .registerTypeAdapter(FindUsernameResult.class, new FindUsernameResultDeserializer()) .registerTypeAdapter(Notification.class, new NotificationDeserializer()) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/InviteResponse.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/InviteResponse.kt index 9e5d5d7bf..b30609eec 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/InviteResponse.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/invitations/InviteResponse.kt @@ -1,3 +1,13 @@ package com.habitrpg.android.habitica.models.invitations -class InviteResponse +sealed class InviteResponse { + data class UserInvite( + val id: String, + val name: String, + val inviter: String + ) : InviteResponse() + + data class EmailInvite( + val email: String + ) : InviteResponse() +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt index 97a19c7cc..e0b38bce0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt @@ -1,6 +1,7 @@ package com.habitrpg.android.habitica.ui.fragments.social.party import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -72,6 +73,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import java.util.UUID import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -279,14 +281,25 @@ fun PartyInviteView( inviteButtonState = LoadingButtonState.CONTENT } }) { - val responses = viewModel.sendInvites() - if ((responses?.size ?: 0) > 0) { + val responses: List? = viewModel.sendInvites() + if (!responses.isNullOrEmpty()) { inviteButtonState = LoadingButtonState.SUCCESS - delay(2.toDuration(DurationUnit.SECONDS)) + // we are not differentiating between user and email invites here, however in the event we do - we can handle it + responses.forEach { resp -> + when (resp) { + is InviteResponse.UserInvite -> { + // UserInvite is a UUID + } + is InviteResponse.EmailInvite -> { + // EmailInvite is an email address + } + } + } + delay(2.seconds) dismiss() } else { inviteButtonState = LoadingButtonState.FAILED - delay(2.toDuration(DurationUnit.SECONDS)) + delay(2.seconds) inviteButtonState = LoadingButtonState.CONTENT } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/InviteResponseDeserializer.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/InviteResponseDeserializer.kt new file mode 100644 index 000000000..57c72f004 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/InviteResponseDeserializer.kt @@ -0,0 +1,32 @@ +package com.habitrpg.android.habitica.utils + +import com.google.gson.JsonDeserializer +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.habitrpg.android.habitica.models.invitations.InviteResponse +import io.realm.RealmList +import java.lang.reflect.Type + +class InviteResponseDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): InviteResponse { + return when { + json.isJsonPrimitive && json.asJsonPrimitive.isString -> { + InviteResponse.EmailInvite(json.asString) + } + json.isJsonObject -> { + val obj = json.asJsonObject + InviteResponse.UserInvite( + id = obj["id"].asString, + name = obj["name"].asString, + inviter = obj["inviter"].asString + ) + } + else -> throw JsonParseException("Unexpected InviteResponse: $json") + } + } +} \ No newline at end of file From 028a9ecc12cc2cd3f080a6ac4777e0290bed5145 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 4 Jun 2025 12:00:08 -0500 Subject: [PATCH 34/57] Implement change password bottom sheet Replaces the old change password dialog with a bottom sheet using compose. --- Habitica/res/values/strings.xml | 7 +- .../preferences/AccountPreferenceFragment.kt | 37 +-- .../preferences/ChangePasswordBottomSheet.kt | 68 +++++ .../habitica/ui/views/ChangePasswordScreen.kt | 270 ++++++++++++++++++ 4 files changed, 347 insertions(+), 35 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 98ee7ac2a..6ce390d7f 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -752,6 +752,7 @@ Login names are now unique usernames that will be visible beside your display name and used for invitations, chat @mentions, and messaging. If you’d like to learn more about this change, visit our wiki. Usernames should conform to our Terms of Service and Community Guidelines. If you didn’t previously set a login name, your username was auto-generated. + You agree to our Terms of Service and have read our Privacy Policy. Are you sure you want to confirm your current username? Confirming your username will make it public for invitations, @mentions and messaging. You can change your username from settings at any time. Cancel @@ -1582,10 +1583,8 @@ Error getting credentials for authentication. Received invalid credentials. Unknown error during authentication. - Start chatting! - Remember to be friendly and follow the Community Guidelines. - - + Passwords must be 8 characters or more. Changing your password will log you out of any other devices and third-party tools you may use. + Confirm new Password You diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index ba327967d..bc5c6eb36 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -271,46 +271,21 @@ class AccountPreferenceFragment : } private fun showChangePasswordDialog() { - val inflater = context?.layoutInflater - val view = inflater?.inflate(R.layout.dialog_edittext_change_pw, null) - val oldPasswordEditText = - view?.findViewById(R.id.old_password_edit_text) - val passwordEditText = view?.findViewById(R.id.new_password_edit_text) - passwordEditText?.validator = { (it?.length ?: 0) >= 8 } - passwordEditText?.errorText = getString(R.string.password_too_short, 8) - val passwordRepeatEditText = - view?.findViewById(R.id.new_password_repeat_edit_text) - passwordRepeatEditText?.validator = { it == passwordEditText?.text } - passwordRepeatEditText?.errorText = getString(R.string.password_not_matching) - context?.let { context -> - val dialog = HabiticaAlertDialog(context) - dialog.setTitle(R.string.change_password) - dialog.addButton(R.string.change, true, false, false) { d, _ -> + ChangePasswordBottomSheet{ oldPassword, newPassword -> + lifecycleScope.launchCatching { KeyboardUtil.dismissKeyboard(activity) - passwordEditText?.showErrorIfNecessary() - passwordRepeatEditText?.showErrorIfNecessary() - if (passwordEditText?.isValid != true || passwordRepeatEditText?.isValid != true) return@addButton lifecycleScope.launchCatching { val response = userRepository.updatePassword( - oldPasswordEditText?.text ?: "", - passwordEditText.text ?: "", - passwordRepeatEditText.text ?: "", + oldPassword, + newPassword, + newPassword, ) response?.apiToken?.let { viewModel.saveTokens(it, user?.id ?: "") } - (activity as? SnackbarActivity)?.showSnackbar( - content = context.getString(R.string.password_changed), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS, - ) } - d.dismiss() } - dialog.addCancelButton() - dialog.setAdditionalContentView(view) - dialog.setAdditionalContentSidePadding(12) - dialog.show() - } + }.show(childFragmentManager, ChangePasswordBottomSheet.TAG) } private fun showAddPasswordDialog(showEmail: Boolean) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt new file mode 100644 index 000000000..95aa258e5 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt @@ -0,0 +1,68 @@ +package com.habitrpg.android.habitica.ui.fragments.preferences + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.ComposeView +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.habitrpg.android.habitica.ui.views.ChangePasswordScreen +import com.habitrpg.common.habitica.theme.HabiticaTheme +import androidx.compose.runtime.setValue +import com.habitrpg.android.habitica.R + +class ChangePasswordBottomSheet(val onForgotPassword: () -> Unit = {}, val onPasswordChanged: (oldPassword: String, newPassword: String) -> Unit = { _, _ -> }) : BottomSheetDialogFragment() { + override fun onStart() { + super.onStart() + dialog?.let { dlg -> + val bottomSheet = dlg.findViewById(com.google.android.material.R.id.design_bottom_sheet) + bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT + val behavior = com.google.android.material.bottomsheet.BottomSheetBehavior.from(bottomSheet!!) + behavior.state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED + behavior.isDraggable = false + behavior.skipCollapsed = true + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setContent { + HabiticaTheme { + var visible by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { visible = true } + AnimatedVisibility( + visible = visible, + enter = fadeIn() + ) { + ChangePasswordScreen( + onBack = { dismiss() }, + onSave = { oldPassword, newPassword -> + onPasswordChanged(oldPassword, newPassword) + dismiss() + }, + onForgotPassword = { + onForgotPassword() + dismiss() + } + ) + } + } + } + } + } + + + companion object { + const val TAG = "ChangePasswordFragment" + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt new file mode 100644 index 000000000..2c8803f42 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -0,0 +1,270 @@ +package com.habitrpg.android.habitica.ui.views + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.habitrpg.android.habitica.R +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.TextButton +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import com.habitrpg.android.habitica.ui.theme.colors +import com.habitrpg.common.habitica.theme.HabiticaTheme +import androidx.compose.material3.IconButton +import androidx.compose.material3.Icon +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.colorResource + + + + + + + +@Composable +fun ChangePasswordScreen( + onBack: () -> Unit, + onSave: (oldPassword: String, newPassword: String) -> Unit, + onForgotPassword: () -> Unit +) { + val colors = HabiticaTheme.colors + val backgroundColor = colors.windowBackground + val fieldColor = colors.contentBackground + val labelColor = colors.textSecondary + val buttonColor = colors.tintedUiMain + val textColor = colors.textPrimary + + var oldPassword by remember { mutableStateOf("") } + var newPassword by remember { mutableStateOf("") } + var confirmPassword by remember { mutableStateOf("") } + var attemptedSave by remember { mutableStateOf(false) } + + val passwordValid = newPassword.length >= 8 + val passwordsMatch = newPassword == confirmPassword && newPassword.isNotEmpty() + val canSave = passwordValid && passwordsMatch && oldPassword.isNotBlank() + + Surface( + modifier = Modifier.fillMaxSize(), + color = backgroundColor + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + ) { + Spacer(modifier = Modifier.height(16.dp)) + Row( + Modifier + .fillMaxWidth() + .padding(top = 4.dp, bottom = 0.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = onBack, modifier = Modifier.size(40.dp)) { + Icon( + painterResource(id = R.drawable.arrow_back), + contentDescription = stringResource(R.string.action_back), + tint = textColor + ) + } + } + + Text( + text = stringResource(R.string.change_password), + fontWeight = FontWeight.Bold, + fontSize = 28.sp, + color = textColor, + modifier = Modifier + .align(Alignment.Start) + .padding(top = 4.dp) + ) + + Text( + text = stringResource(R.string.password_change_info), + color = labelColor, + fontSize = 14.sp, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 6.dp, bottom = 22.dp) + ) + + PasswordField( + label = stringResource(R.string.old_password), + value = oldPassword, + onValueChange = { oldPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + ) + Spacer(modifier = Modifier.height(14.dp)) + PasswordField( + label = stringResource(R.string.new_password), + value = newPassword, + onValueChange = { newPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + isError = attemptedSave && !passwordValid, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + ) + if (attemptedSave && !passwordValid) { + Text( + text = stringResource(R.string.password_too_short), + color = Color.Red, + fontSize = 13.sp, + modifier = Modifier.padding(start = 8.dp, top = 4.dp) + ) + } + Spacer(modifier = Modifier.height(14.dp)) + PasswordField( + label = stringResource(R.string.confirm_new_password), + value = confirmPassword, + onValueChange = { confirmPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + isError = attemptedSave && !passwordsMatch, + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + ) + if (attemptedSave && !passwordsMatch) { + Text( + text = stringResource(R.string.password_not_matching), + color = Color.Red, + fontSize = 13.sp, + modifier = Modifier.padding(start = 8.dp, top = 4.dp) + ) + } + Spacer(modifier = Modifier.height(32.dp)) + + Button( + onClick = { + attemptedSave = true + if (canSave) onSave(oldPassword, newPassword) + }, + enabled = canSave, + colors = ButtonDefaults.buttonColors( + containerColor = buttonColor, + disabledContainerColor = buttonColor.copy(alpha = 0.3f) + ), + shape = RoundedCornerShape(14.dp), + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + ) { + Text( + text = stringResource(R.string.change_password), + color = Color.White, + fontSize = 17.sp, + fontWeight = FontWeight.Bold, + ) + } + Spacer(modifier = Modifier.height(18.dp)) + + TextButton( + onClick = onForgotPassword, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text( + text = stringResource(R.string.forgot_pw_btn), + color = buttonColor, + fontWeight = FontWeight.Medium, + fontSize = 16.sp + ) + } + } + } +} + +@Composable +fun PasswordField( + label: String, + value: String, + onValueChange: (String) -> Unit, + fieldColor: Color, + labelColor: Color, + textColor: Color, + isError: Boolean = false, + modifier: Modifier = Modifier +) { + val dividerColor = if (value.isNotBlank()) colorResource(id = R.color.purple400_purple500) else colorResource(id = R.color.gray_400) + + Column(modifier = modifier) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { + Text(label, color = labelColor, fontSize = 17.sp) + }, + singleLine = true, + modifier = Modifier.fillMaxWidth().height(56.dp), + shape = RoundedCornerShape(10.dp), + isError = isError, + visualTransformation = PasswordVisualTransformation(), + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = fieldColor, + focusedContainerColor = fieldColor, + unfocusedBorderColor = Color.Transparent, + focusedBorderColor = Color.Transparent, + cursorColor = Color(0xFF9C8DF6), + unfocusedTextColor = textColor, + focusedTextColor = textColor + ) + ) + HorizontalDivider( + color = dividerColor, + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 2.dp) + ) + } +} + + +@Preview(showBackground = true, widthDp = 327, heightDp = 704, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun ChangePasswordScreenPreview() { + HabiticaTheme { + ChangePasswordScreen( + onBack = {}, + onSave = { _, _ -> }, + onForgotPassword = {} + ) + } +} + + + + + From 3f5d6ba25c1fcf09952bbe236f58f5848c217169 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 6 Jun 2025 10:07:00 -0500 Subject: [PATCH 35/57] fixes to change password screen UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Moved the top spacer into the Column’s padding(top = 16.dp) • Removed the unnecessary Row—placed the IconButton directly with bottom padding • Dropped all fixed .height(...) modifiers so fields and the button scale with system font size --- .../habitica/ui/views/ChangePasswordScreen.kt | 113 ++++++++---------- 1 file changed, 47 insertions(+), 66 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt index 2c8803f42..2fd15c1ad 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -1,50 +1,43 @@ package com.habitrpg.android.habitica.ui.views import android.content.res.Configuration -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.unit.dp -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp -import com.habitrpg.android.habitica.R -import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.theme.colors import com.habitrpg.common.habitica.theme.HabiticaTheme -import androidx.compose.material3.IconButton -import androidx.compose.material3.Icon -import androidx.compose.foundation.layout.size -import androidx.compose.material3.HorizontalDivider -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.colorResource - - - - - - @Composable fun ChangePasswordScreen( @@ -75,22 +68,19 @@ fun ChangePasswordScreen( Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp), + .padding(start = 16.dp, end = 16.dp, top = 16.dp) ) { - Spacer(modifier = Modifier.height(16.dp)) - Row( - Modifier - .fillMaxWidth() - .padding(top = 4.dp, bottom = 0.dp), - verticalAlignment = Alignment.CenterVertically + IconButton( + onClick = onBack, + modifier = Modifier + .size(40.dp) + .padding(bottom = 12.dp) ) { - IconButton(onClick = onBack, modifier = Modifier.size(40.dp)) { - Icon( - painterResource(id = R.drawable.arrow_back), - contentDescription = stringResource(R.string.action_back), - tint = textColor - ) - } + Icon( + painterResource(id = R.drawable.arrow_back), + contentDescription = stringResource(R.string.action_back), + tint = textColor + ) } Text( @@ -100,7 +90,7 @@ fun ChangePasswordScreen( color = textColor, modifier = Modifier .align(Alignment.Start) - .padding(top = 4.dp) + .padding(bottom = 8.dp) ) Text( @@ -109,7 +99,7 @@ fun ChangePasswordScreen( fontSize = 14.sp, modifier = Modifier .align(Alignment.CenterHorizontally) - .padding(top = 6.dp, bottom = 22.dp) + .padding(bottom = 22.dp) ) PasswordField( @@ -119,11 +109,10 @@ fun ChangePasswordScreen( fieldColor = fieldColor, labelColor = labelColor, textColor = textColor, - modifier = Modifier - .fillMaxWidth() - .height(56.dp) + modifier = Modifier.fillMaxWidth() ) - Spacer(modifier = Modifier.height(14.dp)) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + PasswordField( label = stringResource(R.string.new_password), value = newPassword, @@ -132,9 +121,7 @@ fun ChangePasswordScreen( labelColor = labelColor, textColor = textColor, isError = attemptedSave && !passwordValid, - modifier = Modifier - .fillMaxWidth() - .height(56.dp) + modifier = Modifier.fillMaxWidth() ) if (attemptedSave && !passwordValid) { Text( @@ -144,7 +131,8 @@ fun ChangePasswordScreen( modifier = Modifier.padding(start = 8.dp, top = 4.dp) ) } - Spacer(modifier = Modifier.height(14.dp)) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + PasswordField( label = stringResource(R.string.confirm_new_password), value = confirmPassword, @@ -153,9 +141,7 @@ fun ChangePasswordScreen( labelColor = labelColor, textColor = textColor, isError = attemptedSave && !passwordsMatch, - modifier = Modifier - .fillMaxWidth() - .height(56.dp) + modifier = Modifier.fillMaxWidth() ) if (attemptedSave && !passwordsMatch) { Text( @@ -165,7 +151,7 @@ fun ChangePasswordScreen( modifier = Modifier.padding(start = 8.dp, top = 4.dp) ) } - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.padding(top = 24.dp)) Button( onClick = { @@ -180,7 +166,7 @@ fun ChangePasswordScreen( shape = RoundedCornerShape(14.dp), modifier = Modifier .fillMaxWidth() - .height(52.dp) + ) { Text( text = stringResource(R.string.change_password), @@ -217,6 +203,7 @@ fun PasswordField( isError: Boolean = false, modifier: Modifier = Modifier ) { + val dividerColor = if (value.isNotBlank()) colorResource(id = R.color.purple400_purple500) else colorResource(id = R.color.gray_400) Column(modifier = modifier) { @@ -227,7 +214,7 @@ fun PasswordField( Text(label, color = labelColor, fontSize = 17.sp) }, singleLine = true, - modifier = Modifier.fillMaxWidth().height(56.dp), + modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(10.dp), isError = isError, visualTransformation = PasswordVisualTransformation(), @@ -251,7 +238,6 @@ fun PasswordField( } } - @Preview(showBackground = true, widthDp = 327, heightDp = 704, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun ChangePasswordScreenPreview() { @@ -263,8 +249,3 @@ fun ChangePasswordScreenPreview() { ) } } - - - - - From 313d251fc7e50319e76e96ab1c33ca3a8d002c8a Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 13:32:01 -0500 Subject: [PATCH 36/57] Add onForgotPassword callback to ChangePasswordBottomSheet - Add onForgotPassword callback to ChangePasswordBottomSheet - Button tweak on changepasswordscreen --- .../preferences/AccountPreferenceFragment.kt | 63 +++++++++++++++---- .../habitica/ui/views/ChangePasswordScreen.kt | 13 ++-- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index bc5c6eb36..082d38fa1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -8,7 +8,10 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Build import android.os.Bundle +import android.text.InputType import android.view.View +import android.widget.EditText +import android.widget.LinearLayout import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService @@ -23,6 +26,7 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.extensions.addCloseButton +import com.habitrpg.android.habitica.extensions.addOkButton import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed @@ -271,21 +275,58 @@ class AccountPreferenceFragment : } private fun showChangePasswordDialog() { - ChangePasswordBottomSheet{ oldPassword, newPassword -> - lifecycleScope.launchCatching { - KeyboardUtil.dismissKeyboard(activity) + ChangePasswordBottomSheet( + onForgotPassword = { showForgotPasswordDialog() }, + onPasswordChanged = { oldPassword, newPassword -> lifecycleScope.launchCatching { - val response = userRepository.updatePassword( - oldPassword, - newPassword, - newPassword, - ) - response?.apiToken?.let { - viewModel.saveTokens(it, user?.id ?: "") + KeyboardUtil.dismissKeyboard(activity) + lifecycleScope.launchCatching { + val response = userRepository.updatePassword( + oldPassword, + newPassword, + newPassword, + ) + response?.apiToken?.let { + viewModel.saveTokens(it, user?.id ?: "") + } } } } - }.show(childFragmentManager, ChangePasswordBottomSheet.TAG) + ).show(childFragmentManager, ChangePasswordBottomSheet.TAG) + + } + + private fun showForgotPasswordDialog() { + val input = EditText(requireContext()) + input.setAutofillHints(EditText.AUTOFILL_HINT_EMAIL_ADDRESS) + input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + input.hint = getString(R.string.forgot_password_hint_example) + input.textSize = 16f + val lp = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + input.layoutParams = lp + val alertDialog = HabiticaAlertDialog(requireContext()) + alertDialog.setTitle(R.string.forgot_password_title) + alertDialog.setMessage(R.string.forgot_password_description) + alertDialog.setAdditionalContentView(input) + alertDialog.addButton(R.string.send, true) { _, _ -> + lifecycleScope.launchCatching { + userRepository.sendPasswordResetEmail(input.text.toString()) + showPasswordEmailConfirmation() + } + } + alertDialog.addCancelButton() + alertDialog.show() + } + + private fun showPasswordEmailConfirmation() { + val alert = HabiticaAlertDialog(requireContext()) + alert.setMessage(R.string.forgot_password_confirmation) + alert.addOkButton() + alert.show() } private fun showAddPasswordDialog(showEmail: Boolean) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt index 2fd15c1ad..25cab6a57 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -160,21 +160,22 @@ fun ChangePasswordScreen( }, enabled = canSave, colors = ButtonDefaults.buttonColors( - containerColor = buttonColor, - disabledContainerColor = buttonColor.copy(alpha = 0.3f) + containerColor = colorResource(id = R.color.purple400_purple500), + disabledContainerColor = colorResource(id = R.color.purple400_purple500).copy(alpha = 0.3f) ), - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(12.dp), modifier = Modifier .fillMaxWidth() - + .height(60.dp) ) { Text( text = stringResource(R.string.change_password), color = Color.White, - fontSize = 17.sp, - fontWeight = FontWeight.Bold, + fontSize = 16.sp, + fontWeight = FontWeight.Medium ) } + Spacer(modifier = Modifier.height(18.dp)) TextButton( From ed618d6b0eba98c3b0be90dd4333cbc4615bfa85 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 13:30:48 -0500 Subject: [PATCH 37/57] UI Tweaks to change password screen --- .../habitica/ui/views/ChangePasswordScreen.kt | 243 ++++++++++-------- 1 file changed, 129 insertions(+), 114 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt index 25cab6a57..43582026b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -1,6 +1,7 @@ package com.habitrpg.android.habitica.ui.views import android.content.res.Configuration +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -26,6 +27,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource @@ -47,7 +49,7 @@ fun ChangePasswordScreen( ) { val colors = HabiticaTheme.colors val backgroundColor = colors.windowBackground - val fieldColor = colors.contentBackground + val fieldColor = colorResource(id = R.color.gray700_gray10) val labelColor = colors.textSecondary val buttonColor = colors.tintedUiMain val textColor = colors.textPrimary @@ -65,16 +67,13 @@ fun ChangePasswordScreen( modifier = Modifier.fillMaxSize(), color = backgroundColor ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(start = 16.dp, end = 16.dp, top = 16.dp) - ) { + Box(modifier = Modifier.fillMaxSize()) { IconButton( onClick = onBack, modifier = Modifier - .size(40.dp) - .padding(bottom = 12.dp) + .size(48.dp) + .align(Alignment.TopStart) + .padding(16.dp) ) { Icon( painterResource(id = R.drawable.arrow_back), @@ -82,117 +81,127 @@ fun ChangePasswordScreen( tint = textColor ) } - - Text( - text = stringResource(R.string.change_password), - fontWeight = FontWeight.Bold, - fontSize = 28.sp, - color = textColor, + Column( modifier = Modifier - .align(Alignment.Start) - .padding(bottom = 8.dp) - ) - - Text( - text = stringResource(R.string.password_change_info), - color = labelColor, - fontSize = 14.sp, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 22.dp) - ) - - PasswordField( - label = stringResource(R.string.old_password), - value = oldPassword, - onValueChange = { oldPassword = it }, - fieldColor = fieldColor, - labelColor = labelColor, - textColor = textColor, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.padding(vertical = 8.dp)) - - PasswordField( - label = stringResource(R.string.new_password), - value = newPassword, - onValueChange = { newPassword = it }, - fieldColor = fieldColor, - labelColor = labelColor, - textColor = textColor, - isError = attemptedSave && !passwordValid, - modifier = Modifier.fillMaxWidth() - ) - if (attemptedSave && !passwordValid) { - Text( - text = stringResource(R.string.password_too_short), - color = Color.Red, - fontSize = 13.sp, - modifier = Modifier.padding(start = 8.dp, top = 4.dp) - ) - } - Spacer(modifier = Modifier.padding(vertical = 8.dp)) - - PasswordField( - label = stringResource(R.string.confirm_new_password), - value = confirmPassword, - onValueChange = { confirmPassword = it }, - fieldColor = fieldColor, - labelColor = labelColor, - textColor = textColor, - isError = attemptedSave && !passwordsMatch, - modifier = Modifier.fillMaxWidth() - ) - if (attemptedSave && !passwordsMatch) { - Text( - text = stringResource(R.string.password_not_matching), - color = Color.Red, - fontSize = 13.sp, - modifier = Modifier.padding(start = 8.dp, top = 4.dp) - ) - } - Spacer(modifier = Modifier.padding(top = 24.dp)) - - Button( - onClick = { - attemptedSave = true - if (canSave) onSave(oldPassword, newPassword) - }, - enabled = canSave, - colors = ButtonDefaults.buttonColors( - containerColor = colorResource(id = R.color.purple400_purple500), - disabledContainerColor = colorResource(id = R.color.purple400_purple500).copy(alpha = 0.3f) - ), - shape = RoundedCornerShape(12.dp), - modifier = Modifier - .fillMaxWidth() - .height(60.dp) + .fillMaxSize() + .padding( + start = 16.dp, + end = 16.dp, + top = 16.dp + 40.dp + 8.dp + ) ) { Text( text = stringResource(R.string.change_password), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Bold, + fontSize = 28.sp, + color = textColor, + modifier = Modifier + .align(Alignment.Start) + .padding(bottom = 8.dp) ) - } - Spacer(modifier = Modifier.height(18.dp)) - - TextButton( - onClick = onForgotPassword, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) { Text( - text = stringResource(R.string.forgot_pw_btn), - color = buttonColor, - fontWeight = FontWeight.Medium, - fontSize = 16.sp + text = stringResource(R.string.password_change_info), + color = labelColor, + fontSize = 14.sp, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(bottom = 22.dp) ) + + PasswordField( + label = stringResource(R.string.old_password), + value = oldPassword, + onValueChange = { oldPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + + PasswordField( + label = stringResource(R.string.new_password), + value = newPassword, + onValueChange = { newPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + isError = attemptedSave && !passwordValid, + modifier = Modifier.fillMaxWidth() + ) + if (attemptedSave && !passwordValid) { + Text( + text = stringResource(R.string.password_too_short), + color = Color.Red, + fontSize = 13.sp, + modifier = Modifier.padding(start = 8.dp, top = 4.dp) + ) + } + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + + PasswordField( + label = stringResource(R.string.confirm_new_password), + value = confirmPassword, + onValueChange = { confirmPassword = it }, + fieldColor = fieldColor, + labelColor = labelColor, + textColor = textColor, + isError = attemptedSave && !passwordsMatch, + modifier = Modifier.fillMaxWidth() + ) + if (attemptedSave && !passwordsMatch) { + Text( + text = stringResource(R.string.password_not_matching), + color = Color.Red, + fontSize = 13.sp, + modifier = Modifier.padding(start = 8.dp, top = 4.dp) + ) + } + Spacer(modifier = Modifier.padding(top = 24.dp)) + + Button( + onClick = { + attemptedSave = true + if (canSave) onSave(oldPassword, newPassword) + }, + enabled = canSave, + colors = ButtonDefaults.buttonColors( + containerColor = colorResource(id = R.color.purple400_purple500), + disabledContainerColor = colorResource(id = R.color.purple400_purple500).copy(alpha = 0.3f) + ), + shape = RoundedCornerShape(12.dp), + modifier = Modifier + .fillMaxWidth() + .height(60.dp) + ) { + Text( + text = stringResource(R.string.change_password), + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(18.dp)) + + TextButton( + onClick = onForgotPassword, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text( + text = stringResource(R.string.forgot_pw_btn), + color = buttonColor, + fontWeight = FontWeight.Medium, + fontSize = 16.sp + ) + } } } } } + @Composable fun PasswordField( label: String, @@ -204,19 +213,23 @@ fun PasswordField( isError: Boolean = false, modifier: Modifier = Modifier ) { + val dividerColor = if (value.isNotBlank()) + colorResource(id = R.color.purple400_purple500) + else + colorResource(id = R.color.gray_400) - val dividerColor = if (value.isNotBlank()) colorResource(id = R.color.purple400_purple500) else colorResource(id = R.color.gray_400) - - Column(modifier = modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .clipToBounds() + ) { OutlinedTextField( value = value, onValueChange = onValueChange, - label = { - Text(label, color = labelColor, fontSize = 17.sp) - }, + label = { Text(label, color = labelColor, fontSize = 17.sp) }, singleLine = true, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(10.dp), + shape = RoundedCornerShape(6.dp), isError = isError, visualTransformation = PasswordVisualTransformation(), colors = OutlinedTextFieldDefaults.colors( @@ -229,12 +242,14 @@ fun PasswordField( focusedTextColor = textColor ) ) + HorizontalDivider( color = dividerColor, thickness = 1.dp, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 2.dp) + .padding(horizontal = 4.dp) + .align(Alignment.BottomStart) ) } } From 0a5d3f893f21cd573d5f3805a12176bc14c462e2 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 3 Jun 2025 11:13:05 -0500 Subject: [PATCH 38/57] Presents API token in a bottom sheet Replaces the simple copy functionality for the API token with a bottom sheet that displays the token and provides information about its security implications. This change helps with user awareness regarding the sensitive nature of the API token and promotes responsible handling of this credential. --- Habitica/res/values/strings.xml | 16 +- .../preferences/AccountPreferenceFragment.kt | 11 +- .../ApiTokenBottomSheetFragment.kt | 30 ++++ .../habitica/ui/views/ApiTokenBottomSheet.kt | 144 ++++++++++++++++++ 4 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 6ce390d7f..2887b3393 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1231,7 +1231,7 @@ Connect Disconnect Add - Copy Token. Be careful, this is a password! + Password token for developers and third-party tools. Added %s authentication Copied %s to clipboard Disconnected %s @@ -1583,8 +1583,18 @@ Error getting credentials for authentication. Received invalid credentials. Unknown error during authentication. - Passwords must be 8 characters or more. Changing your password will log you out of any other devices and third-party tools you may use. - Confirm new Password + API Token + Your API Token is like a password + + Do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github. + + If you need a new API Token + + You can change your password to reset it. Once it is reset, you will need to log back in to any other devices you use Habitica on and provide the new API Token to third-party tools you may use. + + Copy Token + + You diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index 082d38fa1..bcc8e629b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -32,6 +32,7 @@ import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel +import com.habitrpg.android.habitica.ui.views.ApiTokenBottomSheet import com.habitrpg.android.habitica.ui.views.ExtraLabelPreference import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.SnackbarActivity @@ -183,7 +184,15 @@ class AccountPreferenceFragment : } "APIToken" -> { - copyValue(getString(R.string.SP_APIToken_title), hostConfig.apiKey) + ApiTokenBottomSheetFragment( + apiToken = hostConfig.apiKey, { copiedToken -> + (activity as? SnackbarActivity)?.showSnackbar( + content = getString(R.string.copied_to_clipboard, copiedToken), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS, + ) + }, + + ).show(childFragmentManager, ApiTokenBottomSheetFragment.TAG) return true } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt new file mode 100644 index 000000000..16b0325e7 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt @@ -0,0 +1,30 @@ +package com.habitrpg.android.habitica.ui.fragments.preferences + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.habitrpg.android.habitica.ui.views.ApiTokenBottomSheet + +class ApiTokenBottomSheetFragment( + private val apiToken: String, + private val onCopyToken: (String) -> Unit, +) : BottomSheetDialogFragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setContent { + ApiTokenBottomSheet(apiToken = apiToken, onCopyToken = onCopyToken) + } + } + } + + companion object { + const val TAG = "ApiTokenBottomSheet" + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt new file mode 100644 index 000000000..9d4fafd4d --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -0,0 +1,144 @@ +package com.habitrpg.android.habitica.ui.views + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.* +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.* +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.ui.theme.colors +import com.habitrpg.common.habitica.theme.HabiticaTheme + +@Composable +fun ApiTokenBottomSheet( + apiToken: String, + onCopyToken: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val colors = HabiticaTheme.colors + + val background = colors.windowBackground + val fieldBackground = colors.contentBackground + val mainText = colors.textPrimary + val secondaryText = colors.textSecondary + val buttonBg = colors.tintedUiMain + val buttonText = colors.tintedUiDetails + val lockIconColor = colors.textSecondary + + Box( + modifier + .fillMaxWidth() + .background(background, RoundedCornerShape(22.dp)) + .padding(horizontal = 20.dp, vertical = 14.dp) + ) { + Column(Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.height(8.dp)) + Box( + Modifier + .size(width = 40.dp, height = 6.dp) + .align(Alignment.CenterHorizontally) + .clip(RoundedCornerShape(50)) + .background(colors.contentBackgroundOffset) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + stringResource(id = R.string.api_token_title), + color = mainText, + fontSize = 21.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Spacer(modifier = Modifier.height(22.dp)) + Text( + stringResource(id = R.string.api_token_is_password), + color = mainText, + fontSize = 17.sp, + fontWeight = FontWeight.Medium, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + stringResource(id = R.string.api_token_is_password_info), + color = secondaryText, + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + lineHeight = 20.sp, + ) + Spacer(modifier = Modifier.height(22.dp)) + Text( + stringResource(id = R.string.api_token_reset_title), + color = mainText, + fontSize = 17.sp, + fontWeight = FontWeight.Medium, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + stringResource(id = R.string.api_token_reset_info), + color = secondaryText, + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + lineHeight = 20.sp, + ) + Spacer(modifier = Modifier.height(22.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background(fieldBackground) + .padding(horizontal = 10.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Lock, + contentDescription = null, + tint = lockIconColor, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + apiToken, + color = mainText, + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + modifier = Modifier.weight(1f), + maxLines = 1 + ) + } + Spacer(modifier = Modifier.height(22.dp)) + Button( + onClick = { onCopyToken(apiToken) }, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors(containerColor = buttonBg), + modifier = Modifier + .fillMaxWidth() + .height(54.dp) + ) { + Text( + stringResource(id = R.string.copy_token), + color = buttonText, + fontSize = 19.sp, + fontWeight = FontWeight.SemiBold, + ) + } + Spacer(modifier = Modifier.height(8.dp)) + } + } +} + +@Preview(showBackground = true, widthDp = 380, heightDp = 550) +@Composable +fun ApiTokenBottomSheetPreview() { + HabiticaTheme { + ApiTokenBottomSheet( + apiToken = "sample_api_token_1234567890", + onCopyToken = {} + ) + } +} From 0944c64e8b4fcec588ea1ba0585d64bf75ab79b8 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 3 Jun 2025 11:40:44 -0500 Subject: [PATCH 39/57] Updates API token bottom sheet UI Changes the background color of the copy token button to yellow. Dismisses the bottom sheet after copying the token. --- .../preferences/AccountPreferenceFragment.kt | 9 +-------- .../preferences/ApiTokenBottomSheetFragment.kt | 14 +++++++++++--- .../habitica/ui/views/ApiTokenBottomSheet.kt | 3 ++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index bcc8e629b..d4c66e6ff 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -185,14 +185,7 @@ class AccountPreferenceFragment : "APIToken" -> { ApiTokenBottomSheetFragment( - apiToken = hostConfig.apiKey, { copiedToken -> - (activity as? SnackbarActivity)?.showSnackbar( - content = getString(R.string.copied_to_clipboard, copiedToken), - displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS, - ) - }, - - ).show(childFragmentManager, ApiTokenBottomSheetFragment.TAG) + apiToken = hostConfig.apiKey).show(childFragmentManager, ApiTokenBottomSheetFragment.TAG) return true } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt index 16b0325e7..15a2bd82e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt @@ -6,11 +6,13 @@ import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.views.ApiTokenBottomSheet +import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import com.habitrpg.android.habitica.ui.views.SnackbarActivity class ApiTokenBottomSheetFragment( - private val apiToken: String, - private val onCopyToken: (String) -> Unit, + private val apiToken: String ) : BottomSheetDialogFragment() { override fun onCreateView( inflater: LayoutInflater, @@ -19,7 +21,13 @@ class ApiTokenBottomSheetFragment( ): View { return ComposeView(requireContext()).apply { setContent { - ApiTokenBottomSheet(apiToken = apiToken, onCopyToken = onCopyToken) + ApiTokenBottomSheet(apiToken = apiToken, onCopyToken = { copiedToken -> + (activity as? SnackbarActivity)?.showSnackbar( + content = getString(R.string.copied_to_clipboard, copiedToken), + displayType = HabiticaSnackbar.SnackbarDisplayType.SUCCESS, + ) + dismiss() + }) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 9d4fafd4d..2a93bebb8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.* import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.theme.colors import com.habitrpg.common.habitica.theme.HabiticaTheme +import androidx.compose.ui.res.colorResource @Composable fun ApiTokenBottomSheet( @@ -29,7 +30,7 @@ fun ApiTokenBottomSheet( val fieldBackground = colors.contentBackground val mainText = colors.textPrimary val secondaryText = colors.textSecondary - val buttonBg = colors.tintedUiMain + val buttonBg = colorResource(id = R.color.yellow_100) val buttonText = colors.tintedUiDetails val lockIconColor = colors.textSecondary From 8b5e8272744d568c6ae4720a1eac8fcf4bff4b27 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 11 Jun 2025 13:36:05 -0500 Subject: [PATCH 40/57] API Token bottom sheet small UI Tweak (Button color/button text update) --- .../habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 2a93bebb8..91266b4f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -31,7 +31,7 @@ fun ApiTokenBottomSheet( val mainText = colors.textPrimary val secondaryText = colors.textSecondary val buttonBg = colorResource(id = R.color.yellow_100) - val buttonText = colors.tintedUiDetails + val buttonText = colorResource(id = R.color.yellow_1) val lockIconColor = colors.textSecondary Box( @@ -124,7 +124,7 @@ fun ApiTokenBottomSheet( Text( stringResource(id = R.string.copy_token), color = buttonText, - fontSize = 19.sp, + fontSize = 16.sp, fontWeight = FontWeight.SemiBold, ) } From fc3f1b8ae93aedc51fd8d9d9c8a186744a672791 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Tue, 17 Jun 2025 13:05:05 -0500 Subject: [PATCH 41/57] UI Tweaks to api token bottom sheet - Update handle - Color tweaks --- .../habitica/ui/views/ApiTokenBottomSheet.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 91266b4f3..1b31a98d9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.ui.views +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -27,9 +28,10 @@ fun ApiTokenBottomSheet( val colors = HabiticaTheme.colors val background = colors.windowBackground - val fieldBackground = colors.contentBackground - val mainText = colors.textPrimary + val fieldBackground = colorResource(id = R.color.gray600_gray10) + val mainTextColor = colors.textPrimary val secondaryText = colors.textSecondary + val tokenTextColor = colorResource(id = R.color.gray200_gray400) val buttonBg = colorResource(id = R.color.yellow_100) val buttonText = colorResource(id = R.color.yellow_1) val lockIconColor = colors.textSecondary @@ -43,16 +45,16 @@ fun ApiTokenBottomSheet( Column(Modifier.fillMaxWidth()) { Spacer(modifier = Modifier.height(8.dp)) Box( - Modifier - .size(width = 40.dp, height = 6.dp) + modifier = Modifier + .padding(bottom = 16.dp) + .background(colorResource(R.color.content_background_offset)) + .size(24.dp, 3.dp) .align(Alignment.CenterHorizontally) - .clip(RoundedCornerShape(50)) - .background(colors.contentBackgroundOffset) ) Spacer(modifier = Modifier.height(16.dp)) Text( stringResource(id = R.string.api_token_title), - color = mainText, + color = mainTextColor, fontSize = 21.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.align(Alignment.CenterHorizontally) @@ -60,7 +62,7 @@ fun ApiTokenBottomSheet( Spacer(modifier = Modifier.height(22.dp)) Text( stringResource(id = R.string.api_token_is_password), - color = mainText, + color = mainTextColor, fontSize = 17.sp, fontWeight = FontWeight.Medium, ) @@ -75,7 +77,7 @@ fun ApiTokenBottomSheet( Spacer(modifier = Modifier.height(22.dp)) Text( stringResource(id = R.string.api_token_reset_title), - color = mainText, + color = mainTextColor, fontSize = 17.sp, fontWeight = FontWeight.Medium, ) @@ -105,7 +107,7 @@ fun ApiTokenBottomSheet( Spacer(modifier = Modifier.width(10.dp)) Text( apiToken, - color = mainText, + color = tokenTextColor, fontSize = 15.sp, fontWeight = FontWeight.Normal, modifier = Modifier.weight(1f), @@ -133,7 +135,7 @@ fun ApiTokenBottomSheet( } } -@Preview(showBackground = true, widthDp = 380, heightDp = 550) +@Preview(showBackground = true, widthDp = 380, heightDp = 550, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun ApiTokenBottomSheetPreview() { HabiticaTheme { From 69d39a6f54a450123d85d429a7b73b2cce33e423 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 18 Jun 2025 16:42:04 -0500 Subject: [PATCH 42/57] Password reset screen feedback fixes - Match password reset screen textbox styling - Update styling of the explanation text to match designs - Don't disable change password button field for now - Transparent white bottom nav bar when on light mode - Light mode password input text box colors --- .../preferences/ChangePasswordBottomSheet.kt | 27 ++++++++++ .../habitica/ui/views/ChangePasswordScreen.kt | 52 +++++++++++++------ 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt index 95aa258e5..3e80e0379 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ChangePasswordBottomSheet.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.ui.fragments.preferences +import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,11 +16,37 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.habitrpg.android.habitica.ui.views.ChangePasswordScreen import com.habitrpg.common.habitica.theme.HabiticaTheme import androidx.compose.runtime.setValue +import androidx.core.content.ContextCompat +import androidx.core.view.WindowInsetsControllerCompat import com.habitrpg.android.habitica.R class ChangePasswordBottomSheet(val onForgotPassword: () -> Unit = {}, val onPasswordChanged: (oldPassword: String, newPassword: String) -> Unit = { _, _ -> }) : BottomSheetDialogFragment() { override fun onStart() { super.onStart() + + val nightModeFlags = requireContext() + .resources + .configuration + .uiMode and Configuration.UI_MODE_NIGHT_MASK + + if (nightModeFlags == Configuration.UI_MODE_NIGHT_NO) { + dialog?.window?.let { window -> + window.statusBarColor = ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + window.navigationBarColor = ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + + WindowInsetsControllerCompat(window, window.decorView).apply { + isAppearanceLightStatusBars = true + isAppearanceLightNavigationBars = true + } + } + } + dialog?.let { dlg -> val bottomSheet = dlg.findViewById(com.google.android.material.R.id.design_bottom_sheet) bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt index 43582026b..ed3537665 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -1,6 +1,9 @@ package com.habitrpg.android.habitica.ui.views import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -27,7 +30,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource @@ -49,7 +52,7 @@ fun ChangePasswordScreen( ) { val colors = HabiticaTheme.colors val backgroundColor = colors.windowBackground - val fieldColor = colorResource(id = R.color.gray700_gray10) + val fieldColor = colorResource(id = R.color.gray600_gray10) val labelColor = colors.textSecondary val buttonColor = colors.tintedUiMain val textColor = colors.textPrimary @@ -61,7 +64,6 @@ fun ChangePasswordScreen( val passwordValid = newPassword.length >= 8 val passwordsMatch = newPassword == confirmPassword && newPassword.isNotEmpty() - val canSave = passwordValid && passwordsMatch && oldPassword.isNotBlank() Surface( modifier = Modifier.fillMaxSize(), @@ -102,8 +104,9 @@ fun ChangePasswordScreen( Text( text = stringResource(R.string.password_change_info), - color = labelColor, - fontSize = 14.sp, + color = textColor, + fontSize = 16.sp, + fontWeight = FontWeight.Normal, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(bottom = 22.dp) @@ -163,12 +166,14 @@ fun ChangePasswordScreen( Button( onClick = { attemptedSave = true - if (canSave) onSave(oldPassword, newPassword) + onSave(oldPassword, newPassword) }, - enabled = canSave, + enabled = true, colors = ButtonDefaults.buttonColors( containerColor = colorResource(id = R.color.purple400_purple500), - disabledContainerColor = colorResource(id = R.color.purple400_purple500).copy(alpha = 0.3f) + disabledContainerColor = colorResource(id = R.color.purple400_purple500), + contentColor = Color.White, + disabledContentColor = Color.White ), shape = RoundedCornerShape(12.dp), modifier = Modifier @@ -213,28 +218,43 @@ fun PasswordField( isError: Boolean = false, modifier: Modifier = Modifier ) { - val dividerColor = if (value.isNotBlank()) + val onTextChangedColor = if (value.isNotBlank()) colorResource(id = R.color.purple400_purple500) else colorResource(id = R.color.gray_400) + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val active = isFocused || value.isNotBlank() + + val targetFontSize = if (active) 14.sp else 17.sp + val targetLabelColor = if (active) onTextChangedColor else labelColor + + Box( modifier = modifier .fillMaxWidth() - .clipToBounds() + .clip(RoundedCornerShape(6.dp)) + .background(fieldColor) ) { OutlinedTextField( value = value, onValueChange = onValueChange, - label = { Text(label, color = labelColor, fontSize = 17.sp) }, + interactionSource = interactionSource, + label = { + Text( + text = label, + fontSize = targetFontSize, + color = targetLabelColor + ) + }, singleLine = true, modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(6.dp), isError = isError, visualTransformation = PasswordVisualTransformation(), colors = OutlinedTextFieldDefaults.colors( - unfocusedContainerColor = fieldColor, - focusedContainerColor = fieldColor, + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, unfocusedBorderColor = Color.Transparent, focusedBorderColor = Color.Transparent, cursorColor = Color(0xFF9C8DF6), @@ -244,11 +264,11 @@ fun PasswordField( ) HorizontalDivider( - color = dividerColor, + color = onTextChangedColor, thickness = 1.dp, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 4.dp) + .padding(horizontal = 3.dp) .align(Alignment.BottomStart) ) } From 8660fab3e93151ab0b3e9312a419e8bf5ffda236 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 18 Jun 2025 16:58:51 -0500 Subject: [PATCH 43/57] Unify button height to match stable Updated pause damage and copy token buttons to match stable button height --- .../habitica/ui/views/ApiTokenBottomSheet.kt | 21 ++++++++----------- .../preferences/PauseResumeDamageView.kt | 7 +++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 1b31a98d9..672d67821 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -115,20 +115,17 @@ fun ApiTokenBottomSheet( ) } Spacer(modifier = Modifier.height(22.dp)) - Button( - onClick = { onCopyToken(apiToken) }, - shape = RoundedCornerShape(12.dp), - colors = ButtonDefaults.buttonColors(containerColor = buttonBg), - modifier = Modifier + HabiticaButton( + background = buttonBg, + color = buttonText, + onClick = { onCopyToken(apiToken) }, + modifier = Modifier .fillMaxWidth() - .height(54.dp) + .height(48.dp), + contentPadding = PaddingValues(0.dp), + fontSize = 16.sp ) { - Text( - stringResource(id = R.string.copy_token), - color = buttonText, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - ) + Text(stringResource(id = R.string.copy_token)) } Spacer(modifier = Modifier.height(8.dp)) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt index 7afbf0c5f..0c2537e84 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt @@ -2,6 +2,7 @@ package com.habitrpg.android.habitica.ui.views.preferences import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -78,6 +79,9 @@ fun PauseResumeDamageView( HabiticaButton( background = colorResource(R.color.yellow_100), color = colorResource(R.color.yellow_1), + modifier = Modifier + .fillMaxWidth() + .height(48.dp), onClick = { onClick() } ) { Text(stringResource(R.string.resume_damage)) @@ -136,6 +140,9 @@ fun PauseResumeDamageView( HabiticaButton( background = colorResource(R.color.yellow_100), color = colorResource(R.color.yellow_1), + modifier = Modifier + .fillMaxWidth() + .height(48.dp), onClick = { onClick() } ) { Text(stringResource(R.string.pause_damage)) From 3268cc0b80d0a521ee9e1d50883bd63b8b04954e Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 18 Jun 2025 17:02:07 -0500 Subject: [PATCH 44/57] Update lock icon on api token bottomsheet --- .../android/habitica/ui/views/ApiTokenBottomSheet.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 672d67821..818444918 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.theme.colors import com.habitrpg.common.habitica.theme.HabiticaTheme import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource @Composable fun ApiTokenBottomSheet( @@ -99,10 +100,10 @@ fun ApiTokenBottomSheet( verticalAlignment = Alignment.CenterVertically ) { Icon( - imageVector = Icons.Default.Lock, - contentDescription = null, + painter = painterResource(id = R.drawable.icon_lock), + contentDescription = stringResource(R.string.locked), tint = lockIconColor, - modifier = Modifier.size(22.dp) + modifier = Modifier.size(16.dp) ) Spacer(modifier = Modifier.width(10.dp)) Text( From 145de783fbde4508c8d8eecb97ae9d15605cc3da Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 18 Jun 2025 17:25:55 -0500 Subject: [PATCH 45/57] - Update the token sheet to use the handle style/spacing from pause damage - Update Pause/Resume damage view title/description spacings - Update Pasue/Resume damage header font styling (Also added a preview for pause/resume damage compose) --- .../habitica/ui/views/ApiTokenBottomSheet.kt | 7 ++- .../preferences/PauseResumeDamageView.kt | 50 ++++++++++++++++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 818444918..1f83fb096 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -41,10 +41,9 @@ fun ApiTokenBottomSheet( modifier .fillMaxWidth() .background(background, RoundedCornerShape(22.dp)) - .padding(horizontal = 20.dp, vertical = 14.dp) + .padding(horizontal = 20.dp, vertical = 8.dp) ) { Column(Modifier.fillMaxWidth()) { - Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier .padding(bottom = 16.dp) @@ -65,7 +64,7 @@ fun ApiTokenBottomSheet( stringResource(id = R.string.api_token_is_password), color = mainTextColor, fontSize = 17.sp, - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Normal, ) Spacer(modifier = Modifier.height(8.dp)) Text( @@ -80,7 +79,7 @@ fun ApiTokenBottomSheet( stringResource(id = R.string.api_token_reset_title), color = mainTextColor, fontSize = 17.sp, - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Normal, ) Spacer(modifier = Modifier.height(8.dp)) Text( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt index 0c2537e84..cc92b5a95 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/preferences/PauseResumeDamageView.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.ui.views.preferences +import android.content.res.Configuration import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -12,6 +13,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.habitrpg.android.habitica.R @@ -25,6 +27,8 @@ fun PauseResumeDamageView( onClick: () -> Unit, modifier: Modifier = Modifier ) { + val colors = HabiticaTheme.colors + val mainTextColor = colors.textPrimary Column( horizontalAlignment = Alignment.Start, modifier = @@ -89,10 +93,10 @@ fun PauseResumeDamageView( } else { Text( stringResource(R.string.pause_damage), - color = HabiticaTheme.colors.textSecondary, - fontSize = 16.sp, + fontSize = 21.sp, + color = mainTextColor, + fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center, - fontWeight = FontWeight.Medium, modifier = Modifier .padding(bottom = 18.dp) @@ -102,12 +106,16 @@ fun PauseResumeDamageView( stringResource(R.string.pause_damage_1_title), color = HabiticaTheme.colors.textPrimary, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) ) Text( stringResource(R.string.pause_damage_1_description), color = HabiticaTheme.colors.textSecondary, fontSize = 14.sp, + lineHeight = 20.sp, fontWeight = FontWeight.Normal, modifier = Modifier.padding(bottom = 12.dp) ) @@ -115,27 +123,37 @@ fun PauseResumeDamageView( stringResource(R.string.pause_damage_2_title), color = HabiticaTheme.colors.textPrimary, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) ) Text( stringResource(R.string.pause_damage_2_description), color = HabiticaTheme.colors.textSecondary, fontSize = 14.sp, + lineHeight = 20.sp, fontWeight = FontWeight.Normal, - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) ) Text( stringResource(R.string.pause_damage_3_title), color = HabiticaTheme.colors.textPrimary, fontWeight = FontWeight.Normal, - fontSize = 16.sp + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) ) Text( stringResource(R.string.pause_damage_3_description), color = HabiticaTheme.colors.textSecondary, fontSize = 14.sp, + lineHeight = 20.sp, fontWeight = FontWeight.Normal, - modifier = Modifier.padding(bottom = 18.dp) + modifier = Modifier.padding(bottom = 24.dp) ) HabiticaButton( background = colorResource(R.color.yellow_100), @@ -150,3 +168,19 @@ fun PauseResumeDamageView( } } } + +@Preview( + showBackground = true, + widthDp = 360, + name = "Pause Damage (isPaused = false)", + uiMode = Configuration.UI_MODE_NIGHT_YES +) +@Composable +private fun PauseDamagePreview() { + HabiticaTheme { + PauseResumeDamageView( + isPaused = false, + onClick = {} + ) + } +} From b568223c60d1726a7f970058be793da968d3cc9c Mon Sep 17 00:00:00 2001 From: Hafiz Date: Wed, 18 Jun 2025 17:34:09 -0500 Subject: [PATCH 46/57] Fix crash that occurs when changing day/night mode on API Token bottom sheet --- .../preferences/AccountPreferenceFragment.kt | 3 +-- .../ApiTokenBottomSheetFragment.kt | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt index d4c66e6ff..464828684 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt @@ -184,8 +184,7 @@ class AccountPreferenceFragment : } "APIToken" -> { - ApiTokenBottomSheetFragment( - apiToken = hostConfig.apiKey).show(childFragmentManager, ApiTokenBottomSheetFragment.TAG) + ApiTokenBottomSheetFragment.newInstance(hostConfig.apiKey).show(childFragmentManager, ApiTokenBottomSheetFragment.TAG) return true } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt index 15a2bd82e..547e7377c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt @@ -11,9 +11,15 @@ import com.habitrpg.android.habitica.ui.views.ApiTokenBottomSheet import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.SnackbarActivity -class ApiTokenBottomSheetFragment( - private val apiToken: String -) : BottomSheetDialogFragment() { +class ApiTokenBottomSheetFragment : BottomSheetDialogFragment() { + private lateinit var apiToken: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + apiToken = arguments?.getString(ARG_API_TOKEN) ?: "" + } + + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -33,6 +39,14 @@ class ApiTokenBottomSheetFragment( } companion object { + private const val ARG_API_TOKEN = "arg_api_token" + fun newInstance(apiToken: String): ApiTokenBottomSheetFragment = + ApiTokenBottomSheetFragment().apply { + arguments = Bundle().apply { + putString(ARG_API_TOKEN, apiToken) + } + } + const val TAG = "ApiTokenBottomSheet" } } From f4484ea9d154c9063d29398039425bf83d7c657c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 19 Jun 2025 15:12:18 +0200 Subject: [PATCH 47/57] fix merge --- Habitica/res/values/strings.xml | 5 +- .../social/party/PartySeekingFragment.kt | 2 +- gradle/libs.versions.toml | 58 +++++++++---------- version.properties | 2 +- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 2887b3393..cd99fd7b0 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -813,6 +813,8 @@ Old Password New Password Repeat new Password + Passwords must be 8 characters or more. Changing your password will log you out of any other devices and third-party tools you may use. + Confirm new Password Adjust Streak Adjust Counter Password successfully changed @@ -1566,7 +1568,8 @@ Max Gem Cap Instantly start at the max Gem Cap They instantly start at the Max Gem Cap - + Start chatting! + Remember to be friendly and follow the Community Guidelines. Gem Cap Get 12 Mystic Hourglasses immediately after your first 12 month subscription! 12 Mystic Hourglasses diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt index 2688226b7..4cc356705 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt @@ -303,7 +303,7 @@ fun PartySeekingView( configManager = viewModel.configManager, modifier = Modifier - .animateItemPlacement() + .animateItem() .padding(horizontal = 14.dp) ) { member -> scope.launchCatching({ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50855cbbe..3a44d1a0d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,71 +1,70 @@ [versions] minSdk = "26" -targetSdk = "35" +targetSdk = "36" wearOsTargetSdk = "34" accompanist = "0.34.0" -agp = "8.8.2" -amplitude = "1.19.4" +agp = "8.10.1" +amplitude = "1.21.2" androidTest = "1.6.1" androidTestMonitor = "1.7.2" androidTestRunner = "1.6.2" annotationApi = "1.3.2" -appcompat = "1.7.0" +appcompat = "1.7.1" billing = "7.1.1" -coil = "3.0.4" -compose = "1.7.8" +coil = "3.2.0" +compose = "1.8.2" composeActivity = "1.10.1" compose_compiler = "1.5.14" constraintlayout = "2.2.1" -converterMoshi = "2.9.0" +converterMoshi = "3.0.0" coordinatorlayout = "1.3.0" coreSplashscreen = "1.1.0-rc01" -core_ktx = "1.15.0" -coroutines = "1.10.1" -crashlytics = "3.0.3" +core_ktx = "1.16.0" +coroutines = "1.10.2" +crashlytics = "3.0.4" credentials = "1.5.0" -daggerhilt = "2.55" +daggerhilt = "2.56.2" desuggar = "2.1.5" -detekt = "1.19.0" +detekt = "1.23.8" deviceNames = "2.1.1" firebase-perf = "1.4.2" -firebase_bom = "33.12.0" +firebase_bom = "33.15.0" flexbox = "3.0.0" -fragmentKtx = "1.8.6" -fragmentTesting = "1.8.6" +fragmentKtx = "1.8.8" +fragmentTesting = "1.8.8" google-service = "4.4.2" googleid = "1.1.1" -gson = "2.12.0" +gson = "2.13.1" habitRpgPlugin = "0.1.0" inAppReview = "2.0.2" junitKtx = "1.2.1" kaspresso = "1.6.0" kotest = "5.9.1" -kotlin = "2.1.10" -ksp = "2.1.10-1.0.31" -ktlintPlugin = "11.3.1" -leakCanary = "2.10" -lifecycle = "2.8.7" -lifecycleRuntimeKtx = "2.8.7" +kotlin = "2.1.21" +ksp = "2.1.21-2.0.2" +ktlintPlugin = "12.3.0" +leakCanary = "2.14" +lifecycle = "2.9.1" +lifecycleRuntimeKtx = "2.9.1" markwon = "4.6.2" material = "1.12.0" -material3 = "1.3.1" -mockk = "1.13.6" -moshiKotlin = "1.15.0" -navigation = "2.8.9" +material3 = "1.3.2" +mockk = "1.14.2" +moshiKotlin = "1.15.2" +navigation = "2.9.0" okhttp = "4.12.0" orchestrator = "1.5.1" -pagerIndicator = "2.4.1" paging = "3.3.6" play_auth = "21.3.0" play_wearables = "19.0.0" preferences = "1.2.1" realmPlugin = "10.19.0" recyclerview = "1.4.0" -retrofit = "2.11.0" +retrofit = "3.0.0" shimmer = "0.5.0" swipeRefresh = "1.1.0" -turbine = "1.2.0" +turbine = "1.2.1" wear = "1.3.0" wearInput = "1.1.0" @@ -159,7 +158,6 @@ test-runner = { group = "androidx.test", name = "runner", version.ref = "android text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "compose" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" } -viewPagerIndicator = { group = "fr.avianey.com.viewpagerindicator", name = "library", version.ref = "pagerIndicator" } viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } wear = { module = "androidx.wear:wear", version.ref = "wear" } wear-input = { module = "androidx.wear:wear-input", version.ref = "wearInput" } diff --git a/version.properties b/version.properties index 6c0fbd9d7..a7e42658f 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.7.6 -CODE=13391 \ No newline at end of file +CODE=13431 \ No newline at end of file From 05fbb5b3594f734573d4fda9ab519e63fbee6a52 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Fri, 20 Jun 2025 11:28:39 -0500 Subject: [PATCH 48/57] Feedback fixes - Change password button always in purple 400 - Fix styling of change password - Use the existing textinputlayout (textbox) behavior - Show error when changing password (without closing the sheet) - Use transparent white nav bar on API token bottom sheet on day/light mode --- .../ApiTokenBottomSheetFragment.kt | 29 ++++ .../habitica/ui/views/ApiTokenBottomSheet.kt | 2 - .../habitica/ui/views/ChangePasswordScreen.kt | 143 ++++++++---------- .../main/res/layout/component_text_input.xml | 28 ++++ 4 files changed, 120 insertions(+), 82 deletions(-) create mode 100644 Habitica/src/main/res/layout/component_text_input.xml diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt index 547e7377c..7b090c557 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/ApiTokenBottomSheetFragment.kt @@ -1,10 +1,13 @@ package com.habitrpg.android.habitica.ui.fragments.preferences +import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView +import androidx.core.content.ContextCompat +import androidx.core.view.WindowInsetsControllerCompat import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.views.ApiTokenBottomSheet @@ -19,6 +22,32 @@ class ApiTokenBottomSheetFragment : BottomSheetDialogFragment() { apiToken = arguments?.getString(ARG_API_TOKEN) ?: "" } + override fun onStart() { + super.onStart() + + val nightModeFlags = requireContext() + .resources + .configuration + .uiMode and Configuration.UI_MODE_NIGHT_MASK + + if (nightModeFlags == Configuration.UI_MODE_NIGHT_NO) { + dialog?.window?.let { window -> + window.statusBarColor = ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + window.navigationBarColor = ContextCompat.getColor( + requireContext(), + android.R.color.transparent + ) + + WindowInsetsControllerCompat(window, window.decorView).apply { + isAppearanceLightStatusBars = true + isAppearanceLightNavigationBars = true + } + } + } + } override fun onCreateView( inflater: LayoutInflater, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt index 1f83fb096..6f6900627 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ApiTokenBottomSheet.kt @@ -4,8 +4,6 @@ import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Lock import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.* diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt index ed3537665..bdd3c7923 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt @@ -1,9 +1,11 @@ package com.habitrpg.android.habitica.ui.views +import android.content.res.ColorStateList import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState +import android.text.InputType +import android.text.method.PasswordTransformationMethod +import android.view.LayoutInflater +import androidx.appcompat.widget.AppCompatEditText import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -15,11 +17,8 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -30,16 +29,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.widget.doAfterTextChanged +import com.google.android.material.textfield.TextInputLayout import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.theme.colors import com.habitrpg.common.habitica.theme.HabiticaTheme @@ -64,6 +65,7 @@ fun ChangePasswordScreen( val passwordValid = newPassword.length >= 8 val passwordsMatch = newPassword == confirmPassword && newPassword.isNotEmpty() + val canSave = passwordValid && passwordsMatch && oldPassword.isNotBlank() Surface( modifier = Modifier.fillMaxSize(), @@ -75,7 +77,7 @@ fun ChangePasswordScreen( modifier = Modifier .size(48.dp) .align(Alignment.TopStart) - .padding(16.dp) + .padding(start = 22.dp, top = 16.dp) ) { Icon( painterResource(id = R.drawable.arrow_back), @@ -99,7 +101,7 @@ fun ChangePasswordScreen( color = textColor, modifier = Modifier .align(Alignment.Start) - .padding(bottom = 8.dp) + .padding(start = 6.dp, bottom = 12.dp) ) Text( @@ -107,9 +109,10 @@ fun ChangePasswordScreen( color = textColor, fontSize = 16.sp, fontWeight = FontWeight.Normal, + lineHeight = 20.sp, modifier = Modifier .align(Alignment.CenterHorizontally) - .padding(bottom = 22.dp) + .padding(start = 6.dp,bottom = 22.dp) ) PasswordField( @@ -121,7 +124,6 @@ fun ChangePasswordScreen( textColor = textColor, modifier = Modifier.fillMaxWidth() ) - Spacer(modifier = Modifier.padding(vertical = 8.dp)) PasswordField( label = stringResource(R.string.new_password), @@ -131,18 +133,9 @@ fun ChangePasswordScreen( labelColor = labelColor, textColor = textColor, isError = attemptedSave && !passwordValid, + errorMessage = if (attemptedSave && !passwordValid) stringResource(R.string.password_too_short, 8) else null, modifier = Modifier.fillMaxWidth() ) - if (attemptedSave && !passwordValid) { - Text( - text = stringResource(R.string.password_too_short), - color = Color.Red, - fontSize = 13.sp, - modifier = Modifier.padding(start = 8.dp, top = 4.dp) - ) - } - Spacer(modifier = Modifier.padding(vertical = 8.dp)) - PasswordField( label = stringResource(R.string.confirm_new_password), value = confirmPassword, @@ -151,27 +144,19 @@ fun ChangePasswordScreen( labelColor = labelColor, textColor = textColor, isError = attemptedSave && !passwordsMatch, + errorMessage = if (attemptedSave && !passwordsMatch) stringResource(R.string.password_not_matching) else null, modifier = Modifier.fillMaxWidth() ) - if (attemptedSave && !passwordsMatch) { - Text( - text = stringResource(R.string.password_not_matching), - color = Color.Red, - fontSize = 13.sp, - modifier = Modifier.padding(start = 8.dp, top = 4.dp) - ) - } Spacer(modifier = Modifier.padding(top = 24.dp)) - Button( onClick = { attemptedSave = true - onSave(oldPassword, newPassword) + if (canSave) onSave(oldPassword, newPassword) }, enabled = true, colors = ButtonDefaults.buttonColors( - containerColor = colorResource(id = R.color.purple400_purple500), - disabledContainerColor = colorResource(id = R.color.purple400_purple500), + containerColor = colorResource(id = R.color.brand_400), + disabledContainerColor = colorResource(id = R.color.brand_400), contentColor = Color.White, disabledContentColor = Color.White ), @@ -216,6 +201,7 @@ fun PasswordField( labelColor: Color, textColor: Color, isError: Boolean = false, + errorMessage: String? = null, modifier: Modifier = Modifier ) { val onTextChangedColor = if (value.isNotBlank()) @@ -223,55 +209,52 @@ fun PasswordField( else colorResource(id = R.color.gray_400) - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - val active = isFocused || value.isNotBlank() - - val targetFontSize = if (active) 14.sp else 17.sp - val targetLabelColor = if (active) onTextChangedColor else labelColor - - - Box( + AndroidView( modifier = modifier .fillMaxWidth() - .clip(RoundedCornerShape(6.dp)) - .background(fieldColor) - ) { - OutlinedTextField( - value = value, - onValueChange = onValueChange, - interactionSource = interactionSource, - label = { - Text( - text = label, - fontSize = targetFontSize, - color = targetLabelColor - ) - }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - isError = isError, - visualTransformation = PasswordVisualTransformation(), - colors = OutlinedTextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, - focusedContainerColor = Color.Transparent, - unfocusedBorderColor = Color.Transparent, - focusedBorderColor = Color.Transparent, - cursorColor = Color(0xFF9C8DF6), - unfocusedTextColor = textColor, - focusedTextColor = textColor - ) - ) + .padding(vertical = 8.dp), + factory = { ctx -> + LayoutInflater.from(ctx) + .inflate(R.layout.component_text_input, null, false) + }, + update = { view -> + val til = view.findViewById(R.id.text_input_layout) + val edit = view.findViewById(R.id.text_edit_text) + til.hint = label - HorizontalDivider( - color = onTextChangedColor, - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 3.dp) - .align(Alignment.BottomStart) - ) - } + edit.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + edit.transformationMethod = PasswordTransformationMethod.getInstance() + + fun syncColors(focused: Boolean) { + val active = focused || edit.text?.isNotBlank() == true + val stroke = if (active) onTextChangedColor else labelColor + til.defaultHintTextColor = ColorStateList.valueOf(stroke.toArgb()) + til.setBoxStrokeColorStateList(ColorStateList.valueOf(stroke.toArgb())) + } + + syncColors(edit.isFocused) + + edit.setOnFocusChangeListener { _, focused -> + syncColors(focused) + } + + edit.doAfterTextChanged { + syncColors(edit.isFocused) + onValueChange(it.toString()) + } + + if (edit.text.toString() != value) { + edit.setText(value) + edit.setSelection(value.length) + } + + til.isErrorEnabled = isError + til.error = errorMessage + + til.setBoxStrokeWidth(2) + edit.setTextColor(textColor.toArgb()) + } + ) } @Preview(showBackground = true, widthDp = 327, heightDp = 704, uiMode = Configuration.UI_MODE_NIGHT_YES) diff --git a/Habitica/src/main/res/layout/component_text_input.xml b/Habitica/src/main/res/layout/component_text_input.xml new file mode 100644 index 000000000..4ba5cc45f --- /dev/null +++ b/Habitica/src/main/res/layout/component_text_input.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file From ce2a9df78203f66974f4557fb8755b4a45d7e68d Mon Sep 17 00:00:00 2001 From: Hafiz Date: Sat, 21 Jun 2025 13:57:07 -0500 Subject: [PATCH 49/57] Fix to-do's not moving off screen when being checked off Fix to-do's task fragment showing filter active when none were --- .../fragments/tasks/TaskRecyclerViewFragment.kt | 17 ++++++++++++----- .../habitica/ui/viewmodels/TasksViewModel.kt | 9 ++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index ef7bf5142..b1724e5f3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -581,12 +581,19 @@ open class TaskRecyclerViewFragment : if (it != null) { when (taskType) { TaskType.TODO -> { - viewModel.setActiveFilter( - TaskType.TODO, - viewModel.getTaskFilterPreference(TaskType.TODO) - ) + // Handle case where a initial filter preference for to-dos were set for FILTER_ALL + if (viewModel.getTaskFilterPreference(TaskType.TODO) == Task.FILTER_ALL) { + viewModel.setActiveFilter( + TaskType.TODO, + Task.FILTER_ACTIVE + ) + } else { + viewModel.setActiveFilter( + TaskType.TODO, + viewModel.getTaskFilterPreference(TaskType.TODO) + ) + } } - TaskType.DAILY -> { if (!viewModel.initialPreferenceFilterSet) { viewModel.initialPreferenceFilterSet = true diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt index aec24462b..84d806996 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt @@ -199,9 +199,12 @@ constructor( if (activeFilters[type] == null) { return false } - return if (TaskType.TODO == type) { - Task.FILTER_ACTIVE != activeFilters[type] + when(activeFilters[type]) { + Task.FILTER_ACTIVE -> false + Task.FILTER_ALL -> false + else -> true + } } else { Task.FILTER_ALL != activeFilters[type] } @@ -289,7 +292,7 @@ constructor( fun getTaskFilterPreference( type: TaskType ): String { - return sharedPreferences.getString("filter_${type.value}", Task.FILTER_ALL) ?: Task.FILTER_ALL + return sharedPreferences.getString("filter_${type.value}", Task.FILTER_ALL) ?: if (TaskType.TODO == type) Task.FILTER_ACTIVE else Task.FILTER_ALL } fun createQuery(unfilteredData: OrderedRealmCollection): RealmQuery? { From 87a2f147fc6639539affd360fea5af798f74a217 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 23 Jun 2025 16:01:15 +0200 Subject: [PATCH 50/57] bump to 4.7.7 --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 6c0fbd9d7..80ea4188a 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -NAME=4.7.6 -CODE=13391 \ No newline at end of file +NAME=4.7.7 +CODE=13591 From 79e6484ca6223f0d7d227efd3139d57e4ed8af60 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 23 Jun 2025 16:24:10 +0200 Subject: [PATCH 51/57] Version bumped to v4.7.7 --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 80ea4188a..be8973155 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ NAME=4.7.7 -CODE=13591 +CODE=13601 \ No newline at end of file From 36ddfa86b548c7926e78864df3cfbca5546d7165 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Mon, 23 Jun 2025 14:59:36 -0500 Subject: [PATCH 52/57] Fix changing display modes in settings creates multiple instances of settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap initial fragment transaction in if (savedInstanceState == null) so if the activity recreates (theme or system UI changes), fragments don’t stack --- .../android/habitica/ui/activities/PrefsActivity.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt index 18e063e9c..5f508245f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/PrefsActivity.kt @@ -23,10 +23,12 @@ class PrefsActivity : super.onCreate(savedInstanceState) setupToolbar(findViewById(R.id.toolbar)) - - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_container, PreferencesFragment()) - .commit() + + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, PreferencesFragment()) + .commit() + } } override fun onSupportNavigateUp(): Boolean { From 9e17ab09d6d3e93e434499b9aa9149fd3e234ee9 Mon Sep 17 00:00:00 2001 From: Hafiz Date: Thu, 26 Jun 2025 15:19:20 -0500 Subject: [PATCH 53/57] Introduce generic ConfigurableFormScreen for all settings forms Add FieldConfig and FormScreenConfig data models Implement ConfigurableFormScreen and ComponentTextInput to render any settings screen with AndroidView inputs, styling, and validation Refactor ChangePasswordScreen to use the new generic form instead of its bespoke implementation Preserve existing theming, padding, and error behavior across all screens --- Habitica/res/values-night/colors.xml | 2 + Habitica/res/values/colors.xml | 3 +- Habitica/res/values/strings.xml | 8 + Habitica/res/values/styles.xml | 15 + .../preferences/AccountPreferenceFragment.kt | 10 +- .../preferences/ChangePasswordBottomSheet.kt | 13 +- .../habitica/ui/views/ChangePasswordScreen.kt | 270 -------- .../habitica/ui/views/SettingScreen.kt | 599 ++++++++++++++++++ .../main/res/layout/component_text_input.xml | 5 +- 9 files changed, 641 insertions(+), 284 deletions(-) delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ChangePasswordScreen.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SettingScreen.kt diff --git a/Habitica/res/values-night/colors.xml b/Habitica/res/values-night/colors.xml index 812638a21..19c719f21 100644 --- a/Habitica/res/values-night/colors.xml +++ b/Habitica/res/values-night/colors.xml @@ -63,6 +63,7 @@ @color/brand_500 @color/green_500 @color/gray_400 + @color/gray_500 @color/gray_400 @color/gray_10 @color/gray_50 @@ -70,4 +71,5 @@ @color/red_100 @color/brand_600 + diff --git a/Habitica/res/values/colors.xml b/Habitica/res/values/colors.xml index 33617e217..c0dc4a072 100644 --- a/Habitica/res/values/colors.xml +++ b/Habitica/res/values/colors.xml @@ -129,7 +129,8 @@ @color/brand_400 @color/green_10 - @color/gray_10 + @color/gray_100 + @color/gray_100 @color/gray_200 @color/gray_600 @color/gray_600 diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index cd99fd7b0..6dffe6972 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -628,6 +628,8 @@ Edit your public profile. Display Name Photo URL + You can display an image on your Habitica profile for others to see by adding a link to the image here. + Save Photo URL Login Name About App Settings @@ -635,7 +637,9 @@ Change your authentication options. Change Password Change Email Address + This is the email address that you use to log in to Habitica, as well as receive notifications. Change Username + Usernames must be 1 to 20 characters, containing only letters a to z, numbers 0 to 9, hyphens, or underscores. Change Character Level Auto Allocate Points @@ -763,6 +767,8 @@ Username copied to clipboard One of these Veteran Pets will be waiting for you after you’ve finished confirming! What should we call you? + Change Display Name + This is the name that will be displayed for your avatar in Habitica. Unlike username, it does not have to be a unique identifier. Display names must be between 1 and 30 characters Join Habitica (Check me off!) You can either complete this To Do, edit it, or remove it. @@ -1223,6 +1229,8 @@ My Account Public Profile About Me + Add a small blurb about yourself that will appear on your Habitica profile when others view it. + Save About Me API Account Info Login Methods diff --git a/Habitica/res/values/styles.xml b/Habitica/res/values/styles.xml index c0ba51079..b8d88a68a 100644 --- a/Habitica/res/values/styles.xml +++ b/Habitica/res/values/styles.xml @@ -952,6 +952,21 @@ @style/TaskFormHintTextAppearance + +