Skip to content

Commit 5868e38

Browse files
authored
Add Xiaomi content helper without heart changes (#4786)
1 parent a105d7e commit 5868e38

14 files changed

Lines changed: 592 additions & 89 deletions

src/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ when you add a setting remember:
22
- you have to add always as the last settings declared in the settings.qml
33
- if you have to add a setting also on another qml file, you need also to declare it there always putting as the last one
44
- in the qzsettings.cpp there is a allsettingscount that must be updated if you add a setting
5+
- in settings-catalog.json the "settingCount" field must match the number of entries in the "settings" array (the pre-commit hook updates it automatically, but verify if committing without the hook)

src/GPXList.qml

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,35 @@ ColumnLayout {
1313
signal trainprogram_open_clicked(url name)
1414
signal trainprogram_open_other_folder(url name)
1515
signal trainprogram_preview(url name)
16+
property var selectedFileUrl: ""
17+
18+
Connections {
19+
target: rootItem
20+
function onAndroidDocumentPicked(kind, localUrl) {
21+
if (kind === "gpx") {
22+
trainprogram_open_clicked(localUrl)
23+
}
24+
}
25+
}
26+
1627
Loader {
1728
id: fileDialogLoader
1829
active: false
1930
sourceComponent: Component {
2031
FileDialog {
32+
id: fileDialog
2133
title: "Please choose a file"
2234
folder: shortcuts.home
35+
nameFilters: ["GPX files (*.gpx *.GPX)", "All files (*)"]
2336
visible: true
2437
onAccepted: {
25-
console.log("You chose: " + fileUrl)
38+
var chosenFile = fileDialog.fileUrl || fileDialog.file || (fileDialog.fileUrls && fileDialog.fileUrls.length > 0 ? fileDialog.fileUrls[0] : "")
39+
console.log("You chose: " + chosenFile)
40+
selectedFileUrl = chosenFile
2641
if(OS_VERSION === "Android") {
27-
trainprogram_open_other_folder(fileUrl)
42+
trainprogram_open_other_folder(chosenFile)
2843
} else {
29-
trainprogram_open_clicked(fileUrl)
44+
trainprogram_open_clicked(chosenFile)
3045
}
3146
close()
3247
// Destroy and recreate the dialog for next use
@@ -71,7 +86,7 @@ ColumnLayout {
7186
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
7287
filter+="*"
7388
print(filter)
74-
folderModel.nameFilters = [filter + ".gpx"]
89+
folderModel.nameFilters = [filter + ".gpx", filter + ".GPX"]
7590
}
7691
id: filterField
7792
onTextChanged: updateFilter()
@@ -95,7 +110,7 @@ ColumnLayout {
95110
id: list
96111
FolderListModel {
97112
id: folderModel
98-
nameFilters: ["*.gpx"]
113+
nameFilters: ["*.gpx", "*.GPX"]
99114
folder: "file://" + rootItem.getWritableAppDir() + 'gpx'
100115
showDotAndDotDot: false
101116
showDirs: true
@@ -273,8 +288,11 @@ ColumnLayout {
273288
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
274289
onClicked: {
275290
console.log("folder is " + rootItem.getWritableAppDir() + 'gpx')
276-
// Create a fresh FileDialog instance
277-
fileDialogLoader.active = true
291+
if (Qt.platform.os === "android") {
292+
rootItem.openAndroidDocumentPicker("gpx")
293+
} else {
294+
fileDialogLoader.active = true
295+
}
278296
}
279297
anchors {
280298
bottom: parent.bottom

src/SettingsList.qml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import QtQuick.Dialogs 1.0
77

88
ColumnLayout {
99
signal loadSettings(url name)
10+
11+
Connections {
12+
target: rootItem
13+
function onAndroidDocumentPicked(kind, localUrl) {
14+
if (kind === "settings") {
15+
loadSettings(localUrl)
16+
}
17+
}
18+
}
19+
1020
Loader {
1121
id: fileDialogLoader
1222
active: false
@@ -116,8 +126,11 @@ ColumnLayout {
116126
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
117127
onClicked: {
118128
console.log("folder is " + rootItem.getWritableAppDir() + 'settings')
119-
// Create a fresh FileDialog instance
120-
fileDialogLoader.active = true
129+
if (Qt.platform.os === "android") {
130+
rootItem.openAndroidDocumentPicker("settings")
131+
} else {
132+
fileDialogLoader.active = true
133+
}
121134
}
122135
anchors {
123136
bottom: parent.bottom

src/TrainingProgramsList.qml

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ ColumnLayout {
1515
property url selectedWorkoutUrl: ""
1616
property url initialWorkoutUrl: ""
1717

18+
Connections {
19+
target: rootItem
20+
function onAndroidDocumentPicked(kind, localUrl) {
21+
if (kind === "training") {
22+
trainprogram_open_clicked(localUrl)
23+
}
24+
}
25+
}
26+
1827
function openWorkoutPreview(fileUrl) {
1928
if (!fileUrl || fileUrl.toString() === "") {
2029
return
@@ -56,12 +65,13 @@ ColumnLayout {
5665
id: fileDialogLoader
5766
active: false
5867
sourceComponent: Component {
59-
FileDialog {
60-
title: "Please choose a file"
61-
folder: shortcuts.home
62-
visible: true
63-
onAccepted: {
64-
console.log("You chose: " + fileUrl)
68+
FileDialog {
69+
title: "Please choose a file"
70+
folder: shortcuts.home
71+
nameFilters: ["Training programs (*.xml *.zwo)", "All files (*)"]
72+
visible: true
73+
onAccepted: {
74+
console.log("You chose: " + fileUrl)
6575
if(OS_VERSION === "Android") {
6676
trainprogram_open_other_folder(fileUrl)
6777
} else {
@@ -371,8 +381,11 @@ ColumnLayout {
371381
text: "Other folders"
372382
onClicked: {
373383
console.log("folder is " + rootItem.getWritableAppDir() + 'training')
374-
// Create a fresh FileDialog instance
375-
fileDialogLoader.active = true
384+
if (Qt.platform.os === "android") {
385+
rootItem.openAndroidDocumentPicker("training")
386+
} else {
387+
fileDialogLoader.active = true
388+
}
376389
}
377390
}
378391
anchors {

src/TrainingProgramsListJS.qml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ ColumnLayout {
2525
property var selectedFileUrl: ""
2626
property bool isSearching: false
2727

28+
Connections {
29+
target: rootItem
30+
function onAndroidDocumentPicked(kind, localUrl) {
31+
if (kind === "training") {
32+
trainprogram_open_clicked(localUrl)
33+
}
34+
}
35+
}
36+
2837
function openWorkoutPreview(fileUrl) {
2938
if (!fileUrl || fileUrl.toString() === "") {
3039
return
@@ -73,6 +82,7 @@ ColumnLayout {
7382
id: fileDialog
7483
title: "Please choose a file"
7584
folder: shortcuts.home
85+
nameFilters: ["Training programs (*.xml *.zwo)", "All files (*)"]
7686
visible: true
7787
onAccepted: {
7888
var chosenFile = fileDialog.fileUrl || fileDialog.file || (fileDialog.fileUrls && fileDialog.fileUrls.length > 0 ? fileDialog.fileUrls[0] : "")
@@ -276,7 +286,11 @@ ColumnLayout {
276286
height: 50
277287
text: "Other folders"
278288
onClicked: {
279-
fileDialogLoader.active = true
289+
if (Qt.platform.os === "android") {
290+
rootItem.openAndroidDocumentPicker("training")
291+
} else {
292+
fileDialogLoader.active = true
293+
}
280294
}
281295
}
282296
}

src/android/src/ContentHelper.java

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,125 @@
44
import android.database.Cursor;
55
import android.net.Uri;
66
import android.provider.OpenableColumns;
7+
import java.io.File;
8+
import java.io.FileOutputStream;
9+
import java.io.InputStream;
710
import org.cagnulen.qdomyoszwift.QLog;
811

912
public class ContentHelper {
1013

1114
public static String getFileName(Context context, Uri uri) {
15+
if (uri == null) {
16+
return null;
17+
}
18+
1219
String result = null;
13-
if (uri.getScheme().equals("content")) {
20+
String scheme = uri.getScheme();
21+
if ("content".equals(scheme)) {
1422
QLog.d("ContentHelper", "content");
15-
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
16-
QLog.d("ContentHelper", "cursor " + cursor);
23+
Cursor cursor = null;
1724
try {
25+
cursor = context.getContentResolver().query(uri, new String[] {OpenableColumns.DISPLAY_NAME}, null, null, null);
26+
QLog.d("ContentHelper", "cursor " + cursor);
1827
if (cursor != null && cursor.moveToFirst()) {
19-
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
20-
QLog.d("ContentHelper", "result " + result);
28+
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
29+
if (nameIndex >= 0) {
30+
result = cursor.getString(nameIndex);
31+
QLog.d("ContentHelper", "result " + result);
32+
}
2133
}
34+
} catch (Exception e) {
35+
QLog.d("ContentHelper", "getFileName query failed " + e);
2236
} finally {
23-
cursor.close();
37+
if (cursor != null) {
38+
cursor.close();
39+
}
2440
}
2541
}
42+
if ((result == null || result.isEmpty()) && uri.getLastPathSegment() != null) {
43+
result = uri.getLastPathSegment();
44+
}
2645
return result;
2746
}
47+
48+
public static boolean copyContentToFile(Context context, Uri uri, String destinationPath) {
49+
if (context == null || uri == null || destinationPath == null || destinationPath.isEmpty()) {
50+
return false;
51+
}
52+
53+
InputStream inputStream = null;
54+
FileOutputStream outputStream = null;
55+
try {
56+
inputStream = context.getContentResolver().openInputStream(uri);
57+
if (inputStream == null) {
58+
QLog.d("ContentHelper", "copyContentToFile null input stream for " + uri);
59+
return false;
60+
}
61+
62+
outputStream = new FileOutputStream(destinationPath, false);
63+
byte[] buffer = new byte[8192];
64+
int read;
65+
while ((read = inputStream.read(buffer)) != -1) {
66+
outputStream.write(buffer, 0, read);
67+
}
68+
outputStream.flush();
69+
return true;
70+
} catch (Exception e) {
71+
QLog.d("ContentHelper", "copyContentToFile failed " + e);
72+
return false;
73+
} finally {
74+
try {
75+
if (inputStream != null) {
76+
inputStream.close();
77+
}
78+
} catch (Exception ignored) {
79+
}
80+
try {
81+
if (outputStream != null) {
82+
outputStream.close();
83+
}
84+
} catch (Exception ignored) {
85+
}
86+
}
87+
}
88+
89+
public static String importContentToAppDir(Context context, Uri uri, String destinationDirPath) {
90+
if (context == null || uri == null || destinationDirPath == null || destinationDirPath.isEmpty()) {
91+
return "";
92+
}
93+
94+
String fileName = sanitizeFileName(getFileName(context, uri));
95+
if (fileName.isEmpty()) {
96+
fileName = "imported_file";
97+
}
98+
99+
File destinationDir = new File(destinationDirPath);
100+
if (!destinationDir.exists() && !destinationDir.mkdirs()) {
101+
QLog.d("ContentHelper", "importContentToAppDir could not create " + destinationDirPath);
102+
return "";
103+
}
104+
105+
File destinationFile = new File(destinationDir, fileName);
106+
if (destinationFile.exists() && !destinationFile.delete()) {
107+
QLog.d("ContentHelper", "importContentToAppDir could not replace " + destinationFile.getAbsolutePath());
108+
return "";
109+
}
110+
111+
if (!copyContentToFile(context, uri, destinationFile.getAbsolutePath())) {
112+
if (destinationFile.exists()) {
113+
destinationFile.delete();
114+
}
115+
return "";
116+
}
117+
118+
return destinationFile.getAbsolutePath();
119+
}
120+
121+
private static String sanitizeFileName(String value) {
122+
if (value == null) {
123+
return "";
124+
}
125+
126+
return value.replace('\\', '_').replace('/', '_').trim();
127+
}
28128
}

0 commit comments

Comments
 (0)