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

Feature/account profile settings #120

Merged
merged 10 commits into from
Dec 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import bisq.security.SecurityService
import bisq.security.pow.ProofOfWork
import bisq.user.UserService
import bisq.user.identity.NymIdGenerator
import bisq.user.profile.UserProfile
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.service.AndroidNodeCatHashService
import network.bisq.mobile.client.replicated_model.common.network.Address
import network.bisq.mobile.client.replicated_model.common.network.TransportType
import network.bisq.mobile.client.replicated_model.network.identity.NetworkId
import network.bisq.mobile.client.replicated_model.security.keys.PubKey
import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
import network.bisq.mobile.domain.PlatformImage
import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.utils.Logging
Expand Down Expand Up @@ -100,8 +104,34 @@ class NodeUserProfileServiceFacade(private val applicationService: AndroidApplic
}

// Private
private fun getSelectedUserProfile(): UserProfile? {
return userService.userIdentityService.selectedUserIdentity?.userProfile
override suspend fun getSelectedUserProfile(): UserProfile? {
// TODO move to bridge mapper
return userService.userIdentityService.selectedUserIdentity?.userProfile?.let {
UserProfile(
it.nickName,
network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork(
it.proofOfWork.solution,
it.proofOfWork.counter,
it.proofOfWork.challenge,
it.proofOfWork.difficulty,
it.proofOfWork.payload,
it.proofOfWork.duration
),
NetworkId(it.networkId.addressByTransportTypeMap.map{
(key, value) ->
TransportType.entries[key.ordinal] to Address(value.host, value.port) }.toMap(),
PubKey( it.networkId.pubKey.publicKey.toString(), it.networkId.pubKey.keyId)),
it.terms,
it.statement,
it.avatarVersion,
it.applicationVersion,
it.id,
it.nym,
it.userName,
it.pubKeyHash.toString(),
it.publishDate
)
}
}

private fun createSimulatedDelay(powDuration: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package network.bisq.mobile.android.node.presentation

import network.bisq.mobile.domain.data.repository.SettingsRepository
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.components.molecules.settings.MenuItem
import network.bisq.mobile.presentation.ui.uicases.settings.ISettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.MenuItem
import network.bisq.mobile.presentation.ui.uicases.settings.SettingsPresenter

class NodeSettingsPresenter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import network.bisq.mobile.client.replicated_model.security.pow.ProofOfWork

@Serializable
data class UserProfile(
val nickName: String,
val proofOfWork: ProofOfWork,
val networkId: NetworkId,
val terms: String,
val statement: String,
val avatarVersion: Int,
val applicationVersion: String,
val id: String,
val nym: String,
val userName: String,
val pubKeyHash: String,
val publishDate: Long
val nickName: String? = null,
val proofOfWork: ProofOfWork? = null,
val networkId: NetworkId? = null,
val terms: String? = null,
val statement: String? = null,
val avatarVersion: Int? = null,
val applicationVersion: String? = null,
val id: String? = null,
val nym: String? = null,
val userName: String? = null,
val pubKeyHash: String? = null,
val publishDate: Long? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class ClientUserProfileServiceFacade(
}

// Private
private suspend fun getSelectedUserProfile(): UserProfile {
override suspend fun getSelectedUserProfile(): UserProfile {
return apiGateway.getSelectedUserProfile()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ import network.bisq.mobile.domain.PlatformImage

@Serializable
open class User: BaseModel() {
var tradeTerms: String? = null
var statement: String? = null
var lastActivity: Long? = null
var uniqueAvatar: PlatformImage? = null
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package network.bisq.mobile.domain.service.user_profile

import network.bisq.mobile.client.replicated_model.user.profile.UserProfile
import network.bisq.mobile.domain.PlatformImage

interface UserProfileServiceFacade {
Expand Down Expand Up @@ -40,6 +41,12 @@ interface UserProfileServiceFacade {

/**
* Applies the selected user identity to the user profile model
* @return Triple containing nickname, nym and id
*/
suspend fun applySelectedUserProfile():Triple<String?, String?, String?>

/**
* @return UserProfile if existent, null otherwise
*/
suspend fun getSelectedUserProfile(): UserProfile?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package network.bisq.mobile.utils

import kotlinx.datetime.*
import kotlin.time.ExperimentalTime

object DateUtils {

/**
* @return years, months, days past since timestamp
*/
fun periodFrom(timetamp: Long): Triple<Int, Int, Int> {
val creationInstant = Instant.fromEpochMilliseconds(timetamp)
val creationDate = creationInstant.toLocalDateTime(TimeZone.currentSystemDefault()).date
val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date

// Calculate the difference
val period = creationDate.until(currentDate, DateTimeUnit.DAY)
val years = period / 365
val remainingDaysAfterYears = period % 365
val months = remainingDaysAfterYears / 30
val days = remainingDaysAfterYears % 30

// Format the result
return Triple(years, months, days)
}

fun toDateString(epochMillis: Long, timeZone: TimeZone = TimeZone.currentSystemDefault()): String {
val instant = Instant.fromEpochMilliseconds(epochMillis)
val localDateTime = instant.toLocalDateTime(timeZone)
return localDateTime.toString()
.split(".")[0] // remove ms
.replace("T", " ") // separate date time
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ abstract class BaseClientCatHashService(private val baseDirPath: String) :
companion object {
const val SIZE_OF_CACHED_ICONS = 60
const val MAX_CACHE_SIZE = 5000
const val CATHASH_ICONS_PATH = "db/cache/cat_hash_icons"
}

private val fileSystem: FileSystem = FileSystem.SYSTEM
Expand All @@ -29,13 +30,18 @@ abstract class BaseClientCatHashService(private val baseDirPath: String) :
protected abstract fun readRawImage(iconFilePath: String): PlatformImage?

fun getImage(userProfile: UserProfile, size: Int): PlatformImage? {
val pubKeyHash = userProfile.pubKeyHash.hexToByteArray()
return getImage(
pubKeyHash,
userProfile.proofOfWork.solution,
userProfile.avatarVersion,
size
)
try {
val pubKeyHash = userProfile.pubKeyHash!!.hexToByteArray()
return getImage(
pubKeyHash,
userProfile.proofOfWork!!.solution,
userProfile.avatarVersion!!,
size
)
} catch (e: Exception) {
log.e(e) { "Failed to get image from profile" }
return null
}
}

override fun getImage(
Expand Down Expand Up @@ -103,7 +109,7 @@ abstract class BaseClientCatHashService(private val baseDirPath: String) :
fun pruneOutdatedProfileIcons(userProfiles: Collection<UserProfile>) {
if (userProfiles.isEmpty()) return

val iconsDirectory = baseDirPath.toPath().resolve("db/cache/cat_hash_icons")
val iconsDirectory = baseDirPath.toPath().resolve(CATHASH_ICONS_PATH)
val versionDirs =
fileSystem.listOrNull(iconsDirectory)?.filter { fileSystem.metadata(it).isDirectory }
?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ abstract class BasePresenter(private val rootPresenter: MainPresenter?): ViewPre
protected var view: Any? = null
// Coroutine scope for the presenter
protected val presenterScope = CoroutineScope(Dispatchers.Main + Job())
protected val uiScope = CoroutineScope(Dispatchers.Main)
protected val backgroundScope = CoroutineScope(BackgroundDispatcher)

private val dependants = if (isRoot()) mutableListOf<BasePresenter>() else null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.CreateOffe
import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.ICreateOfferPresenter
import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.*
import network.bisq.mobile.presentation.ui.uicases.settings.ISettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.IUserProfileSettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.SettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.UserProfileSettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfilePresenter
import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.ITrustedNodeSetupPresenter
Expand Down Expand Up @@ -58,6 +60,8 @@ val presentationModule = module {

single<SettingsPresenter> { SettingsPresenter(get(), get()) } bind ISettingsPresenter::class

single<UserProfileSettingsPresenter> { UserProfileSettingsPresenter(get(), get(), get()) } bind IUserProfileSettingsPresenter::class

single<GettingStartedPresenter> {
GettingStartedPresenter(
get(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package network.bisq.mobile.presentation.ui.components.atoms

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.TextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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 network.bisq.mobile.presentation.ui.theme.BisqTheme

@Composable
fun SettingsTextField(
label: String,
value: String,
editable: Boolean,
onValueChange: (String) -> Unit = {}
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = label,
color = BisqTheme.colors.grey1,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 4.dp)
)
TextField(
value = value,
enabled = editable,
onValueChange = onValueChange,
colors = TextFieldDefaults.colors(
disabledContainerColor = BisqTheme.colors.secondaryDisabled,
disabledTextColor = BisqTheme.colors.light5,
focusedTextColor = BisqTheme.colors.light3,
unfocusedTextColor = BisqTheme.colors.secondaryHover,
unfocusedIndicatorColor = BisqTheme.colors.secondary,
focusedIndicatorColor = Color.Transparent,
focusedContainerColor = BisqTheme.colors.secondary,
cursorColor = Color.Blue,
unfocusedContainerColor = BisqTheme.colors.secondary
),
// fontSize = 14.sp,
// textAlign = TextAlign.Start,
modifier = Modifier
.fillMaxWidth()
.background(BisqTheme.colors.dark1, RoundedCornerShape(8.dp))
.padding(8.dp)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fun BisqScrollLayout(
padding: PaddingValues = PaddingValues(all = BisqUIConstants.ScreenPadding),
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
onModifier: ((Modifier) -> Modifier)? = null, // allows to customize modifier settings
content: @Composable ColumnScope.() -> Unit
) {
Column(
Expand All @@ -25,6 +26,7 @@ fun BisqScrollLayout(
.background(color = BisqTheme.colors.backgroundColor)
.padding(padding)
.verticalScroll(rememberScrollState())
.run { onModifier?.invoke(this) ?: this }
) {
content()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package network.bisq.mobile.presentation.ui.components.molecules.settings

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import network.bisq.mobile.presentation.ui.theme.BisqTheme

@Composable
fun BreadcrumbNavigation(
path: List<MenuItem>,
onBreadcrumbClick: (Int) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
path.forEachIndexed { index, menuItem ->
Text(
text = menuItem.label,
style = MaterialTheme.typography.bodyLarge.copy(color = BisqTheme.colors.grey1),
modifier = Modifier.clickable { onBreadcrumbClick(index) }
)
if (index != path.lastIndex) {
Text(" > ", color = BisqTheme.colors.grey1) // Separator
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package network.bisq.mobile.presentation.ui.components.molecules.settings

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import network.bisq.mobile.presentation.ui.theme.BisqTheme

@Composable
fun SettingsButton(label: String, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(BisqTheme.colors.grey5)
.clickable(onClick = onClick)
.padding(vertical = 12.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
style = MaterialTheme.typography.bodyLarge.copy(color = BisqTheme.colors.light1 , fontSize = 16.sp),
modifier = Modifier.weight(1f)
)
Text(
text = ">",
textAlign = TextAlign.End,
style = MaterialTheme.typography.bodyLarge.copy(color = BisqTheme.colors.light1, fontSize = 16.sp),
modifier = Modifier.weight(1f)
)
}
}
Loading
Loading