package main import ( "image/color" "runtime" "strings" "gioui.org/layout" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" ) func (u *ui) syncDialog(gtx layout.Context) layout.Dimensions { return layout.Stack{}.Layout(gtx, layout.Expanded(func(gtx layout.Context) layout.Dimensions { paint.FillShape(gtx.Ops, color.NRGBA{A: 90}, clip.Rect{Max: gtx.Constraints.Max}.Op()) return layout.Dimensions{Size: gtx.Constraints.Max} }), layout.Stacked(func(gtx layout.Context) layout.Dimensions { return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions { width := gtx.Dp(unit.Dp(620)) if width > gtx.Constraints.Max.X { width = gtx.Constraints.Max.X - gtx.Dp(unit.Dp(24)) } if width < 1 { width = gtx.Constraints.Max.X } gtx.Constraints.Min.X = width gtx.Constraints.Max.X = width return card(gtx, u.syncDialogContent) }) }), ) } func (u *ui) syncDialogContent(gtx layout.Context) layout.Dimensions { matchingCredentials := u.matchingAdvancedSyncRemoteCredentialEntries() if len(u.syncRemoteCredentialClicks) < len(matchingCredentials) { u.syncRemoteCredentialClicks = make([]widget.Clickable, len(matchingCredentials)) } return material.List(u.theme, &u.syncDialogList).Layout(gtx, 1, func(gtx layout.Context, _ int) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(20), u.syncDialogTitle()) lbl.Color = accentColor return lbl.Layout(gtx) }), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(14), u.syncDialogDescription()) lbl.Color = mutedColor return lbl.Layout(gtx) }), layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if !u.shouldShowSyncDirectionChoices() { return layout.Dimensions{} } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(syncDialogSectionLabel(u.theme, "Direction")), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return syncChoiceButton(gtx, u.theme, &u.showSyncPull, "Pull Into Current Vault", u.syncDirection == syncDirectionPull) }), layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return syncChoiceButton(gtx, u.theme, &u.showSyncPush, "Push Current Vault Out", u.syncDirection == syncDirectionPush) }), ) }), ) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if !u.shouldShowSyncDirectionChoices() { return layout.Dimensions{} } return layout.Spacer{Height: unit.Dp(12)}.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if !u.shouldShowSyncSourceChoices() { return layout.Dimensions{} } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(syncDialogSectionLabel(u.theme, "Other Source")), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return syncChoiceButton(gtx, u.theme, &u.showSyncLocal, "Local File", u.syncSourceMode == syncSourceLocal) }), layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return syncChoiceButton(gtx, u.theme, &u.showSyncRemote, "Remote WebDAV", u.syncSourceMode == syncSourceRemote) }), ) }), ) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if !u.shouldShowSyncSourceChoices() { return layout.Dimensions{} } return layout.Spacer{Height: unit.Dp(12)}.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return syncDialogSummaryCard(gtx, u.theme, u.syncDialogPurpose, u.syncSourceMode, u.syncDirection) }), layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if u.syncSourceMode == syncSourceRemote { children := []layout.FlexChild{ layout.Rigid(labeledEditorHelp(u.theme, "Remote Base URL", "WebDAV base URL for the other source.", &u.syncRemoteBaseURL, false)), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(labeledEditorHelp(u.theme, "Remote Path", "Path to the other remote .kdbx file.", &u.syncRemotePath, false)), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(labeledEditorHelp(u.theme, "Remote Username", "Username for the other WebDAV source.", &u.syncRemoteUsername, false)), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return u.syncPasswordField(gtx) }), } if u.syncDialogPurpose == syncDialogPurposeRemoteSetup { children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { check := material.CheckBox(u.theme, &u.syncSetupAutomatic, "Sync automatically on open and save") check.Color = accentColor return check.Layout(gtx) }), ) } if len(matchingCredentials) > 0 { children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(11), "Matching vault credentials") lbl.Color = mutedColor return lbl.Layout(gtx) }), ) for i, entry := range matchingCredentials { i := i entry := entry children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { label := entry.Title if strings.TrimSpace(entry.Username) != "" { label += " ยท " + strings.TrimSpace(entry.Username) } selected := strings.TrimSpace(u.selectedSyncRemoteCredentialEntryID) == entry.ID return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions { return u.syncRemoteCredentialClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(13), label) lbl.Color = accentColor return lbl.Layout(gtx) }) }) }), ) } } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...) } if supportsDesktopFilePicker(runtime.GOOS) { return selectorEditorHelp(u.theme, "Local Vault Path", "Choose the other local .kdbx file to synchronize with.", &u.syncLocalPath, &u.pickSyncLocalPath, "Choose File", false)(gtx) } return labeledEditorHelp(u.theme, "Local Vault Path", "Enter the shared-storage path to the other local .kdbx file to synchronize with.", &u.syncLocalPath, false)(gtx) }), layout.Rigid(layout.Spacer{Height: unit.Dp(14)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.runAdvancedSync, u.syncDialogConfirmButtonLabel()) }), layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.closeAdvancedSync, "Cancel") }), ) }), ) }) } func (u *ui) syncPasswordField(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(12), "REMOTE PASSWORD") lbl.Color = mutedColor return lbl.Layout(gtx) }), layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { field := func(gtx layout.Context) layout.Dimensions { editor := material.Editor(u.theme, &u.syncRemotePassword, "Remote Password") editor.Color = u.theme.Palette.Fg editor.HintColor = mutedColor return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout) } return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { return u.outlinedFieldState(gtx, false, field) }), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return u.inlinePasswordToggle(gtx, &u.toggleSyncPassword, u.showSyncPassword) }), ) }), layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(12), "Password or app token for the other WebDAV source.") lbl.Color = mutedColor return lbl.Layout(gtx) }), ) }