2024-08-20 16:54:05 +07:00
|
|
|
import QtQuick
|
|
|
|
|
import QtQuick.Controls
|
|
|
|
|
import QtQuick.Layouts
|
|
|
|
|
import QtQuick.Dialogs
|
|
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
|
|
2024-08-20 16:54:05 +07:00
|
|
|
import PageEnum 1.0
|
|
|
|
|
import Style 1.0
|
|
|
|
|
|
|
|
|
|
import "./"
|
|
|
|
|
import "../Controls2"
|
|
|
|
|
import "../Controls2/TextTypes"
|
|
|
|
|
import "../Config"
|
|
|
|
|
import "../Components"
|
|
|
|
|
|
|
|
|
|
PageType {
|
|
|
|
|
id: root
|
|
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
property var processedServer
|
2026-02-17 15:08:42 +04:00
|
|
|
property var groupedRegions: []
|
|
|
|
|
|
|
|
|
|
readonly property var regionDefinitions: [
|
|
|
|
|
{
|
2026-02-18 20:35:51 +04:00
|
|
|
"regionName": "Europe",
|
2026-02-17 15:08:42 +04:00
|
|
|
"countries": [
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "BE", "name": "Belgium" },
|
2026-02-17 15:08:42 +04:00
|
|
|
{ "code": "EE", "name": "Estonia" },
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "FI", "name": "Finland" },
|
|
|
|
|
{ "code": "FR", "name": "France" },
|
|
|
|
|
{ "code": "GE", "name": "Georgia" },
|
2026-02-17 15:08:42 +04:00
|
|
|
{ "code": "DE", "name": "Germany" },
|
|
|
|
|
{ "code": "NL", "name": "Netherlands" },
|
|
|
|
|
{ "code": "PL", "name": "Poland" },
|
|
|
|
|
{ "code": "RU", "name": "Russia" },
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "ES", "name": "Spain" },
|
|
|
|
|
{ "code": "SE", "name": "Sweden" },
|
|
|
|
|
{ "code": "CH", "name": "Switzerland" },
|
|
|
|
|
{ "code": "TR", "name": "Turkey" }
|
2026-02-17 15:08:42 +04:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 20:35:51 +04:00
|
|
|
"regionName": "America",
|
2026-02-17 15:08:42 +04:00
|
|
|
"countries": [
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "BR", "name": "Brazil" },
|
|
|
|
|
{ "code": "CA", "name": "Canada East" },
|
|
|
|
|
{ "code": "US", "name": "USA East" },
|
|
|
|
|
{ "code": "US", "name": "USA West" }
|
2026-02-17 15:08:42 +04:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 20:35:51 +04:00
|
|
|
"regionName": "Asia",
|
2026-02-17 15:08:42 +04:00
|
|
|
"countries": [
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "AE", "name": "UAE" },
|
2026-02-17 15:08:42 +04:00
|
|
|
{ "code": "JP", "name": "Japan" },
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "KZ", "name": "Kazakhstan" },
|
2026-02-17 15:08:42 +04:00
|
|
|
{ "code": "KR", "name": "South Korea" },
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "SG", "name": "Singapore" }
|
2026-02-17 15:08:42 +04:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-18 20:35:51 +04:00
|
|
|
"regionName": "Oceania and Africa",
|
2026-02-17 15:08:42 +04:00
|
|
|
"countries": [
|
|
|
|
|
{ "code": "AU", "name": "Australia" },
|
2026-02-18 20:35:51 +04:00
|
|
|
{ "code": "NZ", "name": "New Zealand" },
|
|
|
|
|
{ "code": "ZA", "name": "South Africa" }
|
2026-02-17 15:08:42 +04:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
function normalizeCountryCode(countryCode) {
|
|
|
|
|
if (!countryCode) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return countryCode.toString().trim().toUpperCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractCountryIsoCode(countryCode) {
|
|
|
|
|
const normalizedCode = normalizeCountryCode(countryCode);
|
|
|
|
|
const match = normalizedCode.match(/[A-Z]{2}/);
|
|
|
|
|
return match ? match[0] : normalizedCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeCountryName(countryName) {
|
|
|
|
|
if (!countryName) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return countryName.toString().trim().toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function findCountryIndexByRef(countryRef, usedIndices) {
|
|
|
|
|
const expectedCode = normalizeCountryCode(countryRef.code);
|
|
|
|
|
const expectedName = normalizeCountryName(countryRef.name);
|
|
|
|
|
const countriesCount = proxyCountriesModel.count !== undefined ? proxyCountriesModel.count : 0;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < countriesCount; ++i) {
|
|
|
|
|
if (usedIndices[i]) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const country = proxyCountriesModel.get(i);
|
|
|
|
|
if (!country || country.countryCode === undefined || country.countryName === undefined) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const modelCode = normalizeCountryCode(country.countryCode);
|
|
|
|
|
const modelName = normalizeCountryName(country.countryName);
|
|
|
|
|
|
|
|
|
|
if (expectedName !== "" && modelName === expectedName) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
if (expectedCode !== "" && modelCode === expectedCode) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rebuildRegionModel() {
|
|
|
|
|
let regions = [];
|
|
|
|
|
|
|
|
|
|
for (let regionIndex = 0; regionIndex < regionDefinitions.length; ++regionIndex) {
|
|
|
|
|
regions.push({
|
|
|
|
|
"regionName": regionDefinitions[regionIndex].regionName,
|
|
|
|
|
"countries": []
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let usedIndices = {};
|
|
|
|
|
for (let regionIndex = 0; regionIndex < regionDefinitions.length; ++regionIndex) {
|
|
|
|
|
const regionDefinition = regionDefinitions[regionIndex];
|
|
|
|
|
for (let countryIndex = 0; countryIndex < regionDefinition.countries.length; ++countryIndex) {
|
|
|
|
|
const countryRef = regionDefinition.countries[countryIndex];
|
|
|
|
|
const sourceIndex = findCountryIndexByRef(countryRef, usedIndices);
|
|
|
|
|
|
|
|
|
|
if (sourceIndex < 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceCountry = proxyCountriesModel.get(sourceIndex);
|
|
|
|
|
if (!sourceCountry || sourceCountry.countryCode === undefined || sourceCountry.countryName === undefined) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
regions[regionIndex].countries.push({
|
|
|
|
|
"sourceIndex": sourceIndex,
|
|
|
|
|
"countryName": sourceCountry.countryName,
|
|
|
|
|
"countryCode": sourceCountry.countryCode,
|
|
|
|
|
"countryImageCode": extractCountryIsoCode(sourceCountry.countryImageCode)
|
|
|
|
|
});
|
|
|
|
|
usedIndices[sourceIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let visibleRegions = [];
|
|
|
|
|
for (let regionIndex = 0; regionIndex < regions.length; ++regionIndex) {
|
|
|
|
|
if (regions[regionIndex].countries.length > 0) {
|
|
|
|
|
visibleRegions.push(regions[regionIndex]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupedRegions = visibleRegions;
|
|
|
|
|
}
|
2025-01-31 10:06:35 +07:00
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
|
target: ServersModel
|
|
|
|
|
|
|
|
|
|
function onProcessedServerChanged() {
|
|
|
|
|
root.processedServer = proxyServersModel.get(0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
Connections {
|
|
|
|
|
target: ApiCountryModel
|
|
|
|
|
|
|
|
|
|
function onModelReset() {
|
|
|
|
|
root.rebuildRegionModel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRowsInserted() {
|
|
|
|
|
root.rebuildRegionModel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRowsRemoved() {
|
|
|
|
|
root.rebuildRegionModel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onDataChanged() {
|
|
|
|
|
root.rebuildRegionModel()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
SortFilterProxyModel {
|
|
|
|
|
id: proxyServersModel
|
|
|
|
|
objectName: "proxyServersModel"
|
|
|
|
|
|
|
|
|
|
sourceModel: ServersModel
|
|
|
|
|
filters: [
|
|
|
|
|
ValueFilter {
|
|
|
|
|
roleName: "isCurrentlyProcessed"
|
|
|
|
|
value: true
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Component.onCompleted: {
|
|
|
|
|
root.processedServer = proxyServersModel.get(0)
|
2026-02-17 15:08:42 +04:00
|
|
|
root.rebuildRegionModel()
|
2025-01-31 10:06:35 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
SortFilterProxyModel {
|
|
|
|
|
id: proxyCountriesModel
|
|
|
|
|
objectName: "proxyCountriesModel"
|
|
|
|
|
|
|
|
|
|
sourceModel: ApiCountryModel
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-24 13:39:03 +07:00
|
|
|
ListViewType {
|
2024-08-20 16:54:05 +07:00
|
|
|
id: menuContent
|
|
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
anchors.fill: parent
|
|
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
model: root.groupedRegions
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
currentIndex: 0
|
|
|
|
|
|
2024-08-20 16:54:05 +07:00
|
|
|
ButtonGroup {
|
|
|
|
|
id: containersRadioButtonGroup
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
header: ColumnLayout {
|
|
|
|
|
width: menuContent.width
|
|
|
|
|
|
|
|
|
|
spacing: 4
|
|
|
|
|
|
|
|
|
|
BackButtonType {
|
|
|
|
|
id: backButton
|
|
|
|
|
objectName: "backButton"
|
|
|
|
|
|
2025-11-11 17:03:27 +03:00
|
|
|
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
2025-01-31 10:06:35 +07:00
|
|
|
}
|
|
|
|
|
|
2025-05-02 23:54:36 -07:00
|
|
|
HeaderTypeWithButton {
|
2025-01-31 10:06:35 +07:00
|
|
|
id: headerContent
|
|
|
|
|
objectName: "headerContent"
|
|
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
Layout.leftMargin: 16
|
|
|
|
|
Layout.rightMargin: 16
|
|
|
|
|
Layout.bottomMargin: 10
|
|
|
|
|
|
|
|
|
|
actionButtonImage: "qrc:/images/controls/settings.svg"
|
|
|
|
|
|
|
|
|
|
headerText: root.processedServer.name
|
2025-03-04 13:33:35 +07:00
|
|
|
descriptionText: qsTr("Location for connection")
|
2025-01-31 10:06:35 +07:00
|
|
|
|
|
|
|
|
actionButtonFunction: function() {
|
2025-02-07 22:22:14 +07:00
|
|
|
PageController.showBusyIndicator(true)
|
2025-02-22 14:42:09 +07:00
|
|
|
let result = ApiSettingsController.getAccountInfo(false)
|
2025-02-07 22:22:14 +07:00
|
|
|
PageController.showBusyIndicator(false)
|
2025-02-10 15:10:59 +07:00
|
|
|
if (!result) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-02-07 22:22:14 +07:00
|
|
|
|
2025-01-31 10:06:35 +07:00
|
|
|
PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-31 04:16:52 +01:00
|
|
|
delegate: ColumnLayout {
|
2026-02-17 15:08:42 +04:00
|
|
|
id: regionContent
|
|
|
|
|
|
|
|
|
|
property var regionData: modelData
|
2024-12-31 04:16:52 +01:00
|
|
|
|
2025-01-08 09:12:55 +03:00
|
|
|
width: menuContent.width
|
2026-02-17 15:08:42 +04:00
|
|
|
height: regionContent.implicitHeight
|
|
|
|
|
spacing: 0
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2026-02-18 20:35:51 +04:00
|
|
|
CaptionTextType {
|
2026-02-17 15:08:42 +04:00
|
|
|
Layout.fillWidth: true
|
2026-02-18 20:35:51 +04:00
|
|
|
Layout.leftMargin: 16
|
|
|
|
|
Layout.rightMargin: 16
|
|
|
|
|
Layout.topMargin: 12
|
|
|
|
|
Layout.bottomMargin: 8
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2026-02-18 20:35:51 +04:00
|
|
|
color: AmneziaStyle.color.mutedGray
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
text: regionData.regionName
|
2026-02-18 20:35:51 +04:00
|
|
|
horizontalAlignment: Text.AlignLeft
|
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
2026-02-17 15:08:42 +04:00
|
|
|
}
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
Repeater {
|
2026-02-18 20:35:51 +04:00
|
|
|
model: regionData.countries
|
2024-08-20 16:54:05 +07:00
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
delegate: ColumnLayout {
|
|
|
|
|
property var countryData: modelData
|
2024-12-09 09:32:49 +03:00
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
width: menuContent.width
|
|
|
|
|
spacing: 0
|
|
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
|
VerticalRadioButton {
|
|
|
|
|
id: containerRadioButton
|
|
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
Layout.leftMargin: 16
|
|
|
|
|
|
2026-02-18 20:35:51 +04:00
|
|
|
text: countryData.countryName
|
2026-02-17 15:08:42 +04:00
|
|
|
|
|
|
|
|
ButtonGroup.group: containersRadioButtonGroup
|
|
|
|
|
|
|
|
|
|
imageSource: "qrc:/images/controls/download.svg"
|
|
|
|
|
|
|
|
|
|
checked: countryData.sourceIndex === ApiCountryModel.currentIndex
|
|
|
|
|
checkable: !ConnectionController.isConnected
|
|
|
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
|
if (ConnectionController.isConnectionInProgress) {
|
|
|
|
|
PageController.showNotificationMessage(qsTr("Unable change server location while trying to make an active connection"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (ConnectionController.isConnected) {
|
|
|
|
|
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (countryData.sourceIndex !== ApiCountryModel.currentIndex) {
|
|
|
|
|
PageController.showBusyIndicator(true)
|
|
|
|
|
var prevIndex = ApiCountryModel.currentIndex
|
|
|
|
|
ApiCountryModel.currentIndex = countryData.sourceIndex
|
|
|
|
|
if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, countryData.countryCode, countryData.countryName)) {
|
|
|
|
|
ApiCountryModel.currentIndex = prevIndex
|
|
|
|
|
}
|
|
|
|
|
PageController.showBusyIndicator(false)
|
|
|
|
|
}
|
2024-08-20 16:54:05 +07:00
|
|
|
}
|
|
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
Keys.onEnterPressed: {
|
|
|
|
|
if (checkable) {
|
|
|
|
|
checked = true
|
|
|
|
|
}
|
|
|
|
|
containerRadioButton.clicked()
|
|
|
|
|
}
|
|
|
|
|
Keys.onReturnPressed: {
|
|
|
|
|
if (checkable) {
|
|
|
|
|
checked = true
|
|
|
|
|
}
|
|
|
|
|
containerRadioButton.clicked()
|
|
|
|
|
}
|
2024-08-20 16:54:05 +07:00
|
|
|
}
|
2026-02-17 15:08:42 +04:00
|
|
|
|
|
|
|
|
Image {
|
|
|
|
|
Layout.rightMargin: 32
|
|
|
|
|
Layout.alignment: Qt.AlignRight
|
|
|
|
|
|
|
|
|
|
source: "qrc:/countriesFlags/images/flagKit/" + countryData.countryImageCode + ".svg"
|
2024-08-20 16:54:05 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 15:08:42 +04:00
|
|
|
DividerType {
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
}
|
2024-08-20 16:54:05 +07:00
|
|
|
}
|
2024-12-31 04:16:52 +01:00
|
|
|
}
|
2024-08-20 16:54:05 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|