Refactor Refactor

This commit is contained in:
Phillip Thelen 2022-12-05 10:48:23 +01:00
parent 8ebb743b10
commit d7523c7bdf
33 changed files with 916 additions and 900 deletions

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#801A181D" />
<corners android:radius="@dimen/bar_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -2,7 +2,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="?colorPrimaryDark" />
<solid android:color="?colorPrimaryDistinct" />
<corners android:radius="4dp" android:topLeftRadius="8dp" android:topRightRadius="8dp" />
</shape>
</item>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="android.widget.LinearLayout">
<FrameLayout
android:id="@+id/icon_wrapper"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/layout_rounded_bg_content">
<com.habitrpg.common.habitica.views.PixelArtView
android:id="@+id/icon_view"
android:layout_width="@dimen/gear_image_size"
android:layout_height="@dimen/gear_image_size"
android:layout_gravity="center"/>
<ImageView
android:id="@+id/local_icon_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/equipment_nothing_equipped"
android:layout_gravity="center" />
<ImageView
android:id="@+id/two_handed_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"/>
</FrameLayout>
<TextView
android:id="@+id/title_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_dimmed"
style="@style/Caption2"
android:layout_marginTop="4dp"
android:gravity="center_horizontal"/>
</merge>

View file

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="android.widget.LinearLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="19dp">
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/weapon_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_weapon" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/shield_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_shield" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/head_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_head" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/armor_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_armor" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="6dp">
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/head_accessory_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_headAccessory" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/body_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_body" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/back_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_back" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp" />
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewItem
android:id="@+id/eyewear_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/outfit_eyewear" />
</LinearLayout>
</merge>

View file

@ -49,12 +49,12 @@
android:layout_marginEnd="8dp"
android:entries="@array/avatar_sizes"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarShirtView"
app:equipmentTitle="@string/avatar_shirt"/>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarSkinView"
@ -73,27 +73,27 @@
android:divider="?android:listDivider"
android:showDividers="middle"
android:background="@drawable/layout_rounded_bg_window">
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarHairColorView"
app:equipmentTitle="@string/avatar_color" />
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarHairBaseView"
app:equipmentTitle="@string/avatar_style" />
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarHairBangsView"
app:equipmentTitle="@string/avatar_bangs" />
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarHairBeardView"
app:equipmentTitle="@string/avatar_beard" />
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarHairMustacheView"
@ -112,34 +112,34 @@
android:divider="?android:listDivider"
android:showDividers="middle"
android:background="@drawable/layout_rounded_bg_window">
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatar_glasses_view"
app:equipmentTitle="@string/avatar_glasses"/>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarChairView"
app:equipmentTitle="@string/avatar_wheelchair"/>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatar_accent_view"
app:equipmentTitle="@string/avatar_accent" />
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatar_animal_ears_view"
android:visibility="gone"
app:equipmentTitle="@string/animal_ears"/>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatar_animal_tail_view"
android:visibility="gone"
app:equipmentTitle="@string/animal_tail"/>
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatar_headband_view"
@ -156,7 +156,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/layout_rounded_bg_window">
<com.habitrpg.android.habitica.ui.views.EquipmentItemRow
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentItemRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/avatarBackgroundView"

View file

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarSize="3dp"
android:scrollbarThumbVertical="@color/scrollbarThumb"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="@dimen/spacing_medium">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/row_padding">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SectionTitle"
android:text="@string/battle_gear"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equip_automatically"
android:layout_marginEnd="6dp"/>
<com.google.android.material.switchmaterial.SwitchMaterial
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/autoEquipSwitch"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
android:id="@+id/battlegear_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_medium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/row_padding">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SectionTitle"
android:text="@string/costume"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="@string/wear_costume"/>
<com.google.android.material.switchmaterial.SwitchMaterial
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/costumeSwitch"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
android:id="@+id/costume_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_medium" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarSize="3dp"
android:scrollbarThumbVertical="@color/scrollbarThumb"
android:scrollbars="vertical" />

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
tools:parentTag="android.widget.LinearLayout">
<LinearLayout
android:id="@+id/positive_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:foreground="?selectableItemBackground"
android:layout_margin="@dimen/spacing_large">
<ImageView
android:id="@+id/positive_image_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginBottom="@dimen/spacing_medium"
tools:src="@drawable/plus"
tools:background="@drawable/habit_scoring_circle_selected"/>
<TextView
android:id="@+id/positive_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/positive_habit_form"/>
</LinearLayout>
<LinearLayout
android:id="@+id/negative_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:foreground="?selectableItemBackground"
android:layout_margin="@dimen/spacing_large">
<ImageView
android:id="@+id/negative_image_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginBottom="@dimen/spacing_medium"
tools:src="@drawable/minus"
tools:background="@drawable/habit_scoring_circle"/>
<TextView
android:id="@+id/negative_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/negative_habit_form"
android:textSize="14sp"/>
</LinearLayout>
</merge>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:foreground="?selectableItemBackground">
<ImageView
android:id="@+id/image_view"
android:layout_width="57dp"
android:layout_height="57dp"
android:background="@drawable/layout_rounded_bg_task_form"
android:scaleType="center"
android:layout_marginBottom="@dimen/spacing_medium"/>
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/trivial"/>
</LinearLayout>

View file

@ -1269,6 +1269,7 @@
<string name="assign">Assign</string>
<string name="edit_assignees">Edit assignees</string>
<string name="assign_to">Assign to...</string>
<string name="promote_to_manager">Promote to Manager</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>
<item quantity="one">You, %d other</item>

View file

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

View file

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

View file

@ -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"
}

View file

@ -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<DrawerLayout>(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)
}

View file

@ -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<Assignable>,
completedAt: Map<String, Date>,
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<Member>,
assignedMembers: List<String>,
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()
)
}
}
}

View file

@ -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<FragmentSkillsBinding>() {
class SkillsFragment : BaseMainFragment<FragmentRecyclerviewBinding>() {
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(

View file

@ -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
}
@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
)

View file

@ -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<Member>) -> 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)))
})
}
}
}

View file

@ -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)
}
}
}

View file

@ -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, { _, _ -> })
}
}

View file

@ -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<Member>,
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<Member> {
override val values: Sequence<Member>
get() {
val list = mutableListOf<Member>()
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 = {})
}

View file

@ -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
)
}
}

View file

@ -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)
}
}

View file

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

View file

@ -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))
}
}
}
}

View file

@ -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, { _, _ -> })
}
}

View file

@ -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<Member>,
assignedMembers: List<String>,
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()
)
}
}
}

View file

@ -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<Assignable>,
completedAt: Map<String, Date>,
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)
)
}
}
}
}

View file

@ -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 -> ""
}
}

View file

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

View file

@ -4,6 +4,7 @@
<string name="million_abbrev">m</string>
<string name="billion_abbrev">b</string>
<string name="trillion_abbrev">t</string>
<string name="quadrillion_abbrev">q</string>
<string name="gems">Gems</string>
<string name="gold_plural">gold</string>
<string name="gold_capitalized">Gold</string>

View file

@ -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 ?: ""

View file

@ -1,2 +1,2 @@
NAME=4.1
CODE=4771
CODE=4781