diff --git a/ErrorDisplayUtil.java b/ErrorDisplayUtil.java new file mode 100644 index 0000000..4d03396 --- /dev/null +++ b/ErrorDisplayUtil.java @@ -0,0 +1,70 @@ +package com.example.myapplication.Tool; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Color; +import android.os.Handler; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +//测试时使用 +public class ErrorDisplayUtil { + + // 显示全量错误信息的对话框 + public static void showErrorDialog(Activity activity, String title, String errorDetail) { + activity.runOnUiThread(() -> { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(title) + .setMessage(errorDetail) + .setPositiveButton("复制错误", (dialog, which) -> { + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("错误详情", errorDetail); + clipboard.setPrimaryClip(clip); + Toast.makeText(activity, "已复制到剪贴板", Toast.LENGTH_SHORT).show(); + }) + .setNegativeButton("关闭", null) + .show(); + }); + } + + // 在界面上固定位置显示错误面板(建议放在登录按钮下方) + public static void showErrorPanel(Activity activity, String errorType, String solution) { + activity.runOnUiThread(() -> { + ViewGroup rootView = activity.findViewById(android.R.id.content); + + // 创建错误面板 + LinearLayout errorPanel = new LinearLayout(activity); + errorPanel.setOrientation(LinearLayout.VERTICAL); + errorPanel.setBackgroundColor(0x22FF0000); // 半透明红色背景 + errorPanel.setPadding(16, 16, 16, 16); + + TextView errorView = new TextView(activity); + errorView.setTextColor(Color.RED); + errorView.setText("⚠️ 错误类型: " + errorType); + + TextView solutionView = new TextView(activity); + solutionView.setTextColor(Color.BLACK); + solutionView.setText("💡 解决方案: " + solution); + + Button detailBtn = new Button(activity); + detailBtn.setText("查看技术详情"); + detailBtn.setOnClickListener(v -> showErrorDialog(activity, "技术详情", errorType + "\n\n" + solution)); + + errorPanel.addView(errorView); + errorPanel.addView(solutionView); + errorPanel.addView(detailBtn); + + // 添加到界面底部 + rootView.addView(errorPanel); + + // 5秒后自动隐藏 + new Handler().postDelayed(() -> rootView.removeView(errorPanel), 5000); + }); + } +} diff --git a/ImageAdapter.java b/ImageAdapter.java new file mode 100644 index 0000000..acc57ba --- /dev/null +++ b/ImageAdapter.java @@ -0,0 +1,87 @@ +package com.example.myapplication.Tool; + +import android.content.Context; +import android.graphics.Color; +import android.util.SparseBooleanArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.ImageView; + +import com.example.myapplication.model.ImageInfo; + +import java.util.ArrayList; +import java.util.List; + +public class ImageAdapter extends BaseAdapter { + private final Context context; + private final List imageList; + private final SparseBooleanArray selectedItems; + + public ImageAdapter(Context context, List imageList) { + this.context = context; + this.imageList = imageList; + this.selectedItems = new SparseBooleanArray(); + } + + @Override + public int getCount() { + return imageList.size(); + } + + @Override + public Object getItem(int position) { + return imageList.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ImageView imageView; + if (convertView == null) { + imageView = new ImageView(context); + imageView.setLayoutParams(new GridView.LayoutParams(200, 200)); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setPadding(5, 5, 5, 5); + } else { + imageView = (ImageView) convertView; + } + + ImageInfo imageInfo = imageList.get(position); + imageView.setImageBitmap(imageInfo.thumbnail); + + // 设置选中状态 + if (selectedItems.get(position, false)) { + imageView.setBackgroundColor(Color.BLUE); + } else { + imageView.setBackgroundColor(Color.TRANSPARENT); + } + + imageView.setOnClickListener(v -> { + if (selectedItems.get(position, false)) { + selectedItems.delete(position); + imageView.setBackgroundColor(Color.TRANSPARENT); + } else { + selectedItems.put(position, true); + imageView.setBackgroundColor(Color.BLUE); + } + }); + + return imageView; + } + + public List getSelectedImages() { + List selected = new ArrayList<>(); + for (int i = 0; i < imageList.size(); i++) { + if (selectedItems.get(i, false)) { + selected.add(imageList.get(i)); + } + } + return selected; + } +} diff --git a/PartListFetcher.java b/PartListFetcher.java new file mode 100644 index 0000000..8d73ee9 --- /dev/null +++ b/PartListFetcher.java @@ -0,0 +1,282 @@ +package com.example.myapplication.Tool; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import com.example.myapplication.DataBase.DatabaseHelper; +import com.example.myapplication.api.PartService; +import com.example.myapplication.model.ApiResponse; +import com.example.myapplication.model.PartResponse; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class PartListFetcher { + private final Context context; + private final String token; + private final String projectId; + + private final PartService partService; + private final DatabaseHelper dbHelper; + + public PartListFetcher(Context context, String token, String projectId) { + this.context = context; + this.token = token; + this.projectId = projectId; + + this.dbHelper = new DatabaseHelper(context); + + // 初始化Retrofit + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build(); + + Retrofit retrofit = RetrofitClient.getClient(token); + + this.partService = retrofit.create(PartService.class); + } + + /** + * 同步获取部件列表 + * @return 部件列表 + * @throws IOException 网络异常 + * @throws ApiException API异常 + */ + public List fetchPartListSync() throws IOException, ApiException { + if (isNetworkAvailable()) { + // 有网络时从服务器获取并更新数据库 + List serverParts = fetchFromServer(); + dbHelper.saveParts(serverParts); + return serverParts; + } else { + // 无网络时从数据库获取 + return dbHelper.getAllParts(); + } + } + + /** + * 异步获取部件列表 + * @param callback 回调接口 + */ + public void fetchPartListAsync(final PartListCallback callback) { + // 先从数据库获取数据快速显示 + List cachedParts = dbHelper.getAllParts(); + if (!cachedParts.isEmpty()) { + callback.onSuccess(cachedParts); + } + + // 检查网络连接 + if (isNetworkAvailable()) { + // 有网络时从服务器获取并更新数据库 + fetchFromServerAsync(new PartListCallback() { + @Override + public void onSuccess(List partList) { + dbHelper.saveParts(partList); + callback.onSuccess(partList); + } + + @Override + public void onFailure(Throwable t) { + // 服务器获取失败,如果之前有缓存数据则不报错 + if (cachedParts.isEmpty()) { + callback.onFailure(t); + } + } + }); + } else if (cachedParts.isEmpty()) { + // 无网络且无缓存数据 + callback.onFailure(new IOException("无网络连接且无缓存数据")); + } + } + public String getPartIdByName(String partName) { + // 先从数据库查询 + String partId = dbHelper.getPartIdByName(partName); + if (partId != null) { + return partId; + } + + // 数据库中没有则尝试从服务器获取 + if (isNetworkAvailable()) { + try { + List parts = fetchFromServer(); + dbHelper.saveParts(parts); + // 再次从数据库查询 + return dbHelper.getPartIdByName(partName); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return null; + } + private List fetchFromServer() throws IOException, ApiException { + Call>> call = partService.getPartList( + projectId, + + null, null, null, null + + ); + + Response>> response = call.execute(); + + if (response.isSuccessful() && response.body() != null) { + ApiResponse> apiResponse = response.body(); + if (apiResponse.isSuccess()) { + return apiResponse.getData(); + } else { + throw new ApiException(apiResponse.getCode(), apiResponse.getMsg()); + } + } else { + throw new IOException("请求失败,状态码: " + response.code()); + } + } + private void fetchFromServerAsync(final PartListCallback callback) { + Call>> call = partService.getPartList( + projectId, + + null, null, null, null + + ); + + call.enqueue(new retrofit2.Callback>>() { + @Override + public void onResponse(Call>> call, + Response>> response) { + if (response.isSuccessful() && response.body() != null) { + ApiResponse> apiResponse = response.body(); + if (apiResponse.isSuccess()) { + callback.onSuccess(apiResponse.getData()); + } else { + callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg())); + } + } else { + callback.onFailure(new IOException("请求失败,状态码: " + response.code())); + } + } + + @Override + public void onFailure(Call>> call, Throwable t) { + callback.onFailure(t); + } + }); + } + + private boolean isNetworkAvailable() { + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager != null) { + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + return false; + } + /** + * 带筛选条件的异步获取部件列表 + * @param keyword 关键字 + * @param manufacturer 厂商 + * @param model 型号 + * @param type 类型 + * @param callback 回调接口 + */ + public void fetchPartListWithFilterAsync(String keyword, String manufacturer, + String model, String type, + PartListCallback callback) { + Call>> call = partService.getPartList( + projectId, + + keyword, + manufacturer, + model, + type + ); + + call.enqueue(new retrofit2.Callback>>() { + @Override + public void onResponse(Call>> call, + Response>> response) { + if (response.isSuccessful() && response.body() != null) { + ApiResponse> apiResponse = response.body(); + if (apiResponse.isSuccess()) { + callback.onSuccess(apiResponse.getData()); + } else { + callback.onFailure(new ApiException(apiResponse.getCode(), apiResponse.getMsg())); + } + } else { + callback.onFailure(new IOException("请求失败,状态码: " + response.code())); + } + } + + @Override + public void onFailure(Call>> call, Throwable t) { + callback.onFailure(t); + } + }); + } + + public interface PartListCallback { + void onSuccess(List partList); + void onFailure(Throwable t); + } + public String getPartIdByNameSync(String partName) throws IOException, ApiException { + List partList = fetchPartListSync(); + for (PartResponse part : partList) { + if (partName.equals(part.getPartName())) { + return part.getPartId(); + } + } + return null; + } + + /** + * 异步根据部件名称获取部件ID + * @param partName 要查找的部件名称 + * @param callback 回调接口 + */ + public void getPartIdByNameAsync(String partName, PartIdCallback callback) { + fetchPartListAsync(new PartListCallback() { + @Override + public void onSuccess(List partList) { + for (PartResponse part : partList) { + if (partName.equals(part.getPartName())) { + callback.onSuccess(part.getPartId()); + return; + } + } + callback.onSuccess(null); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }); + } + + public interface PartIdCallback { + void onSuccess(String partId); + void onFailure(Throwable t); + } + public static class ApiException extends Exception { + private final int code; + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return code; + } + + } +} \ No newline at end of file diff --git a/PermissionUtils.java b/PermissionUtils.java new file mode 100644 index 0000000..2861515 --- /dev/null +++ b/PermissionUtils.java @@ -0,0 +1,86 @@ +package com.example.myapplication.Tool; + +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.os.Build; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +public class PermissionUtils { + // 请求码 + public static final int REQUEST_STORAGE_PERMISSION = 101; + public static final int REQUEST_MEDIA_PERMISSION = 102; // Android 13+ 媒体权限 + + // 检查并请求所有必要权限 + public static boolean checkAndRequestPermissions(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + // Android 14+ 需要检查媒体权限(如果要访问媒体文件) + return checkMediaPermissions(activity); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android 10-13 使用 SAF 或 MediaStore,不需要存储权限 + return true; + } else { + // Android 9 及以下需要传统存储权限 + return checkLegacyStoragePermission(activity); + } + } + + // Android 14+ 媒体权限检查 + private static boolean checkMediaPermissions(Activity activity) { + String[] permissions; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissions = new String[]{ + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO + }; + } else { + permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; + } + + boolean allGranted = true; + for (String perm : permissions) { + if (ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) { + allGranted = false; + break; + } + } + + if (!allGranted) { + ActivityCompat.requestPermissions(activity, permissions, REQUEST_MEDIA_PERMISSION); + return false; + } + return true; + } + + // Android 9 及以下传统存储权限 + private static boolean checkLegacyStoragePermission(Activity activity) { + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(activity, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + REQUEST_STORAGE_PERMISSION); + return false; + } + return true; + } + + // 处理权限请求结果 + public static boolean handlePermissionResult(int requestCode, + String[] permissions, int[] grantResults) { + if (grantResults.length == 0) return false; + + if (requestCode == REQUEST_STORAGE_PERMISSION || + requestCode == REQUEST_MEDIA_PERMISSION) { + for (int result : grantResults) { + if (result != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/ReportGeneratorHelper.java b/ReportGeneratorHelper.java new file mode 100644 index 0000000..f34569f --- /dev/null +++ b/ReportGeneratorHelper.java @@ -0,0 +1,89 @@ +package com.example.myapplication.Tool; + +import android.content.Context; + +import com.chaquo.python.Python; +import com.chaquo.python.android.AndroidPlatform; +import com.google.gson.Gson; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReportGeneratorHelper { + + private Context context; + + public ReportGeneratorHelper(Context context) { + this.context = context; + if (!Python.isStarted()) { + Python.start(new AndroidPlatform(context)); + } + } + + public void generateReport(String templateContent, String outputPath, + String turbineId, String testDate, + List imageDataList, + String weather, String temperature, String humidity, + String startDate, String endDate, + ReportGenerationCallback callback) { + try { + Python py = Python.getInstance(); + + // 转换Java的ImageData列表为Python可接受的格式 + List> pyImageData = new ArrayList<>(); + for (ImageData data : imageDataList) { + List item = new ArrayList<>(); + item.add(data.getImagePath()); + item.add(data.getResistance()); + pyImageData.add(item); + } + + // 准备配置字典 + Map config = new HashMap<>(); + config.put("template_content", templateContent); + config.put("output_path", outputPath); + config.put("turbine_id", turbineId); + config.put("test_date", testDate); + config.put("image_data", pyImageData); + config.put("weather", weather); + config.put("temperature", temperature); + config.put("humidity", humidity); + config.put("start_date", startDate); + config.put("end_date", endDate); + String jsonConfig = new Gson().toJson(config); + // 调用Python模块 + py.getModule("report_generator") + .callAttr("generate_report_from_java", jsonConfig); + // 成功回调 + callback.onSuccess(outputPath); + + } catch (Exception e) { + callback.onError(e.getMessage()); + } + } + + public interface ReportGenerationCallback { + void onSuccess(String outputPath); + void onError(String errorMessage); + } + + public static class ImageData { + private String imagePath; + private String resistance; + + public ImageData(String imagePath, String resistance) { + this.imagePath = imagePath; + this.resistance = resistance; + } + + public String getImagePath() { + return imagePath; + } + + public String getResistance() { + return resistance; + } + } +}