This commit is contained in:
xuyvqi 2025-07-11 18:25:40 +08:00
parent 6199c50c0a
commit b4b45eeb14
53 changed files with 3602 additions and 242 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
My Application

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
id("com.chaquo.python")
}
android {
@ -12,7 +13,10 @@ android {
targetSdk = 35
versionCode = 1
versionName = "1.0"
ndk {
// On Apple silicon, you can omit x86_64.
abiFilters += listOf("arm64-v8a", "x86_64")
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
@ -51,8 +55,19 @@ android {
viewBinding = true
}
}
chaquopy {
defaultConfig {
version = "3.11"
buildPython("C:\\Users\\xuyvqi\\AppData\\Local\\Programs\\Python\\Python311\\python.exe")
pip {
install("typing_extensions")
install("lxml==5.3.0") // python-docx 的依赖
install("python-docx") // 使用兼容性较好的版本
install("Pillow") // 可选,处理图片
}
}
}
dependencies {
@ -68,6 +83,7 @@ dependencies {
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation(libs.okhttp)
implementation("com.google.android.gms:play-services-location:21.0.1")
implementation("androidx.exifinterface:exifinterface:1.3.3")
implementation ("commons-io:commons-io:2.11.0")
@ -79,5 +95,5 @@ dependencies {
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
implementation ("com.squareup.retrofit2:adapter-rxjava2:2.9.0")
implementation ("com.squareup.okhttp3:logging-interceptor:4.9.3")
implementation ("com.google.code.gson:gson:2.10.1")
}

View File

@ -83,4 +83,8 @@
}
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.app.Service
# 保留数据库相关类
-keep class com.example.myapplication.DataBase.** { *; }
-keep class com.example.myapplication.Service.** { *; }
-keep class com.example.myapplication.Tool.** { *; }

Binary file not shown.

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "65748a86249bdef977f472ddeba83ee9",
"identityHash": "ab7260384e2d58c0d78dc0dae5130632",
"entities": [
{
"tableName": "images",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT, `time` INTEGER NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `altitude` REAL NOT NULL, `user` TEXT, `audioPath` TEXT, `blade` INTEGER NOT NULL, `unit` TEXT, `project` TEXT, `b` INTEGER NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT, `time` INTEGER NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `altitude` REAL NOT NULL, `user` TEXT, `audioPath` TEXT, `blade` INTEGER NOT NULL, `unit` TEXT, `unitName` TEXT, `project` TEXT, `b` INTEGER NOT NULL, `temperature` TEXT, `humidity` TEXT, `weather` TEXT)",
"fields": [
{
"fieldPath": "id",
@ -68,6 +68,12 @@
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "unitName",
"columnName": "unitName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "project",
"columnName": "project",
@ -79,6 +85,24 @@
"columnName": "b",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "temperature",
"columnName": "temperature",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "humidity",
"columnName": "humidity",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "weather",
"columnName": "weather",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
@ -188,7 +212,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '65748a86249bdef977f472ddeba83ee9')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab7260384e2d58c0d78dc0dae5130632')"
]
}
}

View File

@ -4,6 +4,9 @@
<!-- 基础权限 -->
<!-- Android 14+ 新增权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@ -41,6 +44,7 @@
android:requestLegacyExternalStorage="true"
android:windowSoftInputMode="stateVisible|adjustResize"
android:theme="@style/Theme.MyApplication"
android:name="com.chaquo.python.android.PyApplication"
tools:targetApi="31"
tools:ignore="ForegroundServicePermission">
<activity
@ -70,7 +74,7 @@
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

View File

@ -1,19 +1,26 @@
package com.example.myapplication.DataBase;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.example.myapplication.model.ImageSourceItem;
import com.example.myapplication.model.PartResponse;
import com.example.myapplication.model.Project;
import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "UserDB";
private static final int DATABASE_VERSION = 3;
private static final String DATABASE_NAME = "AppDB";
private static final int DATABASE_VERSION = 6;
private static final String TABLE_USERS = "users";
private static final String TABLE_PROJECTS = "projects";
@ -28,6 +35,26 @@ public class DatabaseHelper extends SQLiteOpenHelper {
public static final String COLUMN_PROJECT_ID = "project_id";
public static final String COLUMN_PROJECT_NAME = "project_name";
public static final String COLUMN_LAST_UPDATED = "last_updated";
private static final String TABLE_PARTS = "parts";
public static final String COLUMN_PART_ID = "part_id";
public static final String COLUMN_PART_NAME = "part_name";
public static final String COLUMN_PART_CODE = "part_code";
public static final String COLUMN_PART_TYPE = "part_type";
public static final String COLUMN_PART_TYPE_LABEL = "part_type_label";
public static final String COLUMN_Part_PROJECT_ID = "project_id";
public static final String COLUMN_Part_PROJECT_NAME = "project_name";
public static final String COLUMN_TURBINE_ID = "turbine_id";
public static final String COLUMN_TURBINE_NAME = "turbine_name";
public static final String COLUMN_LAST_SYNC = "last_sync";
public static final String TABLE_IMAGE_SOURCES = "image_sources";
public static final String COLUMN_SOURCE_KEY = "source_key";
public static final String COLUMN_SOURCE_VALUE = "source_value";
private static final String TABLE_DYNAMIC_DATA = "dynamic_data";
private static final String COLUMN_JSON_DATA = "json_data";
private static final String COLUMN_DATA_TYPE = "data_type"; // 用于区分不同类型的数据
private final Gson gson = new Gson();
private static final Type MAP_LIST_TYPE = new TypeToken<List<Map<String, String>>>() {}.getType();
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -50,6 +77,30 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ COLUMN_PROJECT_NAME + " TEXT,"
+ COLUMN_LAST_UPDATED + " INTEGER" + ")";
db.execSQL(CREATE_PROJECTS_TABLE);
String CREATE_PARTS_TABLE = "CREATE TABLE " + TABLE_PARTS + "("
+ COLUMN_PART_ID + " TEXT PRIMARY KEY,"
+ COLUMN_PART_NAME + " TEXT,"
+ COLUMN_PART_CODE + " TEXT,"
+ COLUMN_PART_TYPE + " TEXT,"
+ COLUMN_PART_TYPE_LABEL + " TEXT,"
+ COLUMN_Part_PROJECT_ID + " TEXT,"
+ COLUMN_Part_PROJECT_NAME + " TEXT,"
+ COLUMN_TURBINE_ID + " TEXT,"
+ COLUMN_TURBINE_NAME + " TEXT,"
+ COLUMN_LAST_SYNC + " INTEGER" + ")";
db.execSQL(CREATE_PARTS_TABLE);
String CREATE_IMAGE_SOURCES_TABLE = "CREATE TABLE " + TABLE_IMAGE_SOURCES + "("
+ COLUMN_SOURCE_KEY + " TEXT PRIMARY KEY,"
+ COLUMN_SOURCE_VALUE + " TEXT,"
+ COLUMN_LAST_SYNC + " INTEGER" + ")";
db.execSQL(CREATE_IMAGE_SOURCES_TABLE);
// 新增动态数据表
String CREATE_DYNAMIC_DATA_TABLE = "CREATE TABLE " + TABLE_DYNAMIC_DATA + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_DATA_TYPE + " TEXT NOT NULL,"
+ COLUMN_JSON_DATA + " TEXT NOT NULL,"
+ COLUMN_LAST_SYNC + " INTEGER DEFAULT 0" + ")";
db.execSQL(CREATE_DYNAMIC_DATA_TABLE);
}
@Override
@ -74,8 +125,260 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// 版本2到版本3的迁移添加项目名称列
db.execSQL("ALTER TABLE " + TABLE_USERS + " ADD COLUMN " + COLUMN_USER_PROJECT_NAME + " TEXT");
}
if (oldVersion < 4) {
// 版本3到版本4的迁移添加部件表
String CREATE_PARTS_TABLE = "CREATE TABLE " + TABLE_PARTS + "("
+ COLUMN_PART_ID + " TEXT PRIMARY KEY,"
+ COLUMN_PART_NAME + " TEXT,"
+ COLUMN_PART_CODE + " TEXT,"
+ COLUMN_PART_TYPE + " TEXT,"
+ COLUMN_PART_TYPE_LABEL + " TEXT,"
+ COLUMN_Part_PROJECT_ID + " TEXT,"
+ COLUMN_Part_PROJECT_NAME + " TEXT,"
+ COLUMN_TURBINE_ID + " TEXT,"
+ COLUMN_TURBINE_NAME + " TEXT,"
+ COLUMN_LAST_SYNC + " INTEGER" + ")";
db.execSQL(CREATE_PARTS_TABLE);
}
if(oldVersion<5)
{
String CREATE_IMAGE_SOURCES_TABLE = "CREATE TABLE " + TABLE_IMAGE_SOURCES + "("
+ COLUMN_SOURCE_KEY + " TEXT PRIMARY KEY,"
+ COLUMN_SOURCE_VALUE + " TEXT,"
+ COLUMN_LAST_SYNC + " INTEGER" + ")";
db.execSQL(CREATE_IMAGE_SOURCES_TABLE);
}
if (oldVersion < 6) {
// 版本5到版本6的迁移添加动态数据表
String CREATE_DYNAMIC_DATA_TABLE = "CREATE TABLE " + TABLE_DYNAMIC_DATA + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_DATA_TYPE + " TEXT NOT NULL,"
+ COLUMN_JSON_DATA + " TEXT NOT NULL,"
+ COLUMN_LAST_SYNC + " INTEGER DEFAULT 0" + ")";
db.execSQL(CREATE_DYNAMIC_DATA_TABLE);
}
}
/**
* 保存动态数据列表
* @param dataType 数据类型标识"work_types"
* @param dataList 要保存的数据列表
*/
public void saveDynamicData(String dataType, List<Map<String, String>> dataList) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
try {
// 删除旧数据
db.delete(TABLE_DYNAMIC_DATA,
COLUMN_DATA_TYPE + " = ?",
new String[]{dataType});
// 插入新数据
ContentValues values = new ContentValues();
values.put(COLUMN_DATA_TYPE, dataType);
values.put(COLUMN_JSON_DATA, gson.toJson(dataList));
values.put(COLUMN_LAST_SYNC, System.currentTimeMillis());
db.insert(TABLE_DYNAMIC_DATA, null, values);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* 获取动态数据列表
* @param dataType 数据类型标识
* @return 对应的数据列表如果没有则返回空列表
*/
public List<Map<String, String>> getDynamicData(String dataType) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_DYNAMIC_DATA,
new String[]{COLUMN_JSON_DATA},
COLUMN_DATA_TYPE + " = ?",
new String[]{dataType},
null, null,
COLUMN_LAST_SYNC + " DESC",
"1"); // 只获取最新的一条
try {
if (cursor.moveToFirst()) {
String jsonData = cursor.getString(0);
return gson.fromJson(jsonData, MAP_LIST_TYPE);
}
} finally {
cursor.close();
}
return new ArrayList<>(); // 返回空列表而非null
}
/**
* 获取指定数据类型的最后同步时间
*/
public long getDynamicDataLastSync(String dataType) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_DYNAMIC_DATA,
new String[]{"MAX(" + COLUMN_LAST_SYNC + ")"},
COLUMN_DATA_TYPE + " = ?",
new String[]{dataType},
null, null, null);
try {
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
} finally {
cursor.close();
}
return 0;
}
/**
* 清除指定类型的数据
*/
public void clearDynamicData(String dataType) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_DYNAMIC_DATA,
COLUMN_DATA_TYPE + " = ?",
new String[]{dataType});
}
public void saveImageSources(List<ImageSourceItem> sources) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
try {
db.delete(TABLE_IMAGE_SOURCES, null, null);
for (ImageSourceItem source : sources) {
ContentValues values = new ContentValues();
values.put(COLUMN_SOURCE_KEY, source.getKey());
values.put(COLUMN_SOURCE_VALUE, source.getValue());
values.put(COLUMN_LAST_SYNC, System.currentTimeMillis());
db.insert(TABLE_IMAGE_SOURCES, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
public List<ImageSourceItem> getImageSources() {
List<ImageSourceItem> sources = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_IMAGE_SOURCES,
new String[]{COLUMN_SOURCE_KEY, COLUMN_SOURCE_VALUE},
null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
sources.add(new ImageSourceItem(
cursor.getString(0),
cursor.getString(1)
));
} while (cursor.moveToNext());
}
cursor.close();
return sources;
}
/**
* 保存部件列表到数据库
*/
public void saveParts(List<PartResponse> parts) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
try {
// 先清空表
db.delete(TABLE_PARTS, null, null);
// 插入新数据
for (PartResponse part : parts) {
ContentValues values = new ContentValues();
values.put(COLUMN_PART_ID, part.getPartId());
values.put(COLUMN_PART_NAME, part.getPartName());
values.put(COLUMN_PART_CODE, part.getPartCode());
values.put(COLUMN_PART_TYPE, part.getPartType());
values.put(COLUMN_PART_TYPE_LABEL, part.getPartTypeLabel());
values.put(COLUMN_PROJECT_ID, part.getProjectId());
values.put(COLUMN_PROJECT_NAME, part.getProjectName());
values.put(COLUMN_TURBINE_ID, part.getTurbineId());
values.put(COLUMN_TURBINE_NAME, part.getTurbineName());
values.put(COLUMN_LAST_SYNC, System.currentTimeMillis());
db.insert(TABLE_PARTS, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* 根据部件名称获取部件ID
*/
public String getPartIdByName(String partName) {
SQLiteDatabase db = this.getReadableDatabase();
String partId = null;
Cursor cursor = db.query(TABLE_PARTS,
new String[]{COLUMN_PART_ID},
COLUMN_PART_NAME + " = ?",
new String[]{partName},
null, null, null);
if (cursor.moveToFirst()) {
partId = cursor.getString(0);
}
cursor.close();
return partId;
}
/**
* 获取所有部件
*/
@SuppressLint("Range")
public List<PartResponse> getAllParts() {
List<PartResponse> parts = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_PARTS,
null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
PartResponse part = new PartResponse();
part.setPartId(cursor.getString(cursor.getColumnIndex(COLUMN_PART_ID)));
part.setPartName(cursor.getString(cursor.getColumnIndex(COLUMN_PART_NAME)));
part.setPartCode(cursor.getString(cursor.getColumnIndex(COLUMN_PART_CODE)));
part.setPartType(cursor.getString(cursor.getColumnIndex(COLUMN_PART_TYPE)));
part.setPartTypeLabel(cursor.getString(cursor.getColumnIndex(COLUMN_PART_TYPE_LABEL)));
part.setProjectId(cursor.getString(cursor.getColumnIndex(COLUMN_Part_PROJECT_ID)));
part.setProjectName(cursor.getString(cursor.getColumnIndex(COLUMN_Part_PROJECT_NAME)));
part.setTurbineId(cursor.getString(cursor.getColumnIndex(COLUMN_TURBINE_ID)));
part.setTurbineName(cursor.getString(cursor.getColumnIndex(COLUMN_TURBINE_NAME)));
parts.add(part);
} while (cursor.moveToNext());
}
cursor.close();
return parts;
}
/**
* 获取最后同步时间
*/
public long getLastSyncTime() {
SQLiteDatabase db = this.getReadableDatabase();
long lastSync = 0;
Cursor cursor = db.query(TABLE_PARTS,
new String[]{"MAX(" + COLUMN_LAST_SYNC + ")"},
null, null, null, null, null);
if (cursor.moveToFirst()) {
lastSync = cursor.getLong(0);
}
cursor.close();
return lastSync;
}
// 用户操作方法
public boolean addUser(String username, String password, String projectId, String projectName) {
SQLiteDatabase db = this.getWritableDatabase();

View File

@ -40,4 +40,6 @@ public interface ImageDao {
@Query("DELETE FROM images WHERE time BETWEEN :startTime AND :endTime")
int deleteByTimeRange(long startTime, long endTime);
@Query("SELECT * FROM images WHERE time BETWEEN :startTime AND :endTime")
List<ImageEntity> getImagesByTimeRange(long startTime, long endTime);
}

View File

@ -11,11 +11,13 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import android.view.View;
@ -37,9 +39,18 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.DataBase.DatabaseHelper;
import com.example.myapplication.Tool.AuthInterceptor;
import com.example.myapplication.Tool.RetrofitClient;
import com.example.myapplication.Tool.ShowError;
import com.example.myapplication.api.AuthApi;
import com.example.myapplication.api.ProjectApi;
import com.example.myapplication.api.UserApi;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.LoginEntity;
import com.example.myapplication.model.LoginRequest;
import com.example.myapplication.model.PageResult;
import com.example.myapplication.model.Project;
import com.example.myapplication.model.UserInfo;
import java.io.File;
import java.io.FileWriter;
@ -51,6 +62,8 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@ -61,7 +74,13 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
@ -69,15 +88,28 @@ import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.PUT;
public class LoginActivity extends AppCompatActivity {
private EditText etUsername, etPassword;
private CheckBox cbRemember;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final ExecutorService executor = Executors.newFixedThreadPool(7);
private Button btnClearProject;
private Button btnLogin;
private DatabaseHelper dbHelper;
private SharedPreferences sharedPreferences;
private final String AUTH_TOKEN_KEY = "auth_token";
private static final String Name = "name";
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
private String authToken = "";
private static final String PREFS_NAME = "LoginPrefs";
private static final String PREF_USERNAME = "username";
private static final String PREF_PASSWORD = "password";
@ -88,6 +120,9 @@ public class LoginActivity extends AppCompatActivity {
private TextView tvSelectedProjectName;
private TextView tvSelectedProjectId;
private List<Project> projectList = new ArrayList<>();
ShowError errorShower = new ShowError(this);
@SuppressLint("SuspiciousIndentation")
@Override
@ -132,7 +167,16 @@ public class LoginActivity extends AppCompatActivity {
btnLogin.setOnClickListener(v -> attemptLogin());
}
private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return false;
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
return capabilities != null &&
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
}
private void loadSavedPreferences() {
boolean remember = sharedPreferences.getBoolean(PREF_REMEMBER, false);
if (remember) {
@ -176,12 +220,246 @@ public class LoginActivity extends AppCompatActivity {
return;
}
// 在后台线程执行数据库操作
// 先进行认证
if(isNetworkAvailable())
authenticateUser(username, password, projectInput);
else continueLoginProcess(username,password,projectInput);
}
private void authenticateUser(String username, String password, String projectInput) {
executor.execute(() -> {
try {
// 加密密码
String encryptedPassword2 = encryptPassword(username, password);
// 创建认证请求体
LoginRequest loginRequest = new LoginRequest(username, encryptedPassword2);
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
// 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(token);
// 创建认证API服务
AuthApi authApi = retrofit.create(AuthApi.class);
// 执行认证请求
Call<ApiResponse<LoginEntity>> call = authApi.login(loginRequest);
Response<ApiResponse<LoginEntity>> response = call.execute();
if (response.isSuccessful() && response.body() != null) {
if (response.body().getCode() == 500200) {
//修改密码操作
runOnUiThread(() -> showChangePasswordDialog(username));
} else if (response.body().getCode() == 200) {
authToken = response.body().getData().getTokenValue();
Log.e("令牌:",authToken);
sharedPreferences.edit().putString(AUTH_TOKEN_KEY, authToken).apply();
Retrofit retrofit2 = RetrofitClient.getClient(authToken);
// 创建UserApi服务
UserApi userApi = retrofit2.create(UserApi.class);
// 执行查询请求只根据account查询
Call<PageResult<UserInfo>> userCall = userApi.getUserList(
authToken, // 注意token前缀根据你的API要求调整
username, // account参数
null, // deptId
null, // mobile
null, // name
null, // userCode
null, // userStatus
null // userType
);
try {
Response<PageResult<UserInfo>> userResponse = userCall.execute();
if (userResponse.isSuccessful() && userResponse.body() != null
&& userResponse.body().getCode() == 200
&& !userResponse.body().getRows().isEmpty()) {
// 获取第一个匹配用户的name
String name = userResponse.body().getRows().get(0).getName();
sharedPreferences.edit().putString(Name, name).apply();
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
} else {
// 查询失败或没有结果使用username作为name
errorShower.showErrorDialog("查询姓名失败" , userResponse.body().getMsg());
sharedPreferences.edit().putString(Name, "").apply();
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
}
} catch (IOException e) {
e.printStackTrace();
// 发生异常使用username作为name
errorShower.showErrorDialog("出现异常:", e.getMessage());
executor.execute(()-> {
continueLoginProcess(username, password, projectInput);
});
}
}
else {
showToast(password+"密码出错"+response.body().toString());
}
} else {
runOnUiThread(() -> {
String errorMsg = "认证失败: ";
if (response.errorBody() != null) {
try {
errorMsg += response.errorBody().string();
} catch (IOException e) {
errorMsg += "无法读取错误详情";
}
}
showToast(errorMsg);
});
}
} catch (Exception e) {
showToast("认证出错:"+e.getMessage());
}
});
}
// 封装 Toast 显示
private void showToast(String message) {
mainHandler.post(() -> Toast.makeText(LoginActivity.this, message, Toast.LENGTH_SHORT).show());
}
private void showChangePasswordDialog(String username) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("请修改密码");
View view = getLayoutInflater().inflate(R.layout.dialog_change_password, null);
EditText etOldPassword = view.findViewById(R.id.etOldPassword);
EditText etNewPassword = view.findViewById(R.id.etNewPassword);
EditText etConfirmPassword = view.findViewById(R.id.etConfirmPassword);
builder.setView(view);
builder.setPositiveButton("确认", (dialog, which) -> {
String oldPassword = etOldPassword.getText().toString().trim();
String newPassword = etNewPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
if (newPassword.equals(confirmPassword)) {
changePassword(username, oldPassword, newPassword);
} else {
Toast.makeText(this, "新密码与确认密码不匹配", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("取消", null);
builder.setCancelable(false);
builder.show();
}
private void changePassword(String username, String oldPassword, String newPassword) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
// 加密旧密码和新密码
String encryptedOldPassword = encryptPassword(username, oldPassword);
String encryptedNewPassword = encryptPassword(username, newPassword);
// 创建修改密码请求体
ChangePasswordRequest request = new ChangePasswordRequest(
username,
encryptedNewPassword,
encryptedOldPassword
);
// 获取token
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
// 初始化Retrofit
Retrofit retrofit = RetrofitClient.getClient(token);
// 创建API服务
AuthApi authApi = retrofit.create(AuthApi.class);
// 执行修改密码请求
Call<ApiResponse<Void>> call = authApi.modifyPassword(request);
Response<ApiResponse<Void>> response = call.execute();
runOnUiThread(() -> {
if (response.isSuccessful() && response.body() != null) {
if (response.body().isSuccess()) {
Toast.makeText(LoginActivity.this, "密码修改成功,请重新登录", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(LoginActivity.this, "密码修改失败: " + response.body().toString(), Toast.LENGTH_SHORT).show();
}
} else {
String errorMsg = "修改密码失败: ";
if (response.errorBody() != null) {
try {
errorMsg += response.errorBody().string();
} catch (IOException e) {
errorMsg += "无法读取错误详情";
}
}
Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
runOnUiThread(() ->
Toast.makeText(LoginActivity.this, "修改密码出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
});
}
// 新增修改密码请求体
public static class ChangePasswordRequest {
private String account;
private String newPassword;
private String oldPassword;
public ChangePasswordRequest(String account, String newPassword, String oldPassword) {
this.account = account;
this.newPassword = newPassword;
this.oldPassword = oldPassword;
}
// getters and setters
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
}
private void continueLoginProcess(String username, String password, String projectInput) {
// 在后台线程执行数据库操作
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
// 解析项目ID和名称
Pair<String, String> projectInfo = parseProjectInfo(projectInput);
String projectId = projectInfo.first;
String projectName = projectInfo.second;
@ -196,14 +474,14 @@ public class LoginActivity extends AppCompatActivity {
if (userExists) {
// 用户存在验证密码
if (dbHelper.checkUser(username, password)) {
handleLoginSuccess(username, projectId, projectName);
handleLoginSuccess(username, projectId, projectName,password);
} else {
Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
}
} else {
// 用户不存在自动注册
if (dbHelper.addUser(username, password, projectId, projectName)) {
handleLoginSuccess(username, projectId, projectName);
handleLoginSuccess(username, projectId, projectName,password);
} else {
Toast.makeText(LoginActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
}
@ -216,6 +494,47 @@ public class LoginActivity extends AppCompatActivity {
});
}
private String encryptPassword(String username, String password) {
try {
// 1. 对账号做MD5计算
String md5Hash = md5(username);
// 2. 取8-24位作为密钥
String key = md5Hash.substring(8, 24); // 8-24位索引从0开始
Log.e("key:",key);
// 3. AES加密
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // PKCS5Padding等同于PKCS7Padding在Java中
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(password.getBytes());
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT).trim();
} catch (Exception e) {
e.printStackTrace();
showToast("加密密码错误:"+e.getMessage());
return "";
}
}
private String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private Pair<String, String> parseProjectInfo(String projectInput) {
String projectId = "";
String projectName = "";
@ -256,11 +575,12 @@ public class LoginActivity extends AppCompatActivity {
editor.apply();
}
private void handleLoginSuccess(String username, String projectId, String projectName) {
private void handleLoginSuccess(String username, String projectId, String projectName,String password) {
dbHelper.updateUserProject(username, projectId, projectName);
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("username", username);
intent.putExtra("password",password);
intent.putExtra("projectId", projectId);
intent.putExtra("projectName", projectName);
startActivity(intent);
@ -344,23 +664,20 @@ public class LoginActivity extends AppCompatActivity {
private void loadProjects() {
String BASE_URL = "http://pms.dtyx.net:9158/";
try {
// 1. 验证接口定义
if (!validateApiDefinition()) {
return;
}
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
// 2. 初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
.connectTimeout(30, TimeUnit.SECONDS)
.build())
.build();
Retrofit retrofit = RetrofitClient.getClient(authToken);
// 3. 创建API服务
ProjectApi projectApi = retrofit.create(ProjectApi.class);

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
package com.example.myapplication.Service;
import android.annotation.SuppressLint;
@ -66,7 +67,7 @@ public class FloatingWindowService extends Service {
private WindowManager windowManager;
private View floatingView;
private RadioGroup radioGroup;
private static final String EXTRA_PROJECT_ID = "PROJECT_ID";
private String lastUnit = "";
@ -92,7 +93,7 @@ public class FloatingWindowService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.hasExtra(EXTRA_PROJECT_ID)) {
projectID = intent.getStringExtra(EXTRA_PROJECT_ID);
// 你可以在这里使用projectID或者在其他方法中使用这个成员变量
}
return super.onStartCommand(intent, flags, startId);
@ -160,13 +161,14 @@ public class FloatingWindowService extends Service {
}
private void setupAutoCompleteBehavior() {
actvUnit.setFocusableInTouchMode(true); // 允许触摸获取焦点
actvUnit.setFocusable(true);
actvUnit.setOnClickListener(v -> {
if (turbineAdapter.getCount() > 0) {
actvUnit.showDropDown();
} else {
actvUnit.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(actvUnit, InputMethodManager.SHOW_IMPLICIT);
showSoftInputDelayed(actvUnit);
}
});
@ -181,15 +183,28 @@ public class FloatingWindowService extends Service {
if (turbineAdapter.getCount() > 0) {
actvUnit.showDropDown();
} else {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(actvUnit.getWindowToken(), 0);
hideSoftInput(actvUnit);
}
return true;
}
return false;
});
}
private void showSoftInputDelayed(View view) {
view.postDelayed(() -> {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
}, 200);
}
private void hideSoftInput(View view) {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
// 移除 setupInputMethod() 方法因为功能已整合到上面的方法中
@ -202,21 +217,35 @@ public class FloatingWindowService extends Service {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 检查是否点击在输入区域
Rect rect = new Rect();
actvUnit.getGlobalVisibleRect(rect);
if (rect.contains((int)event.getRawX(), (int)event.getRawY())) {
return false; // 让事件继续传递
// 如果是点击输入区域不处理拖动让输入框获取焦点
return false;
}
// 记录初始位置
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
// 隐藏键盘和下拉菜单
hideSoftInput(actvUnit);
actvUnit.dismissDropDown();
return true;
case MotionEvent.ACTION_MOVE:
// 更新悬浮窗位置
params.x = initialX + (int)(event.getRawX() - initialTouchX);
params.y = initialY + (int)(event.getRawY() - initialTouchY);
windowManager.updateViewLayout(floatingView, params);
return true;
case MotionEvent.ACTION_UP:
// 拖动结束不处理焦点
return true;
}
return false;
}
@ -495,12 +524,14 @@ public class FloatingWindowService extends Service {
new Handler().postDelayed(() -> {
actvUnit.setBackgroundColor(originalColor);
radioGroup.setBackgroundColor(Color.TRANSPARENT);
}, 300); // 0.5秒后恢复
}, 300); // 0.3秒后恢复
}
@Override
public void onDestroy() {
super.onDestroy();
hideSoftInput(actvUnit);
actvUnit.dismissDropDown();
if (floatingView != null && windowManager != null) {
windowManager.removeView(floatingView);
}

View File

@ -0,0 +1,26 @@
package com.example.myapplication.Tool;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class AuthInterceptor implements Interceptor {
private String authToken;
public AuthInterceptor(String token) {
this.authToken = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// 添加 Authorization header
Request newRequest = originalRequest.newBuilder()
.header("Authorization", authToken)
.build();
return chain.proceed(newRequest);
}
}

View File

@ -15,12 +15,10 @@ public class BackgroundToast {
}
handler.post(() -> {
// 取消之前的Toast
if (currentToast != null) {
currentToast.cancel();
}
// 创建新的Toast
currentToast = Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_SHORT);
currentToast.show();
});

View File

@ -0,0 +1,115 @@
package com.example.myapplication.Tool;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import com.example.myapplication.DataBase.DatabaseHelper;
import com.example.myapplication.api.CommonService;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.ImageSourceItem;
import java.io.IOException;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class CommonImageSourceFetcher {
private String token;
private final CommonService commonService;
private final DatabaseHelper dbHelper;
private final Context context;
public CommonImageSourceFetcher(String token,Context context) {
this.token=token;
this.dbHelper = new DatabaseHelper(context);
this.context = context;
Retrofit retrofit =RetrofitClient.getClient(token);
this.commonService = retrofit.create(CommonService.class);
}
/**
* 同步获取图像来源列表
*/
public List<ImageSourceItem> fetchImageSourceListSync() throws IOException {
if (isNetworkAvailable()) {
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
Response<ApiResponse<List<ImageSourceItem>>> response = call.execute();
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
List<ImageSourceItem> result = response.body().getData();
// 保存到数据库
dbHelper.saveImageSources(result);
return result;
} else {
throw new IOException("请求失败,状态码: " + response.code());
}
} else {
// 无网络时从数据库获取
return dbHelper.getImageSources();
}
}
/**
* 异步获取图像来源列表
*/
public void fetchImageSourceListAsync(ImageSourceCallback callback) {
List<ImageSourceItem> cachedSources = dbHelper.getImageSources();
if (!cachedSources.isEmpty()) {
callback.onSuccess(cachedSources);
}
// 检查网络连接
if (isNetworkAvailable()) {
Call<ApiResponse<List<ImageSourceItem>>> call = commonService.getImageSourceList();
call.enqueue(new Callback<ApiResponse<List<ImageSourceItem>>>() {
@Override
public void onResponse(Call<ApiResponse<List<ImageSourceItem>>> call,
Response<ApiResponse<List<ImageSourceItem>>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
List<ImageSourceItem> result = response.body().getData();
Log.e("data信息:", result != null ? result.toString() : "Result is null");
// 保存到数据库
dbHelper.saveImageSources(result);
callback.onSuccess(result);
} else if (cachedSources.isEmpty()) {
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
}
}
@Override
public void onFailure(Call<ApiResponse<List<ImageSourceItem>>> call, Throwable t) {
if (cachedSources.isEmpty()) {
callback.onFailure(t);
}
}
});
} else if (cachedSources.isEmpty()) {
callback.onFailure(new IOException("无网络连接且无缓存数据"));
}
}
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;
}
public interface ImageSourceCallback {
void onSuccess(List<ImageSourceItem> imageSources);
void onFailure(Throwable t);
}
}

View File

@ -1,4 +1,4 @@
package com.example.myapp;
package com.example.myapplication.Tool;
@ -11,7 +11,6 @@ import com.chaquo.python.Python;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

View File

@ -0,0 +1,136 @@
package com.example.myapplication.Tool;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import com.example.myapplication.DataBase.DatabaseHelper;
import com.example.myapplication.api.CommonService;
import com.example.myapplication.model.ApiResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class DynamicDataFetcher {
private final String token;
private final CommonService commonService;
private final DatabaseHelper dbHelper;
private final Context context;
private final String dataType; // 新增数据类型标识
/**
* @param token 用户令牌
* @param context 上下文
* @param dataType 数据类型标识"work_types""image_sources"
*/
public DynamicDataFetcher(String token, Context context, String dataType) {
this.token = token;
this.context = context;
this.dataType = dataType;
this.dbHelper = new DatabaseHelper(context);
Retrofit retrofit = RetrofitClient.getClient(token);
this.commonService = retrofit.create(CommonService.class);
}
/**
* 同步获取动态数据列表
*/
public List<Map<String, String>> fetchDynamicDataSync() throws IOException {
if (isNetworkAvailable()) {
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
Response<ApiResponse<List<Map<String, String>>>> response = call.execute();
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
List<Map<String, String>> result = response.body().getData();
// 保存到数据库带数据类型标识
dbHelper.saveDynamicData(dataType, result);
return result;
} else {
throw new IOException("请求失败,状态码: " + response.code());
}
} else {
// 无网络时从数据库获取带数据类型标识
return dbHelper.getDynamicData(dataType);
}
}
/**
* 异步获取动态数据列表
*/
public void fetchDynamicDataAsync(DynamicDataCallback callback) {
List<Map<String, String>> cachedData = dbHelper.getDynamicData(dataType);
if (!cachedData.isEmpty()) {
callback.onSuccess(cachedData);
}
if (isNetworkAvailable()) {
Call<ApiResponse<List<Map<String, String>>>> call = commonService.getDynamicDataList();
call.enqueue(new Callback<ApiResponse<List<Map<String, String>>>>() {
@Override
public void onResponse(Call<ApiResponse<List<Map<String, String>>>> call,
Response<ApiResponse<List<Map<String, String>>>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
Log.e("图片源列表:",response.body().toString());
List<Map<String, String>> result = response.body().getData();
Log.e("result",result.toString());
// 保存到数据库带数据类型标识
dbHelper.saveDynamicData(dataType, result);
callback.onSuccess(result);
} else if (cachedData.isEmpty()) {
callback.onFailure(new IOException("请求失败,状态码: " + response.code()));
}
}
@Override
public void onFailure(Call<ApiResponse<List<Map<String, String>>>> call, Throwable t) {
if (cachedData.isEmpty()) {
callback.onFailure(t);
}
}
});
} else if (cachedData.isEmpty()) {
callback.onFailure(new IOException("无网络连接且无缓存数据"));
}
}
/**
* 检查网络是否可用
*/
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;
}
/**
* 获取该数据类型的最后同步时间
*/
public long getLastSyncTime() {
return dbHelper.getDynamicDataLastSync(dataType);
}
/**
* 清除当前数据类型的缓存
*/
public void clearCache() {
dbHelper.clearDynamicData(dataType);
}
public interface DynamicDataCallback {
void onSuccess(List<Map<String, String>> dataList);
void onFailure(Throwable t);
}
}

View File

@ -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<PartResponse> fetchPartListSync() throws IOException, ApiException {
if (isNetworkAvailable()) {
// 有网络时从服务器获取并更新数据库
List<PartResponse> serverParts = fetchFromServer();
dbHelper.saveParts(serverParts);
return serverParts;
} else {
// 无网络时从数据库获取
return dbHelper.getAllParts();
}
}
/**
* 异步获取部件列表
* @param callback 回调接口
*/
public void fetchPartListAsync(final PartListCallback callback) {
// 先从数据库获取数据快速显示
List<PartResponse> cachedParts = dbHelper.getAllParts();
if (!cachedParts.isEmpty()) {
callback.onSuccess(cachedParts);
}
// 检查网络连接
if (isNetworkAvailable()) {
// 有网络时从服务器获取并更新数据库
fetchFromServerAsync(new PartListCallback() {
@Override
public void onSuccess(List<PartResponse> 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<PartResponse> parts = fetchFromServer();
dbHelper.saveParts(parts);
// 再次从数据库查询
return dbHelper.getPartIdByName(partName);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
private List<PartResponse> fetchFromServer() throws IOException, ApiException {
Call<ApiResponse<List<PartResponse>>> call = partService.getPartList(
projectId,
null, null, null, null
);
Response<ApiResponse<List<PartResponse>>> response = call.execute();
if (response.isSuccessful() && response.body() != null) {
ApiResponse<List<PartResponse>> 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<ApiResponse<List<PartResponse>>> call = partService.getPartList(
projectId,
null, null, null, null
);
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
@Override
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
Response<ApiResponse<List<PartResponse>>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<List<PartResponse>> 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<ApiResponse<List<PartResponse>>> 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<ApiResponse<List<PartResponse>>> call = partService.getPartList(
projectId,
keyword,
manufacturer,
model,
type
);
call.enqueue(new retrofit2.Callback<ApiResponse<List<PartResponse>>>() {
@Override
public void onResponse(Call<ApiResponse<List<PartResponse>>> call,
Response<ApiResponse<List<PartResponse>>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<List<PartResponse>> 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<ApiResponse<List<PartResponse>>> call, Throwable t) {
callback.onFailure(t);
}
});
}
public interface PartListCallback {
void onSuccess(List<PartResponse> partList);
void onFailure(Throwable t);
}
public String getPartIdByNameSync(String partName) throws IOException, ApiException {
List<PartResponse> 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<PartResponse> 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;
}
}
}

View File

@ -1,4 +1,4 @@
package com.example.myapp;
package com.example.myapplication.Tool;
import android.Manifest;
import android.app.Activity;

View File

@ -1,14 +1,16 @@
package com.example.myapp;
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;
import com.google.gson.Gson;
public class ReportGeneratorHelper {
private Context context;

View File

@ -0,0 +1,38 @@
package com.example.myapplication.Tool;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;
public class RetrofitClient {
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
private static Retrofit retrofit;
public static Retrofit getClient(String authToken) {
if (retrofit == null) {
// 创建 OkHttpClient 并添加拦截器
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
// 添加日志拦截器可选
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
okHttpClientBuilder.addInterceptor(loggingInterceptor);
// 添加认证拦截器自动添加 Authorization header
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
// 构建 Retrofit
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

View File

@ -0,0 +1,47 @@
package com.example.myapplication.Tool;
import android.content.Context;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
public class ShowError {
private Context context;
private final Handler mainHandler;
public ShowError(Context context) {
this.context = context;
this.mainHandler = new Handler(Looper.getMainLooper());
}
public void showErrorDialog(String title, String message) {
if (context instanceof AppCompatActivity) {
AppCompatActivity activity = (AppCompatActivity) context;
activity.runOnUiThread(() -> {
new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(message)
.setPositiveButton("确定", null)
.setNegativeButton("复制错误", (dialog, which) -> {
// 将错误复制到剪贴板
ClipboardManager clipboard =
(ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip =
ClipData.newPlainText("错误信息", message);
clipboard.setPrimaryClip(clip);
showToast("已复制错误信息");
})
.show();
});
}
}
private void showToast(String message) {
mainHandler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
}
}

View File

@ -0,0 +1,21 @@
package com.example.myapplication.api;
import com.example.myapplication.LoginActivity;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.LoginEntity;
import com.example.myapplication.model.LoginRequest;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.PUT;
public interface AuthApi {
@POST("/auth/login")
Call<ApiResponse<LoginEntity>> login(@Body LoginRequest loginRequest);
@PUT("/auth/modify-password")
Call<ApiResponse<Void>> modifyPassword(
@Body LoginActivity.ChangePasswordRequest request
);
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.api;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.ImageSourceItem;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
public interface CommonService {
@GET("/common/list/common-image-source")
Call<ApiResponse<List<ImageSourceItem>>> getImageSourceList(
);
@GET("/common/list/common-image-source")
Call<ApiResponse<List<Map<String, String>>>> getDynamicDataList();
}

View File

@ -0,0 +1,22 @@
package com.example.myapplication.api;
import com.example.myapplication.model.ApiResponse;
import com.example.myapplication.model.PartResponse;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface PartService {
@GET("/part/list")
Call<ApiResponse<List<PartResponse>>> getPartList(
@Query("projectId") String projectId,
@Query("keyword") String keyword,
@Query("partManufacturer") String partManufacturer,
@Query("partModel") String partModel,
@Query("partType") String partType
);
}

View File

@ -7,7 +7,7 @@ import java.util.List;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Headers;
// ApiService.java

View File

@ -0,0 +1,23 @@
package com.example.myapplication.api;
import com.example.myapplication.model.PageResult;
import com.example.myapplication.model.UserInfo;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Query;
public interface UserApi {
@GET("/user/list")
Call<PageResult<UserInfo>> getUserList(
@Header("Authorization") String token,
@Query("account") String account,
@Query("deptId") String deptId,
@Query("mobile") String mobile,
@Query("name") String name,
@Query("userCode") String userCode,
@Query("userStatus") String userStatus,
@Query("userType") String userType
);
}

View File

@ -17,10 +17,15 @@ import androidx.room.PrimaryKey;
public String audioPath;
public int blade;
public String unit;
public String unitName;
public String project;
public boolean b;
public String temperature="0"; // 温度
public String humidity="0"; // 湿度
public String weather="0"; // 天气状况
public String imageSource="-1";
public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String unit, int blade, boolean b) {
public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String unit, int blade, boolean b,String unitName,String temperature,String humidity,String weather,String imageSource) {
this.path = path;
this.time = time;
@ -33,6 +38,11 @@ import androidx.room.PrimaryKey;
this.unit=unit;
this.blade=blade;
this.b=b;
this.weather=weather;
this.temperature=temperature;
this.humidity=humidity;
this.unitName=unitName;
this.imageSource=imageSource;
}

View File

@ -0,0 +1,29 @@
package com.example.myapplication.model;
public class ImageSourceItem {
private String key;
private String value;
public ImageSourceItem(String key, String value) {
this.key = key;
this.value = value;
}
// 用于JSON解析的无参构造
public ImageSourceItem() {}
@Override
public String toString() {
return "ImageSourceItem{" +
"key='" + key + '\'' +
", 类型='" + value + '\'' +
'}';
}
// Getters and Setters
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

View File

@ -0,0 +1,125 @@
package com.example.myapplication.model;
public class LoginEntity {
private boolean isLogin;
private String loginDeviceType;
private Object loginId; // Using Object as specified
private String loginType;
private long sessionTimeout; // Changed to long for int64
private String tag;
private long tokenActiveTimeout; // Changed to long for int64
private String tokenName;
private long tokenSessionTimeout; // Changed to long for int64
private long tokenTimeout; // Changed to long for int64
private String tokenValue;
// Default constructor
public LoginEntity() {
}
// Getters and Setters
public boolean isLogin() {
return isLogin;
}
public void setLogin(boolean login) {
isLogin = login;
}
public String getLoginDeviceType() {
return loginDeviceType;
}
public void setLoginDeviceType(String loginDeviceType) {
this.loginDeviceType = loginDeviceType;
}
public Object getLoginId() {
return loginId;
}
public void setLoginId(Object loginId) {
this.loginId = loginId;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
public long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public long getTokenActiveTimeout() {
return tokenActiveTimeout;
}
public void setTokenActiveTimeout(long tokenActiveTimeout) {
this.tokenActiveTimeout = tokenActiveTimeout;
}
public String getTokenName() {
return tokenName;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
public long getTokenSessionTimeout() {
return tokenSessionTimeout;
}
public void setTokenSessionTimeout(long tokenSessionTimeout) {
this.tokenSessionTimeout = tokenSessionTimeout;
}
public long getTokenTimeout() {
return tokenTimeout;
}
public void setTokenTimeout(long tokenTimeout) {
this.tokenTimeout = tokenTimeout;
}
public String getTokenValue() {
return tokenValue;
}
public void setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
}
@Override
public String toString() {
return "Login{" +
"isLogin=" + isLogin +
", loginDeviceType='" + loginDeviceType + '\'' +
", loginId=" + loginId +
", loginType='" + loginType + '\'' +
", sessionTimeout=" + sessionTimeout +
", tag='" + tag + '\'' +
", tokenActiveTimeout=" + tokenActiveTimeout +
", tokenName='" + tokenName + '\'' +
", tokenSessionTimeout=" + tokenSessionTimeout +
", tokenTimeout=" + tokenTimeout +
", tokenValue='" + tokenValue + '\'' +
'}';
}
}

View File

@ -0,0 +1,17 @@
package com.example.myapplication.model;
import com.google.gson.annotations.SerializedName;
public class LoginRequest {
@SerializedName("account")
private String account;
@SerializedName("password")
private String password;
public LoginRequest(String account, String password) {
this.account = account;
this.password = password;
}
}

View File

@ -0,0 +1,54 @@
package com.example.myapplication.model;
import java.util.List;
public class PageResult<T> {
private int code;
private String msg;
private List<T> rows;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public PageResult() {
}
public void setMsg(String msg) {
this.msg = msg;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public PageResult(int code, String msg, List<T> rows, long total) {
this.code = code;
this.msg = msg;
this.rows = rows;
this.total = total;
}
private long total;
}

View File

@ -0,0 +1,85 @@
package com.example.myapplication.model;
public class PartResponse {
private String partCode;
private String partId;
private String partName;
private String partType;
private String partTypeLabel;
private String projectId;
private String projectName;
private String turbineId;
private String turbineName;
public String getPartCode() {
return partCode;
}
public void setPartCode(String partCode) {
this.partCode = partCode;
}
public String getPartId() {
return partId;
}
public void setPartId(String partId) {
this.partId = partId;
}
public String getPartTypeLabel() {
return partTypeLabel;
}
public void setPartTypeLabel(String partTypeLabel) {
this.partTypeLabel = partTypeLabel;
}
public String getPartType() {
return partType;
}
public void setPartType(String partType) {
this.partType = partType;
}
public void setPartName(String partName) {
this.partName = partName;
}
public String getProjectId() {
return projectId;
}
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getTurbineId() {
return turbineId;
}
public void setTurbineId(String turbineId) {
this.turbineId = turbineId;
}
public String getTurbineName() {
return turbineName;
}
public void setTurbineName(String turbineName) {
this.turbineName = turbineName;
}
public String getPartName() {
return partName;
}
}

View File

@ -0,0 +1,74 @@
package com.example.myapplication.model;
public class UserInfo {
private String account;
private String name;
private String deptName;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UserInfo() {
}
public UserInfo(String account, String name, String deptName, String mobile, String userCode, String userId) {
this.account = account;
this.name = name;
this.deptName = deptName;
this.mobile = mobile;
this.userCode = userCode;
this.userId = userId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
private String mobile;
private String userCode;
private String userId;
// 其他字段根据需要添加
// getters and setters
}

View File

@ -126,4 +126,5 @@
android:text="登录"/>
</LinearLayout>

View File

@ -236,6 +236,27 @@
android:text="当前状态:未监听"
android:textColor="#212121"
android:textSize="14sp" />
<TextView
android:id="@+id/tem_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="当前温度:为获取"
android:textColor="#212121"
android:textSize="14sp" />
<TextView
android:id="@+id/hum_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="当前湿度:未获取"
android:textColor="#212121"
android:textSize="14sp" />
<TextView
android:id="@+id/weather_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="当前天气:为获取"
android:textColor="#212121"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@ -467,6 +488,33 @@
android:layout_height="wrap_content"
android:text="查看失败录音"
android:layout_margin="8dp"/>
<Button
android:id="@+id/btn_success_audios"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看成功上传录音"
android:layout_margin="8dp"/>
<Button
android:id="@+id/btn_convert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="根据日期生成日报" />
<Button
android:id="@+id/btn_readdocx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看已经生成日报" />
<Button
android:id="@+id/changePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="修改密码"/>
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="准备就绪" />
<!-- 定时上传图片设置(移动到最下方) -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
@ -567,7 +615,18 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择图像来源:"
android:textSize="18sp"
android:layout_marginBottom="8dp"/>
<Spinner
android:id="@+id/spinner_image_source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:prompt="@string/image_source_prompt"/>
<Button
android:id="@+id/btnShowFloating"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="旧密码:"
android:textSize="16sp" />
<EditText
android:id="@+id/etOldPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新密码:"
android:layout_marginTop="8dp"
android:textSize="16sp" />
<EditText
android:id="@+id/etNewPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认密码:"
android:layout_marginTop="8dp"
android:textSize="16sp" />
<EditText
android:id="@+id/etConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</LinearLayout>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择报告"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="400dp"
android:orientation="vertical">
<ListView
android:id="@+id/report_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="multipleChoice"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:weightSum="4">
<Button
android:id="@+id/btn_delete_selected"
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:text="删除选中" />
<Button
android:id="@+id/btn_delete_all"
android:layout_width="52dp"
android:layout_height="match_parent"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:text="删除全部" />
<Button
android:id="@+id/btn_share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="分享"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="返回"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:focusable="false"
android:clickable="false"/>
<TextView
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:ellipsize="end"
android:maxLines="1"/>
</LinearLayout>

View File

@ -3,6 +3,7 @@
<!-- 应用名称 -->
<string name="app_name">My Application</string>
<string name="image_source_prompt">请选择图像来源</string>
<!-- 解决错误所需的字符串 -->
<string name="next">Next</string>
<string name="previous">Previous</string>

View File

@ -1,4 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.chaquo.python") version "16.1.0" apply false
alias(libs.plugins.android.application) apply false
}

View File

@ -6,9 +6,11 @@ pluginManagement {
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
maven { url = uri("https://chaquo.com/maven") }
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
@ -16,6 +18,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://chaquo.com/maven") }
}
}