Skip to content

Commit

Permalink
Implement new onboarding screens
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandarIlic committed Mar 13, 2024
1 parent f868b77 commit 0ab928c
Show file tree
Hide file tree
Showing 29 changed files with 1,853 additions and 172 deletions.
56 changes: 55 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,58 @@
-keepclassmembers class net.sourceforge.zbar.SymbolSet { *; }

# BicoinJ-Core
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.slf4j.impl.StaticLoggerBinder


############
# Retrofit #
############

# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>

# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>

# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ fun MediaGalleryScreen(
.height(32.dp)
.fillMaxWidth()
.align(Alignment.BottomCenter),
imagesCount = imagesCount,
pagesCount = imagesCount,
currentPage = pagerState.currentPage,
)
}
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/kotlin/net/primal/android/auth/compose/Avatar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.primal.android.auth.compose

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import net.primal.android.core.compose.icons.PrimalIcons
import net.primal.android.core.compose.icons.primaliconpack.AvatarDefault

val defaultOnboardingAvatarBackground = Color(0xFF7E382C)
val defaultAvatarForeground = Color(0xFFFDB7AB)

@Composable
fun DefaultOnboardingAvatar() {
Box(
modifier = Modifier
.background(color = defaultOnboardingAvatarBackground)
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = PrimalIcons.AvatarDefault,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
tint = defaultAvatarForeground,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.primal.android.auth.compose

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.DpSize

@Composable
fun ColumnWithBackground(
modifier: Modifier = Modifier,
backgroundPainter: Painter,
content: @Composable (DpSize) -> Unit,
) {
BoxWithConstraints(
modifier = modifier,
) {
val maxSize = DpSize(width = this.maxWidth, height = this.maxHeight)

Image(
modifier = Modifier.fillMaxSize(),
painter = backgroundPainter,
contentScale = ContentScale.FillBounds,
alignment = Alignment.Center,
contentDescription = null,
)

Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
) {
content(maxSize)
}
}
}
101 changes: 32 additions & 69 deletions app/src/main/kotlin/net/primal/android/auth/login/LoginScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package net.primal.android.auth.login

import android.widget.Toast
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -15,13 +13,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
Expand All @@ -33,7 +29,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
Expand All @@ -53,8 +48,11 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import net.primal.android.R
import net.primal.android.auth.compose.ColumnWithBackground
import net.primal.android.auth.compose.DefaultOnboardingAvatar
import net.primal.android.auth.compose.ONE_HALF
import net.primal.android.auth.compose.OnboardingButton
import net.primal.android.auth.compose.defaultOnboardingAvatarBackground
import net.primal.android.core.compose.AppBarIcon
import net.primal.android.core.compose.AvatarThumbnail
import net.primal.android.core.compose.PrimalDefaults
Expand All @@ -63,7 +61,6 @@ import net.primal.android.core.compose.detectUiDensityModeFromMaxHeight
import net.primal.android.core.compose.foundation.keyboardVisibilityAsState
import net.primal.android.core.compose.icons.PrimalIcons
import net.primal.android.core.compose.icons.primaliconpack.ArrowBack
import net.primal.android.core.compose.icons.primaliconpack.AvatarDefault
import net.primal.android.core.compose.isCompactOrLower
import net.primal.android.core.compose.profile.model.ProfileDetailsUi
import net.primal.android.core.utils.isValidNostrPrivateKey
Expand Down Expand Up @@ -107,52 +104,38 @@ fun LoginScreen(
eventPublisher: (LoginContract.UiEvent) -> Unit,
onClose: () -> Unit,
) {
BoxWithConstraints {
val uiMode = this.maxHeight.detectUiDensityModeFromMaxHeight()
ColumnWithBackground(
backgroundPainter = painterResource(id = R.drawable.onboarding_spot2),
) { size ->
val uiMode = size.height.detectUiDensityModeFromMaxHeight()

Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.onboarding_spot2),
contentScale = ContentScale.FillBounds,
alignment = Alignment.Center,
contentDescription = null,
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
titleContentColor = Color.White,
navigationIconContentColor = Color.White,
),
title = {
Text(text = stringResource(id = R.string.login_title))
},
navigationIcon = {
AppBarIcon(
icon = PrimalIcons.ArrowBack,
onClick = onClose,
)
},
)

Column(
LoginContent(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
) {
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
titleContentColor = Color.White,
navigationIconContentColor = Color.White,
),
title = {
Text(text = stringResource(id = R.string.login_title))
},
navigationIcon = {
AppBarIcon(
icon = PrimalIcons.ArrowBack,
onClick = {
onClose()
},
)
},
)

LoginContent(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(horizontal = 32.dp),
state = state,
uiMode = uiMode,
onLoginInputChanged = { eventPublisher(LoginContract.UiEvent.UpdateLoginInput(newInput = it)) },
onLoginClick = { eventPublisher(LoginContract.UiEvent.LoginRequestEvent) },
)
}
.imePadding()
.padding(horizontal = 32.dp),
state = state,
uiMode = uiMode,
onLoginInputChanged = { eventPublisher(LoginContract.UiEvent.UpdateLoginInput(newInput = it)) },
onLoginClick = { eventPublisher(LoginContract.UiEvent.LoginRequestEvent) },
)
}
}

Expand Down Expand Up @@ -416,8 +399,8 @@ private fun ProfileDetailsColumn(
avatarSize = 100.dp,
hasBorder = profileDetails.avatarCdnImage != null,
borderColor = Color.White,
backgroundColor = defaultAvatarBackground,
defaultAvatar = { DefaultAvatar() },
backgroundColor = defaultOnboardingAvatarBackground,
defaultAvatar = { DefaultOnboardingAvatar() },
)

Spacer(modifier = Modifier.height(16.dp))
Expand All @@ -443,26 +426,6 @@ private fun ProfileDetailsColumn(
}
}

private val defaultAvatarBackground = Color(0xFF7E382C)
private val defaultAvatarForeground = Color(0xFFFDB7AB)

@Composable
private fun DefaultAvatar() {
Box(
modifier = Modifier
.background(color = defaultAvatarBackground)
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = PrimalIcons.AvatarDefault,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
tint = defaultAvatarForeground,
)
}
}

@Composable
fun LaunchedErrorHandler(viewModel: LoginViewModel) {
val genericMessage = stringResource(id = R.string.app_generic_error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package net.primal.android.auth.onboarding

import java.io.IOException
import javax.inject.Inject
import kotlinx.coroutines.withContext
import net.primal.android.auth.AuthRepository
import net.primal.android.auth.onboarding.api.Suggestion
import net.primal.android.core.coroutines.CoroutineDispatcherProvider
import net.primal.android.networking.relays.errors.NostrPublishException
import net.primal.android.networking.sockets.errors.WssException
import net.primal.android.profile.domain.ProfileMetadata
import net.primal.android.profile.repository.ProfileRepository
import net.primal.android.settings.repository.SettingsRepository
import net.primal.android.user.repository.RelayRepository
import net.primal.android.user.repository.UserRepository
import timber.log.Timber

class CreateAccountHandler @Inject constructor(
private val dispatcherProvider: CoroutineDispatcherProvider,
private val authRepository: AuthRepository,
private val relayRepository: RelayRepository,
private val userRepository: UserRepository,
private val profileRepository: ProfileRepository,
private val settingsRepository: SettingsRepository,
) {

suspend fun createNostrAccount(profileMetadata: ProfileMetadata, interests: List<Suggestion>): String {
val userId = authRepository.createAccountAndLogin()
try {
withContext(dispatcherProvider.io()) {
userRepository.setProfileMetadata(
userId = userId,
profileMetadata = profileMetadata,
)
profileRepository.setFollowList(
userId = userId,
contacts = setOf(userId) + interests.mapToContacts(),
)
relayRepository.bootstrapDefaultUserRelays(userId)
settingsRepository.fetchAndPersistAppSettings(userId = userId)
}
} catch (error: WssException) {
Timber.w(error)
throw AccountCreationException(cause = error)
} catch (error: NostrPublishException) {
Timber.w(error)
throw AccountCreationException(cause = error)
}
return userId
}

private fun List<Suggestion>.mapToContacts(): Set<String> {
return flatMap { it.members.map { member -> member.pubkey } }.toSet()
}

class AccountCreationException(cause: Throwable) : IOException(cause)
}
Loading

0 comments on commit 0ab928c

Please sign in to comment.