[settings-sync] IJPL-188452: Use combobox for account selector dropdown

- Account selector now follows the design
- Add checkbox indicating selected account
- Add provider icon
- Handle logging out from an active account (disable sync and switch to another account)


(cherry picked from commit 7549862a1acb9e902a636e0e98c26b45275bb49b)

IJ-CR-174183

GitOrigin-RevId: c8aa48ab8325db9070442b2b80fd115c9d26bff9
This commit is contained in:
Aleksandra Olemskaia
2025-09-01 12:41:54 +02:00
committed by intellij-monorepo-bot
parent 3806d403d2
commit 9737a5e6c2
2 changed files with 217 additions and 174 deletions

View File

@@ -60,7 +60,7 @@ enable.dialog.change=Change
enable.sync.check.server.data.progress=Checking server data\u2026 enable.sync.check.server.data.progress=Checking server data\u2026
enable.sync.get.from.server.progress=Getting Settings from Server\u2026 enable.sync.get.from.server.progress=Getting Settings from Server\u2026
enable.sync.push.to.server.progress=Pushing Settings to Server\u2026 enable.sync.push.to.server.progress=Pushing Settings to Server\u2026
enable.sync.add.account=Add Account enable.sync.add.account=Add Account\u2026
enable.sync.choose.data.provider.title=Choose Provider enable.sync.choose.data.provider.title=Choose Provider
enable.sync.choose.data.provider.text=Please select a provider that will store and process your data. enable.sync.choose.data.provider.text=Please select a provider that will store and process your data.
# {0} - name of the provider - JetBrains or Google # {0} - name of the provider - JetBrains or Google
@@ -82,7 +82,7 @@ status.action.settings.sync.is.off=Off
status.action.settings.sync.is.on=On status.action.settings.sync.is.on=On
status.action.settings.sync.failed=Failed status.action.settings.sync.failed=Failed
status.action.settings.sync.pending.action=Action Required status.action.settings.sync.pending.action=Action Required
logout.link.text=Log out {0} Account logout.link.text=Log Out {0} Account\u2026
# logout.dialog.title=Confirm Log Out # logout.dialog.title=Confirm Log Out
# logout.dialog.message=Are you sure you want to log out? # logout.dialog.message=Are you sure you want to log out?
# logout.dialog.button=Log Out # logout.dialog.button=Log Out

View File

@@ -6,7 +6,6 @@ package com.intellij.settingsSync.core.config
import com.intellij.BundleBase import com.intellij.BundleBase
import com.intellij.CommonBundle import com.intellij.CommonBundle
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.application.EDT import com.intellij.openapi.application.EDT
@@ -20,14 +19,11 @@ import com.intellij.openapi.options.BoundConfigurable
import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableProvider import com.intellij.openapi.options.ConfigurableProvider
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.MessageDialogBuilder.Companion.yesNo import com.intellij.openapi.ui.MessageDialogBuilder.Companion.yesNo
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.ListSeparator
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.IntellijInternalApi import com.intellij.openapi.util.IntellijInternalApi
import com.intellij.openapi.util.text.HtmlBuilder import com.intellij.openapi.util.text.HtmlBuilder
@@ -45,29 +41,25 @@ import com.intellij.settingsSync.core.communicator.SettingsSyncUserData
import com.intellij.settingsSync.core.communicator.getAvailableSyncProviders import com.intellij.settingsSync.core.communicator.getAvailableSyncProviders
import com.intellij.settingsSync.core.config.SettingsSyncEnabler.State import com.intellij.settingsSync.core.config.SettingsSyncEnabler.State
import com.intellij.settingsSync.core.statistics.SettingsSyncEventsStatistics import com.intellij.settingsSync.core.statistics.SettingsSyncEventsStatistics
import com.intellij.ui.RelativeFont import com.intellij.ui.MutableCollectionComboBoxModel
import com.intellij.ui.components.DropDownLink import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.JBHtmlPane import com.intellij.ui.components.JBHtmlPane
import com.intellij.ui.components.JBRadioButton import com.intellij.ui.components.JBRadioButton
import com.intellij.ui.dsl.builder.* import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.builder.components.DslLabel import com.intellij.ui.dsl.listCellRenderer.listCellRenderer
import com.intellij.ui.dsl.builder.components.DslLabelType
import com.intellij.ui.dsl.gridLayout.UnscaledGaps import com.intellij.ui.dsl.gridLayout.UnscaledGaps
import com.intellij.ui.layout.ComponentPredicate import com.intellij.ui.layout.ComponentPredicate
import com.intellij.ui.layout.and import com.intellij.ui.layout.and
import com.intellij.ui.layout.not import com.intellij.ui.layout.not
import com.intellij.ui.layout.selected import com.intellij.ui.layout.selected
import com.intellij.ui.popup.list.ListPopupImpl
import com.intellij.ui.scale.JBUIScale.scale
import com.intellij.util.Consumer import com.intellij.util.Consumer
import com.intellij.util.asDisposable import com.intellij.util.asDisposable
import com.intellij.util.IconUtil
import com.intellij.util.text.DateFormatUtil import com.intellij.util.text.DateFormatUtil
import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBUI import com.intellij.util.ui.JBUI
import com.intellij.util.ui.NamedColorUtil import com.intellij.util.ui.NamedColorUtil
import com.intellij.util.ui.StartupUiUtil.labelFont
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.awt.event.ItemEvent
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
@@ -85,18 +77,21 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
private lateinit var configPanel: DialogPanel private lateinit var configPanel: DialogPanel
private lateinit var enableCheckbox: JCheckBox private lateinit var enableCheckbox: JCheckBox
private lateinit var cellDropDownLink: Cell<DropDownLink<UserProviderHolder?>>
private lateinit var userDropDownLink: DropDownLink<UserProviderHolder?> private val userAccountsList = arrayListOf<UserProviderHolder>()
private var userAccountsLogout: UserProviderHolder? = null
private val userComboBoxModel = MutableCollectionComboBoxModel<UserProviderHolder>()
private var userProviderHolder: UserProviderHolder? = currentUser()
private lateinit var cellUserComboBox: Cell<ComboBox<UserProviderHolder>>
private lateinit var syncTypeLabel: JBHtmlPane private lateinit var syncTypeLabel: JBHtmlPane
private lateinit var syncConfigPanel: DialogPanel private lateinit var syncConfigPanel: DialogPanel
private val syncEnabler = SettingsSyncEnabler() private val syncEnabler = SettingsSyncEnabler()
private val enableSyncOption = AtomicProperty<InitSyncType>(InitSyncType.GET_FROM_SERVER) private val enableSyncOption = AtomicProperty<InitSyncType>(InitSyncType.GET_FROM_SERVER)
private val disableSyncOption = AtomicProperty<DisableSyncType>(DisableSyncType.DISABLE) private val disableSyncOption = AtomicProperty<DisableSyncType>(DisableSyncType.DISABLE)
private val remoteSettingsExist = AtomicBooleanProperty(false) private val remoteSettingsExist = AtomicBooleanProperty(false)
private val wasUsedBefore = AtomicBooleanProperty(currentUser() != null) private val wasUsedBefore = AtomicBooleanProperty(currentUser() != null)
private val userAccountsList = arrayListOf<UserProviderHolder>()
private val userAccountListIsNotEmpty = AtomicBooleanProperty(false) private val userAccountListIsNotEmpty = AtomicBooleanProperty(false)
private val syncPanelHolder = SettingsSyncPanelHolder() private val syncPanelHolder = SettingsSyncPanelHolder()
private val hasMultipleProviders = AtomicBooleanProperty(RemoteCommunicatorHolder.getExternalProviders().isNotEmpty()) private val hasMultipleProviders = AtomicBooleanProperty(RemoteCommunicatorHolder.getExternalProviders().isNotEmpty())
@@ -120,13 +115,19 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
SettingsSyncEvents.getInstance().removeListener(this) SettingsSyncEvents.getInstance().removeListener(this)
} }
private fun currentUserId() = SettingsSyncLocalSettings.getInstance().userId
private fun currentUser(): UserProviderHolder? { private fun currentUser(): UserProviderHolder? {
val userId = SettingsSyncLocalSettings.getInstance().userId ?: return null val userId = currentUserId() ?: return null
val providerCode = SettingsSyncLocalSettings.getInstance().providerCode ?: return null val providerCode = SettingsSyncLocalSettings.getInstance().providerCode ?: return null
val authService = RemoteCommunicatorHolder.getProvider(providerCode)?.authService ?: return null val authService = RemoteCommunicatorHolder.getProvider(providerCode)?.authService ?: return null
return authService.getAvailableUserAccounts().find { return authService.getAvailableUserAccounts().find {
it.id == userId it.id == userId
}?.toUserProviderHolder(authService.providerName) }?.toUserProviderHolder(authService.providerName)
}
private fun selectedUser(): UserProviderHolder? {
return userComboBoxModel.selectedItem as? UserProviderHolder
} }
override fun createPanel(): DialogPanel { override fun createPanel(): DialogPanel {
@@ -136,8 +137,11 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
configPanel = panel { configPanel = panel {
updateUserAccountsList() updateUserAccountsList()
val userProviderHolder: UserProviderHolder? = currentUser() ?: userAccountsList.firstOrNull { it != UserProviderHolder.addAccount } validateCurrentUser()
val authService = userProviderHolder?.let { RemoteCommunicatorHolder.getProvider(userProviderHolder.providerCode) } ?.authService updateUserAccountLogout(userProviderHolder)
userComboBoxModel.selectedItem = userProviderHolder
updateUserComboBoxModel()
val authService = currentUser()?.let { RemoteCommunicatorHolder.getProvider(it.providerCode) } ?.authService
syncPanelHolder.crossSyncSupported.set(authService?.crossSyncSupported() ?: true) syncPanelHolder.crossSyncSupported.set(authService?.crossSyncSupported() ?: true)
val infoRow = row { val infoRow = row {
@Suppress("DialogTitleCapitalization") @Suppress("DialogTitleCapitalization")
@@ -188,22 +192,57 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
enableButtonAction() enableButtonAction()
} }
infoRow.visibleIf(enableCheckbox.selected.not()) infoRow.visibleIf(enableCheckbox.selected.not())
userDropDownLink = DropDownLink<UserProviderHolder?>(userProviderHolder) { link: DropDownLink<UserProviderHolder?>? -> showAccounts(link) }
cellDropDownLink = cell(userDropDownLink).onChangedContext { component, context -> val listCellRenderer = listCellRenderer<UserProviderHolder>("") {
val event = context.event val holder = value
if (event is ItemEvent && event.item == UserProviderHolder.addAccount) { var icon2Apply = IconUtil.getEmptyIcon(false)
val syncTypeDialog = AddAccountDialog(configPanel) when {
if (syncTypeDialog.showAndGet()) { holder.userId == UserProviderHolder.LOGOUT_USER_ID -> {
val providerCode = syncTypeDialog.providerCode separator { text = "" }
val provider = RemoteCommunicatorHolder.getProvider(providerCode) ?: return@onChangedContext text(message("logout.link.text", holder.providerName)) {
component.selectedItem = null attributes = SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES.derive(
component.text = "" SimpleTextAttributes.STYLE_PLAIN,
login(provider, syncConfigPanel) JBUI.CurrentTheme.Link.Foreground.ENABLED,
null,
null
)
font = JBFont.medium()
}
}
holder == UserProviderHolder.ADD_ACCOUNT -> {
icon(icon2Apply)
separator { text = "" }
text(holder.toString())
}
else -> {
if (index == -1) {
RemoteCommunicatorHolder.getProvider(holder.providerCode)?.authService?.icon?.let {
icon2Apply = it
}
}
else {
if (index == 0 || holder.providerCode != userAccountsList[index - 1].providerCode) {
separator { text = holder.providerName }
}
}
if (index >= 0 && holder == userComboBoxModel.selectedItem) {
icon2Apply = AllIcons.Actions.Checked
}
icon(icon2Apply)
text(holder.toString())
} }
} else {
component.text = component.selectedItem.toString()
} }
}.comment("", MAX_LINE_LENGTH_NO_WRAP) }
cellUserComboBox = comboBox(userComboBoxModel, listCellRenderer)
.resizableColumn().align(AlignX.FILL)
.comment("", 50)
cellUserComboBox.whenItemSelectedFromUi { item ->
if (item != userProviderHolder) {
tryChangeAccount(item)
}
}
}.visibleIf(userAccountListIsNotEmpty) }.visibleIf(userAccountListIsNotEmpty)
// settings to sync // settings to sync
@@ -249,26 +288,28 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
wasUsedBefore.set(currentUser() != null) wasUsedBefore.set(currentUser() != null)
hasMultipleProviders.set(RemoteCommunicatorHolder.getExternalProviders().isNotEmpty()) hasMultipleProviders.set(RemoteCommunicatorHolder.getExternalProviders().isNotEmpty())
enableCheckbox.isSelected = SettingsSyncSettings.getInstance().syncEnabled enableCheckbox.isSelected = SettingsSyncSettings.getInstance().syncEnabled
if (currentUser() != null) { userProviderHolder = if (currentUser() != null) {
userDropDownLink.selectedItem = userAccountsList.firstOrNull { it.userId == SettingsSyncLocalSettings.getInstance().userId} userAccountsList.firstOrNull { it.userId == currentUserId() }
} else if (userAccountListIsNotEmpty.get()) { } else if (userAccountListIsNotEmpty.get()) {
userDropDownLink.selectedItem = userAccountsList.firstOrNull { it != UserProviderHolder.addAccount } userAccountsList.firstOrNull { it != UserProviderHolder.ADD_ACCOUNT }
} else { } else {
userDropDownLink.selectedItem = null null
} }
userComboBoxModel.selectedItem = userProviderHolder
syncStatusChanged() syncStatusChanged()
} }
.onIsModified { .onIsModified {
enableCheckbox.isSelected != SettingsSyncSettings.getInstance().syncEnabled enableCheckbox.isSelected != SettingsSyncSettings.getInstance().syncEnabled
|| syncConfigPanel.isModified() || syncConfigPanel.isModified()
|| (SettingsSyncLocalSettings.getInstance().userId != null && userDropDownLink.selectedItem?.userId != SettingsSyncLocalSettings.getInstance().userId) || (currentUserId() != null && selectedUser()?.userId != currentUserId())
} }
.onApply { .onApply {
val selectedUser = selectedUser()
with(SettingsSyncLocalSettings.getInstance()) { with(SettingsSyncLocalSettings.getInstance()) {
userId = userDropDownLink.selectedItem?.userId userId = selectedUser?.userId
providerCode = userDropDownLink.selectedItem?.providerCode providerCode = selectedUser?.providerCode
} }
cellDropDownLink.comment?.text = "" cellUserComboBox.comment?.text = ""
if (enableCheckbox.isSelected) { if (enableCheckbox.isSelected) {
syncConfigPanel.apply() syncConfigPanel.apply()
} }
@@ -349,7 +390,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
return return
} }
runWithModalProgressBlocking(ModalTaskOwner.component(configPanel), message("enable.sync.check.server.data.progress")) { runWithModalProgressBlocking(ModalTaskOwner.component(configPanel), message("enable.sync.check.server.data.progress")) {
val (userId, userData, providerCode, providerName) = userDropDownLink.selectedItem ?: run { val (userId, _, providerCode, providerName) = selectedUser() ?: run {
LOG.warn("No selected user") LOG.warn("No selected user")
showErrorOnEDT(message("enable.dialog.error.no.user")) showErrorOnEDT(message("enable.dialog.error.no.user"))
enableCheckbox.isSelected = false enableCheckbox.isSelected = false
@@ -372,7 +413,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
syncConfigPanel.reset() syncConfigPanel.reset()
triggerUpdateConfigurable() triggerUpdateConfigurable()
} }
cellDropDownLink.comment?.text = "<icon src='AllIcons.General.History'>&nbsp;" + cellUserComboBox.comment?.text = "<icon src='AllIcons.General.History'>&nbsp;" +
message("sync.status.will.enable", message("sync.status.will.enable",
CommonBundle.getApplyButtonText().replace(BundleBase.MNEMONIC_STRING, "")) CommonBundle.getApplyButtonText().replace(BundleBase.MNEMONIC_STRING, ""))
} else { } else {
@@ -384,7 +425,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
val syncDisableOption = showDisableSyncDialog() val syncDisableOption = showDisableSyncDialog()
if (syncDisableOption != DisableSyncType.DONT_DISABLE) { if (syncDisableOption != DisableSyncType.DONT_DISABLE) {
disableSyncOption.set(syncDisableOption) disableSyncOption.set(syncDisableOption)
cellDropDownLink.comment?.text = message("sync.status.will.disable") cellUserComboBox.comment?.text = message("sync.status.will.disable")
handleDisableSync() handleDisableSync()
} else { } else {
enableCheckbox.isSelected = true enableCheckbox.isSelected = true
@@ -395,7 +436,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
} }
private fun showDisableSyncDialog(): DisableSyncType { private fun showDisableSyncDialog(): DisableSyncType {
val providerName = userDropDownLink.selectedItem?.providerName ?: "" val providerName = selectedUser()?.providerName ?: ""
val intResult = if (SettingsSyncStatusTracker.getInstance().currentStatus is SettingsSyncStatusTracker.SyncStatus.ActionRequired) { val intResult = if (SettingsSyncStatusTracker.getInstance().currentStatus is SettingsSyncStatusTracker.SyncStatus.ActionRequired) {
Messages.showDialog( Messages.showDialog(
message("disable.dialog.text", providerName), message("disable.dialog.text", providerName),
@@ -504,134 +545,121 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
syncTypeLabel.text = html.toString() syncTypeLabel.text = html.toString()
} }
private fun showAccounts(link: DropDownLink<UserProviderHolder?>?): JBPopup { private fun tryChangeAccount(selectedValue: UserProviderHolder) {
val accounts = object : BaseListPopupStep<UserProviderHolder>() { when {
private var stepSelectedValue: UserProviderHolder? = null selectedValue == UserProviderHolder.ADD_ACCOUNT -> {
override fun onChosen(selectedValue: UserProviderHolder, finalChoice: Boolean): PopupStep<*>? { if (SettingsSyncSettings.getInstance().syncEnabled && !disableCurrentSyncDialog()) {
stepSelectedValue = selectedValue userComboBoxModel.selectedItem = userProviderHolder
return FINAL_CHOICE return
} }
val syncTypeDialog = AddAccountDialog(configPanel)
override fun getTextFor(value: UserProviderHolder?): String { if (syncTypeDialog.showAndGet()) {
return if (value == UserProviderHolder.addAccount) { val providerCode = syncTypeDialog.providerCode
message("enable.sync.add.account") val provider = RemoteCommunicatorHolder.getProvider(providerCode) ?: return
login(provider, syncConfigPanel)
} }
else { else {
value?.toString() ?: "" userComboBoxModel.selectedItem = userProviderHolder
return
} }
} }
selectedValue.userId == UserProviderHolder.LOGOUT_USER_ID -> {
override fun getSeparatorAbove(value: UserProviderHolder?): ListSeparator? { val logoutFunction = userProviderHolder?.let { user ->
return value?.separatorString?.let { ListSeparator(it) } val provider = RemoteCommunicatorHolder.getProvider(user.providerCode)
} provider?.authService?.logoutFunction
override fun getFinalRunnable(): Runnable? {
if (stepSelectedValue != null) {
return Runnable { tryChangeAccount(stepSelectedValue!!) }
} }
return null if (logoutFunction != null) {
} coroutineScope.launch(ModalityState.current().asContextElement()) {
withContext(Dispatchers.EDT) {
init { logoutFunction(configPanel)
init(null, userAccountsList, emptyList()) configPanel.reset()
defaultOptionIndex = userAccountsList.indexOf(userDropDownLink.selectedItem)
}
}
val currentProviderCode = link?.selectedItem?.providerCode
val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(configPanel))
val provider = currentProviderCode?.let { RemoteCommunicatorHolder.getProvider(it ) }
val logoutFunction = provider?.authService?.logoutFunction
val listPopup = object : ListPopupImpl(project, accounts) {
override fun setFooterComponent(c: JComponent?) {
val thePopup = this
if (logoutFunction == null) {
return super.setFooterComponent(c)
}
super.setFooterComponent(DslLabel(DslLabelType.LABEL).apply {
text = "<a>${message("logout.link.text", provider.authService.providerName ?: "")}</a>"
addHyperlinkListener {
if (it.eventType == HyperlinkEvent.EventType.ACTIVATED) {
thePopup.cancel()
coroutineScope.launch(ModalityState.current().asContextElement()) {
withContext(Dispatchers.EDT) {
logoutFunction(configPanel)
}
}
} }
} }
foreground = JBUI.CurrentTheme.Advertiser.foreground()
background = JBUI.CurrentTheme.Advertiser.background()
setOpaque(true)
setFont(RelativeFont.NORMAL.scale(JBUI.CurrentTheme.Advertiser.FONT_SIZE_OFFSET.get(), scale(11f)).derive(labelFont))
setBorder(JBUI.CurrentTheme.Advertiser.border())
})
}
}
if (currentProviderCode != null && logoutFunction != null) {
listPopup.setAdText(message("logout.link.text", currentProviderCode)) // doesn't matter, will be changed
}
return listPopup
}
private fun tryChangeAccount(selectedValue: UserProviderHolder) {
if (selectedValue == UserProviderHolder.addAccount) {
if (SettingsSyncSettings.getInstance().syncEnabled && !disableCurrentSyncDialog()) {
return
}
val syncTypeDialog = AddAccountDialog(configPanel)
if (syncTypeDialog.showAndGet()) {
val providerCode = syncTypeDialog.providerCode
val provider = RemoteCommunicatorHolder.getProvider(providerCode) ?: return
login(provider, syncConfigPanel)
}
}
else {
val wasEnabled = SettingsSyncSettings.getInstance().syncEnabled
if (enableCheckbox.isSelected) {
if (wasEnabled) {
if (!disableCurrentSyncDialog()) {
return
}
userDropDownLink.selectedItem = selectedValue
enableCheckbox.doClick()
} else { } else {
userDropDownLink.selectedItem = selectedValue userComboBoxModel.selectedItem = userProviderHolder
enableButtonAction() }
}
else -> {
val wasEnabled = SettingsSyncSettings.getInstance().syncEnabled
if (enableCheckbox.isSelected) {
if (wasEnabled) {
if (!disableCurrentSyncDialog()) {
userComboBoxModel.selectedItem = userProviderHolder // setting old value
return
}
enableCheckbox.doClick()
}
else {
enableButtonAction()
}
}
userProviderHolder = selectedValue
if (updateUserAccountLogout(selectedValue)) {
updateUserComboBoxModel()
} }
} else {
userDropDownLink.selectedItem = selectedValue
} }
} }
} }
private fun updateUserAccountsList(): Boolean {
private fun updateUserAccountsList() { val newList = arrayListOf<UserProviderHolder>()
userAccountsList.clear()
val providersList = RemoteCommunicatorHolder.getAvailableProviders() val providersList = RemoteCommunicatorHolder.getAvailableProviders()
providersList.forEach { communicator -> providersList.forEach { communicator ->
val authService = communicator.authService val authService = communicator.authService
val providerName = authService.providerName val providerName = authService.providerName
authService.getAvailableUserAccounts().forEachIndexed { idx, account -> newList.addAll(authService.getAvailableUserAccounts().map { it.toUserProviderHolder(providerName) })
val separatorString = if (idx == 0)
providerName
else
null
userAccountsList.add(account.toUserProviderHolder(providerName, separatorString))
}
} }
if (hasMultipleProviders.get()) { if (hasMultipleProviders.get()) {
userAccountsList.add(UserProviderHolder.addAccount) newList.add(UserProviderHolder.ADD_ACCOUNT)
} }
userAccountListIsNotEmpty.set(userAccountsList.any { it != UserProviderHolder.addAccount }) if (newList != userAccountsList) {
userAccountsList.clear()
userAccountsList.addAll(newList)
userAccountListIsNotEmpty.set(userAccountsList.any { it != UserProviderHolder.ADD_ACCOUNT })
return true
}
return false
} }
private fun updateUserAccountLogout(selectedUser: UserProviderHolder?): Boolean {
val newUserAccountsLogout = selectedUser?.providerCode?.let {
val provider = RemoteCommunicatorHolder.getProvider(it)
provider?.authService?.logoutFunction?.let {
UserProviderHolder.logout(provider.providerCode, provider.authService.providerName)
}
}
if (newUserAccountsLogout != userAccountsLogout) {
userAccountsLogout = newUserAccountsLogout
return true
}
return false
}
private fun updateUserComboBoxModel() {
val selectedUser = selectedUser()
userComboBoxModel.update(userAccountsList + listOfNotNull(userAccountsLogout))
userComboBoxModel.selectedItem = selectedUser
}
private fun validateCurrentUser() {
val currentUser = currentUser()
if (currentUser in userAccountsList) {
userProviderHolder = currentUser
return
}
// logout from an active account could happen somewhere else, should switch users manually
val newCurrentUser = userAccountsList.firstOrNull { it != UserProviderHolder.ADD_ACCOUNT }
if (SettingsSyncSettings.getInstance().syncEnabled) {
handleDisableSync()
}
with(SettingsSyncLocalSettings.getInstance()) {
userId = newCurrentUser?.userId
providerCode = newCurrentUser?.providerCode
}
userProviderHolder = newCurrentUser
}
private fun login( private fun login(
provider: SettingsSyncCommunicatorProvider, provider: SettingsSyncCommunicatorProvider,
syncConfigPanel: DialogPanel, syncConfigPanel: DialogPanel,
@@ -646,17 +674,25 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
val remoteCommunicator = RemoteCommunicatorHolder.createRemoteCommunicator(provider, userData.id, loginDisposable) ?: return@withContext val remoteCommunicator = RemoteCommunicatorHolder.createRemoteCommunicator(provider, userData.id, loginDisposable) ?: return@withContext
if (checkServerState(syncPanelHolder, remoteCommunicator, provider.authService.crossSyncSupported())) { if (checkServerState(syncPanelHolder, remoteCommunicator, provider.authService.crossSyncSupported())) {
SettingsSyncEvents.getInstance().fireLoginStateChanged() SettingsSyncEvents.getInstance().fireLoginStateChanged()
userDropDownLink.selectedItem = UserProviderHolder(userData.id, userData, provider.authService.providerCode, val newHolder = UserProviderHolder(userData.id, userData, provider.authService.providerCode, provider.authService.providerName, null)
provider.authService.providerName, null) userProviderHolder = newHolder
userComboBoxModel.selectedItem = newHolder
updateUserAccountLogout(newHolder)
updateUserComboBoxModel()
enableCheckbox.isSelected = true enableCheckbox.isSelected = true
wasUsedBefore.set(true) wasUsedBefore.set(true)
syncConfigPanel.reset() syncConfigPanel.reset()
triggerUpdateConfigurable() triggerUpdateConfigurable()
} else {
userComboBoxModel.selectedItem = userProviderHolder
updateUserComboBoxModel()
} }
} }
} }
else { else {
LOG.info("Received empty user data from login") LOG.info("Received empty user data from login")
userComboBoxModel.selectedItem = userProviderHolder
} }
} }
catch (ex: CancellationException) { catch (ex: CancellationException) {
@@ -665,6 +701,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
} }
catch (ex: Throwable) { catch (ex: Throwable) {
LOG.warn("Error during login", ex) LOG.warn("Error during login", ex)
userComboBoxModel.selectedItem = userProviderHolder
} }
finally { finally {
Disposer.dispose(loginDisposable) Disposer.dispose(loginDisposable)
@@ -677,16 +714,19 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
UserProviderHolder(id, this, providerCode, providerName, separatorString) UserProviderHolder(id, this, providerCode, providerName, separatorString)
override fun syncStatusChanged() { override fun syncStatusChanged() {
if (!::cellDropDownLink.isInitialized) if (!::cellUserComboBox.isInitialized)
return return
updateUserAccountsList() if (updateUserAccountsList()) {
if (updateUserAccountLogout(selectedUser())) {
updateUserComboBoxModel()
}
}
refreshActionRequired() refreshActionRequired()
if (!enableCheckbox.isSelected) { if (!enableCheckbox.isSelected) {
if (lastRemoveRemoteDataError != null) { if (lastRemoveRemoteDataError != null) {
cellDropDownLink.comment?.text = "<icon src='AllIcons.General.Error'>&nbsp;" + cellUserComboBox.comment?.text = "<icon src='AllIcons.General.Error'>&nbsp;" + message("disable.remove.data.failure", lastRemoveRemoteDataError!!)
message("disable.remove.data.failure", lastRemoveRemoteDataError!!)
} else { } else {
cellDropDownLink.comment?.text = "" cellUserComboBox.comment?.text = ""
} }
return return
} }
@@ -695,19 +735,18 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
if (currentStatus == SettingsSyncStatusTracker.SyncStatus.Success) { if (currentStatus == SettingsSyncStatusTracker.SyncStatus.Success) {
val lastSyncTime = SettingsSyncStatusTracker.getInstance().getLastSyncTime() val lastSyncTime = SettingsSyncStatusTracker.getInstance().getLastSyncTime()
if (lastSyncTime > 0) { if (lastSyncTime > 0) {
cellDropDownLink.comment?.text = "<icon src='AllIcons.General.GreenCheckmark'>&nbsp;" + message("sync.status.last.sync.message", DateFormatUtil.formatPrettyDateTime(lastSyncTime)) cellUserComboBox.comment?.text = "<icon src='AllIcons.General.GreenCheckmark'>&nbsp;" + message("sync.status.last.sync.message", DateFormatUtil.formatPrettyDateTime(lastSyncTime))
} }
else { else {
cellDropDownLink.comment?.text = message("sync.status.enabled") cellUserComboBox.comment?.text = message("sync.status.enabled")
} }
} }
else if (currentStatus is SettingsSyncStatusTracker.SyncStatus.Error) { else if (currentStatus is SettingsSyncStatusTracker.SyncStatus.Error) {
cellDropDownLink.comment?.text = message("sync.status.failed", currentStatus.errorMessage) cellUserComboBox.comment?.text = message("sync.status.failed", currentStatus.errorMessage)
} }
} }
else { else {
//statusLabel.icon = icons.SettingsSyncIcons.StatusNotRun cellUserComboBox.comment?.text = ""
cellDropDownLink.comment?.text = ""
} }
} }
@@ -724,12 +763,12 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
} }
actionRequiredLabel.text = userActionRequired.message actionRequiredLabel.text = userActionRequired.message
actionRequiredButton.text = userActionRequired.actionTitle actionRequiredButton.text = userActionRequired.actionTitle
cellDropDownLink.comment?.text = message("sync.status.action.required.comment", cellUserComboBox.comment?.text = message("sync.status.action.required.comment",
userActionRequired.actionTitle, userActionRequired.actionTitle,
userActionRequired.actionDescription ?: userActionRequired.message) userActionRequired.actionDescription ?: userActionRequired.message)
} }
else { else {
cellDropDownLink.comment?.text = "" cellUserComboBox.comment?.text = ""
actionRequiredAction = null actionRequiredAction = null
actionRequiredLabel.text = "" actionRequiredLabel.text = ""
actionRequiredButton.text = "" actionRequiredButton.text = ""
@@ -739,7 +778,7 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
private fun getPendingUserAction(): PendingUserAction? { private fun getPendingUserAction(): PendingUserAction? {
if (!enableCheckbox.isSelected) if (!enableCheckbox.isSelected)
return null return null
return userDropDownLink.selectedItem?.let { return selectedUser()?.let {
RemoteCommunicatorHolder.getProvider(it.providerCode)?.authService?.getPendingUserAction(it.userId) RemoteCommunicatorHolder.getProvider(it.providerCode)?.authService?.getPendingUserAction(it.userId)
} }
} }
@@ -815,10 +854,14 @@ internal class SettingsSyncConfigurable(private val coroutineScope: CoroutineSco
val providerName: String, val providerName: String,
val separatorString: String?, // separator value, set only for the first account in the list val separatorString: String?, // separator value, set only for the first account in the list
) { ) {
companion object{ companion object {
val addAccount = UserProviderHolder( val ADD_ACCOUNT = UserProviderHolder(
"<ADDACCOUNT>", SettingsSyncUserData("<ADDACCOUNT>", "", null, null), "", "<ADD_ACCOUNT>", SettingsSyncUserData("<ADD_ACCOUNT>", "", null, null, message("enable.sync.add.account")), "",
"", "") "", "")
const val LOGOUT_USER_ID = "<LOGOUT>"
fun logout(providerCode: String, providerName: String) = UserProviderHolder(
LOGOUT_USER_ID, SettingsSyncUserData(LOGOUT_USER_ID, providerCode, null, null, LOGOUT_USER_ID), providerCode,
providerName, "")
} }
override fun toString(): String { override fun toString(): String {