diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index f4cd4bc47..475c54924 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -1405,6 +1405,9 @@ Check out the [Market](/shops/market) to buy just the eggs you need! Check out the [Market](/shops/market) to buy food or a Saddle to instantly raise your Pet! Check out the [Market](/shops/market) to buy standard and seasonal Hatching Potions! + Open Profile + Customize Avatar + Share Avatar You diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt index 996c907f8..c9efd542c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt @@ -1,8 +1,6 @@ package com.habitrpg.android.habitica.interactors -import android.graphics.Bitmap import android.view.ViewGroup -import androidx.core.graphics.scale import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding import com.habitrpg.android.habitica.helpers.SoundManager @@ -64,14 +62,6 @@ constructor( dialogAvatarView.setAvatar(requestValues.user) } - val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) - val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) - avatarView.setAvatar(requestValues.user) - var sharedImage: Bitmap? = null - avatarView.onAvatarImageReady { image -> - sharedImage = image?.scale(image.width * 3, image.height * 3, false) - } - val alert = HabiticaAlertDialog(requestValues.activity) alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel)) alert.setAdditionalContentView(customView) @@ -81,7 +71,16 @@ constructor( } } alert.addButton(R.string.share, false) { _, _ -> - requestValues.activity.shareContent("levelup", message, sharedImage) + MainScope().launchCatching { + val usecase = ShareAvatarUseCase() + usecase.callInteractor(ShareAvatarUseCase.RequestValues( + requestValues.activity, + requestValues.user, + requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel), + "levelup" + )) + } + } alert.isCelebratory = true diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt new file mode 100644 index 000000000..ae313dca1 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt @@ -0,0 +1,23 @@ +package com.habitrpg.android.habitica.interactors + +import android.graphics.Bitmap +import androidx.core.graphics.scale +import com.habitrpg.android.habitica.ui.activities.BaseActivity +import com.habitrpg.common.habitica.views.AvatarView +import com.habitrpg.shared.habitica.models.Avatar +import javax.inject.Inject + +class ShareAvatarUseCase @Inject +constructor() : UseCase() { + override suspend fun run(requestValues: RequestValues) { + val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true) + avatarView.setAvatar(requestValues.avatar) + var sharedImage: Bitmap? = null + avatarView.onAvatarImageReady { image -> + sharedImage = image?.scale(image.width * 3, image.height * 3, false) + requestValues.activity.shareContent(requestValues.identifier, requestValues.message, sharedImage) + } + } + + class RequestValues(val activity: BaseActivity, val avatar: Avatar, val message: String?, val identifier: String): UseCase.RequestValues +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt index 467a02a06..b551ea8a1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt @@ -224,11 +224,13 @@ abstract class BaseActivity : AppCompatActivity() { open fun hideConnectionProblem() { } - fun shareContent(identifier: String, message: String, image: Bitmap? = null) { + fun shareContent(identifier: String, message: String?, image: Bitmap? = null) { Analytics.sendEvent("shared", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("identifier" to identifier)) val sharingIntent = Intent(Intent.ACTION_SEND) sharingIntent.type = "*/*" - sharingIntent.putExtra(Intent.EXTRA_TEXT, message) + if (message?.isNotBlank() == true) { + sharingIntent.putExtra(Intent.EXTRA_TEXT, message) + } if (image != null) { val path = MediaStore.Images.Media.insertImage(this.contentResolver, image, "${(Date())}", null) if (path != null) { 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 68cc8b6bc..6c6fdbf24 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 @@ -17,9 +17,18 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.core.view.children import androidx.core.view.setPadding import androidx.drawerlayout.widget.DrawerLayout @@ -45,13 +54,13 @@ import com.habitrpg.android.habitica.helpers.Analytics import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.EventCategory import com.habitrpg.android.habitica.helpers.HitType -import com.habitrpg.common.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.NotificationOpenHandler import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.helpers.collectAsStateLifecycleAware import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase import com.habitrpg.android.habitica.interactors.NotifyUserUseCase +import com.habitrpg.android.habitica.interactors.ShareAvatarUseCase import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.UserQuestStatus @@ -61,7 +70,9 @@ 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.ComposableAvatarView import com.habitrpg.android.habitica.ui.views.GroupPlanMemberList +import com.habitrpg.android.habitica.ui.views.HabiticaButton 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 @@ -75,6 +86,7 @@ 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.helpers.ExceptionHandler +import com.habitrpg.common.habitica.helpers.MainNavigationController import com.habitrpg.common.habitica.helpers.launchCatching import com.habitrpg.common.habitica.views.AvatarView import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse @@ -284,6 +296,63 @@ open class MainActivity : BaseActivity(), SnackbarActivity { teamPlan = if (canShowTeamHeader) teamPlan else null, teamPlanMembers = teamPlanMembers, isMyProfile = true, + onAvatarClicked = { + showAsBottomSheet { dismiss -> + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(22.dp) + ) { + ComposableAvatarView(avatar = user) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(15.dp), + ) { + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + onClick = { + dismiss() + MainNavigationController.navigate(R.id.openProfileActivity) + }) { + Text(stringResource(id = R.string.open_profile)) + } + + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + onClick = { + dismiss() + MainNavigationController.navigate(R.id.avatarOverviewFragment) + }) { + Text(stringResource(id = R.string.customize_avatar)) + } + + HabiticaButton( + background = HabiticaTheme.colors.tintedUiSub, + color = Color.White, + onClick = { + dismiss() + user?.let { + val usecase = ShareAvatarUseCase() + lifecycleScope.launchCatching { + usecase.callInteractor( + ShareAvatarUseCase.RequestValues( + this@MainActivity, + it, + null, + "avatar_bottomsheet" + ) + ) + } + } + }) { + Text(stringResource(id = R.string.share_avatar)) + } + } + } + } + }, onMemberRowClicked = { showAsBottomSheet { onClose -> val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState( 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 72582c381..5e6d8786e 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 @@ -106,6 +106,7 @@ fun AppHeaderView( isMyProfile : Boolean = false, teamPlan : TeamPlan? = null, teamPlanMembers : List? = null, + onAvatarClicked: (() -> Unit)? = null, onMemberRowClicked : () -> Unit, onClassSelectionClicked: () -> Unit ) { @@ -117,7 +118,7 @@ fun AppHeaderView( .size(110.dp, 100.dp) .padding(end = 16.dp) .clickable { - MainNavigationController.navigate(R.id.avatarOverviewFragment) + onAvatarClicked?.invoke() } ) val animationValue = diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt index ace971c78..389c773d1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt @@ -71,7 +71,7 @@ private fun BottomSheetWrapper( content: @Composable (() -> Unit) -> Unit ) { val coroutineScope = rememberCoroutineScope() - val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) + val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true) var isSheetOpened by remember { mutableStateOf(false) } val systemUiController = rememberSystemUiController() diff --git a/build.gradle b/build.gradle index 358c13946..5c0337d41 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.0' + classpath 'com.android.tools.build:gradle:8.1.1' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.8' diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt index d92f56aef..e81c4d0c6 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt @@ -14,7 +14,6 @@ import android.text.TextUtils import android.util.AttributeSet import android.widget.FrameLayout import android.widget.ImageView -import androidx.core.graphics.drawable.toBitmap import androidx.core.view.marginStart import androidx.core.view.marginTop import coil.dispose @@ -73,8 +72,8 @@ class AvatarView : FrameLayout { if (BuildConfig.DEBUG && (avatar == null || avatarRectF == null)) { error("Assertion failed") } - val viewWidth = if (width > 0) width else layoutParams.width - val viewHeight = if (height > 0) height else layoutParams.height + val viewWidth = if (width > 0) width else (layoutParams?.width ?: 140) + val viewHeight = if (height > 0) height else (layoutParams?.height ?: 147) val canvasRect = Rect(0, 0, if (viewWidth > 0) viewWidth else 140.dpToPx(context), if (viewHeight > 0) viewHeight else 147.dpToPx(context)) if (canvasRect.isEmpty) return null avatarBitmap = Bitmap.createBitmap(