7 changed files with 456 additions and 2 deletions
@ -0,0 +1,294 @@ |
|||||
|
import QtQuick 2.10 |
||||
|
import QtQuick.Controls 2.3 |
||||
|
import QtQuick.Controls.Material 2.3 |
||||
|
import QtQuick.Controls.Universal 2.3 |
||||
|
import QtQuick.Layouts 1.3 |
||||
|
import de.skycoder42.QtDataSync 4.0 |
||||
|
import de.skycoder42.QtMvvm.Core 1.1 |
||||
|
import de.skycoder42.QtMvvm.Quick 1.1 |
||||
|
import de.skycoder42.QtMvvm.DataSync.Core 1.1 |
||||
|
|
||||
|
/*! @brief The view implementation for the QtMvvm::DataSyncViewModel |
||||
|
* |
||||
|
* @extends QtQuick.Controls.Page |
||||
|
* |
||||
|
* @details This is the view used to present a datasync view model. You can extend the class |
||||
|
* if you need to extend that view. |
||||
|
* |
||||
|
* @sa QtMvvm::DataSyncViewModel |
||||
|
*/ |
||||
|
Page { |
||||
|
id: _dataSyncView |
||||
|
|
||||
|
/*! @brief The viewmodel to use |
||||
|
* |
||||
|
* @default{<i>Injected</i>} |
||||
|
* |
||||
|
* @accessors{ |
||||
|
* @memberAc{viewModel} |
||||
|
* @notifyAc{viewModelChanged()} |
||||
|
* } |
||||
|
* |
||||
|
* @sa QtMvvm::DataSyncViewModel |
||||
|
*/ |
||||
|
property DataSyncViewModel viewModel: null |
||||
|
|
||||
|
header: ContrastToolBar { |
||||
|
id: _toolBar |
||||
|
|
||||
|
RowLayout { |
||||
|
id: _toolLayout |
||||
|
anchors.fill: parent |
||||
|
spacing: 0 |
||||
|
|
||||
|
ToolBarLabel { |
||||
|
id: _titleLabel |
||||
|
Layout.fillWidth: true |
||||
|
text: qsTr("Synchronization") |
||||
|
} |
||||
|
|
||||
|
ActionButton { |
||||
|
id: _syncButton |
||||
|
icon.name: "view-refresh" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_sync.svg" |
||||
|
text: qsTr("Synchronize") |
||||
|
onClicked: viewModel.syncOrConnect() |
||||
|
} |
||||
|
|
||||
|
ActionButton { |
||||
|
id: _idButton |
||||
|
icon.name: "fingerprint-gui" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_fingerprint.svg" |
||||
|
text: qsTr("Edit Identity") |
||||
|
onClicked: viewModel.showDeviceInfo() |
||||
|
} |
||||
|
|
||||
|
MenuButton { |
||||
|
id: _moreButton |
||||
|
|
||||
|
MenuItem { |
||||
|
text: qsTr("Update exchange key") |
||||
|
onClicked: viewModel.accountManager.updateExchangeKey() |
||||
|
} |
||||
|
|
||||
|
MenuSeparator {} |
||||
|
|
||||
|
MenuItem { |
||||
|
text: qsTr("Reload devices list") |
||||
|
onClicked: viewModel.accountManager.listDevices() |
||||
|
} |
||||
|
|
||||
|
MenuSeparator {} |
||||
|
|
||||
|
MenuItem { |
||||
|
text: qsTr("Change remote server") |
||||
|
onClicked: viewModel.changeRemote() |
||||
|
} |
||||
|
|
||||
|
MenuItem { |
||||
|
text: qsTr("Reset Identity") |
||||
|
onClicked: viewModel.performReset() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Pane { |
||||
|
anchors.fill: parent |
||||
|
ColumnLayout { |
||||
|
id: _layout |
||||
|
anchors.fill: parent |
||||
|
spacing: 16 |
||||
|
|
||||
|
Switch { |
||||
|
id: _syncSwitch |
||||
|
text: qsTr("Synchronization enabled") |
||||
|
Layout.fillWidth: true |
||||
|
|
||||
|
MvvmBinding { |
||||
|
viewModel: _dataSyncView.viewModel.syncManager |
||||
|
viewModelProperty: "syncEnabled" |
||||
|
view: _syncSwitch |
||||
|
viewProperty: "checked" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Label { |
||||
|
id: _statusLabel |
||||
|
Layout.fillWidth: true |
||||
|
text: viewModel.statusString |
||||
|
font.bold: true |
||||
|
font.pointSize: 16 |
||||
|
horizontalAlignment: Text.AlignHCenter |
||||
|
verticalAlignment: Text.AlignVCenter |
||||
|
} |
||||
|
|
||||
|
ProgressBar { |
||||
|
id: _syncProgress |
||||
|
Layout.fillWidth: true |
||||
|
from: 0 |
||||
|
to: 1 |
||||
|
value: viewModel.syncManager.syncProgress |
||||
|
visible: !_errorLabel.visible |
||||
|
} |
||||
|
|
||||
|
Label { |
||||
|
id: _errorLabel |
||||
|
Layout.fillWidth: true |
||||
|
wrapMode: Text.WordWrap |
||||
|
text: viewModel.syncManager.lastError |
||||
|
visible: text != "" |
||||
|
color: "#aa0000" |
||||
|
font.bold: true |
||||
|
} |
||||
|
|
||||
|
Rectangle { |
||||
|
Layout.fillWidth: true |
||||
|
Layout.minimumHeight: 1 |
||||
|
Layout.maximumHeight: 1 |
||||
|
color: { |
||||
|
if(QuickPresenter.currentStyle === "Material") |
||||
|
return Material.foreground; |
||||
|
else if(QuickPresenter.currentStyle === "Universal") |
||||
|
return Universal.foreground; |
||||
|
else |
||||
|
return "black"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Label { |
||||
|
Layout.fillWidth: true |
||||
|
text: qsTr("Other Devices:") |
||||
|
} |
||||
|
|
||||
|
ScrollView { |
||||
|
id: _devicesScrollView |
||||
|
|
||||
|
Layout.fillWidth: true |
||||
|
Layout.fillHeight: true |
||||
|
clip: true |
||||
|
|
||||
|
ListView { |
||||
|
id: _devicesList |
||||
|
|
||||
|
model: viewModel.sortedModel |
||||
|
delegate: SwipeDelegate { |
||||
|
id: _swipeDelegate |
||||
|
width: _devicesScrollView.width |
||||
|
|
||||
|
contentItem: ColumnLayout { |
||||
|
id: _delegateLayout |
||||
|
spacing: 8 |
||||
|
|
||||
|
Label { |
||||
|
id: _nameLabel |
||||
|
Layout.fillWidth: true |
||||
|
text: name |
||||
|
} |
||||
|
|
||||
|
Label { |
||||
|
id: _fpLabel |
||||
|
font.pointSize: _nameLabel.font.pointSize * 0.8 |
||||
|
Layout.fillWidth: true |
||||
|
Layout.leftMargin: 8 |
||||
|
text: fingerPrint |
||||
|
elide: Text.ElideMiddle |
||||
|
opacity: 0.75 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ListView.onRemove: SequentialAnimation { |
||||
|
PropertyAction { |
||||
|
target: _swipeDelegate |
||||
|
property: "ListView.delayRemove" |
||||
|
value: true |
||||
|
} |
||||
|
NumberAnimation { |
||||
|
target: _swipeDelegate |
||||
|
property: "height" |
||||
|
to: 0 |
||||
|
easing.type: Easing.InOutQuad |
||||
|
} |
||||
|
PropertyAction { |
||||
|
target: _swipeDelegate |
||||
|
property: "ListView.delayRemove" |
||||
|
value: false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
swipe.right: Rectangle { |
||||
|
height: _devicesScrollView.height |
||||
|
width: height |
||||
|
anchors.right: parent.right |
||||
|
color: { |
||||
|
if(QuickPresenter.currentStyle === "Material") |
||||
|
return Material.color(Material.Red); |
||||
|
else if(QuickPresenter.currentStyle === "Universal") |
||||
|
return Universal.color(Universal.Red); |
||||
|
else |
||||
|
return "#FF0000"; |
||||
|
} |
||||
|
|
||||
|
ActionButton { |
||||
|
anchors.centerIn: parent |
||||
|
implicitHeight: parent.height |
||||
|
implicitWidth: parent.width |
||||
|
|
||||
|
icon.name: "user-trash" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_delete_forever.svg" |
||||
|
text: qsTr("Remove Device") |
||||
|
|
||||
|
Material.foreground: "white" |
||||
|
Universal.foreground: "white" |
||||
|
|
||||
|
onClicked: { |
||||
|
_swipeDelegate.swipe.close(); |
||||
|
viewModel.removeDevice(index) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
RoundMenuButton { |
||||
|
id: _addButton |
||||
|
|
||||
|
anchors.right: parent.right |
||||
|
anchors.bottom: parent.bottom |
||||
|
anchors.margins: 16 |
||||
|
|
||||
|
text: qsTr("Add new devices") |
||||
|
icon.name: checked ? "tab-close" : "list-add" |
||||
|
icon.source: checked ? |
||||
|
"qrc:/de/skycoder42/qtmvvm/quick/icons/ic_close.svg" : |
||||
|
"qrc:/de/skycoder42/qtmvvm/quick/icons/ic_add.svg" |
||||
|
stickyToolTips: true |
||||
|
|
||||
|
Action { |
||||
|
text: qsTr("Network Exchange") |
||||
|
icon.name: "network-connect" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_exchange.svg" |
||||
|
|
||||
|
onTriggered: viewModel.startNetworkExchange() |
||||
|
} |
||||
|
|
||||
|
Action { |
||||
|
text: qsTr("Export to file") |
||||
|
icon.name: "document-export" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_export.svg" |
||||
|
|
||||
|
onTriggered: viewModel.startExport() |
||||
|
} |
||||
|
|
||||
|
Action { |
||||
|
text: qsTr("Import from file") |
||||
|
icon.name: "document-import" |
||||
|
icon.source: "qrc:/de/skycoder42/qtmvvm/quick/icons/ic_import.svg" |
||||
|
|
||||
|
onTriggered: viewModel.startImport() |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,156 @@ |
|||||
|
import QtQuick 2.10 |
||||
|
import QtQuick.Controls 2.3 |
||||
|
import de.skycoder42.QtMvvm.Quick 1.1 |
||||
|
|
||||
|
Item { |
||||
|
id: _roundMenuButton |
||||
|
|
||||
|
property alias text: _rootButton.text |
||||
|
property alias icon: _rootButton.icon |
||||
|
property alias checked: _rootButton.checked |
||||
|
|
||||
|
property real buttonSpacing: 16 |
||||
|
property real expansionAngle: 0 |
||||
|
property size subButtonSize: Qt.size(40, 40) |
||||
|
property bool stickyToolTips: false |
||||
|
property bool invertToolTipDirection: Qt.application.layoutDirection == Qt.RightToLeft |
||||
|
|
||||
|
readonly property alias rootButton: _rootButton |
||||
|
|
||||
|
default property list<Action> actions |
||||
|
|
||||
|
implicitWidth: _rootButton.implicitWidth |
||||
|
implicitHeight: _rootButton.implicitHeight |
||||
|
|
||||
|
RoundActionButton { |
||||
|
id: _rootButton |
||||
|
z: 10 |
||||
|
anchors.fill: parent |
||||
|
checkable: true |
||||
|
} |
||||
|
|
||||
|
QtObject { |
||||
|
id: _p |
||||
|
|
||||
|
function toRadians(angle) { |
||||
|
return angle * (Math.PI/180); |
||||
|
} |
||||
|
|
||||
|
readonly property real vOffset: -1 * Math.cos(toRadians(_roundMenuButton.expansionAngle)) |
||||
|
readonly property real hOffset: Math.sin(toRadians(_roundMenuButton.expansionAngle)) |
||||
|
} |
||||
|
|
||||
|
Repeater { |
||||
|
model: actions |
||||
|
|
||||
|
delegate: RoundActionButton { |
||||
|
id: _subButton |
||||
|
|
||||
|
highlighted: false |
||||
|
anchors.horizontalCenter: _rootButton.horizontalCenter |
||||
|
anchors.verticalCenter: _rootButton.verticalCenter |
||||
|
implicitHeight: _roundMenuButton.subButtonSize.height + padding |
||||
|
implicitWidth: _roundMenuButton.subButtonSize.width + padding |
||||
|
state: _rootButton.checked ? "expanded" : "collapsed" |
||||
|
|
||||
|
toolTip: _roundMenuButton.stickyToolTips ? "" : _subButton.text |
||||
|
|
||||
|
onClicked: _rootButton.checked = false |
||||
|
|
||||
|
/* calc offsets as: |
||||
|
* - one subbutton (from center to center) + spacing between buttons |
||||
|
* - that times the number of buttons before this one + +1 |
||||
|
* - that plus the extra height delta from the root button |
||||
|
* - that times the factor from the angle |
||||
|
*/ |
||||
|
readonly property real maxVOffset: _p.vOffset * ((1 + index) * (_subButton.height + _roundMenuButton.buttonSpacing) + (_rootButton.height - _subButton.height)/2) |
||||
|
readonly property real maxHOffset: _p.hOffset * ((1 + index) * (_subButton.width + _roundMenuButton.buttonSpacing) + (_rootButton.width - _subButton.width)/2) |
||||
|
|
||||
|
action: modelData |
||||
|
|
||||
|
ToolTip { |
||||
|
id: _permaToolTip |
||||
|
visible: _roundMenuButton.stickyToolTips && _subButton.text != "" && _subButton.visible |
||||
|
text: _subButton.text |
||||
|
x: invertToolTipDirection ? |
||||
|
_subButton.width + _roundMenuButton.buttonSpacing : |
||||
|
-(_permaToolTip.width + _roundMenuButton.buttonSpacing) |
||||
|
y: (_subButton.height - height)/2 |
||||
|
} |
||||
|
|
||||
|
states: [ |
||||
|
State { |
||||
|
name: "collapsed" |
||||
|
PropertyChanges { |
||||
|
target: _subButton |
||||
|
anchors.verticalCenterOffset: 0 |
||||
|
anchors.horizontalCenterOffset: 0 |
||||
|
visible: false |
||||
|
} |
||||
|
}, |
||||
|
State { |
||||
|
name: "expanded" |
||||
|
PropertyChanges { |
||||
|
target: _subButton |
||||
|
anchors.verticalCenterOffset: maxVOffset |
||||
|
anchors.horizontalCenterOffset: maxHOffset |
||||
|
visible: true |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
transitions: [ |
||||
|
Transition { |
||||
|
from: "collapsed" |
||||
|
to: "expanded" |
||||
|
SequentialAnimation { |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "visible" |
||||
|
duration: 0 |
||||
|
} |
||||
|
ParallelAnimation { |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "anchors.verticalCenterOffset" |
||||
|
duration: 250 |
||||
|
easing.type: Easing.OutCubic |
||||
|
} |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "anchors.horizontalCenterOffset" |
||||
|
duration: 250 |
||||
|
easing.type: Easing.OutCubic |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
Transition { |
||||
|
from: "expanded" |
||||
|
to: "collapsed" |
||||
|
SequentialAnimation { |
||||
|
ParallelAnimation { |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "anchors.verticalCenterOffset" |
||||
|
duration: 250 |
||||
|
easing.type: Easing.InCubic |
||||
|
} |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "anchors.horizontalCenterOffset" |
||||
|
duration: 250 |
||||
|
easing.type: Easing.InCubic |
||||
|
} |
||||
|
} |
||||
|
PropertyAnimation { |
||||
|
target: _subButton |
||||
|
property: "visible" |
||||
|
duration: 0 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue