diff --git a/Habitica/res/drawable/layout_rounded_bg_header_bar.xml b/Habitica/res/drawable/layout_rounded_bg_header_bar.xml
deleted file mode 100644
index 3ab4bfac4..000000000
--- a/Habitica/res/drawable/layout_rounded_bg_header_bar.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/drawable/task_form_control_bg.xml b/Habitica/res/drawable/task_form_control_bg.xml
index 66ba48871..612626f02 100644
--- a/Habitica/res/drawable/task_form_control_bg.xml
+++ b/Habitica/res/drawable/task_form_control_bg.xml
@@ -2,7 +2,7 @@
-
-
+
diff --git a/Habitica/res/layout/equipment_overview_item.xml b/Habitica/res/layout/equipment_overview_item.xml
deleted file mode 100644
index ab8b9eff6..000000000
--- a/Habitica/res/layout/equipment_overview_item.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/layout/equipment_overview_view.xml b/Habitica/res/layout/equipment_overview_view.xml
deleted file mode 100644
index eebc13b54..000000000
--- a/Habitica/res/layout/equipment_overview_view.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/layout/fragment_avatar_overview.xml b/Habitica/res/layout/fragment_avatar_overview.xml
index 0a6b3a683..ec9322b44 100644
--- a/Habitica/res/layout/fragment_avatar_overview.xml
+++ b/Habitica/res/layout/fragment_avatar_overview.xml
@@ -49,12 +49,12 @@
android:layout_marginEnd="8dp"
android:entries="@array/avatar_sizes"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/layout/fragment_skills.xml b/Habitica/res/layout/fragment_skills.xml
deleted file mode 100644
index e10ba38a8..000000000
--- a/Habitica/res/layout/fragment_skills.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/Habitica/res/layout/task_form_habit_scoring.xml b/Habitica/res/layout/task_form_habit_scoring.xml
deleted file mode 100644
index 8d1827b9f..000000000
--- a/Habitica/res/layout/task_form_habit_scoring.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/layout/task_form_task_difficulty.xml b/Habitica/res/layout/task_form_task_difficulty.xml
deleted file mode 100644
index f18c3390f..000000000
--- a/Habitica/res/layout/task_form_task_difficulty.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 95345f3f2..e1decdb78 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -1269,6 +1269,7 @@
Assign
Edit assignees
Assign to...
+ Promote to Manager
- You
- You, %d other
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 c2f65d854..2ded4ccb3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt
@@ -61,6 +61,12 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
override fun onCreate() {
super.onCreate()
+ if (!BuildConfig.DEBUG) {
+ try {
+ AmplitudeManager.initialize(this)
+ } catch (ignored: Resources.NotFoundException) {
+ }
+ }
registerActivityLifecycleCallbacks(this)
setupRealm()
setupDagger()
@@ -73,12 +79,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
- if (!BuildConfig.DEBUG) {
- try {
- AmplitudeManager.initialize(this, sharedPrefs)
- } catch (ignored: Resources.NotFoundException) {
- }
- }
+
setupCoil()
ExceptionHandler.init(analyticsManager)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt
index c8e33ed26..0b65ab681 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt
@@ -51,14 +51,16 @@ object AmplitudeManager {
sendEvent("navigated", EVENT_CATEGORY_NAVIGATION, EVENT_HITTYPE_PAGEVIEW, additionalData)
}
- fun initialize(context: Context, sharedPrefs: SharedPreferences) {
+ fun initialize(context: Context) {
amplitude = Amplitude(
Configuration(
context.getString(R.string.amplitude_app_id),
context
)
)
+ }
+ fun identify(sharedPrefs: SharedPreferences) {
val identify = Identify()
.setOnce("androidStore", BuildConfig.STORE)
sharedPrefs.getString("launch_screen", "")?.let {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt
index 7668413e6..eb5dcceca 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.kt
@@ -55,12 +55,19 @@ open class Group : RealmObject(), BaseMainObject {
}
fun hasTaskEditPrivileges(userID: String): Boolean {
- if (leaderID == userID) {
+ if (isLeader(userID)) {
return true
}
- return managers?.contains(userID) == true
+ return isManager(userID)
}
+ fun canManageManagers(userID: String): Boolean {
+ return isLeader(userID)
+ }
+
+ fun isLeader(userID: String): Boolean = leaderID == userID
+ fun isManager(userID: String): Boolean = managers?.contains(userID) == true
+
companion object {
const val TAVERN_ID = "00000000-0000-4000-A000-000000000000"
}
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 1513621ad..7ad85dcec 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
@@ -16,6 +16,7 @@ import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
+import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
@@ -53,8 +54,10 @@ import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.AppHeaderView
+import com.habitrpg.android.habitica.ui.views.GroupPlanMemberList
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.QuestCompletedDialog
+import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog
import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
import com.habitrpg.android.habitica.widget.DailiesWidgetProvider
@@ -83,18 +86,25 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
@Inject
internal lateinit var apiClient: ApiClient
+
@Inject
internal lateinit var soundManager: SoundManager
+
@Inject
internal lateinit var checkClassSelectionUseCase: CheckClassSelectionUseCase
+
@Inject
internal lateinit var displayItemDropUseCase: DisplayItemDropUseCase
+
@Inject
internal lateinit var notifyUserUseCase: NotifyUserUseCase
+
@Inject
internal lateinit var taskRepository: TaskRepository
+
@Inject
internal lateinit var inventoryRepository: InventoryRepository
+
@Inject
internal lateinit var appConfigManager: AppConfigManager
@@ -171,7 +181,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
val drawerLayout = findViewById(R.id.drawer_layout)
- drawerFragment = supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment
+ drawerFragment =
+ supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment
drawerFragment?.setUp(R.id.navigation_drawer, drawerLayout, notificationsViewModel)
drawerToggle = object : ActionBarDrawerToggle(
@@ -191,7 +202,10 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
window.updateStatusBarColor(getThemeColor(R.attr.colorPrimaryDark), false)
isOpeningDrawer = true
} else if (slideOffset > 0.5f && isOpeningDrawer == null) {
- window.updateStatusBarColor(getThemeColor(R.attr.headerBackgroundColor), true)
+ window.updateStatusBarColor(
+ getThemeColor(R.attr.headerBackgroundColor),
+ true
+ )
isOpeningDrawer = false
}
}
@@ -221,10 +235,23 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
supportActionBar?.setHomeButtonEnabled(true)
setupNotifications()
setupBottomnavigationLayoutListener()
-
+
binding.content.headerView.setContent {
HabiticaTheme {
- AppHeaderView(viewModel.userViewModel)
+ AppHeaderView(viewModel.userViewModel) {
+ showAsBottomSheet { onClose ->
+ GroupPlanMemberList(it, {
+ onClose()
+ FullProfileActivity.open(it)
+ }, { member ->
+ onClose()
+ MainNavigationController.navigate(
+ R.id.inboxMessageListFragment,
+ bundleOf(Pair("username", member.username), Pair("userID", member.id))
+ )
+ })
+ }
+ }
}
}
@@ -268,7 +295,12 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private fun setupBottomnavigationLayoutListener() {
binding.content.bottomNavigation.viewTreeObserver.addOnGlobalLayoutListener {
if (binding.content.bottomNavigation.visibility == View.VISIBLE) {
- snackbarContainer.setPadding(0, 0, 0, binding.content.bottomNavigation.barHeight + 12.dpToPx(this))
+ snackbarContainer.setPadding(
+ 0,
+ 0,
+ 0,
+ binding.content.bottomNavigation.barHeight + 12.dpToPx(this)
+ )
} else {
snackbarContainer.setPadding(0, 0, 0, 0)
}
@@ -310,10 +342,16 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
viewModel.onResume()
- val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navigationController = navHostFragment.navController
MainNavigationController.setup(navigationController)
- navigationController.addOnDestinationChangedListener { _, destination, arguments -> updateToolbarTitle(destination, arguments) }
+ navigationController.addOnDestinationChangedListener { _, destination, arguments ->
+ updateToolbarTitle(
+ destination,
+ arguments
+ )
+ }
viewModel.requestNotificationPermission.observe(this) { requestNotificationPermission ->
if (requestNotificationPermission && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)) {
@@ -337,7 +375,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
resumeFromActivity = false
- if ((intent.hasExtra("notificationIdentifier") || intent.hasExtra("openURL")) && lastNotificationOpen != intent.getLongExtra("notificationTimeStamp", 0)) {
+ if ((intent.hasExtra("notificationIdentifier") || intent.hasExtra("openURL")) && lastNotificationOpen != intent.getLongExtra(
+ "notificationTimeStamp",
+ 0
+ )
+ ) {
lastNotificationOpen = intent.getLongExtra("notificationTimeStamp", 0)
val identifier = intent.getStringExtra("notificationIdentifier") ?: ""
if (intent.hasExtra("sendAnalytics")) {
@@ -387,7 +429,8 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private fun updateWidget(widgetClass: Class<*>) {
val intent = Intent(this, widgetClass)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
- val ids = AppWidgetManager.getInstance(application).getAppWidgetIds(ComponentName(application, widgetClass))
+ val ids = AppWidgetManager.getInstance(application)
+ .getAppWidgetIds(ComponentName(application, widgetClass))
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
sendBroadcast(intent)
}
@@ -412,7 +455,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
val quest = user.party?.quest
if (quest?.completed?.isNotBlank() == true) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
- val questContent = inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstOrNull()
+ val questContent =
+ inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "")
+ .firstOrNull()
if (questContent != null) {
QuestCompletedDialog.showWithQuest(this@MainActivity, questContent)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
index ea95874cf..9b54c937a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
@@ -22,46 +22,11 @@ import android.view.WindowManager
import android.widget.CheckBox
import androidx.activity.viewModels
import androidx.appcompat.widget.AppCompatCheckBox
-import androidx.compose.animation.animateColor
-import androidx.compose.animation.core.FastOutLinearInEasing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.animation.fadeIn
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.toMutableStateList
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
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.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.forEachIndexed
@@ -79,7 +44,6 @@ import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.launchCatching
-import com.habitrpg.android.habitica.models.Assignable
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
@@ -89,10 +53,10 @@ import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.viewmodels.TaskFormViewModel
-import com.habitrpg.android.habitica.ui.views.CompletedAt
-import com.habitrpg.android.habitica.ui.views.UserRow
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
+import com.habitrpg.android.habitica.ui.views.tasks.AssignSheet
+import com.habitrpg.android.habitica.ui.views.tasks.AssignedView
import com.habitrpg.android.habitica.ui.views.tasks.form.HabitScoringSelector
import com.habitrpg.android.habitica.ui.views.tasks.form.LabeledValue
import com.habitrpg.android.habitica.ui.views.tasks.form.TaskDifficultySelector
@@ -863,7 +827,7 @@ class TaskFormActivity : BaseActivity() {
}
private fun showAssignDialog() {
- showAsBottomSheet {
+ showAsBottomSheet { onClose ->
AssignSheet(
groupMembers,
assignedIDs,
@@ -873,7 +837,8 @@ class TaskFormActivity : BaseActivity() {
} else {
assignedIDs.add(it)
}
- }
+ },
+ onClose
)
}
}
@@ -901,152 +866,3 @@ private fun String.toIntCatchOverflow(): Int? {
0
}
}
-
-@Composable
-fun AssignedView(
- assigned: List,
- completedAt: Map,
- backgroundColor: Color,
- color: Color,
- onEditClick: () -> Unit,
- modifier: Modifier = Modifier,
- showEditButton: Boolean = false
-) {
- Column(modifier.fillMaxWidth()) {
- val rowModifier = Modifier
- .padding(vertical = 4.dp)
- .background(
- backgroundColor,
- MaterialTheme.shapes.medium
- )
- .padding(15.dp, 12.dp)
- .heightIn(min = 24.dp)
- .fillMaxWidth()
- for (assignable in assigned) {
- UserRow(
- username = assignable.identifiableName, modifier = rowModifier,
- color = color,
- extraContent = {
- completedAt[assignable.id]?.let { CompletedAt(completedAt = it) }
- }
- )
- }
- if (showEditButton) {
- Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
- .clickable {
- onEditClick()
- }
- .padding(vertical = 4.dp)
- .background(
- backgroundColor,
- MaterialTheme.shapes.medium
- )
- .padding(15.dp, 12.dp)
- .heightIn(min = 24.dp)
- .fillMaxWidth()) {
- Image(
- painterResource(R.drawable.edit),
- null,
- colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
- )
- Text(
- stringResource(R.string.edit_assignees), color = color,
- modifier = Modifier.padding(start = 4.dp)
- )
- }
- }
- }
-}
-
-@Composable
-fun AssignSheet(
- members: List,
- assignedMembers: List,
- onAssignClick: (String) -> Unit,
- modifier: Modifier = Modifier
-) {
- Column(modifier) {
- Box {
- Text(
- stringResource(R.string.assign_to),
- fontSize = 16.sp,
- fontWeight = FontWeight.Medium,
- color = colorResource(R.color.gray_200),
- textAlign = TextAlign.Center,
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp)
- )
- TextButton(
- onClick = {
-
- },
- colors = ButtonDefaults.textButtonColors(),
- modifier = Modifier.align(Alignment.CenterEnd)
- ) {
- Text(stringResource(R.string.done))
- }
- }
- for (member in members) {
- val isAssigned = assignedMembers.contains(member.id)
- val transition = updateTransition(isAssigned, label = "isAssigned")
- val rotation = transition.animateFloat(
- label = "isAssigned",
- transitionSpec = { spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow) }) {
- if (it) 0f else 45f
- }
- val backgroundColor = transition.animateColor(
- label = "isAssigned",
- transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
- if (it) MaterialTheme.colors.primary else colorResource(id = R.color.transparent)
- }
- val color = transition.animateColor(
- label = "isAssigned",
- transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
- fadeIn(tween(10000))
- colorResource(if (it) R.color.white else R.color.text_dimmed)
- }
- val borderColor = transition.animateColor(
- label = "isAssigned",
- transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
- fadeIn(tween(10000))
- if (it) MaterialTheme.colors.primary else colorResource(id = R.color.text_dimmed)
- }
- UserRow(
- username = member.displayName,
- color = colorResource(R.color.text_primary),
- extraContent = {
- Text(
- member.formattedUsername ?: "",
- color = colorResource(R.color.text_ternary)
- )
- }, endContent = {
- Image(
- painterResource(R.drawable.ic_close_white_24dp),
- null,
- colorFilter = ColorFilter.tint(color.value),
- modifier = Modifier
- .rotate(rotation.value)
- .size(24.dp)
- .background(
- backgroundColor.value,
- CircleShape
- )
- .border(
- 2.dp,
- borderColor.value,
- CircleShape
- )
- .padding(3.dp)
- )
- }, modifier = Modifier
- .clickable {
- member.id?.let { onAssignClick(it) }
- }
- .padding(30.dp, 12.dp)
- .heightIn(min = 24.dp)
- .fillMaxWidth()
- )
- }
- }
-}
\ No newline at end of file
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 1166cb6c3..6b7652bb6 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
@@ -12,7 +12,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
-import com.habitrpg.android.habitica.databinding.FragmentSkillsBinding
+import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.Skill
@@ -32,17 +32,17 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject
-class SkillsFragment : BaseMainFragment() {
+class SkillsFragment : BaseMainFragment() {
internal var adapter: SkillsRecyclerViewAdapter? = null
private var selectedSkill: Skill? = null
- override var binding: FragmentSkillsBinding? = null
+ override var binding: FragmentRecyclerviewBinding? = null
@Inject
lateinit var userViewModel: MainUserViewModel
- override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSkillsBinding {
- return FragmentSkillsBinding.inflate(inflater, container, false)
+ override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRecyclerviewBinding {
+ return FragmentRecyclerviewBinding.inflate(inflater, container, false)
}
override fun onCreateView(
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
index 5b138b3ac..1e18e3b43 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/theme/HabiticaTheme.kt
@@ -5,6 +5,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material.Typography
import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
@@ -12,7 +13,10 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.ContextCompat
import com.google.android.material.composethemeadapter.createMdcTheme
+import com.habitrpg.android.habitica.R
+import com.habitrpg.common.habitica.extensions.getThemeColor
@Composable
fun HabiticaTheme(
@@ -113,6 +117,36 @@ val Typography.subtitle3
)
object HabiticaTheme {
val typography: Typography
- @Composable
- get() = MaterialTheme.typography
-}
\ No newline at end of file
+ @Composable
+ get() = MaterialTheme.typography
+
+
+ val shapes: Shapes
+ @Composable
+ get() = MaterialTheme.shapes
+
+ val colors: HabiticaColors
+ @Composable
+ get() {
+ val context = LocalContext.current
+ return HabiticaColors(
+ windowBackground = Color(context.getThemeColor(R.attr.colorWindowBackground)),
+ contentBackground = Color(context.getThemeColor(R.attr.colorContentBackground)),
+ contentBackgroundOffset = Color(context.getThemeColor(R.attr.colorContentBackgroundOffset)),
+ textPrimary = Color(context.getThemeColor(R.attr.textColorPrimary)),
+ textSecondary = Color(context.getThemeColor(R.attr.textColorSecondary)),
+ textTertiary = Color(context.getThemeColor(R.attr.colorTertiary)),
+ textDimmed = Color(ContextCompat.getColor(context, R.color.text_dimmed)),
+ )
+ }
+}
+
+class HabiticaColors(
+ val windowBackground: Color,
+ val contentBackground: Color,
+ val contentBackgroundOffset: Color,
+ val textPrimary: Color,
+ val textSecondary: Color,
+ val textTertiary: Color,
+ val textDimmed: Color
+)
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
index b9e1b0460..451484c8b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -32,7 +33,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
@@ -44,6 +44,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
@@ -71,6 +72,7 @@ fun UserLevelText(user: User) {
@Composable
fun AppHeaderView(
viewModel: MainUserViewModel,
+ onMemberRowClicked: (List) -> Unit
) {
val user by viewModel.user.observeAsState(null)
val teamPlan by viewModel.currentTeamPlan.collectAsState(null)
@@ -82,6 +84,9 @@ fun AppHeaderView(
Modifier
.size(110.dp, 100.dp)
.padding(end = 16.dp)
+ .clickable {
+ MainNavigationController.navigate(R.id.avatarOverviewFragment)
+ }
)
Column(modifier = Modifier.height(100.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {
@@ -112,6 +117,20 @@ fun AppHeaderView(
value = user?.stats?.mp ?: 0.0,
maxValue = user?.stats?.maxMP?.toDouble() ?: 0.0,
displayCompact = teamPlan != null,
+ modifier = Modifier.weight(1f)
+ .clickable {
+ MainNavigationController.navigate(R.id.skillsFragment)
+ }
+ )
+ } else if ((user?.stats?.lvl ?: 0) < 10) {
+ LabeledBar(
+ icon = HabiticaIconsHelper.imageOfMagic(),
+ label = stringResource(R.string.unlock_level, 10),
+ color = colorResource(R.color.mpColor),
+ value = 0.0,
+ maxValue = 1.0,
+ displayCompact = teamPlan != null,
+ disabled = true,
modifier = Modifier.weight(1f)
)
}
@@ -162,10 +181,7 @@ fun AppHeaderView(
colorResource(R.color.window_background)
)
.clickable {
- MainNavigationController.navigate(
- R.id.guildFragment,
- bundleOf("groupID" to teamPlan?.id)
- )
+ teamPlanMembers?.let { onMemberRowClicked(it) }
}
) {
for (member in teamPlanMembers?.filter { it.id != user?.id }?.take(6) ?: emptyList()) {
@@ -185,25 +201,16 @@ fun AppHeaderView(
}
}
}
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (user?.hasClass == true) {
- val icon = when (user?.stats?.habitClass) {
- "warrior" -> HabiticaIconsHelper.imageOfWarriorLightBg().asImageBitmap()
- "wizard" -> HabiticaIconsHelper.imageOfMageLightBg().asImageBitmap()
- "healer" -> HabiticaIconsHelper.imageOfHealerLightBg().asImageBitmap()
- "rogue" -> HabiticaIconsHelper.imageOfRogueLightBg().asImageBitmap()
- else -> null
- }
- if (icon != null) {
- Image(bitmap = icon, "", modifier = Modifier.padding(end = 4.dp))
- }
- }
+ Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.defaultMinSize(minHeight = 28.dp)) {
+ ClassIcon(className = user?.stats?.habitClass, hasClass = user?.hasClass ?: false, modifier = Modifier.padding(4.dp))
user?.let { UserLevelText(it) }
Spacer(Modifier.weight(1f))
user?.hourglassCount?.toDouble()
?.let { CurrencyText("hourglasses", it, modifier = Modifier.padding(end = 12.dp)) }
CurrencyText("gold", user?.stats?.gp ?: 0.0, modifier = Modifier.padding(end = 12.dp))
- CurrencyText("gems", user?.gemCount?.toDouble() ?: 0.0)
+ CurrencyText("gems", user?.gemCount?.toDouble() ?: 0.0, modifier = Modifier.clickable {
+ MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
+ })
}
}
}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ClassIcon.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ClassIcon.kt
new file mode 100644
index 000000000..8293820f8
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/ClassIcon.kt
@@ -0,0 +1,22 @@
+package com.habitrpg.android.habitica.ui.views
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+
+@Composable
+fun ClassIcon(className: String?, hasClass: Boolean, modifier: Modifier = Modifier) {
+ if (hasClass) {
+ val icon = when (className) {
+ "warrior" -> HabiticaIconsHelper.imageOfWarriorLightBg().asImageBitmap()
+ "wizard" -> HabiticaIconsHelper.imageOfMageLightBg().asImageBitmap()
+ "healer" -> HabiticaIconsHelper.imageOfHealerLightBg().asImageBitmap()
+ "rogue" -> HabiticaIconsHelper.imageOfRogueLightBg().asImageBitmap()
+ else -> null
+ }
+ if (icon != null) {
+ Image(bitmap = icon, "", modifier = modifier)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt
deleted file mode 100644
index 052718b98..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentOverviewView.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-package com.habitrpg.android.habitica.ui.views
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.models.user.Outfit
-import com.habitrpg.android.habitica.models.user.Preferences
-import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
-import com.habitrpg.android.habitica.ui.theme.caption2
-
-@Composable
-fun OverviewItem(
- text: String,
- iconName: String?,
- modifier: Modifier = Modifier,
- isTwoHanded: Boolean = false
-) {
- val hasIcon = iconName?.isNotBlank() == true
- Column(
- horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier
- .width(70.dp)
- ) {
- Box(
- Modifier
- .size(70.dp)
- .clip(MaterialTheme.shapes.small)
- .background(colorResource(if (hasIcon) R.color.gray_700 else R.color.gray_10)),
- contentAlignment = Alignment.Center
- ) {
- if (isTwoHanded) {
- Image(painterResource(R.drawable.equipment_two_handed), null)
- } else if (hasIcon) {
- PixelArtView(
- imageName = iconName, Modifier
- .size(70.dp)
- )
- } else {
- Image(painterResource(R.drawable.equipment_nothing_equipped), null)
- }
- }
- Text(
- text,
- style = HabiticaTheme.typography.caption2,
- color = colorResource(R.color.gray_400),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(top = 4.dp)
- )
- }
-}
-
-@Composable
-fun EquipmentOverviewView(
- outfit: Outfit?,
- onEquipmentTap: (String, String?) -> Unit,
- modifier: Modifier = Modifier
-) {
- Column(
- verticalArrangement = Arrangement.spacedBy(18.dp),
- modifier = modifier
- .fillMaxWidth()
- .clip(MaterialTheme.shapes.medium)
- .background(colorResource(R.color.gray_50))
- .padding(12.dp)
- ) {
- Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- OverviewItem(stringResource(R.string.outfit_weapon), outfit?.weapon.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("weapon", null)
- })
- OverviewItem(stringResource(R.string.outfit_shield), outfit?.shield.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("shield", null)
- })
- OverviewItem(stringResource(R.string.outfit_head), outfit?.head.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("head", null)
- })
- OverviewItem(stringResource(R.string.outfit_armor), outfit?.armor.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("armor", null)
- })
- }
- Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- OverviewItem(
- stringResource(R.string.outfit_headAccessory),
- outfit?.headAccessory.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("headAccessory", null)
- })
- OverviewItem(stringResource(R.string.outfit_body), outfit?.body.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("body", null)
- })
- OverviewItem(stringResource(R.string.outfit_back), outfit?.back.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("back", null)
- })
- OverviewItem(
- stringResource(R.string.outfit_eyewear),
- outfit?.eyeWear.let { "shop_$it" }, Modifier.clickable {
- onEquipmentTap("eyewear", null)
- })
- }
- }
-}
-
-@Composable
-fun AvatarCustomizationOverviewView(
- preferences: Preferences?,
- onCustomizationTap: (String, String?) -> Unit,
- modifier: Modifier = Modifier
-) {
- Column(
- verticalArrangement = Arrangement.spacedBy(18.dp),
- modifier = modifier
- .fillMaxWidth()
- .clip(MaterialTheme.shapes.medium)
- .background(colorResource(R.color.gray_50))
- .padding(12.dp)
- ) {
- Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- OverviewItem(
- stringResource(R.string.avatar_shirt),
- preferences?.shirt.let { "${preferences?.size}_shirt$it" }, Modifier.clickable {
- onCustomizationTap("shirt", null)
- })
- OverviewItem(
- stringResource(R.string.avatar_skin),
- preferences?.skin.let { "skin_$it" },
- Modifier.clickable {
- onCustomizationTap("skin", null)
- })
- OverviewItem(
- stringResource(R.string.avatar_hair_color),
- if (preferences?.hair?.color != null && preferences.hair?.color != "") "hair_bangs_1_" + preferences.hair?.color else "",
- Modifier.clickable {
- onCustomizationTap("hair", "color")
- }
- )
- OverviewItem(
- stringResource(R.string.avatar_hair_bangs),
- if (preferences?.hair?.bangs != null && preferences.hair?.bangs != 0) "hair_bangs_" + preferences.hair?.bangs + "_" + preferences.hair?.color else "",
- Modifier.clickable {
- onCustomizationTap("hair", "bangs")
- }
- )
- }
- Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- OverviewItem(
- stringResource(R.string.avatar_style),
- if (preferences?.hair?.base != null && preferences.hair?.base != 0) "hair_base_" + preferences.hair?.base + "_" + preferences.hair?.color else "",
- Modifier.clickable {
- onCustomizationTap("hair", "base")
- }
- )
- OverviewItem(
- stringResource(R.string.avatar_mustache),
- if (preferences?.hair?.mustache != null && preferences.hair?.mustache != 0) "hair_mustache_" + preferences.hair?.mustache + "_" + preferences.hair?.color else "",
- Modifier.clickable {
- onCustomizationTap("hair", "mustache")
- }
- )
- OverviewItem(
- stringResource(R.string.avatar_beard),
- if (preferences?.hair?.beard != null && preferences.hair?.beard != 0) "hair_beard_" + preferences.hair?.beard + "_" + preferences.hair?.color else "",
- Modifier.clickable {
- onCustomizationTap("hair", "beard")
- }
- )
- OverviewItem(
- stringResource(R.string.avatar_flower),
- if (preferences?.hair?.flower != null && preferences.hair?.flower != 0) "hair_flower_" + preferences.hair?.flower else "",
- Modifier.clickable {
- onCustomizationTap("hair", "flower")
- }
- )
- }
- Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- OverviewItem(
- stringResource(R.string.avatar_wheelchair),
- preferences?.chair?.let { if (it.startsWith("handleless")) "chair_$it" else it })
- OverviewItem(
- stringResource(R.string.avatar_background),
- preferences?.background.let { "background_$it" })
- Box(Modifier.size(70.dp))
- Box(Modifier.size(70.dp))
- }
- }
-}
-
-@Preview
-@Composable
-fun EquipmentOverviewItemPreview() {
- Column(Modifier.width(320.dp)) {
- Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- OverviewItem("Main-Hand", "shop_weapon_warrior_1")
- OverviewItem("Off-Hand", null, isTwoHanded = true)
- OverviewItem("Armor", null)
- }
- EquipmentOverviewView(null, { _, _ -> })
- AvatarCustomizationOverviewView(null, { _, _ -> })
- }
-}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/GroupPlanMemberList.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/GroupPlanMemberList.kt
new file mode 100644
index 000000000..034e03f6b
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/GroupPlanMemberList.kt
@@ -0,0 +1,201 @@
+package com.habitrpg.android.habitica.ui.views
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.lazy.LazyColumn
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+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.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
+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.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.models.auth.LocalAuthentication
+import com.habitrpg.android.habitica.models.members.Member
+import com.habitrpg.android.habitica.models.user.Authentication
+import com.habitrpg.android.habitica.models.user.Profile
+import com.habitrpg.android.habitica.models.user.Stats
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.common.habitica.extensions.getThemeColor
+import kotlin.random.Random
+
+@Composable
+fun GroupPlanMemberList(
+ members: List,
+ onMemberClicked: (String) -> Unit,
+ onMoreClicked: (Member) -> Unit
+) {
+ LazyColumn {
+ for (member in members) {
+ item {
+ MemberItem(
+ member,
+ onMemberClicked,
+ onMoreClicked,
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun MemberItem(
+ member: Member,
+ onMemberClicked: (String) -> Unit,
+ onMoreClicked: (Member) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier
+ .fillMaxWidth()
+ .clip(HabiticaTheme.shapes.large)
+ .background(HabiticaTheme.colors.windowBackground)
+ .clickable {
+ member.id?.let { onMemberClicked(it) }
+ }
+ ) {
+ TextButton(
+ onClick = { onMoreClicked(member) }, modifier = Modifier
+ .size(32.dp)
+ .background(
+ Color(LocalContext.current.getThemeColor(R.attr.colorAccent)),
+ WobblyCircle
+ )
+ .align(Alignment.TopEnd)
+ ) {
+ Image(painterResource(R.drawable.menu_messages), null)
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.padding(14.dp)
+ ) {
+ ComposableAvatarView(avatar = member, modifier = Modifier.size(94.dp, 98.dp))
+ Column(verticalArrangement = Arrangement.SpaceBetween, modifier = Modifier.height(100.dp)) {
+ Text(
+ member.displayName,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 16.sp,
+ color = HabiticaTheme.colors.textPrimary
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ member.formattedUsername ?: "",
+ color = HabiticaTheme.colors.textSecondary
+ )
+ Spacer(
+ Modifier
+ .weight(1.0f)
+ .background(Color.Red)
+ )
+ ClassIcon(
+ member.stats?.habitClass,
+ member.hasClass,
+ modifier = Modifier.size(18.dp)
+ )
+ BuffIcon(member.stats?.isBuffed)
+ CurrencyText(currency = "gold", value = member.stats?.gp ?: 0.0)
+ }
+ LabeledBar(
+ color = colorResource(R.color.hpColor),
+ barColor = HabiticaTheme.colors.contentBackgroundOffset,
+ value = member.stats?.hp ?: 0.0,
+ maxValue = (member.stats?.maxHealth ?: 0).toDouble(),
+ displayCompact = true
+ )
+ LabeledBar(
+ color = colorResource(R.color.xpColor),
+ barColor = HabiticaTheme.colors.contentBackgroundOffset,
+ value = member.stats?.exp ?: 0.0,
+ maxValue = (member.stats?.toNextLevel ?: 0).toDouble(),
+ displayCompact = true
+ )
+ if (member.hasClass) {
+ LabeledBar(
+ color = colorResource(R.color.mpColor),
+ barColor = HabiticaTheme.colors.contentBackgroundOffset,
+ value = member.stats?.mp ?: 0.0,
+ maxValue = (member.stats?.maxMP ?: 0).toDouble(),
+ displayCompact = true
+ )
+ }
+ Row(horizontalArrangement = Arrangement.SpaceBetween) {
+ Text(
+ stringResource(R.string.level_unabbreviated, member.stats?.lvl ?: 0),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Normal,
+ color = HabiticaTheme.colors.textPrimary
+ )
+ Text(
+ "", fontWeight = FontWeight.SemiBold, fontSize = 14.sp,
+ color = HabiticaTheme.colors.textPrimary
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun BuffIcon(buffed: Boolean?, modifier: Modifier = Modifier) {
+ if (buffed == true) {
+ Image(HabiticaIconsHelper.imageOfBuffIcon().asImageBitmap(), null, modifier = modifier)
+ }
+}
+
+private class MemberProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() {
+ val list = mutableListOf()
+ for (x in 0..5) {
+ val member = Member()
+ member.profile = Profile()
+ member.profile?.name = "User $x"
+ member.authentication = Authentication()
+ member.authentication?.localAuthentication = LocalAuthentication()
+ member.authentication?.localAuthentication?.username = "user${x}"
+ member.stats = Stats()
+ member.stats?.hp = Random.nextDouble()
+ member.stats?.maxHealth = 50
+ member.stats?.toNextLevel = Random.nextInt()
+ member.stats?.exp =
+ Random.nextDouble(until = (member.stats?.toNextLevel ?: 0).toDouble())
+ member.stats?.maxMP = Random.nextInt()
+ member.stats?.mp = Random.nextDouble(until = (member.stats?.maxMP ?: 0).toDouble())
+ member.stats?.lvl = Random.nextInt()
+ list.add(member)
+ }
+ return list.asSequence()
+ }
+}
+
+@Composable
+@Preview
+private fun Preview(@PreviewParameter(MemberProvider::class) member: Member) {
+ MemberItem(member = member, onMemberClicked = {}, onMoreClicked = {})
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LabeledBar.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LabeledBar.kt
index 7d05e9446..cd7179d3f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LabeledBar.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/LabeledBar.kt
@@ -6,12 +6,14 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.ProgressIndicatorDefaults
@@ -19,43 +21,50 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+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 java.math.RoundingMode
-import java.text.NumberFormat
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.common.habitica.helpers.NumberAbbreviator
@Composable
fun LabeledBar(
- icon: Bitmap,
- label: String,
- color: Color,
+ modifier: Modifier = Modifier,
+ icon: Bitmap? = null,
+ label: String? = null,
+ color: Color = colorResource(R.color.brand),
+ barColor: Color = HabiticaTheme.colors.windowBackground,
value: Double,
maxValue: Double,
displayCompact: Boolean,
- modifier: Modifier = Modifier
+ disabled: Boolean = false
) {
- val formatter = NumberFormat.getInstance()
- formatter.maximumFractionDigits = 1
- formatter.roundingMode = RoundingMode.UP
- formatter.isGroupingUsed = true
-
val cleanedMaxVlaue = java.lang.Double.max(1.0, maxValue)
val animatedValue = animateFloatAsState(
targetValue = value.toFloat(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
).value
- Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
- AnimatedVisibility(
- visible = !displayCompact,
- enter = slideInHorizontally { -18 },
- exit = slideOutHorizontally { -18 }) {
- Image(icon.asImageBitmap(), null, modifier = Modifier.padding(end = 8.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.alpha(if (disabled) 0.5f else 1.0f)
+ ) {
+ icon?.let {
+ AnimatedVisibility(visible = !displayCompact,
+ enter = slideInHorizontally { -18 },
+ exit = slideOutHorizontally { -18 }) {
+ Image(
+ it.asImageBitmap(), null, modifier = Modifier.padding(end = 8.dp)
+ )
+ }
}
Column(modifier = Modifier.weight(1f)) {
LinearProgressIndicator(
@@ -64,7 +73,7 @@ fun LabeledBar(
.fillMaxWidth()
.clip(CircleShape)
.height(8.dp),
- backgroundColor = colorResource(R.color.window_background),
+ backgroundColor = barColor,
color = color
)
AnimatedVisibility(visible = !displayCompact) {
@@ -72,15 +81,51 @@ fun LabeledBar(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp)
) {
- Text(
- "${formatter.format(animatedValue)} / ${formatter.format(cleanedMaxVlaue)}",
- fontSize = 12.sp,
- color = colorResource(R.color.text_ternary)
- )
+ if (!disabled) {
+ Text(
+ "${NumberAbbreviator.abbreviate(LocalContext.current, animatedValue)} / ${NumberAbbreviator.abbreviate(LocalContext.current, cleanedMaxVlaue)}",
+ fontSize = 12.sp,
+ color = colorResource(R.color.text_ternary)
+ )
+ }
Spacer(Modifier.weight(1f))
- Text(label, fontSize = 12.sp, color = colorResource(R.color.text_ternary))
+ if (label != null) {
+ Text(label, fontSize = 12.sp, color = colorResource(R.color.text_ternary))
+ }
}
}
}
}
+}
+
+@Composable
+@Preview
+private fun Preview() {
+ Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.width(180.dp)) {
+ LabeledBar(
+ icon = HabiticaIconsHelper.imageOfHeartLightBg(),
+ label = stringResource(id = R.string.health),
+ color = colorResource(R.color.hpColor),
+ value = 10.0,
+ maxValue = 50.0,
+ displayCompact = false
+ )
+ LabeledBar(
+ icon = HabiticaIconsHelper.imageOfExperience(),
+ label = stringResource(id = R.string.XP_default),
+ color = colorResource(R.color.xpColor),
+ value = 100123.0,
+ maxValue = 50000000000000.0,
+ displayCompact = false
+ )
+ LabeledBar(
+ icon = HabiticaIconsHelper.imageOfMagic(),
+ label = stringResource(id = R.string.unlock_level, 10),
+ color = colorResource(R.color.mpColor),
+ value = 10.0,
+ maxValue = 5000.0,
+ displayCompact = false,
+ disabled = true
+ )
+ }
}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/WobblyCircle.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/WobblyCircle.kt
new file mode 100644
index 000000000..27e3e3e1e
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/WobblyCircle.kt
@@ -0,0 +1,23 @@
+package com.habitrpg.android.habitica.ui.views
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+
+object WobblyCircle : Shape {
+ override fun createOutline(
+ size: Size, layoutDirection: LayoutDirection, density: Density
+ ): Outline {
+ val path = Path().apply {
+ addOval(Rect(Offset(0f, -size.height / 0.4f),
+ Size(size.width * 1.8f, size.height * 1.6f)))
+ }
+
+ return Outline.Generic(path)
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentItemRow.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt
similarity index 96%
rename from Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentItemRow.kt
rename to Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt
index 57612a492..bac0cd229 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/EquipmentItemRow.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt
@@ -1,4 +1,4 @@
-package com.habitrpg.android.habitica.ui.views
+package com.habitrpg.android.habitica.ui.views.equipment
import android.content.Context
import android.util.AttributeSet
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewItem.kt
deleted file mode 100644
index 5c722cc5d..000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewItem.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.habitrpg.android.habitica.ui.views.equipment
-
-import android.content.Context
-import android.graphics.drawable.BitmapDrawable
-import android.util.AttributeSet
-import android.view.View
-import android.widget.LinearLayout
-import androidx.core.content.ContextCompat
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.EquipmentOverviewItemBinding
-import com.habitrpg.common.habitica.extensions.layoutInflater
-import com.habitrpg.common.habitica.extensions.loadImage
-import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
-
-class EquipmentOverviewItem @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr) {
-
- private var binding: EquipmentOverviewItemBinding = EquipmentOverviewItemBinding.inflate(context.layoutInflater, this)
-
- init {
- if (attrs != null) {
- val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.EquipmentOverviewItem)
- binding.titleView.text = styledAttrs.getString(R.styleable.EquipmentOverviewItem_title)
- styledAttrs.recycle()
- }
- orientation = VERTICAL
- }
-
- var identifier: String = ""
-
- fun set(key: String?, isTwoHanded: Boolean = false, isDisabledFromTwoHand: Boolean = false) {
- identifier = key ?: ""
- binding.twoHandedIndicator.setImageDrawable(null)
- if (identifier.isNotEmpty() && !identifier.endsWith("base_0")) {
- binding.iconView.loadImage("shop_$key")
- binding.localIconView.visibility = View.GONE
- binding.iconView.visibility = View.VISIBLE
- binding.iconWrapper.background = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_content)
- if (isTwoHanded) {
- binding.twoHandedIndicator.setImageDrawable(BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfTwoHandedIcon()))
- }
- } else {
- binding.localIconView.visibility = View.VISIBLE
- binding.iconView.visibility = View.GONE
- if (isDisabledFromTwoHand) {
- binding.iconWrapper.background = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_content)
- binding.localIconView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.equipment_two_handed))
- } else {
- binding.iconWrapper.background = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_gray_10)
- binding.localIconView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.equipment_nothing_equipped))
- }
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
index 9d9a3ba0b..2928f2560 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
@@ -1,47 +1,217 @@
-package com.habitrpg.android.habitica.ui.views.equipment
+package com.habitrpg.android.habitica.ui.views
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import androidx.core.content.ContextCompat
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.EquipmentOverviewViewBinding
-import com.habitrpg.common.habitica.extensions.layoutInflater
-import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.models.user.Outfit
+import com.habitrpg.android.habitica.models.user.Preferences
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.theme.caption2
-class EquipmentOverviewView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : LinearLayout(context, attrs, defStyleAttr) {
-
- var onNavigate: ((String, String) -> Unit)? = null
- private var binding: EquipmentOverviewViewBinding = EquipmentOverviewViewBinding.inflate(context.layoutInflater, this)
-
- init {
- background = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_gray_50)
- setScaledPadding(context, 12, 12, 12, 12)
- orientation = VERTICAL
-
- binding.weaponItem.setOnClickListener { onNavigate?.invoke("weapon", binding.weaponItem.identifier) }
- binding.shieldItem.setOnClickListener { onNavigate?.invoke("shield", binding.shieldItem.identifier) }
- binding.headItem.setOnClickListener { onNavigate?.invoke("head", binding.headItem.identifier) }
- binding.armorItem.setOnClickListener { onNavigate?.invoke("armor", binding.armorItem.identifier) }
- binding.headAccessoryItem.setOnClickListener { onNavigate?.invoke("headAccessory", binding.headAccessoryItem.identifier) }
- binding.bodyItem.setOnClickListener { onNavigate?.invoke("body", binding.bodyItem.identifier) }
- binding.backItem.setOnClickListener { onNavigate?.invoke("back", binding.backItem.identifier) }
- binding.eyewearItem.setOnClickListener { onNavigate?.invoke("eyewear", binding.eyewearItem.identifier) }
- }
-
- fun updateData(outfit: Outfit?, isWeaponTwoHanded: Boolean = false) {
- binding.weaponItem.set(outfit?.weapon, isWeaponTwoHanded)
- binding.shieldItem.set(outfit?.shield, false, isWeaponTwoHanded)
- binding.headItem.set(outfit?.head)
- binding.armorItem.set(outfit?.armor)
- binding.headAccessoryItem.set(outfit?.headAccessory)
- binding.bodyItem.set(outfit?.body)
- binding.backItem.set(outfit?.back)
- binding.eyewearItem.set(outfit?.eyeWear)
+@Composable
+fun OverviewItem(
+ text: String,
+ iconName: String?,
+ modifier: Modifier = Modifier,
+ isTwoHanded: Boolean = false
+) {
+ val hasIcon = iconName?.isNotBlank() == true
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier
+ .width(70.dp)
+ ) {
+ Box(
+ Modifier
+ .size(70.dp)
+ .clip(MaterialTheme.shapes.small)
+ .background(colorResource(if (hasIcon) R.color.gray_700 else R.color.gray_10)),
+ contentAlignment = Alignment.Center
+ ) {
+ if (isTwoHanded) {
+ Image(painterResource(R.drawable.equipment_two_handed), null)
+ } else if (hasIcon) {
+ PixelArtView(
+ imageName = iconName, Modifier
+ .size(70.dp)
+ )
+ } else {
+ Image(painterResource(R.drawable.equipment_nothing_equipped), null)
+ }
+ }
+ Text(
+ text,
+ style = HabiticaTheme.typography.caption2,
+ color = colorResource(R.color.gray_400),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(top = 4.dp)
+ )
}
}
+
+@Composable
+fun EquipmentOverviewView(
+ outfit: Outfit?,
+ onEquipmentTap: (String, String?) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(18.dp),
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(MaterialTheme.shapes.medium)
+ .background(colorResource(R.color.gray_50))
+ .padding(12.dp)
+ ) {
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ OverviewItem(stringResource(R.string.outfit_weapon), outfit?.weapon.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("weapon", null)
+ })
+ OverviewItem(stringResource(R.string.outfit_shield), outfit?.shield.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("shield", null)
+ })
+ OverviewItem(stringResource(R.string.outfit_head), outfit?.head.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("head", null)
+ })
+ OverviewItem(stringResource(R.string.outfit_armor), outfit?.armor.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("armor", null)
+ })
+ }
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ OverviewItem(
+ stringResource(R.string.outfit_headAccessory),
+ outfit?.headAccessory.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("headAccessory", null)
+ })
+ OverviewItem(stringResource(R.string.outfit_body), outfit?.body.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("body", null)
+ })
+ OverviewItem(stringResource(R.string.outfit_back), outfit?.back.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("back", null)
+ })
+ OverviewItem(
+ stringResource(R.string.outfit_eyewear),
+ outfit?.eyeWear.let { "shop_$it" }, Modifier.clickable {
+ onEquipmentTap("eyewear", null)
+ })
+ }
+ }
+}
+
+@Composable
+fun AvatarCustomizationOverviewView(
+ preferences: Preferences?,
+ onCustomizationTap: (String, String?) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(18.dp),
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(MaterialTheme.shapes.medium)
+ .background(colorResource(R.color.gray_50))
+ .padding(12.dp)
+ ) {
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ OverviewItem(
+ stringResource(R.string.avatar_shirt),
+ preferences?.shirt.let { "${preferences?.size}_shirt$it" }, Modifier.clickable {
+ onCustomizationTap("shirt", null)
+ })
+ OverviewItem(
+ stringResource(R.string.avatar_skin),
+ preferences?.skin.let { "skin_$it" },
+ Modifier.clickable {
+ onCustomizationTap("skin", null)
+ })
+ OverviewItem(
+ stringResource(R.string.avatar_hair_color),
+ if (preferences?.hair?.color != null && preferences.hair?.color != "") "hair_bangs_1_" + preferences.hair?.color else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "color")
+ }
+ )
+ OverviewItem(
+ stringResource(R.string.avatar_hair_bangs),
+ if (preferences?.hair?.bangs != null && preferences.hair?.bangs != 0) "hair_bangs_" + preferences.hair?.bangs + "_" + preferences.hair?.color else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "bangs")
+ }
+ )
+ }
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ OverviewItem(
+ stringResource(R.string.avatar_style),
+ if (preferences?.hair?.base != null && preferences.hair?.base != 0) "hair_base_" + preferences.hair?.base + "_" + preferences.hair?.color else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "base")
+ }
+ )
+ OverviewItem(
+ stringResource(R.string.avatar_mustache),
+ if (preferences?.hair?.mustache != null && preferences.hair?.mustache != 0) "hair_mustache_" + preferences.hair?.mustache + "_" + preferences.hair?.color else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "mustache")
+ }
+ )
+ OverviewItem(
+ stringResource(R.string.avatar_beard),
+ if (preferences?.hair?.beard != null && preferences.hair?.beard != 0) "hair_beard_" + preferences.hair?.beard + "_" + preferences.hair?.color else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "beard")
+ }
+ )
+ OverviewItem(
+ stringResource(R.string.avatar_flower),
+ if (preferences?.hair?.flower != null && preferences.hair?.flower != 0) "hair_flower_" + preferences.hair?.flower else "",
+ Modifier.clickable {
+ onCustomizationTap("hair", "flower")
+ }
+ )
+ }
+ Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
+ OverviewItem(
+ stringResource(R.string.avatar_wheelchair),
+ preferences?.chair?.let { if (it.startsWith("handleless")) "chair_$it" else it })
+ OverviewItem(
+ stringResource(R.string.avatar_background),
+ preferences?.background.let { "background_$it" })
+ Box(Modifier.size(70.dp))
+ Box(Modifier.size(70.dp))
+ }
+ }
+}
+
+@Preview
+@Composable
+fun EquipmentOverviewItemPreview() {
+ Column(Modifier.width(320.dp)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ OverviewItem("Main-Hand", "shop_weapon_warrior_1")
+ OverviewItem("Off-Hand", null, isTwoHanded = true)
+ OverviewItem("Armor", null)
+ }
+ EquipmentOverviewView(null, { _, _ -> })
+ AvatarCustomizationOverviewView(null, { _, _ -> })
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignSheet.kt
new file mode 100644
index 000000000..c8d2d6c58
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignSheet.kt
@@ -0,0 +1,132 @@
+package com.habitrpg.android.habitica.ui.views.tasks
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.ColorFilter
+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.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.models.members.Member
+import com.habitrpg.android.habitica.ui.views.UserRow
+
+@Composable
+fun AssignSheet(
+ members: List,
+ assignedMembers: List,
+ onAssignClick: (String) -> Unit,
+ onCloseClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(modifier) {
+ Box {
+ Text(
+ stringResource(R.string.assign_to),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = colorResource(R.color.gray_200),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp)
+ )
+ TextButton(
+ onClick = onCloseClick,
+ colors = ButtonDefaults.textButtonColors(),
+ modifier = Modifier.align(Alignment.CenterEnd)
+ ) {
+ Text(stringResource(R.string.done))
+ }
+ }
+ for (member in members) {
+ val isAssigned = assignedMembers.contains(member.id)
+ val transition = updateTransition(isAssigned, label = "isAssigned")
+ val rotation = transition.animateFloat(
+ label = "isAssigned",
+ transitionSpec = { spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow) }) {
+ if (it) 0f else 45f
+ }
+ val backgroundColor = transition.animateColor(
+ label = "isAssigned",
+ transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
+ if (it) MaterialTheme.colors.primary else colorResource(id = R.color.transparent)
+ }
+ val color = transition.animateColor(
+ label = "isAssigned",
+ transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
+ fadeIn(tween(10000))
+ colorResource(if (it) R.color.white else R.color.text_dimmed)
+ }
+ val borderColor = transition.animateColor(
+ label = "isAssigned",
+ transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
+ fadeIn(tween(10000))
+ if (it) MaterialTheme.colors.primary else colorResource(id = R.color.text_dimmed)
+ }
+ UserRow(
+ username = member.displayName,
+ color = colorResource(R.color.text_primary),
+ extraContent = {
+ Text(
+ member.formattedUsername ?: "",
+ color = colorResource(R.color.text_ternary)
+ )
+ }, endContent = {
+ Image(
+ painterResource(R.drawable.ic_close_white_24dp),
+ null,
+ colorFilter = ColorFilter.tint(color.value),
+ modifier = Modifier
+ .rotate(rotation.value)
+ .size(24.dp)
+ .background(
+ backgroundColor.value,
+ CircleShape
+ )
+ .border(
+ 2.dp,
+ borderColor.value,
+ CircleShape
+ )
+ .padding(3.dp)
+ )
+ }, modifier = Modifier
+ .clickable {
+ member.id?.let { onAssignClick(it) }
+ }
+ .padding(30.dp, 12.dp)
+ .heightIn(min = 24.dp)
+ .fillMaxWidth()
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt
new file mode 100644
index 000000000..0e0669568
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/AssignedView.kt
@@ -0,0 +1,81 @@
+package com.habitrpg.android.habitica.ui.views.tasks
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.models.Assignable
+import com.habitrpg.android.habitica.ui.views.CompletedAt
+import com.habitrpg.android.habitica.ui.views.UserRow
+import java.util.Date
+
+@Composable
+fun AssignedView(
+ assigned: List,
+ completedAt: Map,
+ backgroundColor: Color,
+ color: Color,
+ onEditClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ showEditButton: Boolean = false
+) {
+ Column(modifier.fillMaxWidth()) {
+ val rowModifier = Modifier
+ .padding(vertical = 4.dp)
+ .background(
+ backgroundColor,
+ MaterialTheme.shapes.medium
+ )
+ .padding(15.dp, 12.dp)
+ .heightIn(min = 24.dp)
+ .fillMaxWidth()
+ for (assignable in assigned) {
+ UserRow(
+ username = assignable.identifiableName, modifier = rowModifier,
+ color = color,
+ extraContent = {
+ completedAt[assignable.id]?.let { CompletedAt(completedAt = it) }
+ }
+ )
+ }
+ if (showEditButton) {
+ Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
+ .clickable {
+ onEditClick()
+ }
+ .padding(vertical = 4.dp)
+ .background(
+ backgroundColor,
+ MaterialTheme.shapes.medium
+ )
+ .padding(15.dp, 12.dp)
+ .heightIn(min = 24.dp)
+ .fillMaxWidth()) {
+ Image(
+ painterResource(R.drawable.edit),
+ null,
+ colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
+ )
+ Text(
+ stringResource(R.string.edit_assignees), color = color,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
index 11e29dfe4..02b0b7feb 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
@@ -7,6 +7,10 @@ import java.text.DecimalFormat
object NumberAbbreviator {
+ fun abbreviate(context: Context?, number: Float, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
+ return abbreviate(context, number.toDouble(), numberOfDecimals, minForAbbrevation)
+ }
+
fun abbreviate(context: Context?, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
var usedNumber = number
var counter = 0
@@ -28,6 +32,7 @@ object NumberAbbreviator {
2 -> context?.getString(R.string.million_abbrev) ?: "m"
3 -> context?.getString(R.string.billion_abbrev) ?: "b"
4 -> context?.getString(R.string.trillion_abbrev) ?: "t"
+ 5 -> context?.getString(R.string.quadrillion_abbrev) ?: "q"
else -> ""
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
index 2e3a4a87b..d1c739c66 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
@@ -7,13 +7,10 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
-import androidx.core.content.ContextCompat
-import com.habitrpg.common.habitica.extensions.dpToPx
-import com.habitrpg.common.habitica.extensions.getThemeColor
-import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
-import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.databinding.ValueBarBinding
+import com.habitrpg.common.habitica.extensions.dpToPx
+import com.habitrpg.common.habitica.extensions.layoutInflater
import java.math.RoundingMode
import java.text.NumberFormat
@@ -95,7 +92,6 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
R.styleable.ValueBar,
0, 0
)
- setLightBackground(attributes?.getBoolean(R.styleable.ValueBar_lightBackground, !context.isUsingNightModeResources()) == true)
binding.progressBar.barForegroundColor = attributes?.getColor(R.styleable.ValueBar_barForegroundColor, 0) ?: 0
binding.progressBar.barPendingColor = attributes?.getColor(R.styleable.ValueBar_barPendingColor, 0) ?: 0
@@ -163,19 +159,6 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
binding.valueTextView.text = valueText
}
- fun setLightBackground(lightBackground: Boolean) {
- val textColor: Int
- /*if (lightBackground) {
- textColor = ContextCompat.getColor(context, R.color.text_ternary)
- binding.progressBar.setBackgroundResource(R.drawable.layout_rounded_bg_light_gray)
- } else {
- textColor = context.getThemeColor(R.attr.textColorPrimaryDark)
- binding.progressBar.setBackgroundResource(R.drawable.layout_rounded_bg_header_bar)
- }
- binding.valueTextView.setTextColor(textColor)
- binding.descriptionTextView.setTextColor(textColor)*/
- }
-
var animationDuration = 500L
var animationDelay = 0L
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index 327b79e24..f41d9112f 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -4,6 +4,7 @@
m
b
t
+ q
Gems
gold
Gold
diff --git a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt
index 7b30af6dd..6b2f43fab 100644
--- a/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt
+++ b/shared/src/commonMain/kotlin/com/habitrpg/shared/habitica/models/Avatar.kt
@@ -27,12 +27,14 @@ interface Avatar {
val hasClass: Boolean
get() {
- return preferences?.disableClasses != true && flags?.classSelected ?: true == true && stats?.habitClass?.isNotEmpty() == true
+ return preferences?.disableClasses != true
+ && flags?.classSelected != false
+ && stats?.habitClass?.isNotEmpty() == true
+ && (stats?.lvl ?: 0) >= 10
}
val currentMount: String?
get() = items?.currentMount ?: ""
-
val currentPet: String?
get() = items?.currentPet ?: ""
diff --git a/version.properties b/version.properties
index eca0c163d..1dc1c6bf1 100644
--- a/version.properties
+++ b/version.properties
@@ -1,2 +1,2 @@
NAME=4.1
-CODE=4771
\ No newline at end of file
+CODE=4781
\ No newline at end of file