Browse Source

feat:增加导出文件功能

develop
白凤吉 2 months ago
parent
commit
8475808013
  1. 4
      app/build.gradle
  2. 2
      app/src/main/AndroidManifest.xml
  3. 94
      app/src/main/java/com/iflytop/profilometer/api/record/RecordApi.java
  4. 12
      app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt
  5. 55
      app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java

4
app/build.gradle

@ -14,9 +14,9 @@ android {
minSdk 26
targetSdk 35
//
versionCode 24
versionCode 26
//
versionName "1.1.24"
versionName "1.1.26"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

2
app/src/main/AndroidManifest.xml

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:largeHeap="true"
@ -22,6 +23,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Profilometer_android"
android:requestLegacyExternalStorage="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"

94
app/src/main/java/com/iflytop/profilometer/api/record/RecordApi.java

@ -1,19 +1,36 @@
package com.iflytop.profilometer.api.record;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import com.iflytop.profilometer.common.result.Result;
import com.iflytop.profilometer.common.utils.FileUtil;
import com.iflytop.profilometer.core.migration.algo.type.XYPoint;
import com.iflytop.profilometer.dao.ProfileRecordDao;
import com.iflytop.profilometer.dao.ProfileRecordPointSetDao;
import com.iflytop.profilometer.dao.SyncTaskDao;
import com.iflytop.profilometer.model.entity.ProfileRecordDescription;
import com.iflytop.profilometer.model.entity.ProfileRecordPointSet;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
/**
* 测量记录接口
@ -85,12 +102,85 @@ public class RecordApi {
for (int i = 0; i < ids.size(); i++) {
Long id = ids.getLong(i);
profileRecordDao.deleteProfileRecord(id);
try{
try {
syncTaskDao.deleteSyncTask(id);
}catch (Exception ignored){
} catch (Exception ignored) {
}
}
return Result.success();
}
public String download(JSONArray ids) throws Exception {
//确保外部存储可写
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not mounted");
}
new Thread(() -> {
File downloadRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File exportDir = new File(downloadRoot, "廓形导出");
if (!exportDir.exists() && !exportDir.mkdirs()) {
try {
throw new IOException("Failed to create export directory: " + exportDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (ids.size() > 1) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String zipName = "export-" + timestamp + ".zip";
File zipFile = new File(exportDir, zipName);
try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) {
ProfileRecordDao recordDao = new ProfileRecordDao(context);
ProfileRecordPointSetDao pointSetDao = new ProfileRecordPointSetDao(context);
for (int i = 0; i < ids.size(); i++) {
Long id = ids.getLong(i);
ProfileRecordDescription desc = recordDao.getProfileRecordById(id);
ProfileRecordPointSet ps = pointSetDao.getProfileRecordPointSetByUuid(desc.getUuid());
List<XYPoint> pts = JSONUtil.toList(ps.getAlignPoints(), XYPoint.class);
String entryName = "export-" + desc.getName() + ".txt";
zos.putNextEntry(new ZipEntry(entryName));
for (XYPoint pt : pts) {
String line = String.format(Locale.US, "%1$.6f %2$.6f%n", pt.getX(), -pt.getY());
zos.write(line.getBytes(StandardCharsets.UTF_8));
}
zos.closeEntry();
}
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(context, "导出成功\n" + "/Download/廓形导出/\n" + zipName, Toast.LENGTH_LONG).show();
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}else{
Long id = ids.getLong(0);
ProfileRecordDao recordDao = new ProfileRecordDao(context);
ProfileRecordPointSetDao pointSetDao = new ProfileRecordPointSetDao(context);
ProfileRecordDescription desc = recordDao.getProfileRecordById(id);
ProfileRecordPointSet ps = pointSetDao.getProfileRecordPointSetByUuid(desc.getUuid());
List<XYPoint> pts = JSONUtil.toList(ps.getAlignPoints(), XYPoint.class);
StringBuilder sb = new StringBuilder();
for (XYPoint pt : pts) {
sb.append(String.format(Locale.US, "%1$.6f %2$.6f%n", pt.getX(), -pt.getY()));
}
String fileName = "export-" + desc.getName() + ".txt";
try {
FileUtil.writeTextToDownloadSubDir("廓形导出",fileName,sb.toString());
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(context, "导出成功\n" + "/Download/廓形导出/\n" + fileName, Toast.LENGTH_LONG).show();
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
return Result.success();
}
}

12
app/src/main/java/com/iflytop/profilometer/api/record/RecordRoutes.kt

@ -4,7 +4,6 @@ import android.content.Context
import cn.hutool.json.JSONUtil
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
@ -49,5 +48,16 @@ fun Routing.recordRoutes(context: Context) {
val jsonResponse = api.delete(ids)
call.respondText(jsonResponse, ContentType.Application.Json)
}
/**
*
*/
post("/api/record/download") {
val requestBody = call.receiveText()
val jsonObj = JSONUtil.parseObj(requestBody)
val ids = jsonObj.getJSONArray("ids")
val jsonResponse = api.download(ids)
call.respondText(jsonResponse, ContentType.Application.Json)
}
}

55
app/src/main/java/com/iflytop/profilometer/common/utils/FileUtil.java

@ -0,0 +1,55 @@
package com.iflytop.profilometer.common.utils;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileUtil {
/**
* /storage/emulated/0/Download/<subDirName>/ 下创建子目录
* 并在该目录中写入文本文件
*
* 调用前请确保已经
* 1) AndroidManifest.xml 中声明了 WRITE_EXTERNAL_STORAGE 权限
* 2) 已经在运行时向用户请求并获得了该权限
* 3) 如果 targetSdkVersion 29<application> 中开启了 requestLegacyExternalStorage="true"
*
* @param subDirName 子目录名称例如 "MySubFolder"
* @param fileName 要写入的文件名例如 "example.txt"
* @param content 文件内容
* @throws IOException 外部存储不可用目录创建失败或写入失败时抛出
*/
public static void writeTextToDownloadSubDir(String subDirName,
String fileName,
String content) throws IOException {
// 1. 确保外部存储已挂载
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
throw new IOException("External storage is not mounted");
}
// 2. 获取公共 Download 目录
File downloadRoot = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
);
if (downloadRoot == null) {
throw new IOException("Cannot access Download directory");
}
// 3. Download 下创建子目录
File targetDir = new File(downloadRoot, subDirName);
if (!targetDir.exists() && !targetDir.mkdirs()) {
throw new IOException("Failed to create sub-directory: " + targetDir.getAbsolutePath());
}
// 4. 在子目录中创建并写入文件
File outFile = new File(targetDir, fileName);
try (FileOutputStream fos = new FileOutputStream(outFile)) {
fos.write(content.getBytes(StandardCharsets.UTF_8));
fos.flush();
}
}
}
Loading…
Cancel
Save