Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: refactor navigation on User Profile Screen and fix avatar upload bugs [AR-1204][AR-1210][AR-1211] #427

Merged
merged 19 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ kapt {
correctErrorTypes = true
}

configurations {
all {
resolutionStrategy {
// Force dependencies to resolve coroutines versions to native-mt variant
force(Libraries.Kotlin.coroutinesCore)
force(Libraries.Kotlin.coroutinesAndroid)
}
}
}

dependencies {
implementation("com.wire.kalium:kalium-logic")
Expand All @@ -81,7 +90,12 @@ dependencies {
implementation(Libraries.browser)
implementation(Libraries.dataStore)
implementation(Libraries.splashscreen)
// lifecycle

// Image handling
implementation(Libraries.coil)
implementation(Libraries.coilCompose)

/** lifecycle **/
// ViewModel
implementation(Libraries.Lifecycle.viewModel)
// ViewModel utilities for Compose
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@

</provider>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

</application>

</manifest>
13 changes: 13 additions & 0 deletions app/src/main/kotlin/com/wire/android/datastore/UserDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.wire.android.model.UserStatus
import dagger.hilt.android.qualifiers.ApplicationContext
Expand All @@ -24,6 +25,7 @@ class UserDataStore @Inject constructor(@ApplicationContext private val context:
private val SHOW_STATUS_RATIONALE_BUSY = booleanPreferencesKey("show_status_rationale_busy")
private val SHOW_STATUS_RATIONALE_AWAY = booleanPreferencesKey("show_status_rationale_away")
private val SHOW_STATUS_RATIONALE_NONE = booleanPreferencesKey("show_status_rationale_none")
private val USER_AVATAR_ASSET_ID = stringPreferencesKey("user_avatar_asset_id")
}

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = PREFERENCES_NAME)
Expand All @@ -39,6 +41,17 @@ class UserDataStore @Inject constructor(@ApplicationContext private val context:
preferences[getStatusKey(status)] ?: true
}

val avatarAssetId: Flow<String?> = context.dataStore.data
.map { preferences ->
preferences[USER_AVATAR_ASSET_ID]
}

suspend fun updateUserAvatarAssetId(newAssetId: String) {
context.dataStore.edit { preferences ->
preferences[USER_AVATAR_ASSET_ID] = newAssetId
}
}

suspend fun clear() {
context.dataStore.edit { it.clear() }
}
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import com.wire.android.util.DeviceLabel
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.feature.user.UploadUserAvatarUseCase
import com.wire.kalium.logic.feature.asset.GetPublicAssetUseCase
import com.wire.kalium.logic.feature.auth.AuthSession
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.user.GetSelfUserUseCase
Expand Down Expand Up @@ -76,7 +77,6 @@ class UseCaseModule {
fun getServerConfigUserCaseProvider(@KaliumCoreLogic coreLogic: CoreLogic) =
coreLogic.getAuthenticationScope().getServerConfig


@ViewModelScoped
@Provides
// TODO: kind of redundant to CurrentSession - need to rename CurrentSession
Expand All @@ -87,6 +87,13 @@ class UseCaseModule {
fun selfClientsUseCase(@CurrentSession currentSession: AuthSession, clientScopeProviderFactory: ClientScopeProvider.Factory) =
clientScopeProviderFactory.create(currentSession).clientScope.selfClients

@ViewModelScoped
@Provides
fun getPublicAsset(
@KaliumCoreLogic coreLogic: CoreLogic,
@CurrentSession currentSession: AuthSession
): GetPublicAssetUseCase = coreLogic.getSessionScope(currentSession).users.getPublicAsset

@ViewModelScoped
@Provides
fun uploadUserAvatar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import com.google.accompanist.navigation.animation.composable
ExperimentalMaterial3Api::class
)
@Composable
fun NavigationGraph(navController: NavHostController, startDestination: String, arguments: List<Any> = emptyList()) {
fun NavigationGraph(navController: NavHostController, startDestination: String, appInitialArgs: List<Any> = emptyList()) {
AnimatedNavHost(navController, startDestination) {
NavigationItem.values().onEach { item ->
composable(
route = item.getCanonicalRoute(),
content = { navBackStackEntry -> item.content(ContentParams(navBackStackEntry, arguments)) },
content = { navBackStackEntry -> item.content(ContentParams(navBackStackEntry, appInitialArgs)) },
enterTransition = { item.animationConfig.enterTransition },
exitTransition = { item.animationConfig.exitTransition }
)
Expand Down
29 changes: 3 additions & 26 deletions app/src/main/kotlin/com/wire/android/navigation/NavigationItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.wire.android.BuildConfig
import com.wire.android.navigation.NavigationItemDestinationsRoutes.CONVERSATION
import com.wire.android.navigation.NavigationItemDestinationsRoutes.CREATE_PERSONAL_ACCOUNT
Expand Down Expand Up @@ -44,20 +41,17 @@ import io.github.esentsov.PackagePrivate
enum class NavigationItem(
@PackagePrivate
internal val primaryRoute: String,
private val canonicalRoute: String,
val arguments: List<NamedNavArgument> = emptyList(),
private val canonicalRoute: String = primaryRoute,
open val content: @Composable (ContentParams) -> Unit,
val animationConfig: NavigationAnimationConfig = NavigationAnimationConfig.NoAnimation
) {
Welcome(
primaryRoute = WELCOME,
canonicalRoute = WELCOME,
content = { WelcomeScreen() }
),

Login(
primaryRoute = LOGIN,
canonicalRoute = LOGIN,
content = { contentParams ->
val serverConfig = contentParams.arguments.filterIsInstance<ServerConfig>().firstOrNull()
LoginScreen(serverConfig ?: ServerConfig.DEFAULT)
Expand All @@ -66,50 +60,38 @@ enum class NavigationItem(

CreateTeam(
primaryRoute = CREATE_TEAM,
canonicalRoute = CREATE_TEAM,
content = { UnderConstructionScreen("Create Team Screen") }
),

CreatePersonalAccount(
primaryRoute = CREATE_PERSONAL_ACCOUNT,
canonicalRoute = CREATE_PERSONAL_ACCOUNT,
content = { CreatePersonalAccountScreen(ServerConfig.STAGING) }
),

RemoveDevices(
primaryRoute = REMOVE_DEVICES,
canonicalRoute = REMOVE_DEVICES,
content = { RemoveDeviceScreen() }
),

Home(
primaryRoute = HOME,
canonicalRoute = HOME,
content = { HomeScreen(it.navBackStackEntry.arguments?.getString(EXTRA_HOME_TAB_ITEM), hiltViewModel()) },
arguments = listOf(
navArgument(EXTRA_HOME_TAB_ITEM) { type = NavType.StringType }
)
),

Settings(
primaryRoute = SETTINGS,
canonicalRoute = SETTINGS,
content = { SettingsScreen() },
),

Support(
primaryRoute = BuildConfig.SUPPORT_URL,
canonicalRoute = BuildConfig.SUPPORT_URL,
content = { },
),

UserProfile(
primaryRoute = USER_PROFILE,
canonicalRoute = "$USER_PROFILE/{$EXTRA_USER_ID}",
content = { UserProfileScreen() },
arguments = listOf(
navArgument(EXTRA_USER_ID) { type = NavType.StringType }
),
animationConfig = NavigationAnimationConfig.CustomAnimation(smoothSlideInFromRight(), smoothSlideOutFromLeft())
) {
override fun getRouteWithArgs(arguments: List<Any>): String {
Expand All @@ -120,21 +102,17 @@ enum class NavigationItem(

ProfileImagePicker(
primaryRoute = IMAGE_PICKER,
canonicalRoute = IMAGE_PICKER,
content = { AvatarPickerScreen(hiltViewModel()) },
),

Conversation(
primaryRoute = CONVERSATION,
canonicalRoute = "$CONVERSATION/{$EXTRA_CONVERSATION_ID}",
content = { ConversationScreen(hiltViewModel()) },
arguments = listOf(
navArgument(EXTRA_CONVERSATION_ID) { type = NavType.StringType }
)
content = { ConversationScreen(hiltViewModel()) }
) {
override fun getRouteWithArgs(arguments: List<Any>): String {
val conversationId: ConversationId? = arguments.filterIsInstance<ConversationId>().firstOrNull()
return if (conversationId != null) "$primaryRoute/${conversationId.mapIntoArgumentString()}" else primaryRoute
return conversationId?.run { "$primaryRoute/${mapIntoArgumentString()}" } ?: primaryRoute
}
};

Expand Down Expand Up @@ -170,7 +148,6 @@ object NavigationItemDestinationsRoutes {

private const val EXTRA_HOME_TAB_ITEM = "extra_home_tab_item"
private const val EXTRA_USER_ID = "extra_user_id"
private const val EXTRA_INITIAL_BITMAP = "extra_initial_bitmap"
const val EXTRA_CONVERSATION_ID = "extra_conversation_id"

fun NavigationItem.isExternalRoute() = this.getRouteWithArgs().startsWith("http")
Expand Down
61 changes: 14 additions & 47 deletions app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wire.android.ui.common

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand All @@ -15,53 +15,20 @@ 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.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import coil.compose.rememberAsyncImagePainter
import com.wire.android.R
import com.wire.android.model.UserStatus
import com.wire.android.ui.theme.wireDimensions


@Composable
fun UserProfileAvatar(
avatarUrl: String = "",
status: UserStatus = UserStatus.NONE,
size: Dp = MaterialTheme.wireDimensions.userAvatarDefaultSize,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.wrapContentSize()
.clip(CircleShape)
.then(if (onClick != null) Modifier.clickable { onClick() } else Modifier)
.wrapContentSize()
.padding(MaterialTheme.wireDimensions.userAvatarClickablePadding)
) {
Image(
painter = painterResource(getAvatarAsDrawable(avatarUrl)),
contentDescription = stringResource(R.string.content_description_user_avatar),
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(dimensions().userAvatarStatusBorderSize)
.background(Color.Black, CircleShape)
.size(size)
)
UserStatusIndicator(
status = status,
modifier = Modifier.align(Alignment.BottomEnd)
)
}
}
import com.wire.android.util.getDefaultAvatarUri

@Composable
fun UserProfileAvatar(
avatarBitmap: Bitmap,
avatarAssetByteArray: ByteArray? = null,
status: UserStatus = UserStatus.NONE,
isEnabled: Boolean = false,
size: Dp = MaterialTheme.wireDimensions.userAvatarDefaultSize,
Expand All @@ -77,14 +44,19 @@ fun UserProfileAvatar(
.wrapContentSize()
.padding(MaterialTheme.wireDimensions.userAvatarClickablePadding)
) {
val avatarResource = avatarAssetByteArray?.run {
BitmapFactory.decodeByteArray(avatarAssetByteArray, 0, avatarAssetByteArray.size)
} ?: getDefaultAvatarUri(LocalContext.current)

Image(
bitmap = avatarBitmap.asImageBitmap(),
painter = rememberAsyncImagePainter(model = avatarResource),
contentDescription = stringResource(R.string.content_description_user_avatar),
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(dimensions().userAvatarStatusBorderSize)
.clip(CircleShape)
.background(Color.Black, CircleShape)
.size(size)
.clip(CircleShape),
contentScale = ContentScale.FillBounds,
)
UserStatusIndicator(
status = status,
Expand All @@ -93,13 +65,8 @@ fun UserProfileAvatar(
}
}

fun getAvatarAsDrawable(avatarUrl: String): Int {
// TODO: Add the picture loading mechanism with the avatarUrl
return R.drawable.ic_launcher_foreground
}

@Preview
@Composable
fun UserProfileAvatarPreview() {
UserProfileAvatar(avatarUrl = "", status = UserStatus.BUSY) {}
UserProfileAvatar(status = UserStatus.BUSY) {}
}
Loading