Skip to content

Commit

Permalink
Ui: Settings page, User agreement, Chat UI improvements, Dialogs (#179)
Browse files Browse the repository at this point in the history
* - Seller reputation warning dialog

* - User agreement

* - agreementAccepted - Moved to Settings model from User model

* - TakeOffer Review: Progress Dialog and Success Dialog

* - Chat Improvements: Current time; Quoted message trim; Scroll to quoted message on click

* - Settings screen UI 1/2

* - fetch i18n_codes from Bisq2.LanguageRepository (Coulndt find/No service) that wraps this Repository

* - Settings screen UI 2/3

* - User Agreement flow uses settingsServiceFacade, rather than settingsRepository

* - Settings - Backend integration 1

* - searchable dropdown for supported languages; useAnimations, tradeNotifications removed

* - Multi select for Dropdown with Chip control

* - langugeCode, supportedLanguages in androidNode

* - Settings page - Remaining controls wired functionally in androidNode

* - update languagePairs from LanguageServiceFacade, when app language changes

* - TextField validation - Implemented in SettingsScreen

* - Text field validation for TrustedNodeSetupScreen.RemoteBisqURL

* - isInteractive handled in Scaffolds, to block the entire scaffold content with an invisible overlay box that consumes all clicks and interactions

* - show PoW control only in androidNode; println, deadcode cleanup

* - Settings Page functionality for xClients; With LanguageServiceFacade, LanguageAPIGateway (tested in androidClient)

* - TextField validations 1/2

* - TextField validations 2/2

* - useAnimations - to be accompanied with changes in bisq2 repo

* - Cleanup

* - fix pod files references (bug from previous PR) + fix compilation issue on iOS with new useAnimation setting

* - hiding new general settings as they need more work (agreed with buddha)

---------

Co-authored-by: Rodrigo Varela <[email protected]>
  • Loading branch information
nostrbuddha and rodvar authored Feb 4, 2025
1 parent ebc6446 commit e882513
Show file tree
Hide file tree
Showing 133 changed files with 3,262 additions and 546 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package network.bisq.mobile.client
import network.bisq.mobile.client.websocket.WebSocketClientProvider
import network.bisq.mobile.domain.UrlLauncher
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.common.LanguageServiceFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.notifications.OpenTradesNotificationService
import network.bisq.mobile.domain.service.offers.OffersServiceFacade
Expand All @@ -18,10 +19,12 @@ class AndroidClientMainPresenter(openTradesNotificationService: OpenTradesNotifi
applicationBootstrapFacade: ApplicationBootstrapFacade,
offersServiceFacade: OffersServiceFacade,
marketPriceServiceFacade: MarketPriceServiceFacade,
settingsServiceFacade: SettingsServiceFacade, urlLauncher: UrlLauncher
settingsServiceFacade: SettingsServiceFacade,
languageServiceFacade: LanguageServiceFacade,
urlLauncher: UrlLauncher
) : ClientMainPresenter(
openTradesNotificationService, tradesServiceFacade, webSocketClientProvider, applicationBootstrapFacade,
offersServiceFacade, marketPriceServiceFacade, settingsServiceFacade, urlLauncher
offersServiceFacade, marketPriceServiceFacade, settingsServiceFacade, languageServiceFacade, urlLauncher
) {
init {
openTradesNotificationService.notificationServiceController.activityClassForIntents = MainActivity::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ val androidClientModule = module {
NotificationServiceController(get())
}

single<MainPresenter> { AndroidClientMainPresenter(get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class
single<MainPresenter> { AndroidClientMainPresenter(get(), get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import bisq.bonded_roles.security_manager.alert.AlertNotificationsService
import bisq.chat.ChatService
import bisq.common.observable.Observable
import bisq.common.util.ExceptionUtil
import bisq.common.locale.LanguageRepository
import bisq.contract.ContractService
import bisq.identity.IdentityService
import bisq.network.NetworkService
Expand Down Expand Up @@ -108,6 +109,9 @@ class AndroidApplicationService(
Supplier { applicationService.favouriteMarketsService }
var dontShowAgainService: Supplier<DontShowAgainService> =
Supplier { applicationService.dontShowAgainService }

var languageRepository: Supplier<LanguageRepository> =
Supplier { applicationService.languageRepository }
}

companion object {
Expand Down Expand Up @@ -163,6 +167,7 @@ class AndroidApplicationService(
val alertNotificationsService: AlertNotificationsService
val favouriteMarketsService: FavouriteMarketsService
val dontShowAgainService: DontShowAgainService
val languageRepository: LanguageRepository

init {
chatService = ChatService(
Expand Down Expand Up @@ -218,6 +223,8 @@ class AndroidApplicationService(
favouriteMarketsService = FavouriteMarketsService(settingsService)

dontShowAgainService = DontShowAgainService(settingsService)

languageRepository = LanguageRepository()
}

override fun initialize(): CompletableFuture<Boolean> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package network.bisq.mobile.android.node.di

import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.presentation.NodeGettingStartedPresenter
import network.bisq.mobile.android.node.presentation.NodeMainPresenter
import network.bisq.mobile.android.node.presentation.NodeSettingsPresenter
import network.bisq.mobile.android.node.presentation.NodeSplashPresenter
import network.bisq.mobile.android.node.presentation.OnBoardingNodePresenter
import network.bisq.mobile.android.node.presentation.*
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import network.bisq.mobile.android.node.service.AndroidNodeCatHashService
import network.bisq.mobile.android.node.service.bootstrap.NodeApplicationBootstrapFacade
import network.bisq.mobile.android.node.service.explorer.NodeExplorerServiceFacade
import network.bisq.mobile.android.node.service.common.NodeLanguageServiceFacade
import network.bisq.mobile.android.node.service.market_price.NodeMarketPriceServiceFacade
import network.bisq.mobile.android.node.service.mediation.NodeMediationServiceFacade
import network.bisq.mobile.android.node.service.offers.NodeOffersServiceFacade
Expand All @@ -20,6 +17,7 @@ import network.bisq.mobile.domain.AndroidUrlLauncher
import network.bisq.mobile.domain.UrlLauncher
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.explorer.ExplorerServiceFacade
import network.bisq.mobile.domain.service.common.LanguageServiceFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.mediation.MediationServiceFacade
import network.bisq.mobile.domain.service.offers.OffersServiceFacade
Expand All @@ -29,6 +27,8 @@ import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.AppPresenter
import network.bisq.mobile.presentation.ui.uicases.GettingStartedPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.GeneralSettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.IGeneralSettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.ISettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.SettingsPresenter
import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter
Expand Down Expand Up @@ -65,11 +65,13 @@ val androidNodeModule = module {

single<SettingsServiceFacade> { NodeSettingsServiceFacade(get()) }

single<LanguageServiceFacade> { NodeLanguageServiceFacade(get()) }

single<UrlLauncher> { AndroidUrlLauncher(androidContext()) }

// this line showcases both, the possibility to change behaviour of the app by changing one definition
// and binding the same obj to 2 different abstractions
single<MainPresenter> { NodeMainPresenter(get(), get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class
single<MainPresenter> { NodeMainPresenter(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class

single<SplashPresenter> {
NodeSplashPresenter(
Expand All @@ -88,5 +90,7 @@ val androidNodeModule = module {

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

single<GeneralSettingsPresenter> { NodeGeneralSettingsPresenter(get(), get(), get(), get()) } bind IGeneralSettingsPresenter::class

single<IOnboardingPresenter> { OnBoardingNodePresenter(get(), get(), get()) } bind IOnboardingPresenter::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ class Mappings {
settingsService.languageCode.get(),
settingsService.supportedLanguageCodes,
settingsService.maxTradePriceDeviation.get(),
settingsService.useAnimations.get(),
MarketMapping.fromBisq2Model(settingsService.selectedMarket.get())
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package network.bisq.mobile.android.node.presentation

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import network.bisq.mobile.domain.data.repository.SettingsRepository
import network.bisq.mobile.domain.service.common.LanguageServiceFacade
import network.bisq.mobile.domain.service.settings.SettingsServiceFacade
import network.bisq.mobile.presentation.MainPresenter
import network.bisq.mobile.presentation.ui.uicases.settings.GeneralSettingsPresenter

class NodeGeneralSettingsPresenter(
private val settingsRepository: SettingsRepository,
private val settingsServiceFacade: SettingsServiceFacade,
private val languageServiceFacade: LanguageServiceFacade,
mainPresenter: MainPresenter
) : GeneralSettingsPresenter(settingsRepository, settingsServiceFacade, languageServiceFacade, mainPresenter) {

override val shouldShowPoWAdjustmentFactor: StateFlow<Boolean> = MutableStateFlow(true)

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import network.bisq.mobile.android.node.MainActivity
import network.bisq.mobile.android.node.service.AndroidMemoryReportService
import network.bisq.mobile.domain.UrlLauncher
import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade
import network.bisq.mobile.domain.service.common.LanguageServiceFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.service.notifications.OpenTradesNotificationService
import network.bisq.mobile.domain.service.offers.OffersServiceFacade
Expand All @@ -26,7 +27,8 @@ class NodeMainPresenter(
private val settingsServiceFacade: SettingsServiceFacade,
private val offersServiceFacade: OffersServiceFacade,
private val marketPriceServiceFacade: MarketPriceServiceFacade,
) : MainPresenter(openTradesNotificationService, urlLauncher) {
private val languageServiceFacade: LanguageServiceFacade,
) : MainPresenter(openTradesNotificationService, settingsServiceFacade, urlLauncher) {

private var applicationServiceCreated = false

Expand Down Expand Up @@ -60,6 +62,7 @@ class NodeMainPresenter(
offersServiceFacade.activate()
marketPriceServiceFacade.activate()
tradesServiceFacade.activate()
languageServiceFacade.activate()
} else {
log.e("Initializing applicationService failed", throwable)
}
Expand All @@ -71,6 +74,7 @@ class NodeMainPresenter(
marketPriceServiceFacade.activate()
tradesServiceFacade.activate()

languageServiceFacade.activate()
}
}.onFailure { e ->
// TODO give user feedback (we could have a general error screen covering usual
Expand Down Expand Up @@ -100,5 +104,6 @@ class NodeMainPresenter(
// TODO for notifications to work even if the app gets killed this needs to be commented out
// but it can't be done yet because of lack of support in bisq2 jars
tradesServiceFacade.deactivate()
languageServiceFacade.deactivate()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package network.bisq.mobile.android.node.service.common

import bisq.bonded_roles.market_price.MarketPriceService
import bisq.common.locale.LanguageRepository
import bisq.common.observable.Pin
import bisq.presentation.formatters.PriceFormatter
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.zip
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.mapping.Mappings
import network.bisq.mobile.domain.data.model.MarketPriceItem
import network.bisq.mobile.domain.formatters.MarketPriceFormatter
import network.bisq.mobile.domain.service.common.LanguageServiceFacade
import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade
import network.bisq.mobile.domain.utils.Logging

class NodeLanguageServiceFacade(private val applicationService: AndroidApplicationService.Provider) :
LanguageServiceFacade, Logging {

// Dependencies
private val languageService: LanguageRepository by lazy {
applicationService.languageRepository.get()
}

// Properties
private val _i18nPairs: MutableStateFlow<List<Pair<String, String>>> = MutableStateFlow(emptyList())
override val i18nPairs: StateFlow<List<Pair<String, String>>> = _i18nPairs

private val _allPairs: MutableStateFlow<List<Pair<String, String>>> = MutableStateFlow(emptyList())
override val allPairs: StateFlow<List<Pair<String, String>>> = _allPairs

override fun setDefaultLanguage(languageCode: String) {
return LanguageRepository.setDefaultLanguage(languageCode)
}

// Life cycle
override fun activate() {

val displayTextList = mutableListOf<String>()
for (code in LanguageRepository.I18N_CODES) {
displayTextList.add(LanguageRepository.getDisplayString(code))
}
_i18nPairs.value = LanguageRepository.I18N_CODES.zip(displayTextList)

displayTextList.clear()
for (code in LanguageRepository.CODES) {
displayTextList.add(LanguageRepository.getDisplayString(code))
}
_allPairs.value = LanguageRepository.CODES.zip(displayTextList)

}

override suspend fun sync() {
activate()
}

override fun deactivate() {
}

}
Original file line number Diff line number Diff line change
@@ -1,38 +1,123 @@
package network.bisq.mobile.android.node.service.settings

import androidx.compose.runtime.MutableState
import bisq.common.observable.Pin
import bisq.settings.ChatNotificationType
import bisq.settings.SettingsService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import network.bisq.mobile.android.node.AndroidApplicationService
import network.bisq.mobile.android.node.mapping.Mappings
import network.bisq.mobile.domain.data.replicated.chat.notifications.ChatChannelNotificationTypeEnum
import network.bisq.mobile.domain.data.replicated.settings.SettingsVO
import network.bisq.mobile.domain.service.settings.SettingsServiceFacade
import network.bisq.mobile.domain.utils.Logging

class NodeSettingsServiceFacade(applicationService: AndroidApplicationService.Provider) : SettingsServiceFacade, Logging {
class NodeSettingsServiceFacade(applicationService: AndroidApplicationService.Provider) : SettingsServiceFacade,
Logging {
// Dependencies
private val settingsService: SettingsService by lazy { applicationService.settingsService.get() }


// Properties
private val _isTacAccepted: MutableStateFlow<Boolean?> = MutableStateFlow(null)
override val isTacAccepted: StateFlow<Boolean?> get() = _isTacAccepted
override suspend fun confirmTacAccepted(value: Boolean) {
settingsService.isTacAccepted.set(value)
}

private val _tradeRulesConfirmed = MutableStateFlow(false)
override val tradeRulesConfirmed: StateFlow<Boolean> get() = _tradeRulesConfirmed
override suspend fun confirmTradeRules(value: Boolean) {
settingsService.tradeRulesConfirmed.set(value)
}

private val _languageCode: MutableStateFlow<String> = MutableStateFlow("en")
override val languageCode: StateFlow<String> get() = _languageCode
override suspend fun setLanguageCode(value: String) {
settingsService.languageCode.set(value)
}

private val _supportedLanguageCodes: MutableStateFlow<Set<String>> = MutableStateFlow(emptySet())
override val supportedLanguageCodes: StateFlow<Set<String>> get() = _supportedLanguageCodes
override suspend fun setSupportedLanguageCodes(value: Set<String>) {
settingsService.supportedLanguageCodes.setAll(value)
}

private val _chatNotificationType: MutableStateFlow<ChatChannelNotificationTypeEnum> =
MutableStateFlow(ChatChannelNotificationTypeEnum.ALL)
override val chatNotificationType: StateFlow<ChatChannelNotificationTypeEnum> get() = _chatNotificationType
override suspend fun setChatNotificationType(value: ChatChannelNotificationTypeEnum) {
when (value) {
ChatChannelNotificationTypeEnum.ALL -> settingsService.chatNotificationType.set(ChatNotificationType.ALL)
ChatChannelNotificationTypeEnum.MENTION -> settingsService.chatNotificationType.set(ChatNotificationType.MENTION)
ChatChannelNotificationTypeEnum.OFF -> settingsService.chatNotificationType.set(ChatNotificationType.OFF)
ChatChannelNotificationTypeEnum.GLOBAL_DEFAULT -> settingsService.chatNotificationType.set(
ChatNotificationType.ALL
)
}
}

private val _closeMyOfferWhenTaken = MutableStateFlow(true)
override val closeMyOfferWhenTaken: StateFlow<Boolean> get() = _closeMyOfferWhenTaken
override suspend fun setCloseMyOfferWhenTaken(value: Boolean) {
settingsService.closeMyOfferWhenTaken.set(value)
}

private val _maxTradePriceDeviation = MutableStateFlow(5.0)
override val maxTradePriceDeviation: StateFlow<Double> get() = _maxTradePriceDeviation
override suspend fun setMaxTradePriceDeviation(value: Double) {
// TODO: settingsService.maxTradePriceDeviation is readyOnly? It's readyonly after rebase and latest bisq2 code.
// settingsService.maxTradePriceDeviation.set(value)
}

private val _useAnimations: MutableStateFlow<Boolean> = MutableStateFlow(true)
override val useAnimations: StateFlow<Boolean> get() = _useAnimations
override suspend fun setUseAnimations(value: Boolean) {
settingsService.useAnimations.set(value)
}

private val _difficultyAdjustmentFactor: MutableStateFlow<Double> = MutableStateFlow(1.0)
override val difficultyAdjustmentFactor: StateFlow<Double> get() = _difficultyAdjustmentFactor
override suspend fun setDifficultyAdjustmentFactor(value: Double) {
settingsService.difficultyAdjustmentFactor.set(value)
}

private val _ignoreDiffAdjustmentFromSecManager: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val ignoreDiffAdjustmentFromSecManager: StateFlow<Boolean> get() = _ignoreDiffAdjustmentFromSecManager
override suspend fun setIgnoreDiffAdjustmentFromSecManager(value: Boolean) {
settingsService.ignoreDiffAdjustmentFromSecManager.set(value)
}

// Misc
private var tradeRulesConfirmedPin: Pin? = null


override fun activate() {
settingsService.languageCode.addObserver { code ->
_languageCode.value = code
}
tradeRulesConfirmedPin = settingsService.isTacAccepted.addObserver { isTacAccepted ->
_isTacAccepted.value = isTacAccepted
}
tradeRulesConfirmedPin = settingsService.tradeRulesConfirmed.addObserver { isConfirmed ->
_tradeRulesConfirmed.value = isConfirmed
}
settingsService.closeMyOfferWhenTaken.addObserver { value ->
_closeMyOfferWhenTaken.value = value
}
settingsService.maxTradePriceDeviation.addObserver { value ->
_maxTradePriceDeviation.value = value
}
settingsService.useAnimations.addObserver { value ->
_useAnimations.value = value
}
settingsService.difficultyAdjustmentFactor.addObserver { value ->
_difficultyAdjustmentFactor.value = value
}
settingsService.ignoreDiffAdjustmentFromSecManager.addObserver { value ->
_ignoreDiffAdjustmentFromSecManager.value = value
}
}

override fun deactivate() {
Expand All @@ -44,11 +129,4 @@ class NodeSettingsServiceFacade(applicationService: AndroidApplicationService.Pr
return Result.success(Mappings.SettingsMapping.from(settingsService))
}

override suspend fun confirmTacAccepted(value: Boolean) {
settingsService.isTacAccepted.set(value)
}

override suspend fun confirmTradeRules(value: Boolean) {
settingsService.tradeRulesConfirmed.set(value)
}
}
Loading

0 comments on commit e882513

Please sign in to comment.