Compare commits

...

39 Commits
main ... master

Author SHA1 Message Date
刘骐瑞 ae7cb082c1 最终版
3 weeks ago
刘骐瑞 4c7a4236c0 刘骐瑞
2 months ago
刘骐瑞 d5fe0481ac other
2 months ago
刘骐瑞 d86c0e6c70 25.10.23
2 months ago
刘骐瑞 5b3b5c1923 gai
2 months ago
刘骐瑞 be3945fb1e Merge remote-tracking branch 'remotes/origin/caijianyu_part'
2 months ago
刘骐瑞 fc2ab52293 ddd
2 months ago
刘骐瑞 8acf678eba add
2 months ago
刘骐瑞 0aa8412c57 add
2 months ago
刘骐瑞 a9de196957 add
2 months ago
刘骐瑞 ab66af1810 change
2 months ago
刘骐瑞 582e64efb4 aaa
2 months ago
刘骐瑞 d13982857f 3
2 months ago
刘骐瑞 26b6390c5d 合并用
2 months ago
px8tqwyol a1ecc77bbf Merge pull request '新版本' (#13) from wangze_part into master
2 months ago
wangze 659b971314 gai4
2 months ago
px8tqwyol 384eb58528 Merge pull request '新版本' (#12) from wangze_part into master
2 months ago
刘骐瑞 0210408f49 change
2 months ago
刘骐瑞 005589ac49 add
2 months ago
蔡建宇 f6563858e3 商品详情界面
2 months ago
wangze 33aa051fb3 gai3
2 months ago
px8tqwyol ed4abf927e Merge pull request '新版本' (#10) from niefangkai_part into master
2 months ago
聂方凯 d79fbbb4ea add
2 months ago
p5tyirj4z 9b3e8e0e54 Merge pull request 'gai2' (#9) from wangze_part into master
2 months ago
wangze 885d563780 gai2
2 months ago
px8tqwyol dfc96a769c Merge pull request '消息界面' (#8) from niefangkai_part into master
2 months ago
聂方凯 b925012b78 add
2 months ago
px8tqwyol 09c08ba9f3 Merge pull request 'new' (#7) from master into wangze_part
2 months ago
刘骐瑞 17e6b564ea change
2 months ago
刘骐瑞 e8244c0fad add
2 months ago
p5tyirj4z a69b944ab2 Merge pull request '我要' (#6) from master into wangze_part
2 months ago
wangze 5c48c5d87e gai
2 months ago
聂方凯 70024a0ce4 add
2 months ago
p7986wnly 5d8ed8b1e3 Merge pull request '发布界面' (#5) from caijianyu_part into master
2 months ago
蔡建宇 a67ea7cfee 2025.10.16
2 months ago
px8tqwyol 94548cb17d Merge pull request '新版本' (#3) from master into niefangkai_part
2 months ago
px8tqwyol 9f74135f2d Merge pull request '新版本' (#4) from master into wangze_part
2 months ago
px8tqwyol f9857ad318 Merge pull request '新版本' (#1) from master into caijianyu_part
2 months ago
刘骐瑞 aa1935e759 实现了登陆界面
2 months ago

3
.idea/.gitignore vendored

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/LLRiseTabBarDemo.iml" filepath="$PROJECT_DIR$/.idea/LLRiseTabBarDemo.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/Project.iml" filepath="$PROJECT_DIR$/.idea/Project.iml" />
</modules>
</component>
</project>

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

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -0,0 +1,292 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="actionbar">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\asus\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcher">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="C:\Users\asus\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\asus\Desktop\logo.png" />
<entry key="scalingPercent" value="102" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcherLegacy">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\asus\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="notification">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\asus\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvBanner">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvChannel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="C:\Users\asus\AppData\Local\Temp\ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-10-11T11:49:33.560456900Z">
<DropdownSelection timestamp="2025-12-03T22:48:00.917970600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\asus\.android\avd\Pixel.avd" />
<DeviceId pluginId="Default" identifier="serial=127.0.0.1:7555;connection=5928369f" />
</handle>
</Target>
</DropdownSelection>

@ -6,7 +6,7 @@
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="gradleJvm" value="jbr-21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

@ -182,6 +182,13 @@
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="1.0" />
</ProjectState>
</option>
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
<component name="VcsProjectSettings">
<option name="detectVcsMappingsAutomatically" value="false" />
</component>
</project>

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

@ -0,0 +1,103 @@
# 百度AI API密钥配置指南
## 前言
为了使AI商品识别功能正常工作您需要配置百度AI开放平台的API密钥。本文档将指导您如何获取并配置这些密钥。
## 步骤1注册百度AI开放平台账号
1. 访问 [百度AI开放平台](https://ai.baidu.com/)
2. 点击右上角「登录」按钮,如果没有账号请先注册
3. 完成实名认证部分API需要
## 步骤2创建应用并获取API密钥
1. 登录后,进入[控制台](https://console.bce.baidu.com/ai/)
2. 在左侧导航栏选择「图像识别」服务
3. 点击「创建应用」按钮
4. 填写应用信息:
- 应用名称:建议填写项目名称,如"商品识别应用"
- 应用类型:选择「移动应用」
- 应用描述:简要描述应用用途
5. 勾选需要的接口,至少选择「通用物体和场景识别」和「商品识别」
6. 点击「立即创建」按钮
## 步骤3获取API密钥
1. 创建成功后,在应用列表中找到您刚创建的应用
2. 复制「API Key」和「Secret Key」
## 步骤4在应用中配置密钥
### 方法一:在代码中配置(开发测试阶段)
打开 `BaiduImageRecognitionService.java` 文件,在 `getAccessToken()` 方法中替换默认的密钥:
```java
private String getAccessToken() {
// 替换为您的API Key和Secret Key
String apiKey = "iQRtDkCyWZ9EP1NHAVcyxHIl";
String secretKey = "4waKrdNPh1cIQvnaO8FX5NgtW4LBLWeY";
// 以下代码保持不变...
}
```
### 方法二:在配置文件中配置(推荐)
1. 在 `app/src/main/res/values` 目录下创建或编辑 `strings.xml` 文件
2. 添加以下内容:
```xml
<string name="baidu_api_key">iQRtDkCyWZ9EP1NHAVcyxHIl</string>
<string name="baidu_secret_key">4waKrdNPh1cIQvnaO8FX5NgtW4LBLWeY</string>
```
3. 修改 `BaiduImageRecognitionService.java` 文件:
```java
private String getAccessToken() {
// 从资源文件获取密钥
String apiKey = context.getString(R.string.baidu_api_key);
String secretKey = context.getString(R.string.baidu_secret_key);
// 以下代码保持不变...
}
```
### 方法三:使用环境变量或安全存储(生产环境)
对于生产环境建议使用Android Keystore或其他安全机制存储敏感信息
1. 在构建配置中设置环境变量
2. 使用Gradle构建变量注入
3. 使用Android Keystore系统安全存储密钥
## 步骤5测试配置
1. 编译并运行应用
2. 进入发布商品页面
3. 上传一张商品图片
4. 点击「使用AI识别商品信息」按钮
5. 如果识别成功并显示结果,则配置正确
## 注意事项
1. **密钥安全**请妥善保管您的API密钥不要在公开代码中暴露
2. **配额管理**百度AI开放平台对API调用有免费额度和计费标准请关注使用情况
3. **错误处理**:如果出现"invalid api key"或"quota exhausted"等错误,请检查密钥配置和使用配额
4. **定期更新**如果密钥泄露请立即在百度AI平台重新生成新密钥
## 常见问题排查
1. **认证失败**检查API Key和Secret Key是否正确
2. **配额不足**:检查是否已达到免费调用额度
3. **网络错误**:确保应用有网络权限且设备可正常联网
4. **参数错误**:检查请求参数格式是否正确
## 参考文档
- [百度AI开放平台文档中心](https://ai.baidu.com/ai-doc/IMAGERECOGNITION/Fk3bcxc9b)
- [百度AI开放平台SDK下载](https://ai.baidu.com/sdk)
如有其他问题请参考百度AI开放平台的官方文档或联系技术支持。

@ -0,0 +1,115 @@
# APK安装指南
本文档提供了多种将APK安装到Android设备的方法。
## 一、不同APK版本的使用场景
在项目中生成了两种APK
1. **调试版本 (Debug APK)**
- 路径:`app/build/outputs/apk/debug/app-debug.apk`
- 特点:使用调试密钥签名,包含调试信息,便于开发测试
- 适用场景:开发过程中进行功能测试和调试
2. **发布版本 (Release APK)**
- 路径:`app/build/outputs/apk/release/app-release-unsigned.apk`
- 特点:当前是未签名版本,不包含调试信息,性能更好
- 适用场景:需要正式签名后用于发布或最终用户测试
## 二、使用ADB命令安装APK
### 使用提供的脚本安装
1. 确保您的Android设备已通过USB连接到电脑
2. 确保在设备上启用了「USB调试」模式通常在设置 → 开发者选项中)
3. 双击运行项目根目录下的 `install_apk_with_adb.bat` 脚本
4. 按照脚本提示操作它会自动检测设备并安装调试版本的APK
### 手动使用ADB命令
如果您熟悉命令行,可以手动执行以下步骤:
1. 打开命令提示符或PowerShell
2. 导航到项目目录
3. 执行以下命令:
```
adb install -r app\build\outputs\apk\debug\app-debug.apk
```
`-r` 参数表示如果应用已存在则替换安装)
## 三、通过文件传输安装APK
### 方法1通过USB数据线传输
1. 将Android设备通过USB数据线连接到电脑
2. 在手机上选择「文件传输」模式(通常在通知栏中选择)
3. 在电脑上打开设备的存储
4. 将APK文件推荐使用debug版本复制到设备的任意文件夹中
5. 在手机上使用文件管理器找到并点击APK文件
6. 按照提示完成安装(需要允许「安装未知来源应用」)
### 方法2通过云存储或邮件传输
1. 将APK文件上传到云存储服务如Google Drive、百度网盘等或通过邮件发送给自己
2. 在手机上打开相应的云存储应用或邮件应用
3. 下载APK文件到手机
4. 使用文件管理器找到并点击下载的APK文件
5. 按照提示完成安装
## 四、安装注意事项和常见问题
### 启用「安装未知来源应用」权限
Android设备默认不允许安装非应用商店的应用。您需要
1. 打开手机「设置」
2. 找到「安全」或「隐私」选项
3. 启用「允许安装未知来源应用」或类似选项
4. 对于Android 8.0及以上版本,可能需要为特定应用(如文件管理器)单独授予此权限
### 常见问题解决
1. **安装失败,提示「解析包时出现问题」**
- 可能原因APK文件损坏或不完整
- 解决方法重新复制或下载APK文件
2. **安装失败,提示「应用未安装」**
- 可能原因:版本冲突或签名不一致
- 解决方法先卸载旧版本应用或使用相同签名的APK
3. **ADB命令找不到设备**
- 可能原因USB调试未开启或驱动未正确安装
- 解决方法确保已开启USB调试尝试重新连接数据线或安装正确的USB驱动
4. **签名验证失败**
- 可能原因使用了未签名的release版本APK
- 解决方法使用debug版本APK或为release版本APK签名
### 如何为Release版本APK签名
如果需要正式发布应用您应该为release版本的APK签名
1. 打开 `app/build.gradle` 文件
2. 取消注释并修改签名配置部分,填入您的密钥信息:
```gradle
signingConfigs {
release {
storeFile file("your_keystore.jks")
storePassword "your_keystore_password"
keyAlias "your_key_alias"
keyPassword "your_key_password"
}
}
```
3. 取消注释签名配置引用:
```gradle
buildTypes {
release {
// 其他配置...
signingConfig signingConfigs.release
}
}
```
4. 重新执行构建命令:`./gradlew assembleRelease`
完成这些步骤后您将获得一个已签名的release版本APK可以用于发布。

@ -0,0 +1,96 @@
# AI商品识别功能使用说明
## 功能概述
本应用新增了基于AI的商品识别功能用户在发布商品时可以通过上传商品图片或视频自动识别商品信息并提供价格预估。这大大简化了商品发布流程提高了用户体验。
## 主要功能
1. **图片/视频上传**:支持从相册选择或直接拍摄图片/视频
2. **AI商品识别**:自动识别商品名称和类别
3. **价格预估**:基于识别结果提供合理的价格范围建议
4. **一键应用**:快速将识别结果应用到商品发布表单
## 使用方法
### 1. 上传商品媒体
在发布商品页面,点击图片网格中的「+」按钮,选择以下方式之一上传商品媒体:
- 拍摄照片
- 从相册选择照片
- 拍摄视频
- 从相册选择视频
### 2. 启动AI识别
- **自动识别**上传第一张媒体文件后系统会自动触发AI识别可在设置中关闭此功能
- **手动识别**点击「使用AI识别商品信息」按钮手动启动识别
### 3. 查看识别结果
识别完成后,系统会显示:
- 识别的商品名称
- 商品类别
- 预估价格(包含识别可信度)
### 4. 应用识别结果
点击「应用识别结果」按钮,系统会自动:
- 将商品名称填充到标题字段
- 选择对应的商品类别
- 将预估价格填充到价格字段
- 将生成的描述添加到描述字段
### 5. 调整和发布
- 您可以在应用识别结果后,根据需要调整各项信息
- 确认无误后,点击「发布」按钮完成商品发布
## 技术实现
### AI识别服务
本功能使用百度AI开放平台的图像识别API通过以下步骤实现
1. 将用户上传的图片转换为Base64编码
2. 调用百度AI的商品识别接口
3. 解析返回的识别结果
4. 使用内置的价格预估算法计算价格区间
### 视频处理
对于视频文件,系统会:
1. 提取视频的关键帧(第一帧)
2. 将提取的帧作为图片进行识别
## 注意事项
1. **网络要求**AI识别功能需要网络连接请确保设备处于联网状态
2. **识别准确率**:识别准确率取决于图片质量和商品特征清晰度
3. **价格参考**:预估价格仅供参考,建议根据实际市场情况调整
4. **权限要求**:使用本功能需要以下权限:
- 相机权限(用于拍摄照片/视频)
- 存储权限(用于保存和读取媒体文件)
- 网络权限用于调用AI服务
## 常见问题
### Q: 识别失败怎么办?
A: 请尝试上传更清晰、特征更明显的图片,或从不同角度拍摄商品。
### Q: 预估价格不准确怎么办?
A: 预估价格基于商品类别和市场数据计算,您可以手动调整为实际价格。
### Q: 支持识别哪些类型的商品?
A: 系统支持识别多种常见商品,包括电子产品、服装鞋包、家居用品等多个品类。
### Q: 视频文件有大小限制吗?
A: 为保证性能建议上传不超过100MB的视频文件。
## 故障排除
1. **无法上传媒体**:请检查存储权限是否已授予
2. **识别超时**:请检查网络连接并重试
3. **应用崩溃**:请尝试清除应用缓存或重启应用
如遇到其他问题,请联系应用客服获取支持。

@ -0,0 +1,112 @@
# 课程表PDF导入功能使用说明
## 功能概述
本应用新增了课程表PDF文件导入功能用户可以上传包含课程信息的PDF文件系统会自动识别课程内容并根据两字符相似度匹配规则搜索相关的已发布商品如教材。这大大提高了用户查找课程相关资料的效率。
## 主要功能
1. **PDF文件导入**支持从设备存储选择并上传PDF格式的课程表
2. **课程信息自动识别**从PDF文件中提取并解析课程名称和相关信息
3. **智能商品匹配**:基于两字符相似度算法,自动匹配相关的已发布商品
4. **快速查看与购买**:直接查看匹配到的商品详情和购买选项
## 使用方法
### 1. 进入课程表导入页面
- 在应用主界面导航栏中找到「课程表」选项
- 进入课程表页面后,点击「导入课程表」按钮
### 2. 选择PDF文件
- 点击「选择文件」按钮
- 在文件选择器中导航到包含课程表PDF文件的文件夹
- 选择目标PDF文件并点击确定
### 3. 查看识别结果
系统会自动处理PDF文件并显示识别到的课程列表
- 识别出的课程名称
- 课程对应的时间安排(如有)
- 每个课程匹配到的相关商品数量
### 4. 查看匹配的商品
- 对于每个识别出的课程,点击「查看相关商品」按钮
- 系统会显示基于两字符相似度匹配到的相关商品列表
- 商品列表按匹配度排序,最相关的商品会显示在前面
## 技术实现
### PDF文件处理
本功能使用Apache PDFBox库处理PDF文件通过以下步骤实现文本提取
1. 使用PDFBox加载用户选择的PDF文件
2. 从PDF文档中逐页提取文本内容
3. 合并所有页面的文本信息
4. 处理文本格式,分割为可解析的行
### 课程信息解析
提取文本后,系统会:
1. 清理和标准化文本内容
2. 使用预定义的正则表达式模式识别课程名称
3. 构建结构化的课程信息对象列表
4. 传递给商品匹配模块
### 两字符相似度匹配算法
本功能使用创新的两字符匹配算法,具体实现如下:
1. 从课程名称和商品信息中提取所有可能的连续两字符组合
2. 生成这些两字符组合的集合
3. 检查课程名称和商品信息的两字符组合集合是否存在交集
4. 如果存在至少一个相同的两字符组合,则认为匹配成功
与传统的精确匹配或模糊匹配相比,两字符匹配算法在处理不同表达方式的课程名称时表现更好,例如:
- "高等数学" 可以匹配 "高等数学(一)"
- "计算机网络" 可以匹配 "计算机网络基础"
- "英语写作" 可以匹配 "大学英语写作技巧"
### 匹配评分和排序
系统会为每个匹配项计算相似度分数:
- 基础分两字符匹配成功得50分
- 加分项完整关键词匹配加30分
- 加分项复合关键词部分匹配每个加10分
- 最高得分100分
最终结果按相似度分数降序排列,分数相同时按商品标题长度排序。
## 注意事项
1. **文件格式要求**请确保上传的是标准PDF格式文件加密或扫描的PDF可能无法正确识别
2. **识别质量**识别质量取决于PDF文件的文本清晰度和格式化程度
3. **匹配精度**:两字符匹配算法提高了匹配成功率,但可能会返回一些不太相关的结果
4. **权限要求**:使用本功能需要以下权限:
- 存储权限用于读取PDF文件
- 网络权限用于调用OCR服务和商品搜索
## 常见问题
### Q: PDF文件无法识别怎么办
A: 请确保PDF文件包含可提取的文本内容而非扫描的图片。如果是扫描文档建议先转换为文本PDF。
### Q: 识别出的课程名称不准确怎么办?
A: 系统会尽量从PDF中提取完整的课程名称但复杂的表格或特殊格式可能影响识别效果。您可以手动编辑课程名称后重新搜索。
### Q: 为什么有些明显相关的商品没有匹配到?
A: 可能是因为商品标题、描述或关键词中没有与课程名称形成两字符匹配。您可以尝试调整课程名称或使用更精确的关键词搜索。
### Q: PDF文件大小有限制吗
A: 为保证性能建议上传不超过20MB的PDF文件页数不超过50页。
## 故障排除
1. **文件选择失败**:请检查存储权限是否已授予
2. **识别超时**请检查网络连接并重试大型PDF文件可能需要更长时间
3. **匹配结果为空**:尝试简化课程名称或确认平台上是否有相关商品
如遇到其他问题,请联系应用客服获取支持。

@ -1,28 +1,134 @@
apply plugin: 'com.android.application'
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android' // Kotlin
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
namespace "com.startsmake.llrisetabbardemo"
compileSdk 33
// -
signingConfigs {
release {
// 便
//
// 使APK
// storeFile file("your_keystore.jks")
// storePassword "your_keystore_password"
// keyAlias "your_key_alias"
// keyPassword "your_key_password"
}
}
defaultConfig {
applicationId "com.startsmake.llrisetabbardemo"
minSdkVersion 14
targetSdkVersion 23
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
namespace "com.startsmake.llrisetabbardemo"
// PDFBox
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 便APK
//
// signingConfig signingConfigs.release
}
}
// lintOptions
lintOptions {
checkReleaseBuilds false
// 使lint
// abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.12'
implementation 'com.android.support:appcompat-v7:23.1.0'
testImplementation 'junit:junit:4.13.2'
// AIOCR SDK
implementation 'com.baidu.aip:java-sdk:4.16.14'
// PDFBox
implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
// AndroidX
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
// ListenableFuture
configurations.all {
resolutionStrategy {
// 使listenablefuture:1.0
force 'com.google.guava:listenablefuture:1.0'
}
// guavalistenablefuture
exclude group: 'com.google.guava', module: 'guava'
}
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// Navigation
implementation 'androidx.navigation:navigation-fragment:2.6.0'
implementation 'androidx.navigation:navigation-ui:2.6.0'
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.20"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
//gson
implementation 'com.google.code.gson:gson:2.10.1'
// Google ML Kit Text Recognition
implementation 'com.google.android.gms:play-services-vision:20.1.3'
// OpenCVOpenCV
// implementation 'org.opencv:opencv-android:4.8.0'
//
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
// WebSocket
implementation 'org.java-websocket:Java-WebSocket:1.5.3'
implementation 'io.socket:socket.io-client:2.1.0'
// 访socket
testImplementation 'org.java-websocket:Java-WebSocket:1.5.3'
testImplementation 'io.socket:socket.io-client:2.1.0'
//
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'androidx.test.ext:junit:1.1.3'
testImplementation 'org.robolectric:robolectric:4.8.1'
//
implementation project(':mainnavigatetabbar')
}
//
configurations {
all {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
}
}

@ -1,19 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 网络相关权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 存储相关权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 媒体权限用于Android 13及以上版本 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- 相机相关权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- 通知相关权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config">
<category android:name="android.intent.category.LAUNCHER"/>
<!-- 主要活动 -->
<activity
android:name=".activity.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 认证相关活动 -->
<activity
android:name=".activity.LoginActivity"
android:exported="false" />
<activity
android:name=".activity.RegisterActivity"
android:exported="false" />
<activity
android:name=".activity.ForgotPasswordActivity"
android:exported="false" />
<!-- 聊天相关活动 -->
<activity
android:name=".activity.ChatActivity"
android:exported="false" />
<!-- 搜索相关活动 -->
<activity
android:name=".activity.SearchActivity"
android:exported="false" />
<activity
android:name=".activity.SearchResultsActivity"
android:exported="false" />
<!-- 详情页面相关活动 -->
<activity
android:name=".activity.ShopDetailActivity"
android:exported="false" />
<activity
android:name=".activity.StallDetailActivity"
android:exported="false" />
<!-- 添加FileProvider支持用于摄像头功能 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- 图片预览页面 -->
<activity
android:name=".activity.ImagePreviewActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.NoActionBar" />
</application>
</manifest>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

@ -0,0 +1,76 @@
package adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Product;
import java.util.List;
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ViewHolder> {
private Context context;
private List<Product> itemList;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(Product item);
}
public ProductAdapter(Context context, List<Product> itemList, OnItemClickListener listener) {
this.context = context;
this.itemList = itemList;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_product, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Product item = itemList.get(position);
holder.tvTitle.setText(item.getName());
holder.tvDescription.setText(item.getDescription());
holder.tvPrice.setText(String.format("¥%.2f", item.getPrice()));
holder.tvCategory.setText(item.getCategory());
// 设置点击事件
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(item);
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvTitle;
TextView tvDescription;
TextView tvCategory;
TextView tvPrice;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.product_name);
tvDescription = itemView.findViewById(R.id.product_description);
tvCategory = itemView.findViewById(R.id.product_category);
tvPrice = itemView.findViewById(R.id.product_price);
}
}
}

@ -0,0 +1,126 @@
package com.example.llrisetabbardemo.models;
import com.example.llrisetabbardemo.utils.PriceEstimator;
public class ProductRecognitionInfo {
private String productName; // 商品名称
private String category; // 商品类别
private String description; // 商品描述
private PriceEstimator.EstimatedPrice estimatedPrice; // 预估价格信息
private float recognitionConfidence; // 识别可信度
private String imagePath; // 用于识别的图片路径
// 默认构造函数
public ProductRecognitionInfo() {
}
// 完整构造函数
public ProductRecognitionInfo(String productName, String category,
String description, float recognitionConfidence,
String imagePath) {
this.productName = productName;
this.category = category;
this.description = description;
this.recognitionConfidence = recognitionConfidence;
this.imagePath = imagePath;
}
// Getters and Setters
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public PriceEstimator.EstimatedPrice getEstimatedPrice() {
return estimatedPrice;
}
public void setEstimatedPrice(PriceEstimator.EstimatedPrice estimatedPrice) {
this.estimatedPrice = estimatedPrice;
}
public float getRecognitionConfidence() {
return recognitionConfidence;
}
public void setRecognitionConfidence(float recognitionConfidence) {
this.recognitionConfidence = recognitionConfidence;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
// 获取格式化的价格字符串
public String getFormattedPrice() {
if (estimatedPrice != null) {
return "¥" + String.format("%.2f", estimatedPrice.suggestedPrice);
}
return "--";
}
// 获取价格建议范围字符串
public String getFormattedPriceRange() {
if (estimatedPrice != null) {
return "¥" + String.format("%.2f", estimatedPrice.minSuggestedPrice) +
" - ¥" + String.format("%.2f", estimatedPrice.maxSuggestedPrice);
}
return "--";
}
// 获取格式化的可信度百分比
public String getFormattedConfidence() {
return String.format("%.1f%%", recognitionConfidence * 100);
}
// 检查是否有效(有商品名称和类别)
public boolean isValid() {
return productName != null && !productName.isEmpty() &&
category != null && !category.isEmpty();
}
// 克隆对象
public ProductRecognitionInfo clone() {
ProductRecognitionInfo clone = new ProductRecognitionInfo();
clone.productName = this.productName;
clone.category = this.category;
clone.description = this.description;
clone.recognitionConfidence = this.recognitionConfidence;
clone.imagePath = this.imagePath;
clone.estimatedPrice = this.estimatedPrice;
return clone;
}
@Override
public String toString() {
return "ProductRecognitionInfo{" +
"productName='" + productName + '\'' +
", category='" + category + '\'' +
", confidence=" + recognitionConfidence +
", price=" + (estimatedPrice != null ? estimatedPrice.suggestedPrice : "null") +
'}';
}
}

@ -0,0 +1,283 @@
package com.example.llrisetabbardemo.services;
import android.os.AsyncTask;
import android.util.Log;
import com.example.llrisetabbardemo.utils.ImageUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
public class BaiduImageRecognitionService {
private static final String TAG = "BaiduImageRecognitionService";
// 百度AI应用信息已配置实际的API Key和Secret Key
private static final String API_KEY = "nEtt2r4InNkXlzXUHqV2hJWr";
private static final String SECRET_KEY = "5sn1FwG2tAFj9aYDneLW4zbLzt87FauK";
// API接口地址
private static final String ACCESS_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token";
private static final String IMAGE_RECOGNITION_URL = "https://aip.baidubce.com/rest/2.0/image-classify/v1/realtime_search/product";
private String accessToken;
private OnRecognitionListener listener;
public interface OnRecognitionListener {
void onRecognitionSuccess(RecognitionResult result);
void onRecognitionFailure(String errorMessage);
void onRecognitionStart();
void onRecognitionComplete();
}
public static class RecognitionResult {
public String productName;
public String category;
public float price;
public float confidence;
public RecognitionResult(String productName, String category, float price, float confidence) {
this.productName = productName;
this.category = category;
this.price = price;
this.confidence = confidence;
}
}
public BaiduImageRecognitionService(OnRecognitionListener listener) {
this.listener = listener;
}
/**
*
* @param imagePath
*/
public void recognizeProduct(String imagePath) {
new RecognitionTask().execute(imagePath);
}
private class RecognitionTask extends AsyncTask<String, Void, RecognitionResult> {
private String errorMessage;
@Override
protected void onPreExecute() {
super.onPreExecute();
if (listener != null) {
listener.onRecognitionStart();
}
}
@Override
protected RecognitionResult doInBackground(String... params) {
try {
String imagePath = params[0];
// 1. 获取AccessToken
if (accessToken == null || accessToken.isEmpty()) {
accessToken = getAccessToken();
if (accessToken == null) {
errorMessage = "获取AccessToken失败";
return null;
}
}
// 2. 将图片转换为Base64
String imageBase64 = ImageUtils.imageToBase64(imagePath);
if (imageBase64 == null) {
errorMessage = "图片处理失败";
return null;
}
// 3. 调用商品识别API
String result = callImageRecognitionAPI(imageBase64);
if (result == null) {
errorMessage = "API调用失败";
return null;
}
// 4. 解析返回结果
return parseRecognitionResult(result);
} catch (Exception e) {
e.printStackTrace();
errorMessage = "识别过程中出错: " + e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(RecognitionResult result) {
super.onPostExecute(result);
if (listener != null) {
listener.onRecognitionComplete();
if (result != null) {
listener.onRecognitionSuccess(result);
} else {
listener.onRecognitionFailure(errorMessage);
}
}
}
}
/**
* AIAccessToken
*/
private String getAccessToken() {
try {
String url = ACCESS_TOKEN_URL + "?grant_type=client_credentials&client_id=" +
URLEncoder.encode(API_KEY, "UTF-8") + "&client_secret=" +
URLEncoder.encode(SECRET_KEY, "UTF-8");
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
JSONObject jsonObject = new JSONObject(response.toString());
return jsonObject.getString("access_token");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* API
*/
private String callImageRecognitionAPI(String imageBase64) {
try {
String url = IMAGE_RECOGNITION_URL + "?access_token=" + accessToken;
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 设置请求头
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
con.setDoOutput(true);
// 构建请求体
String postData = "image=" + URLEncoder.encode(imageBase64, "UTF-8") +
"&baike_num=1";
// 发送请求
OutputStream os = con.getOutputStream();
os.write(postData.getBytes("UTF-8"));
os.flush();
os.close();
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
*/
private RecognitionResult parseRecognitionResult(String jsonString) {
String errorMessage = "";
try {
JSONObject jsonObject = new JSONObject(jsonString);
// 检查是否有错误
if (jsonObject.has("error_code")) {
errorMessage = "API错误: " + jsonObject.getString("error_msg");
return null;
}
// 解析结果
String productName = "未知商品";
String category = "其他";
float price = 0.0f;
float confidence = 0.0f;
// 从结果中提取商品信息
if (jsonObject.has("result")) {
JSONObject result = jsonObject.getJSONObject("result");
if (result.has("name")) {
productName = result.getString("name");
}
if (result.has("baike_info")) {
JSONObject baikeInfo = result.getJSONObject("baike_info");
if (baikeInfo.has("description")) {
// 可以从描述中提取更多信息
String description = baikeInfo.getString("description");
// 简单的关键词匹配来提取类别
if (description.contains("手机") || description.contains("智能手机")) {
category = "手机数码";
} else if (description.contains("电脑") || description.contains("笔记本")) {
category = "电脑办公";
} else if (description.contains("服装") || description.contains("衣服")) {
category = "服装鞋包";
} else if (description.contains("食品") || description.contains("零食")) {
category = "食品生鲜";
}
}
}
if (result.has("score")) {
confidence = (float) result.getDouble("score");
}
}
// 这里使用模拟价格,可以后续集成价格预估服务
// 基于商品类别给出一个合理的价格范围
if (category.equals("手机数码")) {
price = 2999.0f; // 模拟手机价格
} else if (category.equals("电脑办公")) {
price = 4999.0f; // 模拟电脑价格
} else if (category.equals("服装鞋包")) {
price = 399.0f; // 模拟服装价格
} else if (category.equals("食品生鲜")) {
price = 59.9f; // 模拟食品价格
} else {
price = 199.0f; // 默认价格
}
return new RecognitionResult(productName, category, price, confidence);
} catch (JSONException e) {
e.printStackTrace();
errorMessage = "解析结果失败";
return null;
}
}
}

@ -0,0 +1,435 @@
package com.example.llrisetabbardemo.services;
import android.os.AsyncTask;
import android.util.Log;
import com.startsmake.llrisetabbardemo.fragment.CourseScheduleImportFragment;
import com.example.llrisetabbardemo.utils.ImageUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class BaiduOCRScheduleService {
private static final String TAG = "BaiduOCRScheduleService";
// 百度AI应用信息使用现有的API Key和Secret Key
private static final String API_KEY = "iQRtDkCyWZ9EP1NHAVcyxHIl";
private static final String SECRET_KEY = "4waKrdNPh1cIQvnaO8FX5NgtW4LBLWeY";
// API接口地址
private static final String ACCESS_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token";
private static final String TABLE_RECOGNITION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/table";
private static final String GENERAL_RECOGNITION_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic";
private String accessToken;
private OnRecognitionListener listener;
private String errorMessage;
public interface OnRecognitionListener {
void onRecognitionSuccess(List<CourseScheduleImportFragment.CourseInfo> courseInfoList);
void onRecognitionFailure(String errorMessage);
void onRecognitionStart();
void onRecognitionComplete();
}
public BaiduOCRScheduleService(OnRecognitionListener listener) {
this.listener = listener;
}
/**
*
* @param imagePath
*/
public void recognizeSchedule(String imagePath) {
new ScheduleRecognitionTask().execute(imagePath);
}
private class ScheduleRecognitionTask extends AsyncTask<String, Void, List<CourseScheduleImportFragment.CourseInfo>> {
private String errorMessage;
@Override
protected void onPreExecute() {
super.onPreExecute();
if (listener != null) {
listener.onRecognitionStart();
}
}
@Override
protected List<CourseScheduleImportFragment.CourseInfo> doInBackground(String... params) {
try {
String imagePath = params[0];
// 1. 获取AccessToken
if (accessToken == null || accessToken.isEmpty()) {
accessToken = getAccessToken();
if (accessToken == null) {
errorMessage = "获取AccessToken失败";
return null;
}
}
// 2. 将图片转换为Base64
String imageBase64 = ImageUtils.imageToBase64(imagePath);
if (imageBase64 == null) {
errorMessage = "图片处理失败";
return null;
}
// 3. 调用表格识别API
String result = callTableRecognitionAPI(imageBase64);
if (result == null) {
// 如果表格识别失败,尝试使用通用文字识别
result = callGeneralRecognitionAPI(imageBase64);
if (result == null) {
errorMessage = "API调用失败";
return null;
}
// 处理通用文字识别结果
return parseGeneralRecognitionResult(result);
} else {
// 处理表格识别结果
return parseTableRecognitionResult(result);
}
} catch (Exception e) {
e.printStackTrace();
errorMessage = "识别过程中出错: " + e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(List<CourseScheduleImportFragment.CourseInfo> result) {
super.onPostExecute(result);
if (listener != null) {
listener.onRecognitionComplete();
if (result != null && !result.isEmpty()) {
listener.onRecognitionSuccess(result);
} else {
listener.onRecognitionFailure(errorMessage != null ? errorMessage : "未识别到有效课程信息");
}
}
}
}
/**
* AIAccessToken
*/
private String getAccessToken() {
try {
String url = ACCESS_TOKEN_URL + "?grant_type=client_credentials&client_id=" +
URLEncoder.encode(API_KEY, "UTF-8") + "&client_secret=" +
URLEncoder.encode(SECRET_KEY, "UTF-8");
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
JSONObject jsonObject = new JSONObject(response.toString());
return jsonObject.getString("access_token");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* API
*/
private String callTableRecognitionAPI(String imageBase64) {
try {
String url = TABLE_RECOGNITION_URL + "?access_token=" + accessToken;
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 设置请求头
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setConnectTimeout(15000);
con.setReadTimeout(15000);
con.setDoOutput(true);
// 构建请求体
String postData = "image=" + URLEncoder.encode(imageBase64, "UTF-8") +
"&language_type=CHN_ENG" +
"&output_type=json";
// 发送请求
OutputStream os = con.getOutputStream();
os.write(postData.getBytes("UTF-8"));
os.flush();
os.close();
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* API
*/
private String callGeneralRecognitionAPI(String imageBase64) {
try {
String url = GENERAL_RECOGNITION_URL + "?access_token=" + accessToken;
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 设置请求头
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
con.setDoOutput(true);
// 构建请求体
String postData = "image=" + URLEncoder.encode(imageBase64, "UTF-8") +
"&language_type=CHN_ENG";
// 发送请求
OutputStream os = con.getOutputStream();
os.write(postData.getBytes("UTF-8"));
os.flush();
os.close();
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return response.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
*/
private List<CourseScheduleImportFragment.CourseInfo> parseTableRecognitionResult(String jsonString) {
List<CourseScheduleImportFragment.CourseInfo> courseInfoList = new ArrayList<>();
try {
JSONObject jsonObject = new JSONObject(jsonString);
// 检查是否有错误
if (jsonObject.has("error_code")) {
errorMessage = "API错误: " + jsonObject.getString("error_msg");
return null;
}
// 提取表格数据
if (jsonObject.has("tables_result")) {
JSONArray tables = jsonObject.getJSONArray("tables_result");
// 处理每个表格
for (int i = 0; i < tables.length(); i++) {
JSONObject table = tables.getJSONObject(i);
String html = table.getString("html");
// 解析HTML表格数据
parseHtmlTable(html, courseInfoList);
}
}
} catch (JSONException e) {
e.printStackTrace();
errorMessage = "解析表格结果失败";
return null;
}
return courseInfoList;
}
/**
*
*/
private List<CourseScheduleImportFragment.CourseInfo> parseGeneralRecognitionResult(String jsonString) {
List<CourseScheduleImportFragment.CourseInfo> courseInfoList = new ArrayList<>();
try {
JSONObject jsonObject = new JSONObject(jsonString);
// 检查是否有错误
if (jsonObject.has("error_code")) {
errorMessage = "API错误: " + jsonObject.getString("error_msg");
return null;
}
// 提取识别到的文字
if (jsonObject.has("words_result")) {
JSONArray wordsResult = jsonObject.getJSONArray("words_result");
// 简单的课程信息提取逻辑
StringBuilder allText = new StringBuilder();
for (int i = 0; i < wordsResult.length(); i++) {
JSONObject word = wordsResult.getJSONObject(i);
allText.append(word.getString("words")).append("\n");
}
// 从文本中提取课程信息(简单规则匹配)
extractCoursesFromText(allText.toString(), courseInfoList);
}
} catch (JSONException e) {
e.printStackTrace();
errorMessage = "解析通用识别结果失败";
return null;
}
return courseInfoList;
}
/**
* HTML
*/
private void parseHtmlTable(String html, List<CourseScheduleImportFragment.CourseInfo> courseInfoList) {
// 简单的HTML表格解析逻辑
// 这里只是一个示例实际解析需要更复杂的HTML解析器
try {
// 提取tr标签
String[] rows = html.split("<tr>");
for (String row : rows) {
if (row.contains("<td>")) {
// 提取td标签内容
String[] cells = row.split("<td>");
if (cells.length >= 3) {
// 假设格式:课程名称、教师、地点、时间等
String courseName = extractTextFromTag(cells[1]);
String teacher = extractTextFromTag(cells[2]);
String location = cells.length > 3 ? extractTextFromTag(cells[3]) : "";
// 创建课程信息对象
if (!courseName.isEmpty()) {
// 使用正确的构造函数参数
String weeks = "全周"; // 默认值
CourseScheduleImportFragment.CourseInfo courseInfo = new CourseScheduleImportFragment.CourseInfo(
courseName, teacher, location, 1, 1, 2, weeks);
courseInfoList.add(courseInfo);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "解析HTML表格失败", e);
}
}
/**
*
*/
private String extractTextFromTag(String tagContent) {
// 移除HTML标签
return tagContent.replaceAll("<[^>]*>", "").trim();
}
/**
*
*/
private void extractCoursesFromText(String text, List<CourseScheduleImportFragment.CourseInfo> courseInfoList) {
// 简单的课程信息提取规则
String[] lines = text.split("\n");
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
if (!line.isEmpty()) {
// 尝试识别课程名称
if (isPotentialCourseName(line)) {
// 尝试从下一行提取教师信息
String teacher = "未知";
if (i + 1 < lines.length) {
String nextLine = lines[i + 1].trim();
if (nextLine.contains("老师") || nextLine.contains("教授") || nextLine.contains("讲师")) {
teacher = nextLine;
}
}
// 使用正确的构造函数参数
String location = "";
String weeks = "全周"; // 默认值
CourseScheduleImportFragment.CourseInfo courseInfo = new CourseScheduleImportFragment.CourseInfo(
line, teacher, location, 1, 1, 2, weeks);
courseInfoList.add(courseInfo);
}
}
}
}
/**
*
*/
private boolean isPotentialCourseName(String text) {
// 简单的判断规则
if (text.length() < 2) return false;
// 常见课程关键词
String[] courseKeywords = {
"数学", "物理", "化学", "生物", "语文", "英语", "历史", "地理", "政治",
"计算机", "编程", "算法", "数据结构", "数据库", "网络", "操作系统",
"体育", "音乐", "美术", "哲学", "经济", "管理", "心理", "教育"
};
for (String keyword : courseKeywords) {
if (text.contains(keyword)) {
return true;
}
}
return false;
}
}

@ -0,0 +1,189 @@
package com.example.llrisetabbardemo.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.os.Environment;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ImageUtils {
// 最大图片大小 (1MB)
private static final int MAX_IMAGE_SIZE = 1024 * 1024;
// 图片压缩质量
private static final int COMPRESS_QUALITY = 80;
/**
*
* @param imagePath
* @return
*/
public static String compressImage(String imagePath) {
try {
// 获取图片旋转角度
int rotateAngle = getImageRotateAngle(imagePath);
// 解码图片,获取原始尺寸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
// 计算采样率,使图片尺寸合理
int inSampleSize = calculateInSampleSize(options, 1024, 1024);
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
// 解码并旋转图片
Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);
if (rotateAngle != 0) {
bitmap = rotateBitmap(bitmap, rotateAngle);
}
// 压缩图片到指定大小
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int quality = COMPRESS_QUALITY;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// 如果压缩后的图片还是太大,继续压缩
while (baos.toByteArray().length > MAX_IMAGE_SIZE && quality > 10) {
baos.reset();
quality -= 10;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
}
// 保存压缩后的图片
String compressedPath = createCompressedImagePath();
FileOutputStream fos = new FileOutputStream(compressedPath);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
bitmap.recycle();
return compressedPath;
} catch (Exception e) {
e.printStackTrace();
return imagePath; // 如果压缩失败,返回原始路径
}
}
/**
* Base64
* @param imagePath
* @return Base64
*/
public static String imageToBase64(String imagePath) {
try {
// 先压缩图片
String compressedPath = compressImage(imagePath);
// 读取压缩后的图片
Bitmap bitmap = BitmapFactory.decodeFile(compressedPath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, COMPRESS_QUALITY, baos);
byte[] imageBytes = baos.toByteArray();
bitmap.recycle();
// 转换为Base64
return Base64.encodeToString(imageBytes, Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
*/
private static int getImageRotateAngle(String imagePath) {
try {
ExifInterface exif = new ExifInterface(imagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
}
} catch (IOException e) {
e.printStackTrace();
return 0;
}
}
/**
*
*/
private static Bitmap rotateBitmap(Bitmap bitmap, int degrees) {
if (degrees != 0 && bitmap != null) {
Matrix matrix = new Matrix();
matrix.setRotate(degrees, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
try {
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return rotatedBitmap;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return bitmap;
}
}
return bitmap;
}
/**
*
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
*
*/
private static String createCompressedImagePath() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "COMPRESSED_" + timeStamp + ".jpg";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
storageDir.mkdirs();
}
File imageFile = new File(storageDir, imageFileName);
if (!imageFile.exists()) {
imageFile.createNewFile();
}
return imageFile.getAbsolutePath();
}
}

@ -0,0 +1,221 @@
package com.example.llrisetabbardemo.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import androidx.core.content.FileProvider;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class MediaPickerHelper {
public static final int REQUEST_IMAGE_CAPTURE = 1;
public static final int REQUEST_VIDEO_CAPTURE = 2;
public static final int REQUEST_PICK_IMAGE = 3;
public static final int REQUEST_PICK_VIDEO = 4;
private static String currentImagePath = null;
private static String currentVideoPath = null;
/**
*
* @param activity Activity
* @return
*/
public static boolean dispatchTakePictureIntent(Activity activity) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
if (photoFile != null) {
Uri photoURI;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
photoURI = FileProvider.getUriForFile(activity,
activity.getPackageName() + ".fileprovider",
photoFile);
// 授予临时权限
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(
takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, photoURI,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
photoURI = Uri.fromFile(photoFile);
}
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
activity.startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
return true;
}
}
return false;
}
/**
*
* @param activity Activity
* @return
*/
public static boolean dispatchTakeVideoIntent(Activity activity) {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(activity.getPackageManager()) != null) {
File videoFile = null;
try {
videoFile = createVideoFile();
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
if (videoFile != null) {
Uri videoURI;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
videoURI = FileProvider.getUriForFile(activity,
activity.getPackageName() + ".fileprovider",
videoFile);
// 授予临时权限
List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(
takeVideoIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
activity.grantUriPermission(packageName, videoURI,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
videoURI = Uri.fromFile(videoFile);
}
// 设置视频质量
takeVideoIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
// 设置最大时长10秒
takeVideoIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoURI);
activity.startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
return true;
}
}
return false;
}
/**
*
* @param activity Activity
*/
public static void dispatchPickImageIntent(Activity activity) {
Intent pickImageIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickImageIntent.setType("image/*");
activity.startActivityForResult(Intent.createChooser(pickImageIntent, "选择图片"), REQUEST_PICK_IMAGE);
}
/**
*
* @param activity Activity
*/
public static void dispatchPickVideoIntent(Activity activity) {
Intent pickVideoIntent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
pickVideoIntent.setType("video/*");
// 移除可能导致编译错误的常量
// pickVideoIntent.putExtra(Intent.EXTRA_SIZE_LIMIT, 5 * 1024 * 1024L);
activity.startActivityForResult(Intent.createChooser(pickVideoIntent, "选择视频"), REQUEST_PICK_VIDEO);
}
/**
*
*/
private static File createImageFile() throws Exception {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
storageDir.mkdirs();
}
File image = File.createTempFile(
imageFileName, /* 前缀 */
".jpg", /* 后缀 */
storageDir /* 目录 */
);
currentImagePath = image.getAbsolutePath();
return image;
}
/**
*
*/
private static File createVideoFile() throws Exception {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String videoFileName = "VIDEO_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
if (!storageDir.exists()) {
storageDir.mkdirs();
}
File video = File.createTempFile(
videoFileName, /* 前缀 */
".mp4", /* 后缀 */
storageDir /* 目录 */
);
currentVideoPath = video.getAbsolutePath();
return video;
}
/**
*
*/
public static String getCurrentImagePath() {
return currentImagePath;
}
/**
*
*/
public static String getCurrentVideoPath() {
return currentVideoPath;
}
/**
* URI
* @param context
* @param uri URI
* @return
*/
public static String getPathFromUri(Context context, Uri uri) {
String filePath = null;
try {
String[] projection = {MediaStore.Images.Media.DATA};
android.database.Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
filePath = cursor.getString(columnIndex);
cursor.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return filePath;
}
}

@ -0,0 +1,274 @@
package com.example.llrisetabbardemo.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class PriceEstimator {
private static final String TAG = "PriceEstimator";
private Context context;
private Map<String, PriceRange> categoryPriceRanges;
private Map<String, Float> brandMultipliers;
// 价格范围类
private static class PriceRange {
public float minPrice;
public float maxPrice;
public float averagePrice;
public PriceRange(float minPrice, float maxPrice, float averagePrice) {
this.minPrice = minPrice;
this.maxPrice = maxPrice;
this.averagePrice = averagePrice;
}
}
public PriceEstimator(Context context) {
this.context = context;
initCategoryPriceRanges();
initBrandMultipliers();
}
/**
*
*/
private void initCategoryPriceRanges() {
categoryPriceRanges = new HashMap<>();
// 文具类别 - 添加专门的类别以解决价格识别过高问题
categoryPriceRanges.put("文具", new PriceRange(0.5f, 50f, 5f));
categoryPriceRanges.put("笔类", new PriceRange(0.5f, 20f, 2f));
categoryPriceRanges.put("圆珠笔", new PriceRange(0.5f, 10f, 1.5f));
// 电子产品类别
categoryPriceRanges.put("手机数码", new PriceRange(999f, 9999f, 3999f));
categoryPriceRanges.put("电脑办公", new PriceRange(2999f, 19999f, 6999f));
categoryPriceRanges.put("家用电器", new PriceRange(499f, 14999f, 2999f));
categoryPriceRanges.put("智能设备", new PriceRange(199f, 4999f, 1299f));
// 服装鞋包类别
categoryPriceRanges.put("服装鞋包", new PriceRange(99f, 2999f, 499f));
categoryPriceRanges.put("男装", new PriceRange(149f, 1999f, 599f));
categoryPriceRanges.put("女装", new PriceRange(99f, 2999f, 499f));
categoryPriceRanges.put("鞋靴", new PriceRange(199f, 3999f, 799f));
categoryPriceRanges.put("箱包", new PriceRange(299f, 5999f, 999f));
// 美妆个护类别
categoryPriceRanges.put("美妆个护", new PriceRange(59f, 1999f, 299f));
categoryPriceRanges.put("护肤品", new PriceRange(99f, 2999f, 399f));
categoryPriceRanges.put("彩妆", new PriceRange(59f, 1499f, 249f));
// 食品生鲜类别
categoryPriceRanges.put("食品生鲜", new PriceRange(9.9f, 299f, 59.9f));
categoryPriceRanges.put("零食", new PriceRange(19.9f, 199f, 49.9f));
categoryPriceRanges.put("生鲜", new PriceRange(9.9f, 299f, 69.9f));
// 家居家装类别
categoryPriceRanges.put("家居家装", new PriceRange(29.9f, 4999f, 399f));
categoryPriceRanges.put("家具", new PriceRange(499f, 9999f, 1999f));
categoryPriceRanges.put("家居日用品", new PriceRange(29.9f, 499f, 129f));
// 运动户外类别
categoryPriceRanges.put("运动户外", new PriceRange(149f, 3999f, 699f));
categoryPriceRanges.put("运动服装", new PriceRange(199f, 1999f, 599f));
categoryPriceRanges.put("运动器材", new PriceRange(299f, 5999f, 999f));
// 母婴玩具类别
categoryPriceRanges.put("母婴玩具", new PriceRange(49.9f, 1999f, 299f));
categoryPriceRanges.put("母婴用品", new PriceRange(99f, 1499f, 349f));
categoryPriceRanges.put("玩具", new PriceRange(49.9f, 999f, 249f));
// 图书音像类别
categoryPriceRanges.put("图书音像", new PriceRange(19.9f, 399f, 79.9f));
// 其他类别
categoryPriceRanges.put("其他", new PriceRange(1f, 500f, 50f));
categoryPriceRanges.put("其他商品", new PriceRange(1f, 500f, 50f)); // 兼容前端使用的类别名称
}
/**
*
*/
private void initBrandMultipliers() {
brandMultipliers = new HashMap<>();
// 电子品牌
brandMultipliers.put("苹果", 1.8f);
brandMultipliers.put("华为", 1.5f);
brandMultipliers.put("小米", 1.2f);
brandMultipliers.put("三星", 1.4f);
brandMultipliers.put("OPPO", 1.1f);
brandMultipliers.put("vivo", 1.1f);
// 电脑品牌
brandMultipliers.put("联想", 1.3f);
brandMultipliers.put("戴尔", 1.4f);
brandMultipliers.put("惠普", 1.3f);
brandMultipliers.put("华硕", 1.2f);
// 服装品牌
brandMultipliers.put("耐克", 1.7f);
brandMultipliers.put("阿迪达斯", 1.6f);
brandMultipliers.put("优衣库", 1.2f);
brandMultipliers.put("ZARA", 1.4f);
// 美妆品牌
brandMultipliers.put("兰蔻", 2.2f);
brandMultipliers.put("雅诗兰黛", 2.3f);
brandMultipliers.put("SK-II", 2.5f);
brandMultipliers.put("资生堂", 1.8f);
}
/**
*
* @param productName
* @param category
* @param confidence
* @return
*/
public EstimatedPrice estimatePrice(String productName, String category, float confidence) {
// 获取类别价格范围
PriceRange priceRange = categoryPriceRanges.get(category);
if (priceRange == null) {
// 如果没有找到匹配的类别,使用默认类别
priceRange = categoryPriceRanges.get("其他");
}
// 提取品牌信息
String brand = extractBrand(productName);
// 计算品牌溢价倍数
float brandMultiplier = getBrandMultiplier(brand);
// 根据可信度调整基础价格
float basePrice = calculateBasePrice(priceRange, confidence);
// 应用品牌溢价
float finalPrice = basePrice * brandMultiplier;
// 添加一些随机性,使价格更自然
finalPrice = applyRandomVariation(finalPrice, 0.1f); // 10%的随机浮动
// 确保价格在合理范围内
finalPrice = Math.max(priceRange.minPrice, Math.min(priceRange.maxPrice, finalPrice));
// 四舍五入到合适的精度
finalPrice = Math.round(finalPrice * 10) / 10.0f;
// 生成价格建议范围
float minSuggestPrice = Math.max(priceRange.minPrice, finalPrice * 0.7f);
float maxSuggestPrice = Math.min(priceRange.maxPrice, finalPrice * 1.3f);
// 更新历史数据(简单实现)
updateHistoryData(category, finalPrice);
return new EstimatedPrice(finalPrice, minSuggestPrice, maxSuggestPrice, brand);
}
/**
*
*/
private String extractBrand(String productName) {
if (TextUtils.isEmpty(productName)) {
return null;
}
// 遍历品牌列表,检查商品名称是否包含品牌名
for (String brand : brandMultipliers.keySet()) {
if (productName.contains(brand)) {
return brand;
}
}
return null;
}
/**
*
*/
private float getBrandMultiplier(String brand) {
if (brand != null && brandMultipliers.containsKey(brand)) {
return brandMultipliers.get(brand);
}
return 1.0f; // 默认无溢价
}
/**
*
*/
private float calculateBasePrice(PriceRange priceRange, float confidence) {
// 可信度范围转换假设confidence在0-1之间
float normalizedConfidence = Math.max(0f, Math.min(1f, confidence));
// 可信度高时,使用更接近平均值的价格;可信度低时,使用更保守的价格
if (normalizedConfidence > 0.7f) {
// 高可信度:使用类别平均价格
return priceRange.averagePrice;
} else if (normalizedConfidence > 0.4f) {
// 中等可信度:使用类别平均价格和最低价格之间的值
return priceRange.minPrice + (priceRange.averagePrice - priceRange.minPrice) *
((normalizedConfidence - 0.4f) / 0.3f);
} else {
// 低可信度:使用较低的价格,偏向保守
return priceRange.minPrice + (priceRange.averagePrice - priceRange.minPrice) * 0.3f;
}
}
/**
*
*/
private float applyRandomVariation(float price, float variationRate) {
Random random = new Random();
float variation = 1.0f - variationRate + 2 * variationRate * random.nextFloat();
return price * variation;
}
/**
*
*/
private void updateHistoryData(String category, float price) {
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
// 记录该类别最近的预估价格
String key = "last_estimated_price_" + category;
editor.putFloat(key, price);
editor.apply();
} catch (Exception e) {
Log.e(TAG, "更新历史数据失败: " + e.getMessage());
}
}
/**
*
*/
public static class EstimatedPrice {
public float suggestedPrice; // 建议价格
public float minSuggestedPrice; // 最低建议价格
public float maxSuggestedPrice; // 最高建议价格
public String detectedBrand; // 检测到的品牌可能为null
public EstimatedPrice(float suggestedPrice, float minSuggestedPrice,
float maxSuggestedPrice, String detectedBrand) {
this.suggestedPrice = suggestedPrice;
this.minSuggestedPrice = minSuggestedPrice;
this.maxSuggestedPrice = maxSuggestedPrice;
this.detectedBrand = detectedBrand;
}
@Override
public String toString() {
return "建议价格: " + suggestedPrice +
" (范围: " + minSuggestedPrice + "-" + maxSuggestedPrice + ")" +
(detectedBrand != null ? " 品牌: " + detectedBrand : "");
}
}
}

@ -0,0 +1,111 @@
package com.example.llrisetabbardemo.utils;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class VideoUtils {
/**
*
* @param videoPath
* @return
*/
public static String extractKeyFrame(String videoPath) {
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(videoPath);
// 获取视频时长
String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
long duration = durationStr != null ? Long.parseLong(durationStr) : 0;
// 提取视频中间部分的帧作为关键帧
long frameTime = duration / 2;
Bitmap keyFrame = retriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
retriever.release();
if (keyFrame != null) {
// 保存关键帧为图片
String framePath = saveBitmapToFile(keyFrame);
keyFrame.recycle();
return framePath;
}
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Bitmap
* @param bitmap
* @return
*/
private static String saveBitmapToFile(Bitmap bitmap) {
try {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "VIDEO_FRAME_" + timeStamp + ".jpg";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
storageDir.mkdirs();
}
File imageFile = new File(storageDir, imageFileName);
FileOutputStream fos = new FileOutputStream(imageFile);
// 压缩并保存图片
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
fos.close();
return imageFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* @param filePath
* @return
*/
public static boolean isVideoFile(String filePath) {
if (filePath == null) {
return false;
}
String extension = getFileExtension(filePath).toLowerCase();
return extension.equals("mp4") ||
extension.equals("3gp") ||
extension.equals("mov") ||
extension.equals("avi") ||
extension.equals("mkv");
}
/**
*
* @param filePath
* @return
*/
private static String getFileExtension(String filePath) {
int lastDotIndex = filePath.lastIndexOf(".");
if (lastDotIndex > 0 && lastDotIndex < filePath.length() - 1) {
return filePath.substring(lastDotIndex + 1);
}
return "";
}
}

@ -0,0 +1,158 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.adapter.AddressAdapter;
import com.startsmake.llrisetabbardemo.model.Address;
import com.startsmake.llrisetabbardemo.service.AddressService;
import java.util.List;
public class AddressListActivity extends AppCompatActivity {
private RecyclerView rvAddresses;
private Button btnAddAddress;
private AddressAdapter addressAdapter;
private AddressService addressService;
private boolean isFromOrder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_address_list);
// 检查是否来自订单页面
isFromOrder = getIntent().getBooleanExtra("from_order", false);
initViews();
initServices();
loadAddresses();
}
private void initViews() {
rvAddresses = findViewById(R.id.rv_addresses);
btnAddAddress = findViewById(R.id.btn_add_address);
// 设置RecyclerView
rvAddresses.setLayoutManager(new LinearLayoutManager(this));
addressAdapter = new AddressAdapter(this, null, new AddressAdapter.OnAddressClickListener() {
@Override
public void onSelect(Address address) {
// 如果从订单页面跳转过来,选择地址后返回
if (isFromOrder) {
Intent resultIntent = new Intent();
resultIntent.putExtra("selected_address", address);
setResult(RESULT_OK, resultIntent);
finish();
}
}
@Override
public void onEdit(Address address) {
// 编辑地址
Intent intent = new Intent(AddressListActivity.this, EditAddressActivity.class);
intent.putExtra("address", address);
startActivityForResult(intent, 100);
}
@Override
public void onDelete(Address address) {
// 删除地址
deleteAddress(address);
}
@Override
public void onSetDefault(Address address) {
// 设置默认地址
setDefaultAddress(address);
}
});
rvAddresses.setAdapter(addressAdapter);
// 添加新地址按钮
btnAddAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(AddressListActivity.this, EditAddressActivity.class);
startActivityForResult(intent, 100);
}
});
// 返回按钮
findViewById(R.id.tv_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
}
private void initServices() {
addressService = AddressService.getInstance(this);
}
private void loadAddresses() {
addressService.getUserAddresses(new AddressService.AddressCallback() {
@Override
public void onSuccess(List<Address> addresses) {
addressAdapter.setAddresses(addresses);
addressAdapter.notifyDataSetChanged();
}
@Override
public void onError(String message) {
Toast.makeText(AddressListActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
private void deleteAddress(final Address address) {
addressService.deleteAddress(address.getId(), new AddressService.OperationCallback() {
@Override
public void onSuccess() {
Toast.makeText(AddressListActivity.this, "地址删除成功", Toast.LENGTH_SHORT).show();
// 重新加载地址列表
loadAddresses();
}
@Override
public void onError(String message) {
Toast.makeText(AddressListActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
private void setDefaultAddress(final Address address) {
addressService.setDefaultAddress(address.getId(), new AddressService.OperationCallback() {
@Override
public void onSuccess() {
Toast.makeText(AddressListActivity.this, "设置默认地址成功", Toast.LENGTH_SHORT).show();
// 重新加载地址列表
loadAddresses();
}
@Override
public void onError(String message) {
Toast.makeText(AddressListActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100 && resultCode == RESULT_OK) {
// 地址添加或编辑成功,重新加载列表
loadAddresses();
}
}
}

@ -0,0 +1,356 @@
package com.startsmake.llrisetabbardemo.activity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.adapter.ConversationAdapter;
import com.startsmake.llrisetabbardemo.api.ApiService;
import com.startsmake.llrisetabbardemo.api.response.BaseResponse;
import com.startsmake.llrisetabbardemo.api.response.UserResponse;
import com.startsmake.llrisetabbardemo.model.Conversation;
import com.startsmake.llrisetabbardemo.model.User;
import com.startsmake.llrisetabbardemo.api.ApiClient;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import com.startsmake.llrisetabbardemo.manager.WebSocketManager;
import io.socket.emitter.Emitter;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Handler;
import android.os.Looper;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.content.Intent;
import android.text.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ConversationActivity extends AppCompatActivity {
private static final String TAG = "ConversationActivity";
private ImageView ivBack;
private TextView tvTitle;
private RecyclerView rvConversations;
private ConversationAdapter conversationAdapter;
private List<Conversation> conversationList;
private ApiService apiService;
private UserManager userManager;
private WebSocketManager socketManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conversation);
initView();
initData();
setupClickListeners();
// 先获取会话列表,确保界面上显示会话项
fetchConversations();
// 然后再设置WebSocket连接
setupWebSocket();
}
private void initView() {
ivBack = findViewById(R.id.ivBack);
tvTitle = findViewById(R.id.tvTitle);
rvConversations = findViewById(R.id.rvConversations);
tvTitle.setText("消息");
}
private void initData() {
conversationList = new ArrayList<>();
conversationAdapter = new ConversationAdapter(this, conversationList);
rvConversations.setLayoutManager(new LinearLayoutManager(this));
rvConversations.setAdapter(conversationAdapter);
apiService = ApiClient.getClient().create(ApiService.class);
userManager = UserManager.getInstance(this);
socketManager = WebSocketManager.getInstance();
}
private void setupClickListeners() {
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
// 添加刷新按钮功能
tvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击标题可以刷新会话列表
fetchConversations();
showToast("会话列表已刷新");
}
});
}
/**
* WebSocket
*/
private void setupWebSocket() {
try {
// 初始化SocketManager
socketManager = WebSocketManager.getInstance();
if (userManager.isLoggedIn()) {
User user = userManager.getCurrentUser();
String userId = user != null ? user.getPhone() : null;
String username = user != null ? user.getUsername() : userId;
if (!TextUtils.isEmpty(userId)) {
Log.d(TAG, "WebSocket连接初始化用户ID: " + userId);
// 连接WebSocket
socketManager.connect(userId, username);
// 注册新消息监听器
socketManager.on("newMessage", onNewMessage);
// 注册未读消息数量更新监听器
socketManager.on("unreadCount", onUnreadCountUpdate);
// 注册新对话通知监听器
socketManager.on("newConversation", onNewConversation);
} else {
Log.e(TAG, "用户ID为空WebSocket连接失败");
showToast("用户信息异常,无法建立聊天连接");
}
} else {
Log.e(TAG, "用户未登录WebSocket连接失败");
showToast("用户未登录,无法建立聊天连接");
}
} catch (Exception e) {
Log.e(TAG, "设置WebSocket失败: " + e.getMessage());
showToast("聊天服务初始化失败,请稍后重试");
}
}
// 使用Looper.getMainLooper()以确保在主线程上创建Handler
private Handler reconnectHandler = new Handler(Looper.getMainLooper());
private int reconnectAttempts = 0;
private static final int MAX_RECONNECT_ATTEMPTS = 5;
private static final int RECONNECT_DELAY_MS = 3000;
private void startReconnectTimer() {
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
Log.e(TAG, "已达到最大重连次数,停止自动重连");
showToast("聊天连接失败,请检查网络连接后重试");
return;
}
reconnectAttempts++;
Log.d(TAG, "计划在" + RECONNECT_DELAY_MS + "ms后进行第" + reconnectAttempts + "次重连");
reconnectHandler.postDelayed(() -> {
if (!isFinishing()) {
Log.d(TAG, "执行第" + reconnectAttempts + "次重连");
setupWebSocket();
}
}, RECONNECT_DELAY_MS);
}
private void showToast(String message) {
if (!isFinishing()) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
private Emitter.Listener onNewMessage = new Emitter.Listener() {
@Override
public void call(Object... args) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
JSONObject data = (JSONObject) args[0];
String senderId = data.getString("senderId");
String content = data.getString("content");
String time = data.getString("time");
// 更新会话列表中的最后一条消息
updateConversation(senderId, content, time);
} catch (JSONException e) {
Log.e(TAG, "解析消息失败: " + e.getMessage());
}
}
});
}
};
/**
*
*/
private Emitter.Listener onUnreadCountUpdate = new Emitter.Listener() {
@Override
public void call(Object... args) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
JSONObject data = (JSONObject) args[0];
String userId = data.getString("userId");
int unreadCount = data.getInt("count");
// 更新会话列表中的未读消息数量
updateUnreadCount(userId, unreadCount);
} catch (JSONException e) {
Log.e(TAG, "解析未读消息数量更新失败: " + e.getMessage());
}
}
});
}
};
/**
*
*/
private Emitter.Listener onNewConversation = new Emitter.Listener() {
@Override
public void call(Object... args) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 刷新会话列表
fetchConversations();
}
});
}
};
/**
*
*/
private void updateConversation(String userId, String content, String time) {
boolean conversationExists = false;
for (Conversation conv : conversationList) {
if (conv.getId().equals(userId)) {
// 更新现有会话
conv.setLastMessage(content);
conv.setTime(time);
conv.setUnreadCount(conv.getUnreadCount() + 1);
conversationExists = true;
break;
}
}
if (!conversationExists) {
// 刷新整个列表以获取新会话
fetchConversations();
} else {
// 按最后消息时间排序并刷新适配器
Collections.sort(conversationList, new Comparator<Conversation>() {
@Override
public int compare(Conversation o1, Conversation o2) {
return o2.getTime().compareTo(o1.getTime());
}
});
conversationAdapter.notifyDataSetChanged();
}
}
/**
*
*/
private void updateUnreadCount(String userId, int count) {
for (Conversation conv : conversationList) {
if (conv.getId().equals(userId)) {
conv.setUnreadCount(count);
conversationAdapter.notifyDataSetChanged();
break;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 取消重连定时器
if (reconnectHandler != null) {
reconnectHandler.removeCallbacksAndMessages(null);
}
// 移除WebSocket监听器
if (socketManager != null) {
try {
socketManager.off("newMessage");
socketManager.off("unreadCount");
socketManager.off("newConversation");
} catch (Exception e) {
Log.e(TAG, "移除WebSocket监听器失败: " + e.getMessage());
}
}
Log.d(TAG, "ConversationActivity销毁WebSocket资源清理完成");
}
private void fetchConversations() {
Log.d(TAG, "开始获取会话列表");
// 无论是否登录,都显示测试账号的会话
conversationList.clear();
if (!userManager.isLoggedIn()) {
Log.d(TAG, "用户未登录,显示测试账号登录提示会话");
// 用户未登录,添加测试账号提示会话
String guideMessage = "请使用测试账号登录: 13800138001/13800138002/13800138003";
conversationList.add(new Conversation("test_account_guide", "测试账号指南", guideMessage, getCurrentTime(), 0));
Toast.makeText(this, "请使用测试账号登录以体验聊天功能", Toast.LENGTH_LONG).show();
} else {
// 用户已登录,添加其他测试账号的会话
User currentUser = userManager.getCurrentUser();
if (currentUser != null) {
Log.d(TAG, "用户已登录,当前账号: " + currentUser.getPhone());
String currentPhone = currentUser.getPhone();
// 添加其他两个测试账号的会话
if (!currentPhone.equals("13800138001")) {
conversationList.add(new Conversation("13800138001", "测试账号1", "开始聊天吧", getCurrentTime(), 0));
Log.d(TAG, "已添加测试账号1会话");
}
if (!currentPhone.equals("13800138002")) {
conversationList.add(new Conversation("13800138002", "测试账号2", "开始聊天吧", getCurrentTime(), 0));
Log.d(TAG, "已添加测试账号2会话");
}
if (!currentPhone.equals("13800138003")) {
conversationList.add(new Conversation("13800138003", "测试账号3", "开始聊天吧", getCurrentTime(), 0));
Log.d(TAG, "已添加测试账号3会话");
}
} else {
Log.e(TAG, "用户已登录但无法获取用户信息");
conversationList.add(new Conversation("error", "登录异常", "请重新登录测试账号", getCurrentTime(), 0));
}
}
conversationAdapter.notifyDataSetChanged();
Log.d(TAG, "会话列表更新完成,共" + conversationList.size() + "个会话");
}
private String getCurrentTime() {
return new SimpleDateFormat("HH:mm").format(new Date());
}
}

@ -0,0 +1,182 @@
package com.startsmake.llrisetabbardemo.activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Address;
import com.startsmake.llrisetabbardemo.service.AddressService;
public class EditAddressActivity extends AppCompatActivity {
private EditText etName;
private EditText etPhone;
private EditText etProvince;
private EditText etCity;
private EditText etDistrict;
private EditText etDetailAddress;
private CheckBox cbDefault;
private Button btnSave;
private Address address;
private AddressService addressService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_address);
initViews();
initServices();
initData();
}
private void initViews() {
etName = findViewById(R.id.et_name);
etPhone = findViewById(R.id.et_phone);
etProvince = findViewById(R.id.et_province);
etCity = findViewById(R.id.et_city);
etDistrict = findViewById(R.id.et_district);
etDetailAddress = findViewById(R.id.et_detail_address);
cbDefault = findViewById(R.id.cb_default);
btnSave = findViewById(R.id.btn_save);
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
saveAddress();
}
});
// 返回按钮
findViewById(R.id.tv_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
}
private void initServices() {
addressService = AddressService.getInstance(this);
}
private void initData() {
// 检查是否是编辑模式
address = getIntent().getParcelableExtra("address");
if (address != null) {
// 编辑模式
etName.setText(address.getName());
etPhone.setText(address.getPhone());
etProvince.setText(address.getProvince());
etCity.setText(address.getCity());
etDistrict.setText(address.getDistrict());
etDetailAddress.setText(address.getDetailAddress());
cbDefault.setChecked(address.isDefault());
} else {
// 新增模式,默认设为默认地址
cbDefault.setChecked(true);
}
}
private void saveAddress() {
// 验证输入
String name = etName.getText().toString().trim();
String phone = etPhone.getText().toString().trim();
String province = etProvince.getText().toString().trim();
String city = etCity.getText().toString().trim();
String district = etDistrict.getText().toString().trim();
String detailAddress = etDetailAddress.getText().toString().trim();
boolean isDefault = cbDefault.isChecked();
if (TextUtils.isEmpty(name)) {
Toast.makeText(this, "请输入收件人姓名", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(phone)) {
Toast.makeText(this, "请输入联系电话", Toast.LENGTH_SHORT).show();
return;
}
// 简单的手机号验证
if (!phone.matches("^1[3-9]\\d{9}$")) {
Toast.makeText(this, "请输入正确的手机号码", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(province)) {
Toast.makeText(this, "请输入省份", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(city)) {
Toast.makeText(this, "请输入城市", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(district)) {
Toast.makeText(this, "请输入区县", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(detailAddress)) {
Toast.makeText(this, "请输入详细地址", Toast.LENGTH_SHORT).show();
return;
}
// 创建或更新地址对象
if (address == null) {
// 新增地址
address = new Address();
}
address.setName(name);
address.setPhone(phone);
address.setProvince(province);
address.setCity(city);
address.setDistrict(district);
address.setDetailAddress(detailAddress);
address.setDefault(isDefault);
// 保存地址
if (TextUtils.isEmpty(address.getId())) {
// 新增
addressService.addAddress(address, new AddressService.SingleAddressCallback() {
@Override
public void onSuccess(Address newAddress) {
Toast.makeText(EditAddressActivity.this, "地址添加成功", Toast.LENGTH_SHORT).show();
setResult(RESULT_OK);
finish();
}
@Override
public void onError(String message) {
Toast.makeText(EditAddressActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
} else {
// 更新
addressService.updateAddress(address, new AddressService.SingleAddressCallback() {
@Override
public void onSuccess(Address updatedAddress) {
Toast.makeText(EditAddressActivity.this, "地址更新成功", Toast.LENGTH_SHORT).show();
setResult(RESULT_OK);
finish();
}
@Override
public void onError(String message) {
Toast.makeText(EditAddressActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
}

@ -0,0 +1,257 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.api.ApiService;
import com.startsmake.llrisetabbardemo.api.response.BaseResponse;
import com.startsmake.llrisetabbardemo.api.response.UserResponse;
import com.startsmake.llrisetabbardemo.api.ApiClient;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import com.startsmake.llrisetabbardemo.model.User;
import java.util.HashMap;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class EditProfileActivity extends AppCompatActivity {
private static final String TAG = "EditProfileActivity";
private ImageView ivBack;
private TextView tvTitle;
private EditText etUsername;
private EditText etBio;
private Button btnSave;
private ApiService apiService;
private UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_profile);
initView();
initData();
setupClickListeners();
loadUserInfo();
}
private void initView() {
ivBack = findViewById(R.id.ivBack);
tvTitle = findViewById(R.id.tvTitle);
etUsername = findViewById(R.id.etUsername);
etBio = findViewById(R.id.etBio);
btnSave = findViewById(R.id.btnSave);
tvTitle.setText("编辑个人资料");
}
private void initData() {
apiService = ApiClient.getClient().create(ApiService.class);
userManager = UserManager.getInstance(this);
}
/**
* UserManager
*/
private String getUserNameFromUserManager() {
User user = userManager.getCurrentUser();
if (user != null) {
return user.getUsername();
}
// 如果获取不到用户对象尝试直接从SharedPreferences读取
return getSharedPreferences("user_data", MODE_PRIVATE)
.getString("user_name", "");
}
/**
* SharedPreferences
*/
private void saveUserToPreferences(User user) {
if (user != null) {
getSharedPreferences("user_data", MODE_PRIVATE)
.edit()
.putString("user_name", user.getUsername())
.apply();
}
}
private void setupClickListeners() {
ivBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
saveUserInfo();
}
});
}
private void loadUserInfo() {
if (!userManager.isLoggedIn()) {
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// 从UserManager获取当前用户信息
String token = userManager.getUserToken();
if (token == null) {
Toast.makeText(this, "登录信息异常", Toast.LENGTH_SHORT).show();
return;
}
// 从API获取最新的用户信息
Call<UserResponse> call = apiService.getUserInfo("Bearer " + token);
call.enqueue(new Callback<UserResponse>() {
@Override
public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
if (response.isSuccessful() && response.body() != null) {
UserResponse.UserInfo userInfo = response.body().getData();
if (userInfo != null) {
if (etUsername != null) etUsername.setText(userInfo.getUsername());
if (etBio != null && userInfo.getBio() != null) {
etBio.setText(userInfo.getBio());
}
// 更新本地用户信息
User currentUser = userManager.getCurrentUser();
if (currentUser != null) {
currentUser.setUsername(userInfo.getUsername());
// 通过UserManager的其他公开方法或直接使用SharedPreferences更新
saveUserToPreferences(currentUser);
}
}
} else {
Log.e(TAG, "加载用户信息失败: " + (response.errorBody() != null ? response.errorBody().toString() : "未知错误"));
// 如果API调用失败尝试使用本地缓存如果有
// 使用用户名和空的bio作为缓存信息
UserResponse.UserInfo cachedInfo = new UserResponse.UserInfo();
cachedInfo.setUsername(getUserNameFromUserManager());
cachedInfo.setBio("");
if (cachedInfo != null) {
if (etUsername != null) etUsername.setText(cachedInfo.getUsername());
if (etBio != null && cachedInfo.getBio() != null) {
etBio.setText(cachedInfo.getBio());
}
}
}
}
@Override
public void onFailure(Call<UserResponse> call, Throwable t) {
Log.e(TAG, "网络错误: " + t.getMessage());
Toast.makeText(EditProfileActivity.this, "加载用户信息失败", Toast.LENGTH_SHORT).show();
// 尝试使用本地缓存
// 使用用户名和空的bio作为缓存信息
UserResponse.UserInfo cachedInfo = new UserResponse.UserInfo();
cachedInfo.setUsername(getUserNameFromUserManager());
cachedInfo.setBio("");
if (cachedInfo != null) {
if (etUsername != null) etUsername.setText(cachedInfo.getUsername());
if (etBio != null && cachedInfo.getBio() != null) {
etBio.setText(cachedInfo.getBio());
}
}
}
});
}
private void saveUserInfo() {
String username = etUsername.getText().toString().trim();
String bio = etBio.getText().toString().trim();
if (TextUtils.isEmpty(username)) {
Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show();
return;
}
if (!userManager.isLoggedIn()) {
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
String token = userManager.getUserToken();
if (token == null) {
Toast.makeText(this, "登录信息异常", Toast.LENGTH_SHORT).show();
return;
}
// 显示加载状态
btnSave.setEnabled(false);
btnSave.setText("保存中...");
// 准备更新数据
Map<String, Object> updateData = new HashMap<>();
updateData.put("username", username);
updateData.put("bio", bio);
// 调用API更新用户信息
Call<BaseResponse<UserResponse.UserInfo>> call = apiService.updateUserInfo("Bearer " + token, updateData);
call.enqueue(new Callback<BaseResponse<UserResponse.UserInfo>>() {
@Override
public void onResponse(Call<BaseResponse<UserResponse.UserInfo>> call, Response<BaseResponse<UserResponse.UserInfo>> response) {
btnSave.setEnabled(true);
btnSave.setText("保存");
if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) {
UserResponse.UserInfo updatedUserInfo = response.body().getData();
if (updatedUserInfo != null) {
// 更新本地用户信息
User currentUser = userManager.getCurrentUser();
if (currentUser != null) {
currentUser.setUsername(username);
saveUserToPreferences(currentUser);
}
Toast.makeText(EditProfileActivity.this, "保存成功", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(EditProfileActivity.this, "更新失败,请重试", Toast.LENGTH_SHORT).show();
}
} else {
String errorMsg = "保存失败,请重试";
try {
// 尝试从错误响应中获取详细信息
if (response.errorBody() != null) {
String errorBody = response.errorBody().string();
Log.e(TAG, "保存失败响应: " + errorBody);
}
} catch (Exception e) {
Log.e(TAG, "解析错误响应失败", e);
}
Toast.makeText(EditProfileActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<BaseResponse<UserResponse.UserInfo>> call, Throwable t) {
btnSave.setEnabled(true);
btnSave.setText("保存");
Log.e(TAG, "网络错误: " + t.getMessage());
Toast.makeText(EditProfileActivity.this, "网络错误,请稍后重试", Toast.LENGTH_SHORT).show();
}
});
}
}

@ -0,0 +1,221 @@
package com.startsmake.llrisetabbardemo.activity;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.RecognitionFeedback;
import com.startsmake.llrisetabbardemo.service.FeedbackService;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* OCR
*/
public class FeedbackEditActivity extends AppCompatActivity {
private static final String TAG = "FeedbackEditActivity";
// Intent参数
public static final String EXTRA_ORIGINAL_TEXT = "original_text";
public static final String EXTRA_IMAGE_PATH = "image_path";
public static final String EXTRA_FEEDBACK_RESULT = "feedback_result";
private EditText etOriginalText;
private EditText etCorrectedText;
private Button btnSubmit;
private ImageView ivOriginalImage;
private Toolbar toolbar;
private String originalText;
private String imagePath;
private Bitmap originalImage;
private FeedbackService feedbackService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feedback_edit);
initView();
initData();
initListener();
}
private void initView() {
toolbar = findViewById(R.id.toolbar);
etOriginalText = findViewById(R.id.et_original_text);
etCorrectedText = findViewById(R.id.et_corrected_text);
btnSubmit = findViewById(R.id.btn_submit_feedback);
ivOriginalImage = findViewById(R.id.iv_original_image);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("校正识别结果");
}
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void initData() {
feedbackService = FeedbackService.getInstance(this);
// 获取传入的数据
Intent intent = getIntent();
if (intent != null) {
originalText = intent.getStringExtra(EXTRA_ORIGINAL_TEXT);
imagePath = intent.getStringExtra(EXTRA_IMAGE_PATH);
}
// 设置原始文本(不可编辑)
etOriginalText.setText(originalText);
etOriginalText.setSelection(etOriginalText.getText().length());
etOriginalText.setFocusable(false);
etOriginalText.setFocusableInTouchMode(false);
// 预填充校正文本
etCorrectedText.setText(originalText);
etCorrectedText.setSelection(etCorrectedText.getText().length());
// 加载原始图像
if (!TextUtils.isEmpty(imagePath)) {
loadImageFromPath(imagePath);
}
}
private void initListener() {
btnSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
submitFeedback();
}
});
}
private void loadImageFromPath(String path) {
try {
File file = new File(path);
if (file.exists()) {
originalImage = BitmapFactory.decodeStream(new FileInputStream(file));
ivOriginalImage.setImageBitmap(originalImage);
} else {
Log.e(TAG, "图像文件不存在: " + path);
Toast.makeText(this, "无法加载原始图像", Toast.LENGTH_SHORT).show();
}
} catch (FileNotFoundException e) {
Log.e(TAG, "加载图像失败: " + e.getMessage());
Toast.makeText(this, "加载图像失败", Toast.LENGTH_SHORT).show();
}
}
private void submitFeedback() {
String correctedText = etCorrectedText.getText().toString().trim();
if (TextUtils.isEmpty(correctedText)) {
Toast.makeText(this, "请输入校正后的文本", Toast.LENGTH_SHORT).show();
return;
}
if (correctedText.equals(originalText)) {
Toast.makeText(this, "校正后的文本与原始文本相同,请修改后再提交", Toast.LENGTH_SHORT).show();
return;
}
// 显示提交中状态
btnSubmit.setEnabled(false);
btnSubmit.setText("提交中...");
// 异步提交反馈
feedbackService.submitFeedbackAsync(originalText, correctedText, originalImage, new FeedbackService.FeedbackCallback() {
@Override
public void onFeedbackSubmitted(final boolean success, final RecognitionFeedback feedback) {
runOnUiThread(new Runnable() {
@Override
public void run() {
btnSubmit.setEnabled(true);
btnSubmit.setText("提交反馈");
if (success) {
Toast.makeText(FeedbackEditActivity.this, "反馈提交成功,感谢您的帮助!", Toast.LENGTH_LONG).show();
// 设置结果并返回,确保包含校正后的文本
Intent resultIntent = new Intent();
resultIntent.putExtra(EXTRA_FEEDBACK_RESULT, feedback);
resultIntent.putExtra("corrected_text", correctedText);
setResult(Activity.RESULT_OK, resultIntent);
// 延迟关闭,让用户看到成功提示
btnSubmit.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 1500);
} else {
Toast.makeText(FeedbackEditActivity.this, "反馈提交失败,请稍后再试", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onFeedbackError(final String errorMessage) {
runOnUiThread(new Runnable() {
@Override
public void run() {
btnSubmit.setEnabled(true);
btnSubmit.setText("提交反馈");
Toast.makeText(FeedbackEditActivity.this, "错误: " + errorMessage, Toast.LENGTH_SHORT).show();
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 释放Bitmap资源
if (originalImage != null && !originalImage.isRecycled()) {
originalImage.recycle();
originalImage = null;
}
}
/**
*
* @param context
* @param originalText
* @param imagePath
* @param requestCode
*/
public static void startFeedbackActivity(Context context, String originalText, String imagePath, int requestCode) {
Intent intent = new Intent(context, FeedbackEditActivity.class);
intent.putExtra(EXTRA_ORIGINAL_TEXT, originalText);
intent.putExtra(EXTRA_IMAGE_PATH, imagePath);
// 由于是从Fragment启动需要使用Activity.startActivityForResult
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
}
}
}

@ -0,0 +1,299 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import java.util.Random;
public class ForgotPasswordActivity extends AppCompatActivity {
private TextInputEditText etPhone, etVerificationCode, etNewPassword;
private Button btnSendCode, btnResetPassword;
private ImageButton btnBack;
private TextView tvLogin;
private CountDownTimer countDownTimer;
private boolean isCounting = false;
private String verificationCode = "";
private UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forgot_password);
// 确保使用带Context参数的getInstance方法
userManager = UserManager.getInstance(this);
initViews();
setupClickListeners();
}
private void initViews() {
try {
etPhone = findViewById(R.id.et_phone);
etVerificationCode = findViewById(R.id.et_verification_code);
etNewPassword = findViewById(R.id.et_new_password);
btnSendCode = findViewById(R.id.btn_send_code);
btnResetPassword = findViewById(R.id.btn_reset_password);
btnBack = findViewById(R.id.btn_back);
tvLogin = findViewById(R.id.tv_login);
} catch (Exception e) {
Log.e("ForgotPassword", "Error initializing views", e);
}
}
private void setupClickListeners() {
// 返回按钮
btnBack.setOnClickListener(v -> finish());
// 发送验证码
btnSendCode.setOnClickListener(v -> sendVerificationCode());
// 重置密码按钮
btnResetPassword.setOnClickListener(v -> attemptResetPassword());
// 返回登录
tvLogin.setOnClickListener(v -> {
Intent intent = new Intent(ForgotPasswordActivity.this, LoginActivity.class);
startActivity(intent);
finish();
});
}
private void sendVerificationCode() {
String phone = etPhone.getText().toString().trim();
if (!validatePhone(phone)) {
return;
}
// 检查用户是否存在
if (!userManager.isPhoneRegistered(phone)) {
etPhone.setError("该手机号未注册");
etPhone.requestFocus();
Toast.makeText(this, "该手机号未注册,请先注册", Toast.LENGTH_SHORT).show();
return;
}
if (!isCounting) {
// 生成随机验证码
verificationCode = generateVerificationCode();
startCountDown();
// 模拟发送验证码(在实际应用中应该通过短信发送)
Toast.makeText(this, "验证码已发送: " + verificationCode, Toast.LENGTH_LONG).show();
}
}
private boolean validatePhone(String phone) {
if (phone.isEmpty()) {
etPhone.setError("请输入手机号");
etPhone.requestFocus();
return false;
}
if (phone.length() != 11) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (!phone.startsWith("1")) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
return true;
}
private String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
private void startCountDown() {
isCounting = true;
btnSendCode.setEnabled(false);
countDownTimer = new CountDownTimer(60000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
btnSendCode.setText(millisUntilFinished / 1000 + "秒后重发");
}
@Override
public void onFinish() {
isCounting = false;
btnSendCode.setEnabled(true);
btnSendCode.setText("发送验证码");
// 验证码过期
verificationCode = "";
}
}.start();
}
private void attemptResetPassword() {
String phone = etPhone.getText().toString().trim();
String code = etVerificationCode.getText().toString().trim();
String newPassword = etNewPassword.getText().toString().trim();
if (!validateResetInput(phone, code, newPassword)) {
return;
}
// 验证验证码
if (!code.equals(verificationCode)) {
etVerificationCode.setError("验证码错误");
etVerificationCode.requestFocus();
Toast.makeText(this, "请输入正确的验证码", Toast.LENGTH_SHORT).show();
return;
}
// 检查验证码是否过期
if (verificationCode.isEmpty()) {
etVerificationCode.setError("验证码已过期,请重新获取");
etVerificationCode.requestFocus();
Toast.makeText(this, "验证码已过期,请重新获取", Toast.LENGTH_SHORT).show();
return;
}
// 重置密码
boolean success = userManager.resetPassword(phone, newPassword);
if (success) {
performResetSuccess(phone);
} else {
Toast.makeText(this, "重置密码失败,请检查手机号是否正确", Toast.LENGTH_SHORT).show();
etPhone.setError("手机号未注册或不存在");
etPhone.requestFocus();
}
}
private boolean validateResetInput(String phone, String code, String newPassword) {
// 验证手机号
if (phone.isEmpty()) {
etPhone.setError("请输入手机号");
etPhone.requestFocus();
return false;
}
if (phone.length() != 11) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (!phone.startsWith("1")) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
// 验证验证码
if (code.isEmpty()) {
etVerificationCode.setError("请输入验证码");
etVerificationCode.requestFocus();
return false;
}
if (code.length() != 6) {
etVerificationCode.setError("验证码格式不正确");
etVerificationCode.requestFocus();
return false;
}
// 验证新密码
if (newPassword.isEmpty()) {
etNewPassword.setError("请输入新密码");
etNewPassword.requestFocus();
return false;
}
if (newPassword.length() < 6) {
etNewPassword.setError("密码至少6位");
etNewPassword.requestFocus();
return false;
}
if (newPassword.length() > 20) {
etNewPassword.setError("密码最多20位");
etNewPassword.requestFocus();
return false;
}
// 检查密码强度(可选增强)
if (!isPasswordStrong(newPassword)) {
etNewPassword.setError("密码过于简单,建议包含字母和数字");
etNewPassword.requestFocus();
return false;
}
return true;
}
private boolean isPasswordStrong(String password) {
// 简单的密码强度检查:至少包含字母和数字
boolean hasLetter = false;
boolean hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isLetter(c)) {
hasLetter = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
}
// 如果已经满足条件,提前返回
if (hasLetter && hasDigit) {
return true;
}
}
return hasLetter && hasDigit;
}
private void performResetSuccess(String phone) {
Toast.makeText(this, "密码重置成功!", Toast.LENGTH_SHORT).show();
// 重置成功后跳转到登录页面,并传递手机号方便用户登录
Intent intent = new Intent(ForgotPasswordActivity.this, LoginActivity.class);
intent.putExtra("phone", phone);
intent.putExtra("from_forgot_password", true);
startActivity(intent);
finish();
}
@Override
protected void onResume() {
super.onResume();
// 如果从Intent中获取到手机号自动填充
Intent intent = getIntent();
if (intent != null && intent.hasExtra("phone")) {
String phone = intent.getStringExtra("phone");
if (phone != null && !phone.isEmpty()) {
etPhone.setText(phone);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (countDownTimer != null) {
countDownTimer.cancel();
}
}
}

@ -0,0 +1,82 @@
package com.startsmake.llrisetabbardemo.activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.adapter.ImagePreviewAdapter;
import java.util.ArrayList;
import java.util.List;
public class ImagePreviewActivity extends AppCompatActivity {
public static final String EXTRA_IMAGE_URLS = "image_urls";
public static final String EXTRA_CURRENT_POSITION = "current_position";
private ViewPager2 viewPager;
private TextView tvPosition;
private ImageView ivClose;
private List<String> imageUrls;
private int currentPosition;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 全屏显示,隐藏状态栏
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
);
setContentView(R.layout.activity_image_preview);
// 获取传递的图片URL列表和当前位置
if (getIntent() != null) {
imageUrls = getIntent().getStringArrayListExtra(EXTRA_IMAGE_URLS);
currentPosition = getIntent().getIntExtra(EXTRA_CURRENT_POSITION, 0);
}
// 初始化视图
viewPager = findViewById(R.id.view_pager_preview);
tvPosition = findViewById(R.id.tv_position);
ivClose = findViewById(R.id.iv_close);
// 设置适配器
ImagePreviewAdapter adapter = new ImagePreviewAdapter(imageUrls);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(currentPosition, false);
// 更新位置显示
updatePositionDisplay(currentPosition);
// 设置ViewPager页面变化监听
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
currentPosition = position;
updatePositionDisplay(position);
}
});
// 设置关闭按钮点击事件
ivClose.setOnClickListener(v -> finish());
}
private void updatePositionDisplay(int position) {
if (imageUrls != null && !imageUrls.isEmpty()) {
tvPosition.setText((position + 1) + "/" + imageUrls.size());
}
}
@Override
public void onBackPressed() {
finish();
}
}

@ -0,0 +1,174 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import com.startsmake.llrisetabbardemo.model.User;
import manager.DataManager;
public class LoginActivity extends AppCompatActivity {
private TextInputEditText etPhone, etPassword;
private Button btnLogin, btnSkipLogin;
private TextView tvForgotPassword, tvRegister;
private UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 初始化用户管理器传入Context参数
userManager = UserManager.getInstance(this);
// 初始化数据管理器并设置Context用于数据持久化
DataManager.getInstance().setContext(this);
// 检查用户是否已登录,如果已登录则直接跳转到主页面
if (userManager.isLoggedIn()) {
navigateToMain();
return;
}
initViews();
setupClickListeners();
// 检查是否有从其他页面传递过来的手机号
handleIntentData();
}
private void initViews() {
etPhone = findViewById(R.id.et_phone);
etPassword = findViewById(R.id.et_password);
btnLogin = findViewById(R.id.btn_login);
btnSkipLogin = findViewById(R.id.btn_skip_login);
tvForgotPassword = findViewById(R.id.tv_forgot_password);
tvRegister = findViewById(R.id.tv_register);
}
private void setupClickListeners() {
// 登录按钮
btnLogin.setOnClickListener(v -> attemptLogin());
// 跳过登录按钮
btnSkipLogin.setOnClickListener(v -> skipToMain());
// 忘记密码
tvForgotPassword.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, ForgotPasswordActivity.class);
startActivity(intent);
});
// 注册
tvRegister.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
});
}
private void handleIntentData() {
Intent intent = getIntent();
if (intent != null && intent.hasExtra("phone")) {
String phone = intent.getStringExtra("phone");
if (phone != null && !phone.isEmpty()) {
etPhone.setText(phone);
// 自动聚焦到密码输入框
etPassword.requestFocus();
}
}
}
private void attemptLogin() {
String phone = etPhone.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (!validateInput(phone, password)) {
return;
}
// 使用 UserManager 进行登录验证
User user = userManager.loginUser(phone, password);
if (user != null) {
// 登录成功
performLoginSuccess(user);
} else {
// 登录失败
showLoginError();
}
}
private boolean validateInput(String phone, String password) {
if (phone.isEmpty()) {
etPhone.setError("请输入手机号");
etPhone.requestFocus();
return false;
}
if (password.isEmpty()) {
etPassword.setError("请输入密码");
etPassword.requestFocus();
return false;
}
if (phone.length() != 11) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (!phone.startsWith("1")) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (password.length() < 6) {
etPassword.setError("密码至少6位");
etPassword.requestFocus();
return false;
}
return true;
}
private void performLoginSuccess(User user) {
// 登录状态和用户信息已经在UserManager.loginUser方法中保存
Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show();
navigateToMain();
}
private void showLoginError() {
Toast.makeText(this, "手机号或密码错误", Toast.LENGTH_SHORT).show();
etPassword.setError("密码错误");
etPassword.requestFocus();
}
private void skipToMain() {
// 执行游客登录
userManager.loginGuest();
Toast.makeText(this, "游客模式进入", Toast.LENGTH_SHORT).show();
navigateToMain();
}
private void navigateToMain() {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 清理资源
}
}

@ -1,53 +1,346 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.RadioButton;
import android.widget.Toast;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import manager.DataManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.fragment.CityFragment;
import com.startsmake.llrisetabbardemo.fragment.HomeFragment;
import com.startsmake.llrisetabbardemo.fragment.MessageFragment;
import com.startsmake.llrisetabbardemo.fragment.PersonFragment;
import com.startsmake.llrisetabbardemo.fragment.PublishFragment;
import com.startsmake.mainnavigatetabbar.widget.MainNavigateTabBar;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG_PAGE_HOME = "首页";
private static final String TAG_PAGE_CITY = "同城";
private static final String TAG_PAGE_CITY = "集市";
private static final String TAG_PAGE_PUBLISH = "发布";
private static final String TAG_PAGE_MESSAGE = "消息";
private static final String TAG_PAGE_PERSON = "我的";
private MainNavigateTabBar mNavigateTabBar;
private UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigateTabBar = (MainNavigateTabBar) findViewById(R.id.mainTabBar);
mNavigateTabBar.onRestoreInstanceState(savedInstanceState);
// 初始化用户管理器传入Context参数
userManager = UserManager.getInstance(this);
// 初始化数据管理器并设置Context用于数据持久化
DataManager.getInstance().setContext(this);
// 初始化视图,传递 savedInstanceState
initViews(savedInstanceState);
mNavigateTabBar.addTab(HomeFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_home, R.mipmap.comui_tab_home_selected, TAG_PAGE_HOME));
mNavigateTabBar.addTab(CityFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_city, R.mipmap.comui_tab_city_selected, TAG_PAGE_CITY));
mNavigateTabBar.addTab(null, new MainNavigateTabBar.TabParam(0, 0, TAG_PAGE_PUBLISH));
mNavigateTabBar.addTab(MessageFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_message, R.mipmap.comui_tab_message_selected, TAG_PAGE_MESSAGE));
mNavigateTabBar.addTab(PersonFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_person, R.mipmap.comui_tab_person_selected, TAG_PAGE_PERSON));
// 检查登录状态
checkLoginStatus();
}
private void initViews(Bundle savedInstanceState) {
try {
mNavigateTabBar = findViewById(R.id.mainTabBar);
if (mNavigateTabBar == null) {
Toast.makeText(this, "底部导航栏初始化失败", Toast.LENGTH_SHORT).show();
return;
}
mNavigateTabBar.onRestoreInstanceState(savedInstanceState);
// 添加Tab
mNavigateTabBar.addTab(HomeFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_home, R.mipmap.comui_tab_home_selected, TAG_PAGE_HOME));
mNavigateTabBar.addTab(CityFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_city, R.mipmap.comui_tab_city_selected, TAG_PAGE_CITY));
mNavigateTabBar.addTab(null, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_post, R.mipmap.comui_tab_post, TAG_PAGE_PUBLISH)); // 发布按钮
mNavigateTabBar.addTab(MessageFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_message, R.mipmap.comui_tab_message_selected, TAG_PAGE_MESSAGE));
mNavigateTabBar.addTab(PersonFragment.class, new MainNavigateTabBar.TabParam(R.mipmap.comui_tab_person, R.mipmap.comui_tab_person_selected, TAG_PAGE_PERSON));
} catch (Exception e) {
Toast.makeText(this, "界面初始化失败", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mNavigateTabBar.onSaveInstanceState(outState);
if (mNavigateTabBar != null) {
mNavigateTabBar.onSaveInstanceState(outState);
}
}
private void checkLoginStatus() {
// 检查登录状态,如果未登录则跳转到登录页面
if (!userManager.isLoggedIn()) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
}
// 发布按钮点击事件
public void onClickPublish(View v) {
Toast.makeText(this, "发布", Toast.LENGTH_LONG).show();
// 切换到发布Fragment
switchToPublishFragment();
}
// 切换到发布Fragment
public void switchToPublishFragment() {
try {
PublishFragment publishFragment = new PublishFragment();
// 使用FragmentTransaction来显示发布页面
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// 替换当前显示的Fragment
transaction.replace(R.id.main_container, publishFragment, "PublishFragment");
transaction.addToBackStack("publish"); // 允许用户按返回键回到之前的Fragment
transaction.commit();
} catch (Exception e) {
Toast.makeText(this, "打开发布页面失败", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
// 切换到HomeFragment
public void switchToHomeFragment() {
switchToHomeFragment(false);
}
/**
* Fragment"新发布"
* @param showNewTab "新发布"
*/
public void switchToHomeFragment(boolean showNewTab) {
try {
// 检查Activity状态
if (isFinishing() || isDestroyed()) {
Log.e("MainActivity", "Activity正在销毁或已销毁取消Fragment切换操作");
return;
}
// 获取FragmentManager并检查其状态
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.isStateSaved()) {
Log.w("MainActivity", "FragmentManager状态已保存使用commitAllowingStateLoss");
}
// 首先尝试通过后退栈返回首页
if (fragmentManager.getBackStackEntryCount() > 0) {
try {
fragmentManager.popBackStackImmediate();
Log.d("MainActivity", "已通过后退栈返回首页");
// 如果需要显示新发布标签需要在Fragment可见后触发
if (showNewTab) {
// 增加延迟时间到1000毫秒确保Fragment完全加载后再显示新发布标签
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
if (fragment instanceof HomeFragment && fragment.isAdded()) {
HomeFragment homeFragment = (HomeFragment) fragment;
// 确保Fragment的视图已经创建
if (homeFragment.getView() != null) {
Log.d("MainActivity", "Fragment视图已创建开始显示新发布商品");
// 直接调用HomeFragment的refreshAndShowNewProducts方法它会同时刷新数据和显示新发布标签
homeFragment.refreshAndShowNewProducts();
Log.d("MainActivity", "已调用refreshAndShowNewProducts方法");
}
}
}, 1000);
}
} catch (Exception e) {
Log.w("MainActivity", "通过后退栈返回首页失败,尝试直接切换", e);
// 如果通过后退栈失败,尝试直接切换
showHomeFragmentDirectly(fragmentManager, showNewTab);
return;
}
} else {
// 如果后退栈为空直接显示HomeFragment
showHomeFragmentDirectly(fragmentManager, showNewTab);
}
// 确保底部导航栏选中首页
if (mNavigateTabBar != null) {
try {
mNavigateTabBar.setCurrentSelectedTab(0); // 0 是首页的索引
Log.d("MainActivity", "底部导航栏已选中首页");
} catch (Exception e) {
Log.w("MainActivity", "设置底部导航栏选中状态失败", e);
// 这个错误不是致命的,可以继续执行
}
}
} catch (Exception e) {
Log.e("MainActivity", "切换到HomeFragment失败", e);
// 使用try-catch确保Toast不会引发额外异常
try {
Toast.makeText(this, "切换到首页失败", Toast.LENGTH_SHORT).show();
} catch (Exception toastException) {
Log.e("MainActivity", "显示Toast失败", toastException);
}
}
}
// 直接显示HomeFragment的辅助方法
private void showHomeFragmentDirectly(FragmentManager fragmentManager, boolean showNewTab) {
try {
// 查找现有的HomeFragment实例
HomeFragment homeFragment = getHomeFragment();
if (homeFragment == null) {
// 如果不存在,创建新实例
homeFragment = new HomeFragment();
}
// 检查容器View是否存在
if (findViewById(R.id.main_container) == null) {
Log.e("MainActivity", "容器View不存在无法显示Fragment");
return;
}
// 使用FragmentTransaction来显示HomeFragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.main_container, homeFragment, TAG_PAGE_HOME);
// 根据FragmentManager状态选择适当的提交方式
if (fragmentManager.isStateSaved()) {
transaction.commitAllowingStateLoss();
Log.d("MainActivity", "使用commitAllowingStateLoss切换到HomeFragment");
} else {
transaction.commit();
Log.d("MainActivity", "使用commit切换到HomeFragment");
}
// 如果需要显示新发布标签
if (showNewTab) {
// 创建final副本以在Lambda表达式中使用
final HomeFragment finalHomeFragment = homeFragment;
// 增加延迟时间到1000毫秒确保Fragment完全加载后再显示新发布标签
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (finalHomeFragment.isAdded() && finalHomeFragment.getView() != null) {
Log.d("MainActivity", "Fragment视图已创建开始显示新发布商品");
// 直接调用HomeFragment的refreshAndShowNewProducts方法它会同时刷新数据和显示新发布标签
finalHomeFragment.refreshAndShowNewProducts();
Log.d("MainActivity", "已调用refreshAndShowNewProducts方法");
}
}, 1000);
}
} catch (Exception e) {
Log.e("MainActivity", "直接显示HomeFragment失败", e);
throw e; // 重新抛出异常,让调用者处理
}
}
// 获取HomeFragment实例的方法
public HomeFragment getHomeFragment() {
try {
// 方法1通过Tag查找
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PAGE_HOME);
if (fragment instanceof HomeFragment) {
return (HomeFragment) fragment;
}
// 方法2遍历所有Fragment找到HomeFragment实例
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragmentItem : fragments) {
if (fragmentItem instanceof HomeFragment && fragmentItem.isVisible()) {
return (HomeFragment) fragmentItem;
}
}
}
// 方法3查找当前显示的Fragment
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
if (currentFragment instanceof HomeFragment) {
return (HomeFragment) currentFragment;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void onBackPressed() {
// 处理返回键
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
// 如果有Fragment在后退栈中先弹出
getSupportFragmentManager().popBackStack();
// 确保底部导航栏状态正确
if (mNavigateTabBar != null) {
mNavigateTabBar.setCurrentSelectedTab(0); // 回到首页
}
} else {
// 双击退出应用
if (isTaskRoot()) {
// 如果是应用的根Activity可以实现双击退出
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(this, "再按一次退出应用", Toast.LENGTH_SHORT).show();
handler.postDelayed(() -> doubleBackToExitPressedOnce = false, 2000);
} else {
super.onBackPressed();
}
}
}
// 双击退出相关变量
private boolean doubleBackToExitPressedOnce = false;
private android.os.Handler handler = new android.os.Handler();
@Override
protected void onResume() {
super.onResume();
// 每次应用回到前台时检查登录状态
checkLoginStatus();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 清理Handler防止内存泄漏
handler.removeCallbacksAndMessages(null);
}
// 切换到指定Tab的方法 - 简化版本
public void switchToTab(int tabIndex) {
if (mNavigateTabBar != null && tabIndex >= 0 && tabIndex <= 4) { // 假设有5个tab
mNavigateTabBar.setCurrentSelectedTab(tabIndex);
}
}
// 显示Toast的便捷方法
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
}

@ -0,0 +1,271 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import com.startsmake.llrisetabbardemo.model.User;
import java.util.Random;
public class RegisterActivity extends AppCompatActivity {
private TextInputEditText etPhone, etVerificationCode, etPassword;
private Button btnSendCode, btnRegister;
private ImageButton btnBack;
private TextView tvLogin;
private CountDownTimer countDownTimer;
private boolean isCounting = false;
private String verificationCode = "";
private UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
// 确保使用带Context参数的getInstance方法
userManager = UserManager.getInstance(this);
initViews();
setupClickListeners();
}
private void initViews() {
etPhone = findViewById(R.id.et_phone);
etVerificationCode = findViewById(R.id.et_verification_code);
etPassword = findViewById(R.id.et_password);
btnSendCode = findViewById(R.id.btn_send_code);
btnRegister = findViewById(R.id.btn_register);
btnBack = findViewById(R.id.btn_back);
tvLogin = findViewById(R.id.tv_login);
}
private void setupClickListeners() {
// 返回按钮
btnBack.setOnClickListener(v -> finish());
// 发送验证码
btnSendCode.setOnClickListener(v -> sendVerificationCode());
// 注册按钮
btnRegister.setOnClickListener(v -> attemptRegister());
// 立即登录
tvLogin.setOnClickListener(v -> {
Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);
startActivity(intent);
finish();
});
}
private void sendVerificationCode() {
String phone = etPhone.getText().toString().trim();
if (!validatePhone(phone)) {
return;
}
// 检查用户是否已存在
if (userManager.isPhoneRegistered(phone)) {
etPhone.setError("该手机号已注册");
etPhone.requestFocus();
return;
}
if (!isCounting) {
// 生成随机验证码
verificationCode = generateVerificationCode();
startCountDown();
// 模拟发送验证码(在实际应用中应该通过短信发送)
Toast.makeText(this, "验证码已发送: " + verificationCode, Toast.LENGTH_LONG).show();
}
}
private boolean validatePhone(String phone) {
if (phone.isEmpty()) {
etPhone.setError("请输入手机号");
etPhone.requestFocus();
return false;
}
if (phone.length() != 11) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (!phone.startsWith("1")) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
return true;
}
private String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
private void startCountDown() {
isCounting = true;
btnSendCode.setEnabled(false);
countDownTimer = new CountDownTimer(60000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
btnSendCode.setText(millisUntilFinished / 1000 + "秒后重发");
}
@Override
public void onFinish() {
isCounting = false;
btnSendCode.setEnabled(true);
btnSendCode.setText("发送验证码");
// 验证码过期
verificationCode = "";
}
}.start();
}
private void attemptRegister() {
String phone = etPhone.getText().toString().trim();
String code = etVerificationCode.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (!validateRegisterInput(phone, code, password)) {
return;
}
// 验证验证码
if (!code.equals(verificationCode)) {
etVerificationCode.setError("验证码错误");
etVerificationCode.requestFocus();
Toast.makeText(this, "请输入正确的验证码", Toast.LENGTH_SHORT).show();
return;
}
// 检查验证码是否过期
if (verificationCode.isEmpty()) {
etVerificationCode.setError("验证码已过期,请重新获取");
etVerificationCode.requestFocus();
Toast.makeText(this, "验证码已过期,请重新获取", Toast.LENGTH_SHORT).show();
return;
}
// 注册用户 - 使用后端API
String username = "用户_" + phone.substring(phone.length() - 4); // 生成默认用户名
userManager.registerUserWithApi(phone, password, username, new UserManager.AuthCallback() {
@Override
public void onSuccess(User user) {
Toast.makeText(RegisterActivity.this, "注册成功!", Toast.LENGTH_SHORT).show();
// 跳转到主页面
Intent intent = new Intent(RegisterActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
@Override
public void onError(String message) {
Toast.makeText(RegisterActivity.this, "注册失败: " + message, Toast.LENGTH_SHORT).show();
etPhone.setError("注册失败,请重试");
etPhone.requestFocus();
}
});
}
private boolean validateRegisterInput(String phone, String code, String password) {
// 验证手机号
if (phone.isEmpty()) {
etPhone.setError("请输入手机号");
etPhone.requestFocus();
return false;
}
if (phone.length() != 11) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
if (!phone.startsWith("1")) {
etPhone.setError("手机号格式不正确");
etPhone.requestFocus();
return false;
}
// 验证验证码
if (code.isEmpty()) {
etVerificationCode.setError("请输入验证码");
etVerificationCode.requestFocus();
return false;
}
if (code.length() != 6) {
etVerificationCode.setError("验证码格式不正确");
etVerificationCode.requestFocus();
return false;
}
// 验证密码
if (password.isEmpty()) {
etPassword.setError("请输入密码");
etPassword.requestFocus();
return false;
}
if (password.length() < 6) {
etPassword.setError("密码至少6位");
etPassword.requestFocus();
return false;
}
if (password.length() > 20) {
etPassword.setError("密码最多20位");
etPassword.requestFocus();
return false;
}
return true;
}
private void performRegisterSuccess(String phone, String password) {
Toast.makeText(this, "注册成功!", Toast.LENGTH_SHORT).show();
// 自动登录
User user = userManager.loginUser(phone, password);
if (user != null) {
// 跳转到主页面
Intent intent = new Intent(RegisterActivity.this, MainActivity.class);
startActivity(intent);
finish();
} else {
// 如果自动登录失败,跳转到登录页面
Toast.makeText(this, "注册成功,请登录", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);
startActivity(intent);
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (countDownTimer != null) {
countDownTimer.cancel();
}
}
}

@ -0,0 +1,363 @@
package com.startsmake.llrisetabbardemo.activity;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Product;
import com.startsmake.llrisetabbardemo.model.Item;
import manager.DataManager;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class SearchActivity extends AppCompatActivity {
private EditText searchEditText;
private ImageButton backButton;
private ImageButton cameraButton;
private com.google.android.flexbox.FlexboxLayout historyContainer;
private com.google.android.flexbox.FlexboxLayout recommendContainer;
private TextView clearHistoryText;
private TextView expandHistoryText;
private SharedPreferences sharedPreferences;
private static final String SEARCH_HISTORY = "search_history";
private static final int MAX_HISTORY_COUNT = 6; // 最大存储6条
private static final int VISIBLE_HISTORY_COUNT = 4; // 默认显示4条
private boolean isHistoryExpanded = false;
// 相机相关变量
private static final int CAMERA_REQUEST_CODE = 1001;
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002;
private String currentPhotoPath;
private List<Product> allProducts;
private List<String> recommendKeywords;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
// 设置DataManager的Context以防用户直接从通知或其他方式进入此Activity
DataManager.getInstance().setContext(this);
initViews();
initData();
loadSearchHistory();
setupRecommendations();
}
private void initViews() {
searchEditText = findViewById(R.id.search_edit_text);
backButton = findViewById(R.id.back_button);
cameraButton = findViewById(R.id.camera_button);
historyContainer = findViewById(R.id.history_container);
recommendContainer = findViewById(R.id.recommend_container);
clearHistoryText = findViewById(R.id.clear_history_text);
expandHistoryText = findViewById(R.id.expand_history_text);
TextView searchButton = findViewById(R.id.search_button);
// 设置返回按钮
backButton.setOnClickListener((View v) -> finish());
// 设置搜索按钮点击事件
searchButton.setOnClickListener((View v) -> {
performSearch();
});
// 设置搜索页面的相机按钮
cameraButton.setOnClickListener((View v) -> {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
} else {
openCamera();
}
}
});
// 设置搜索功能
searchEditText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
performSearch();
return true;
}
return false;
});
// 清空历史记录
clearHistoryText.setOnClickListener((View v) -> clearSearchHistory());
// 设置展开/收起按钮
expandHistoryText.setOnClickListener((View v) -> {
isHistoryExpanded = !isHistoryExpanded;
loadSearchHistory();
});
}
private void initData() {
sharedPreferences = getSharedPreferences("search_prefs", MODE_PRIVATE);
// 保留本地模拟数据作为备用
allProducts = new ArrayList<>();
allProducts.add(new Product("1", "Java编程思想", "计算机专业教材", "学习资料", 45.0, ""));
allProducts.add(new Product("2", "高等数学教材", "大学数学课本", "学习资料", 30.0, ""));
allProducts.add(new Product("3", "笔记本电脑", "二手联想笔记本", "数码产品", 1200.0, ""));
allProducts.add(new Product("4", "台灯", "护眼学习台灯", "生活用品", 25.0, ""));
allProducts.add(new Product("5", "Python入门", "编程学习书籍", "学习资料", 35.0, ""));
// 初始化推荐关键词
recommendKeywords = Arrays.asList(
"Java编程教材", "Python入门书籍", "高等数学课本", "英语四级真题", "考研政治资料", "计算机专业课", "电路分析教程", "机械制图教材", "经济学原理", "心理学导论", "设计素描本", "专业课程笔记","二手笔记本电脑", "机械键盘", "无线鼠标", "蓝牙耳机", "平板电脑", "智能手机", "充电宝", "U盘硬盘", "显示器", "路由器", "相机镜头", "游戏手柄","台灯", "插排", "收纳箱", "穿衣镜",
"瑜伽垫", "体重秤", "电风扇", "暖手宝", "床上桌", "衣柜", "鞋架", "晾衣架","羽毛球拍", "篮球足球", "滑板轮滑", "吉他乐器",
"画笔画具", "围棋象棋", "游泳装备", "健身器材", "登山背包", "帐篷睡袋", "摄影三脚架", "书法字帖"
);
}
private void loadSearchHistory() {
Set<String> historySet = sharedPreferences.getStringSet(SEARCH_HISTORY, new HashSet<>());
List<String> historyList = new ArrayList<>(historySet);
// 按照搜索顺序排序(后搜索的在前)
Collections.reverse(historyList);
historyContainer.removeAllViews();
if (historyList.isEmpty()) {
findViewById(R.id.history_title).setVisibility(View.GONE);
clearHistoryText.setVisibility(View.GONE);
expandHistoryText.setVisibility(View.GONE);
} else {
findViewById(R.id.history_title).setVisibility(View.VISIBLE);
clearHistoryText.setVisibility(View.VISIBLE);
// 计算要显示的历史记录数量
int showCount = historyList.size();
if (!isHistoryExpanded && historyList.size() > VISIBLE_HISTORY_COUNT) {
showCount = VISIBLE_HISTORY_COUNT;
}
// 显示历史记录标签
for (int i = 0; i < showCount; i++) {
String keyword = historyList.get(i);
TextView historyTag = createTagView(keyword, true);
historyContainer.addView(historyTag);
}
// 显示展开/收起按钮
if (historyList.size() > VISIBLE_HISTORY_COUNT) {
expandHistoryText.setVisibility(View.VISIBLE);
if (isHistoryExpanded) {
expandHistoryText.setText("收起");
} else {
expandHistoryText.setText("展开更多(" + (historyList.size() - VISIBLE_HISTORY_COUNT) + ")");
}
} else {
expandHistoryText.setVisibility(View.GONE);
}
}
}
private void saveSearchHistory(String query) {
Set<String> historySet = sharedPreferences.getStringSet(SEARCH_HISTORY, new HashSet<>());
Set<String> newSet = new LinkedHashSet<>(); // 使用LinkedHashSet保持顺序
// 先添加新的搜索(确保在最前面)
newSet.add(query);
// 添加其他历史记录(排除重复项)
for (String item : historySet) {
if (!item.equals(query)) {
newSet.add(item);
}
}
// 如果超过最大数量,移除最旧的
if (newSet.size() > MAX_HISTORY_COUNT) {
List<String> list = new ArrayList<>(newSet);
// 保留最新的6条
List<String> newList = list.subList(0, MAX_HISTORY_COUNT);
newSet = new LinkedHashSet<>(newList);
}
sharedPreferences.edit().putStringSet(SEARCH_HISTORY, newSet).apply();
}
// 相机相关方法保持不变
private void openCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show();
}
if (photoFile != null) {
currentPhotoPath = photoFile.getAbsolutePath();
Uri photoURI = FileProvider.getUriForFile(this,
getPackageName() + ".fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, CAMERA_REQUEST_CODE);
}
} else {
Toast.makeText(this, "未找到相机应用", Toast.LENGTH_SHORT).show();
}
}
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return File.createTempFile(
imageFileName,
".jpg",
storageDir
);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CAMERA_REQUEST_CODE && resultCode == RESULT_OK) {
if (currentPhotoPath != null) {
Toast.makeText(this, "拍照成功,开始搜索...", Toast.LENGTH_SHORT).show();
searchEditText.setText("图片搜索中...");
performImageSearch();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
Toast.makeText(this, "需要相机权限才能拍照搜索", Toast.LENGTH_SHORT).show();
}
}
}
// 图片搜索功能
private void performImageSearch() {
// 图片搜索暂时使用本地模拟数据
List<Product> similarProducts = findSimilarProducts();
// 跳转到搜索结果页面
Intent intent = new Intent(SearchActivity.this, SearchResultsActivity.class);
intent.putExtra("search_type", "image");
intent.putExtra("similar_products", (java.io.Serializable) similarProducts);
startActivity(intent);
}
private List<Product> findSimilarProducts() {
List<Product> results = new ArrayList<>();
// 简单模拟随机返回3个产品作为图片搜索结果
int count = 0;
for (Product product : allProducts) {
if (count >= 3) break;
results.add(product);
count++;
}
return results;
}
private void setupRecommendations() {
List<String> randomRecommends = new ArrayList<>(recommendKeywords);
Collections.shuffle(randomRecommends);
List<String> selectedRecommends = randomRecommends.subList(0, Math.min(6, randomRecommends.size()));
recommendContainer.removeAllViews();
for (String keyword : selectedRecommends) {
TextView recommendTag = createTagView(keyword, false);
recommendContainer.addView(recommendTag);
}
}
private TextView createTagView(String keyword, boolean isHistory) {
TextView tagView = new TextView(this);
com.google.android.flexbox.FlexboxLayout.LayoutParams params = new com.google.android.flexbox.FlexboxLayout.LayoutParams(
com.google.android.flexbox.FlexboxLayout.LayoutParams.WRAP_CONTENT,
com.google.android.flexbox.FlexboxLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 0, 16, 16);
tagView.setLayoutParams(params);
tagView.setPadding(32, 16, 32, 16);
tagView.setText(keyword);
tagView.setTextSize(14);
tagView.setBackgroundResource(R.drawable.tag_background);
tagView.setTextColor(getResources().getColor(android.R.color.darker_gray));
tagView.setOnClickListener(v -> {
searchEditText.setText(keyword);
performSearch();
});
return tagView;
}
private void performSearch() {
String query = searchEditText.getText().toString().trim();
if (TextUtils.isEmpty(query)) {
Toast.makeText(this, "请输入搜索内容", Toast.LENGTH_SHORT).show();
return;
}
// 保存搜索历史
saveSearchHistory(query);
// 跳转到搜索结果页面 - 后端API搜索将在SearchResultsActivity中执行
Intent intent = new Intent(SearchActivity.this, SearchResultsActivity.class);
intent.putExtra("search_query", query);
startActivity(intent);
}
private void clearSearchHistory() {
sharedPreferences.edit().remove(SEARCH_HISTORY).apply();
loadSearchHistory();
}
@Override
protected void onResume() {
super.onResume();
loadSearchHistory();
}
}

@ -0,0 +1,158 @@
package com.startsmake.llrisetabbardemo.activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.fragment.app.FragmentTransaction;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.adapter.SearchAdapter;
import com.startsmake.llrisetabbardemo.model.Product;
import com.startsmake.llrisetabbardemo.model.Item;
import java.util.ArrayList;
import java.util.List;
public class SearchResultsActivity extends AppCompatActivity {
private RecyclerView resultsRecyclerView;
private TextView searchQueryText;
private SearchAdapter searchAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search_results);
initViews();
loadSearchResults();
}
private void initViews() {
resultsRecyclerView = findViewById(R.id.results_recycler_view);
searchQueryText = findViewById(R.id.search_query_text);
String query = getIntent().getStringExtra("search_query");
searchQueryText.setText("搜索结果: " + query);
searchAdapter = new SearchAdapter(new ArrayList<>());
resultsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
resultsRecyclerView.setAdapter(searchAdapter);
// 设置商品点击事件处理
searchAdapter.setOnItemClickListener(this::navigateToItemDetail);
// 设置返回按钮
findViewById(R.id.back_button).setOnClickListener((View v) -> {
finish();
});
}
private void loadSearchResults() {
String searchType = getIntent().getStringExtra("search_type");
String query = getIntent().getStringExtra("search_query");
if ("image".equals(searchType)) {
// 图片搜索结果
List<Product> similarProducts = null;
try {
// 安全地转换类型
Object serializedExtra = getIntent().getSerializableExtra("similar_products");
if (serializedExtra instanceof List) {
similarProducts = new ArrayList<>();
for (Object obj : (List<?>) serializedExtra) {
if (obj instanceof Product) {
similarProducts.add((Product) obj);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (similarProducts != null && !similarProducts.isEmpty()) {
searchQueryText.setText("图片搜索结果");
searchAdapter.updateData(similarProducts);
findViewById(R.id.no_results_text).setVisibility(View.GONE);
resultsRecyclerView.setVisibility(View.VISIBLE);
} else {
searchQueryText.setText("图片搜索结果");
findViewById(R.id.no_results_text).setVisibility(View.VISIBLE);
resultsRecyclerView.setVisibility(View.GONE);
}
} else {
// 文本搜索结果
if (query != null) {
searchQueryText.setText("搜索结果: " + query);
findViewById(R.id.no_results_text).setVisibility(View.GONE);
resultsRecyclerView.setVisibility(View.GONE);
// 由于DataManager不存在直接使用本地搜索
List<Product> localResults = searchProducts(query);
if (localResults.isEmpty()) {
findViewById(R.id.no_results_text).setVisibility(View.VISIBLE);
resultsRecyclerView.setVisibility(View.GONE);
} else {
searchAdapter.updateData(localResults);
findViewById(R.id.no_results_text).setVisibility(View.GONE);
resultsRecyclerView.setVisibility(View.VISIBLE);
}
} else {
searchQueryText.setText("搜索结果");
findViewById(R.id.no_results_text).setVisibility(View.VISIBLE);
resultsRecyclerView.setVisibility(View.GONE);
}
}
}
private List<Product> searchProducts(String keyword) {
List<Product> results = new ArrayList<>();
List<Product> allProducts = getMockProducts();
String lowerKeyword = keyword.toLowerCase();
for (Product product : allProducts) {
if (product.getName().toLowerCase().contains(lowerKeyword) ||
product.getDescription().toLowerCase().contains(lowerKeyword) ||
product.getCategory().toLowerCase().contains(lowerKeyword)) {
results.add(product);
}
}
return results;
}
private List<Product> getMockProducts() {
List<Product> products = new ArrayList<>();
products.add(new Product("1", "Java编程思想", "计算机专业教材", "学习资料", 45.0, ""));
products.add(new Product("2", "高等数学教材", "大学数学课本", "学习资料", 30.0, ""));
products.add(new Product("3", "笔记本电脑", "二手联想笔记本", "数码产品", 1200.0, ""));
products.add(new Product("4", "台灯", "护眼学习台灯", "生活用品", 25.0, ""));
products.add(new Product("5", "Python入门", "编程学习书籍", "学习资料", 35.0, ""));
return products;
}
private void navigateToItemDetail(Product product) {
try {
// 创建新Item对象不再使用DataManager
Item item = new Item();
item.setId(product.getId());
item.setTitle(product.getName());
item.setDescription(product.getDescription());
item.setPrice(product.getPrice());
item.setCategory(product.getCategory());
item.setLocation("未知位置");
item.setContact("联系方式");
item.setPublishTime(System.currentTimeMillis());
// 跳转到商品详情Fragment
// 由于可能没有main_container布局简化处理
Toast.makeText(this, "查看商品详情: " + product.getName(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "点击处理出错", Toast.LENGTH_SHORT).show();
}
}
}

@ -0,0 +1,542 @@
package com.startsmake.llrisetabbardemo.activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Shop;
import com.startsmake.llrisetabbardemo.activity.StallDetailActivity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class ShopDetailActivity extends AppCompatActivity {
public static final String EXTRA_AREA_NAME = "area_name";
public static final String EXTRA_AREA_DESCRIPTION = "area_description";
public static final String EXTRA_STALL_NAME = "stall_name";
public static final String EXTRA_STALL_POSITION = "stall_position";
public static final String EXTRA_SHOP_DATA = "extra_shop_data";
private static final String TAG = "ShopDetailActivity";
private static final int PICK_IMAGE_REQUEST = 1001;
private static final String SHOP_PREFS_NAME = "ShopDetailPrefs";
private ImageView ivAreaImage;
private Bitmap importedAreaImage;
// 区域小摊数据 - 每个区域都有完全独立的小摊
private Map<String, String[]> stallNamesMap = new HashMap<>();
private Map<String, String[]> stallDescriptionsMap = new HashMap<>();
// 区域图片映射,支持每个区域单独定义图片
private Map<String, Integer> areaImageMap = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shop_detail);
Log.d(TAG, "onCreate: Activity started");
// 初始化区域小摊数据
initStallData();
// 调试检查所有传递的intent数据
Intent intent = getIntent();
String areaName = intent.getStringExtra(EXTRA_AREA_NAME);
String areaDescription = intent.getStringExtra(EXTRA_AREA_DESCRIPTION);
Log.d(TAG, "Area name from intent: " + areaName);
Log.d(TAG, "Area description from intent: " + areaDescription);
// 不再尝试获取 Shop 对象,直接使用基本数据
if (areaName == null) {
// 如果基本数据为空,尝试从 Shop 对象获取(向后兼容)
Shop shop = (Shop) intent.getSerializableExtra(EXTRA_SHOP_DATA);
if (shop != null) {
areaName = shop.getName();
areaDescription = shop.getDescription();
Log.d(TAG, "Got data from Shop object: " + areaName);
}
}
// 确保数据不为空
if (areaName == null || areaName.isEmpty()) {
areaName = "默认区域";
}
if (areaDescription == null || areaDescription.isEmpty()) {
areaDescription = "欢迎来到" + areaName + "区域,这里有各种精彩的小摊等待您的探索!";
}
// 设置 Intent 中的数据,确保后续方法能获取到
intent.putExtra(EXTRA_AREA_NAME, areaName);
intent.putExtra(EXTRA_AREA_DESCRIPTION, areaDescription);
initAreaDetail();
initStalls();
initImportButton();
}
// 初始化每个区域的独立小摊数据
private void initStallData() {
// 军事迷区域 - 完全独立的小摊
stallNamesMap.put("军事迷", new String[]{
"军事迷摊位一", "军事迷摊位二", "军事迷摊位三",
"军事迷摊位四", "军事迷摊位五", "军事迷摊位六"
});
stallDescriptionsMap.put("军事迷", new String[]{
"专业战术装备,满足军迷需求", "稀有军品收藏,历史价值兼备",
"野外生存必备,专业级装备", "精致军事模型,细节还原",
"军迷文化周边,展现军事热情", "战备级物资,品质保证"
});
// 游戏玩家区域 - 完全独立的小摊
stallNamesMap.put("游戏玩家", new String[]{
"电竞装备中心", "游戏周边馆", "主机游戏天地",
"VR体验站", "游戏卡带铺", "外设专卖店"
});
stallDescriptionsMap.put("游戏玩家", new String[]{
"顶级电竞装备,提升游戏体验", "正版游戏周边,收藏实用兼备",
"最新主机游戏,畅享游戏乐趣", "沉浸式VR体验感受科技魅力",
"各类游戏卡带,经典新品俱全", "专业游戏外设,操作更流畅"
});
// 服饰区域 - 完全独立的小摊
stallNamesMap.put("服饰", new String[]{
"潮流服饰馆", "精品服装店", "时尚搭配坊",
"设计师品牌", "个性定制屋", "品牌折扣站"
});
stallDescriptionsMap.put("服饰", new String[]{
"最新潮流服饰,展现时尚品味", "精选优质服装,舒适美观",
"专业搭配建议,打造完美形象", "独立设计师品牌,独特个性",
"个性化定制服务,专属风格", "品牌特惠商品,性价比高"
});
// 老吃家区域 - 完全独立的小摊
stallNamesMap.put("老吃家", new String[]{
"特色小吃摊", "传统美食坊", "甜品饮料站",
"地方特色馆", "烧烤炸串铺", "健康轻食屋"
});
stallDescriptionsMap.put("老吃家", new String[]{
"地道特色小吃,满足味蕾", "传统工艺制作,传承美食文化",
"甜蜜饮品甜点,治愈心情", "各地特色美食,一站式品尝",
"现烤现炸美味,香气扑鼻", "健康营养搭配,美味无负担"
});
// 默认区域
stallNamesMap.put("默认区域", new String[]{
"精品小摊一", "精品小摊二", "精品小摊三",
"精品小摊四", "精品小摊五", "精品小摊六"
});
stallDescriptionsMap.put("默认区域", new String[]{
"精选商品,品质保证", "优质货源,价格实惠",
"特色商品,独特设计", "新品上市,抢先体验",
"经典商品,口碑推荐", "热销商品,人气爆棚"
});
// 为其他可能的区域名称创建映射
stallNamesMap.put("美食", stallNamesMap.get("老吃家"));
stallDescriptionsMap.put("美食", stallDescriptionsMap.get("老吃家"));
stallNamesMap.put("服装", stallNamesMap.get("服饰"));
stallDescriptionsMap.put("服装", stallDescriptionsMap.get("服饰"));
stallNamesMap.put("娱乐", stallNamesMap.get("游戏玩家"));
stallDescriptionsMap.put("娱乐", stallDescriptionsMap.get("游戏玩家"));
}
private void initAreaDetail() {
TextView tvAreaTitle = findViewById(R.id.tvAreaTitle);
TextView tvAreaDescription = findViewById(R.id.tvAreaDescription);
ivAreaImage = findViewById(R.id.ivAreaImage);
String areaName = getIntent().getStringExtra(EXTRA_AREA_NAME);
String areaDescription = getIntent().getStringExtra(EXTRA_AREA_DESCRIPTION);
Log.d(TAG, "initAreaDetail: Area - " + areaName);
if (areaName == null || areaName.isEmpty()) {
areaName = "默认区域";
}
tvAreaTitle.setText(areaName);
if (areaDescription != null && !areaDescription.isEmpty()) {
tvAreaDescription.setText(areaDescription);
} else {
tvAreaDescription.setText("欢迎来到" + areaName + "区域,这里有各种精彩的小摊等待您的探索!");
}
// 初始化区域图片映射
initAreaImageMap();
// 图片加载逻辑
// 对于游戏玩家和老吃家区域优先使用drawable资源
if ("游戏玩家".equals(areaName) || "老吃家".equals(areaName)) {
// 直接使用drawable资源不加载保存的图片
int drawableId = getAreaDrawableId(areaName);
ivAreaImage.setImageResource(drawableId);
} else {
// 其他区域优先从偏好设置加载已保存的图片
Bitmap savedAreaImage = loadAreaImageFromPreferences();
if (savedAreaImage != null) {
ivAreaImage.setImageBitmap(savedAreaImage);
importedAreaImage = savedAreaImage;
} else {
// 如果没有保存的图片从drawable目录加载区域对应的图片
int drawableId = getAreaDrawableId(areaName);
ivAreaImage.setImageResource(drawableId);
}
}
}
// 初始化区域图片映射,支持每个区域单独定义图片
private void initAreaImageMap() {
areaImageMap.put("军事迷", R.drawable.junshimi);
areaImageMap.put("游戏玩家", R.drawable.youxiwanjia);
areaImageMap.put("服饰", R.drawable.fushi);
areaImageMap.put("老吃家", R.drawable.laochijia);
areaImageMap.put("潮流玩艺", R.drawable.chaoliuwanyi);
areaImageMap.put("影室", R.drawable.yingshi);
areaImageMap.put("书虫", R.drawable.shuchong);
}
// 获取区域对应的drawable资源ID
private int getAreaDrawableId(String areaName) {
// 优先从映射中查找精确匹配
if (areaImageMap.containsKey(areaName)) {
return areaImageMap.get(areaName);
}
// 然后进行模糊匹配
if (areaName.contains("美食") || areaName.contains("老吃家")) {
return R.drawable.ic_coupons;
} else if (areaName.contains("服装") || areaName.contains("服饰")) {
return R.drawable.ic_store;
} else if (areaName.contains("娱乐") || areaName.contains("游戏")) {
return R.drawable.ic_my_listings;
} else if (areaName.contains("军事")) {
return R.drawable.ic_qr_code;
} else {
// 默认图片
return R.drawable.ic_launcher_playstore;
}
}
private void initStalls() {
LinearLayout llStallsContainer = findViewById(R.id.stallsContainer);
String areaName = getIntent().getStringExtra(EXTRA_AREA_NAME);
if (areaName == null) {
areaName = "默认区域";
}
Log.d(TAG, "initStalls: Creating stalls for area: " + areaName);
// 获取该区域的小摊数据
String[] stallNames = getStallNamesForArea(areaName);
String[] stallDescriptions = getStallDescriptionsForArea(areaName);
// 创建6个小摊
for (int i = 0; i < 6; i++) {
View stallView = createStallItemView(areaName, i, stallNames[i], stallDescriptions[i]);
llStallsContainer.addView(stallView);
}
}
/**
*
*/
private String[] getStallNamesForArea(String areaName) {
// 先尝试精确匹配
if (stallNamesMap.containsKey(areaName)) {
Log.d(TAG, "Exact match found for area: " + areaName);
return stallNamesMap.get(areaName);
}
// 关键词匹配
if (areaName.contains("军事")) {
Log.d(TAG, "Keyword match: 军事 -> 军事迷");
return stallNamesMap.get("军事迷");
} else if (areaName.contains("美食") || areaName.contains("老吃家")) {
Log.d(TAG, "Keyword match: 美食/老吃家 -> 老吃家");
return stallNamesMap.get("老吃家");
} else if (areaName.contains("服装") || areaName.contains("服饰")) {
Log.d(TAG, "Keyword match: 服装/服饰 -> 服饰");
return stallNamesMap.get("服饰");
} else if (areaName.contains("游戏") || areaName.contains("娱乐")) {
Log.d(TAG, "Keyword match: 游戏/娱乐 -> 游戏玩家");
return stallNamesMap.get("游戏玩家");
} else {
Log.d(TAG, "No match found, using default");
return stallNamesMap.get("默认区域");
}
}
/**
*
*/
private String[] getStallDescriptionsForArea(String areaName) {
if (stallDescriptionsMap.containsKey(areaName)) {
return stallDescriptionsMap.get(areaName);
}
if (areaName.contains("军事")) {
return stallDescriptionsMap.get("军事迷");
} else if (areaName.contains("美食") || areaName.contains("老吃家")) {
return stallDescriptionsMap.get("老吃家");
} else if (areaName.contains("服装") || areaName.contains("服饰")) {
return stallDescriptionsMap.get("服饰");
} else if (areaName.contains("游戏") || areaName.contains("娱乐")) {
return stallDescriptionsMap.get("游戏玩家");
} else {
return stallDescriptionsMap.get("默认区域");
}
}
private View createStallItemView(String areaName, int stallPosition, String stallName, String stallDescription) {
LinearLayout stallContainer = new LinearLayout(this);
stallContainer.setOrientation(LinearLayout.VERTICAL);
stallContainer.setBackgroundResource(R.drawable.stall_item_background);
stallContainer.setPadding(20, 20, 20, 20);
LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
containerParams.setMargins(0, 0, 0, 16);
stallContainer.setLayoutParams(containerParams);
LinearLayout stallLayout = new LinearLayout(this);
stallLayout.setOrientation(LinearLayout.HORIZONTAL);
// 设置摊位图片
ImageView ivStallImage = new ImageView(this);
LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams(140, 140);
imageParams.setMargins(0, 0, 20, 0);
ivStallImage.setLayoutParams(imageParams);
ivStallImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
// 从assets目录加载摊位图片
String imagePath = getStallImagePath(areaName, stallPosition);
Bitmap bitmap = loadImageFromAssets(imagePath);
if (bitmap != null) {
ivStallImage.setImageBitmap(bitmap);
} else {
// 如果加载失败,使用默认图标
ivStallImage.setImageResource(R.drawable.ic_launcher_playstore);
}
// 设置摊位信息
LinearLayout infoLayout = new LinearLayout(this);
infoLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.WRAP_CONTENT,
1
);
infoLayout.setLayoutParams(infoParams);
TextView tvStallName = new TextView(this);
tvStallName.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
tvStallName.setText(stallName);
tvStallName.setTypeface(null, Typeface.BOLD);
tvStallName.setTextSize(18);
tvStallName.setTextColor(getResources().getColor(android.R.color.black));
tvStallName.setPadding(0, 0, 0, 8);
TextView tvStallDescription = new TextView(this);
tvStallDescription.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
tvStallDescription.setText(stallDescription);
tvStallDescription.setTextSize(14);
tvStallDescription.setTextColor(getResources().getColor(android.R.color.darker_gray));
tvStallDescription.setLineSpacing(0, 1.2f);
infoLayout.addView(tvStallName);
infoLayout.addView(tvStallDescription);
stallLayout.addView(ivStallImage);
stallLayout.addView(infoLayout);
stallContainer.addView(stallLayout);
// 设置点击事件 - 传递正确的区域信息
stallContainer.setOnClickListener(v -> {
Log.d(TAG, "Stall clicked: " + stallName + " in area: " + areaName);
Log.d(TAG, "Stall position: " + stallPosition);
navigateToStallDetail(areaName, stallName, stallPosition);
});
return stallContainer;
}
private void navigateToStallDetail(String areaName, String stallName, int stallPosition) {
try {
Intent intent = new Intent(this, StallDetailActivity.class);
intent.putExtra(EXTRA_AREA_NAME, areaName);
intent.putExtra(EXTRA_STALL_NAME, stallName);
intent.putExtra(EXTRA_STALL_POSITION, stallPosition);
Log.d(TAG, "Navigating to stall detail - Area: " + areaName +
", Stall: " + stallName + ", Position: " + stallPosition);
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "Error starting StallDetailActivity", e);
Toast.makeText(this, "无法打开小摊详情", Toast.LENGTH_SHORT).show();
}
}
// 以下方法保持不变
private void initImportButton() {
String areaName = getIntent().getStringExtra(EXTRA_AREA_NAME);
TextView tvImportStatus = findViewById(R.id.tvImportStatus);
// 对于默认区域,隐藏导入状态文本
if ("默认区域".equals(areaName)) {
tvImportStatus.setVisibility(View.GONE);
}
}
private void openImagePicker() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*");
startActivityForResult(intent, PICK_IMAGE_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null) {
Uri selectedImageUri = data.getData();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), selectedImageUri);
Bitmap compressedBitmap = compressBitmap(bitmap, 800, 600);
importedAreaImage = compressedBitmap;
ivAreaImage.setImageBitmap(compressedBitmap);
saveAreaImageToPreferences(compressedBitmap);
TextView tvImportStatus = findViewById(R.id.tvImportStatus);
tvImportStatus.setText("图片导入成功!");
tvImportStatus.setVisibility(View.VISIBLE);
} catch (IOException e) {
Log.e(TAG, "Error loading imported image", e);
}
}
}
private void saveAreaImageToPreferences(Bitmap bitmap) {
SharedPreferences prefs = getSharedPreferences(SHOP_PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
String areaName = getIntent().getStringExtra(EXTRA_AREA_NAME);
if (areaName == null || areaName.equals("默认区域")) return;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 80, baos);
byte[] imageBytes = baos.toByteArray();
String imageString = Base64.encodeToString(imageBytes, Base64.DEFAULT);
String key = "area_image_" + areaName;
editor.putString(key, imageString);
editor.apply();
}
private Bitmap loadAreaImageFromPreferences() {
SharedPreferences prefs = getSharedPreferences(SHOP_PREFS_NAME, MODE_PRIVATE);
String areaName = getIntent().getStringExtra(EXTRA_AREA_NAME);
if (areaName == null || areaName.equals("默认区域")) return null;
String key = "area_image_" + areaName;
String imageString = prefs.getString(key, null);
if (imageString != null) {
try {
byte[] imageBytes = Base64.decode(imageString, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
} catch (Exception e) {
Log.e(TAG, "Error loading area image from preferences", e);
}
}
return null;
}
private Bitmap compressBitmap(Bitmap original, int maxWidth, int maxHeight) {
int width = original.getWidth();
int height = original.getHeight();
if (width > maxWidth || height > maxHeight) {
float ratio = Math.min((float) maxWidth / width, (float) maxHeight / height);
width = Math.round(width * ratio);
height = Math.round(height * ratio);
return Bitmap.createScaledBitmap(original, width, height, true);
}
return original;
}
// 保留原方法用于兼容,内部调用新的实现
private String getAreaImagePath(String areaName) {
// 由于我们已经切换到drawable资源这里返回一个默认路径
return "default_area.png";
}
private String getStallImagePath(String areaName, int stallPosition) {
int stallNumber = stallPosition % 3 + 1;
if (areaName.contains("美食") || areaName.contains("老吃家")) {
return "food_stall_" + stallNumber + ".png";
} else if (areaName.contains("服装") || areaName.contains("服饰")) {
return "clothing_stall_" + stallNumber + ".png";
} else if (areaName.contains("娱乐") || areaName.contains("游戏")) {
return "game_stall_" + stallNumber + ".png";
} else if (areaName.contains("军事")) {
return "military_stall_" + stallNumber + ".png";
} else {
return "default_stall_" + stallNumber + ".png";
}
}
private Bitmap loadImageFromAssets(String imagePath) {
try {
InputStream is = getAssets().open(imagePath);
return BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e(TAG, "Error loading image from assets: " + imagePath, e);
return null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (importedAreaImage != null && !importedAreaImage.isRecycled()) {
importedAreaImage.recycle();
importedAreaImage = null;
}
}
}

@ -0,0 +1,127 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Address;
import java.util.ArrayList;
import java.util.List;
public class AddressAdapter extends RecyclerView.Adapter<AddressAdapter.AddressViewHolder> {
private Context context;
private List<Address> addresses;
private OnAddressClickListener listener;
public interface OnAddressClickListener {
void onSelect(Address address);
void onEdit(Address address);
void onDelete(Address address);
void onSetDefault(Address address);
}
public AddressAdapter(Context context, List<Address> addresses, OnAddressClickListener listener) {
this.context = context;
this.addresses = addresses != null ? addresses : new ArrayList<>();
this.listener = listener;
}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses != null ? addresses : new ArrayList<>();
}
@NonNull
@Override
public AddressViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_address, parent, false);
return new AddressViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull AddressViewHolder holder, int position) {
final Address address = addresses.get(position);
// 显示地址信息
holder.tvName.setText(address.getName());
holder.tvPhone.setText(address.getPhone());
holder.tvAddress.setText(address.getAddressWithContact());
// 显示默认标签
if (address.isDefault()) {
holder.tvDefault.setVisibility(View.VISIBLE);
} else {
holder.tvDefault.setVisibility(View.GONE);
}
// 设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onSelect(address);
}
}
});
holder.btnEdit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onEdit(address);
}
}
});
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onDelete(address);
}
}
});
holder.btnSetDefault.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onSetDefault(address);
}
}
});
}
@Override
public int getItemCount() {
return addresses.size();
}
static class AddressViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
TextView tvPhone;
TextView tvAddress;
TextView tvDefault;
TextView btnEdit;
TextView btnDelete;
TextView btnSetDefault;
AddressViewHolder(@NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tv_name);
tvPhone = itemView.findViewById(R.id.tv_phone);
tvAddress = itemView.findViewById(R.id.tv_address);
tvDefault = itemView.findViewById(R.id.tv_default);
btnEdit = itemView.findViewById(R.id.btn_edit);
btnDelete = itemView.findViewById(R.id.btn_delete);
btnSetDefault = itemView.findViewById(R.id.btn_set_default);
}
}
}

@ -0,0 +1,152 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.ChatMessage;
import java.util.List;
public class ChatMessageAdapter extends RecyclerView.Adapter<ChatMessageAdapter.ViewHolder> {
private Context context;
private List<ChatMessage> messageList;
public ChatMessageAdapter(Context context, List<ChatMessage> messageList) {
this.context = context;
this.messageList = messageList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_chat_message, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 安全检查
if (messageList == null || position < 0 || position >= messageList.size() || holder == null) {
return;
}
ChatMessage message = messageList.get(position);
// 检查消息对象是否为空
if (message == null) {
return;
}
if (message.isMe()) {
// 自己发送的消息 - 右侧显示
holder.layoutLeft.setVisibility(View.GONE);
holder.layoutRight.setVisibility(View.VISIBLE);
holder.tvRightMessage.setText(message.getContent());
holder.tvRightTime.setText(formatTime(message.getTime()));
holder.ivTestAccount.setVisibility(View.GONE); // 自己发送的消息隐藏测试账号标识
// 暂时注释掉消息状态相关代码
// 设置消息状态
// if (holder.ivMessageStatus != null) {
// holder.ivMessageStatus.setVisibility(View.VISIBLE);
// setMessageStatusIcon(holder.ivMessageStatus, message.getStatus());
// }
} else {
// 对方发送的消息 - 左侧显示
holder.layoutRight.setVisibility(View.GONE);
holder.layoutLeft.setVisibility(View.VISIBLE);
holder.tvLeftMessage.setText(message.getContent());
holder.tvLeftTime.setText(formatTime(message.getTime()));
// 显示测试账号标识
if (message.getSenderId() != null && (
message.getSenderId().equals("13800138001") ||
message.getSenderId().equals("13800138002") ||
message.getSenderId().equals("13800138003"))
) {
holder.ivTestAccount.setVisibility(View.VISIBLE);
} else {
holder.ivTestAccount.setVisibility(View.GONE);
}
// 暂时注释掉消息状态相关代码
// 隐藏自己发送消息才显示的状态图标
// if (holder.ivMessageStatus != null) {
// holder.ivMessageStatus.setVisibility(View.GONE);
// }
}
}
/**
*
*/
private String formatTime(String timeStr) {
// 简单的时间格式化如果需要更复杂的格式可以使用SimpleDateFormat
return timeStr;
}
/**
*
*/
private void setMessageStatusIcon(ImageView statusIcon, String status) {
// 暂时移除对不存在drawable资源的引用
statusIcon.setVisibility(View.GONE);
/*switch (status) {
case ChatMessage.STATUS_SENDING:
// 设置发送中图标的样式(可以使用不同的图标或颜色)
// statusIcon.setImageResource(R.drawable.ic_message_sending);
break;
case ChatMessage.STATUS_SENT:
// 设置已发送图标的样式
// statusIcon.setImageResource(R.drawable.ic_message_sent);
break;
case ChatMessage.STATUS_DELIVERED:
// 设置已送达图标的样式
// statusIcon.setImageResource(R.drawable.ic_message_delivered);
break;
case ChatMessage.STATUS_READ:
// 设置已读图标的样式
// statusIcon.setImageResource(R.drawable.ic_message_read);
break;
default:
// 默认隐藏图标
statusIcon.setVisibility(View.GONE);
}*/
}
@Override
public int getItemCount() {
return messageList != null ? messageList.size() : 0;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout layoutLeft;
LinearLayout layoutRight;
TextView tvLeftMessage;
TextView tvRightMessage;
TextView tvLeftTime;
TextView tvRightTime;
ImageView ivTestAccount;
// 暂时注释掉以避免找不到视图ID的错误
// ImageView ivMessageStatus; // 消息状态图标
public ViewHolder(@NonNull View itemView) {
super(itemView);
layoutLeft = itemView.findViewById(R.id.layoutLeft);
layoutRight = itemView.findViewById(R.id.layoutRight);
tvLeftMessage = itemView.findViewById(R.id.tvLeftMessage);
tvRightMessage = itemView.findViewById(R.id.tvRightMessage);
tvLeftTime = itemView.findViewById(R.id.tvLeftTime);
tvRightTime = itemView.findViewById(R.id.tvRightTime);
ivTestAccount = itemView.findViewById(R.id.ivTestAccount);
// 暂时注释掉以避免找不到视图ID的错误
// ivMessageStatus = itemView.findViewById(R.id.ivMessageStatus); // 初始化状态图标
}
}
}

@ -0,0 +1,92 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.activity.ChatActivity;
import com.startsmake.llrisetabbardemo.model.Conversation;
import java.util.List;
public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ViewHolder> {
private Context context;
private List<Conversation> conversationList;
public ConversationAdapter(Context context, List<Conversation> conversationList) {
this.context = context;
this.conversationList = conversationList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_conversation, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Conversation conversation = conversationList.get(position);
holder.tvUsername.setText(conversation.getUsername());
holder.tvLastMessage.setText(conversation.getLastMessage());
holder.tvTime.setText(conversation.getTime());
// 设置未读消息数量
if (conversation.getUnreadCount() > 0) {
holder.tvUnreadCount.setVisibility(View.VISIBLE);
holder.tvUnreadCount.setText(String.valueOf(conversation.getUnreadCount()));
} else {
holder.tvUnreadCount.setVisibility(View.GONE);
}
// 显示测试账号标识
if (conversation.getId().equals("13800138001") ||
conversation.getId().equals("13800138002") ||
conversation.getId().equals("13800138003")) {
holder.ivTestAccount.setVisibility(View.VISIBLE);
} else {
holder.ivTestAccount.setVisibility(View.GONE);
}
// 设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 跳转到聊天页面
Intent intent = new Intent(context, ChatActivity.class);
intent.putExtra("chat_title", conversation.getUsername());
intent.putExtra("user_id", conversation.getId());
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return conversationList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvUsername;
TextView tvLastMessage;
TextView tvTime;
TextView tvUnreadCount;
ImageView ivTestAccount;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvUsername = itemView.findViewById(R.id.tvUsername);
tvLastMessage = itemView.findViewById(R.id.tvLastMessage);
tvTime = itemView.findViewById(R.id.tvTime);
tvUnreadCount = itemView.findViewById(R.id.tvUnreadCount);
ivTestAccount = itemView.findViewById(R.id.ivTestAccount);
}
}
}

@ -0,0 +1,84 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.startsmake.llrisetabbardemo.R;
import java.util.List;
public class ImageAdapter extends BaseAdapter {
private Context context;
private List<Uri> imageUris;
private static final int MAX_IMAGES = 9;
public ImageAdapter(Context context, List<Uri> imageUris) {
this.context = context;
this.imageUris = imageUris;
}
@Override
public int getCount() {
return Math.min(imageUris.size() + 1, MAX_IMAGES);
}
@Override
public Object getItem(int position) {
if (position < imageUris.size()) {
return imageUris.get(position);
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_image, parent, false);
holder = new ViewHolder();
holder.imageView = convertView.findViewById(R.id.imageView);
holder.deleteButton = convertView.findViewById(R.id.btnDelete);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position < imageUris.size()) {
// 显示已选择的图片
Uri imageUri = imageUris.get(position);
Glide.with(context)
.load(imageUri)
.placeholder(android.R.drawable.ic_menu_gallery) // 使用系统图标作为占位符
.into(holder.imageView);
holder.deleteButton.setVisibility(View.VISIBLE);
holder.deleteButton.setOnClickListener(v -> {
imageUris.remove(position);
notifyDataSetChanged();
});
} else {
// 显示添加按钮
holder.imageView.setImageResource(android.R.drawable.ic_input_add); // 使用系统图标
holder.deleteButton.setVisibility(View.GONE);
}
return convertView;
}
static class ViewHolder {
ImageView imageView;
ImageView deleteButton;
}
}

@ -0,0 +1,79 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.startsmake.llrisetabbardemo.R;
import java.io.File;
import java.util.List;
public class ImagePreviewAdapter extends RecyclerView.Adapter<ImagePreviewAdapter.PreviewViewHolder> {
private List<String> imageUrls;
public ImagePreviewAdapter(List<String> imageUrls) {
this.imageUrls = imageUrls;
}
@NonNull
@Override
public PreviewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_image_preview, parent, false);
return new PreviewViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull PreviewViewHolder holder, int position) {
if (imageUrls != null && position < imageUrls.size()) {
String imageUrl = imageUrls.get(position);
loadImage(holder.imageView, imageUrl);
}
}
@Override
public int getItemCount() {
return imageUrls != null ? imageUrls.size() : 0;
}
private void loadImage(ImageView imageView, String imageUrl) {
try {
// 检查是否是本地图片路径
if (imageUrl != null && imageUrl.startsWith("/")) {
// 对于本地图片创建File对象并检查是否存在
File file = new File(imageUrl);
if (file.exists() && file.isFile() && file.length() > 0) {
// 使用Glide加载本地图片
Glide.with(imageView.getContext())
.load(file)
.fitCenter()
.into(imageView);
}
} else {
// 使用Glide加载网络图片
Glide.with(imageView.getContext())
.load(imageUrl)
.fitCenter()
.into(imageView);
}
} catch (Exception e) {
// 加载失败,不做特殊处理
}
}
static class PreviewViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
public PreviewViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.iv_preview_image);
}
}
}

@ -0,0 +1,102 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.MessageItem;
import java.util.List;
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder> {
private Context context;
private List<MessageItem> messageList;
private OnItemClickListener onItemClickListener;
public MessageAdapter(Context context, List<MessageItem> messageList) {
this.context = context;
this.messageList = messageList;
}
// 添加点击监听接口
public interface OnItemClickListener {
void onItemClick(MessageItem item);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_message, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
MessageItem item = messageList.get(position);
// 设置默认白色头像背景
holder.ivAvatar.setBackgroundResource(R.drawable.bg_avatar_placeholder);
holder.tvTitle.setText(item.getTitle());
holder.tvContent.setText(item.getContent());
holder.tvTime.setText(item.getTime());
// 未读消息数量
if (item.getUnreadCount() > 0) {
holder.tvUnreadCount.setVisibility(View.VISIBLE);
holder.tvUnreadCount.setText(String.valueOf(item.getUnreadCount()));
} else {
holder.tvUnreadCount.setVisibility(View.GONE);
}
// 官方标识
if (item.isOfficial()) {
holder.ivOfficial.setVisibility(View.VISIBLE);
} else {
holder.ivOfficial.setVisibility(View.GONE);
}
// 添加点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(item);
}
}
});
}
@Override
public int getItemCount() {
return messageList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivAvatar;
TextView tvTitle;
TextView tvContent;
TextView tvTime;
TextView tvUnreadCount;
ImageView ivOfficial;
public ViewHolder(@NonNull View itemView) {
super(itemView);
ivAvatar = itemView.findViewById(R.id.ivAvatar);
tvTitle = itemView.findViewById(R.id.tvTitle);
tvContent = itemView.findViewById(R.id.tvContent);
tvTime = itemView.findViewById(R.id.tvTime);
tvUnreadCount = itemView.findViewById(R.id.tvUnreadCount);
ivOfficial = itemView.findViewById(R.id.ivOfficial);
}
}
}

@ -0,0 +1,222 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Product;
import java.util.ArrayList;
import java.util.List;
public class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.ViewHolder> {
private List<Product> productList;
private OnItemClickListener listener;
private OnItemLongClickListener longClickListener;
public interface OnItemClickListener {
void onItemClick(Product product);
}
public interface OnItemLongClickListener {
boolean onItemLongClick(Product product);
}
public SearchAdapter(List<Product> productList) {
this.productList = productList;
}
public void updateData(List<Product> newList) {
this.productList = newList;
notifyDataSetChanged();
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) {
this.longClickListener = longClickListener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_product, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Product product = productList.get(position);
holder.productName.setText(product.getName());
holder.productDescription.setText(product.getDescription());
holder.productCategory.setText(product.getCategory());
holder.productPrice.setText(String.format("¥%.2f", product.getPrice()));
// 显示点赞和浏览量
holder.productStats.setText(String.format("❤️ %d 👁 %d",
product.getLikeCount() != null ? product.getLikeCount() : 0,
product.getViewCount() != null ? product.getViewCount() : 0));
// 加载商品图片
if (product.getImageUrl() != null && !product.getImageUrl().isEmpty()) {
Log.d("SearchAdapter", "开始加载图片URL: " + product.getImageUrl());
// 检查是否是本地文件路径或网络URL
if (product.getImageUrl().startsWith("http")) {
// 对于网络URL我们可以使用占位图实际应用中应使用图片加载库
holder.productImage.setImageResource(R.mipmap.ic_launcher);
Log.d("SearchAdapter", "使用网络URL: " + product.getImageUrl());
} else {
// 对于本地文件路径,直接从文件加载
try {
String imagePath = product.getImageUrl();
Log.d("SearchAdapter", "尝试加载本地图片: " + imagePath);
File imageFile = new File(imagePath);
Log.d("SearchAdapter", "文件路径: " + imageFile.getAbsolutePath() + ", 存在: " + imageFile.exists() + ", 可读: " + imageFile.canRead());
if (imageFile.exists() && imageFile.canRead()) {
// 首先检查文件大小
long fileSize = imageFile.length();
Log.d("SearchAdapter", "图片文件大小: " + fileSize + " 字节");
// 使用Options来解码图片信息不加载到内存
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
// 检查图片维度
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
Log.d("SearchAdapter", "图片维度: " + imageWidth + "x" + imageHeight);
// 对图片进行适当的缩放,避免内存问题
int maxWidth = 300;
int maxHeight = 300;
// 计算inSampleSize以减少内存使用
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);
if (bitmap != null) {
Log.d("SearchAdapter", "成功解码图片,缩放后大小: " + bitmap.getWidth() + "x" + bitmap.getHeight());
// 确保图片大小适合显示
if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) {
// 计算缩放比例
float scaleWidth = ((float) maxWidth) / bitmap.getWidth();
float scaleHeight = ((float) maxHeight) / bitmap.getHeight();
float scale = Math.min(scaleWidth, scaleHeight);
// 创建矩阵进行缩放
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
// 缩放图片
Bitmap scaledBitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
bitmap.recycle(); // 回收原图
holder.productImage.setImageBitmap(scaledBitmap);
Log.d("SearchAdapter", "成功加载并缩放本地图片: " + imagePath);
} else {
holder.productImage.setImageBitmap(bitmap);
Log.d("SearchAdapter", "成功加载本地图片(无需缩放): " + imagePath);
}
} else {
Log.e("SearchAdapter", "无法解码图片: " + imagePath);
holder.productImage.setImageResource(R.mipmap.ic_launcher);
}
} else {
Log.e("SearchAdapter", "本地图片文件不存在或不可读: " + imagePath);
holder.productImage.setImageResource(R.mipmap.ic_launcher);
}
} catch (Exception e) {
Log.e("SearchAdapter", "加载本地图片失败: " + e.getMessage(), e);
holder.productImage.setImageResource(R.mipmap.ic_launcher);
}
}
} else {
Log.d("SearchAdapter", "图片URL为空使用默认占位图");
// 使用默认占位图
holder.productImage.setImageResource(R.mipmap.ic_launcher);
}
// 设置点击事件
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(product);
}
});
// 设置长按事件
holder.itemView.setOnLongClickListener(v -> {
if (longClickListener != null) {
return longClickListener.onItemLongClick(product);
}
return false;
});
}
@Override
public int getItemCount() {
return productList.size();
}
// 计算合适的inSampleSize以减少内存使用
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 原始图片尺寸
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// 计算最大的inSampleSize值该值是2的幂并且保持结果尺寸不小于请求的尺寸
while ((halfHeight / inSampleSize) >= reqHeight &&
(halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView productImage;
TextView productName;
TextView productDescription;
TextView productCategory;
TextView productPrice;
TextView productStats;
public ViewHolder(@NonNull View itemView) {
super(itemView);
productImage = itemView.findViewById(R.id.product_image);
productName = itemView.findViewById(R.id.product_name);
productDescription = itemView.findViewById(R.id.product_description);
productCategory = itemView.findViewById(R.id.product_category);
productPrice = itemView.findViewById(R.id.product_price);
productStats = itemView.findViewById(R.id.product_stats);
}
}
}

@ -0,0 +1,89 @@
package com.startsmake.llrisetabbardemo.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Shop;
import java.util.List;
public class ShopAdapter extends RecyclerView.Adapter<ShopAdapter.ViewHolder> {
private List<Shop> shopList;
private OnItemClickListener onItemClickListener;
// 定义点击监听接口
public interface OnItemClickListener {
void onItemClick(Shop shop, int position);
}
// 构造方法
public ShopAdapter(List<Shop> shopList) {
this.shopList = shopList;
}
// 设置点击监听器(只有一个方法)
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_shop, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Shop shop = shopList.get(position);
holder.textName.setText(shop.getName());
holder.textDescription.setText(shop.getDescription());
if (shop.getImage() != null) {
holder.imageView.setImageBitmap(shop.getImage());
} else {
// 使用自定义的默认图片
holder.imageView.setImageResource(R.drawable.ic_about);
}
// 添加点击事件
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(shop, position);
}
});
}
@Override
public int getItemCount() {
return shopList != null ? shopList.size() : 0;
}
// 更新数据方法
public void updateData(List<Shop> newShopList) {
this.shopList = newShopList;
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView textName;
public TextView textDescription;
public ViewHolder(View view) {
super(view);
imageView = view.findViewById(R.id.imgShop);
textName = view.findViewById(R.id.txtShopName);
textDescription = view.findViewById(R.id.txtShopDescription);
}
}
}

@ -0,0 +1,29 @@
package com.startsmake.llrisetabbardemo.api;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;
public class ApiClient {
public static final String BASE_URL = "http://10.0.2.2:8080/api/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

@ -0,0 +1,214 @@
package com.startsmake.llrisetabbardemo.api;
import com.startsmake.llrisetabbardemo.api.response.BaseResponse;
import com.startsmake.llrisetabbardemo.api.response.ChatMessageResponse;
import com.startsmake.llrisetabbardemo.api.response.ProductResponse;
import com.startsmake.llrisetabbardemo.api.response.UserResponse;
import com.startsmake.llrisetabbardemo.model.Address;
import com.startsmake.llrisetabbardemo.model.Conversation;
import java.util.List;
import java.util.Map;
import retrofit2.http.Body;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
public interface ApiService {
// 获取API状态
@GET("api")
Call<BaseResponse> getApiStatus();
// 用户登录
@FormUrlEncoded
@POST("api/login")
Call<UserResponse> login(@Field("phone") String phone, @Field("password") String password);
// 用户注册
@FormUrlEncoded
@POST("api/register")
Call<UserResponse> register(@Field("phone") String phone, @Field("password") String password,
@Field("username") String username);
// 获取商品列表
@GET("api/products")
Call<BaseResponse<List<ProductResponse>>> getProducts();
// 搜索商品
@GET("api/products/search")
Call<BaseResponse<List<ProductResponse>>> searchProducts(@Query("keyword") String keyword);
// 获取商品详情
@GET("api/products/detail")
Call<BaseResponse<ProductResponse>> getProductDetail(@Query("id") String productId);
// 获取用户信息
@GET("api/user/info")
Call<UserResponse> getUserInfo(@Header("Authorization") String token);
// 更新用户信息
@POST("/api/users/update")
Call<BaseResponse<UserResponse.UserInfo>> updateUserInfo(
@Header("Authorization") String token,
@Body Map<String, Object> userInfo
);
// 获取会话列表
@GET("/api/conversations")
Call<BaseResponse<List<Conversation>>> getUserConversations(
@Header("Authorization") String token
);
// 获取会话消息
@GET("/api/conversations/messages")
Call<BaseResponse<List<ChatMessageResponse>>> getConversationMessages(
@Header("Authorization") String token,
@Query("userId") String userId,
@Query("targetId") String targetId
);
// 发送消息
@POST("/api/messages")
Call<BaseResponse<ChatMessageResponse>> sendMessage(
@Header("Authorization") String token,
@Query("targetId") String targetId,
@Query("content") String content,
@Query("itemId") String itemId
);
// 标记消息已读
@PUT("/api/messages/read")
Call<BaseResponse> markMessagesAsRead(
@Header("Authorization") String token,
@Query("userId") String userId,
@Query("targetId") String targetId
);
// 订单相关接口
// 创建订单
@POST("/api/orders")
Call<BaseResponse<Map<String, Object>>> createOrder(
@Header("Authorization") String token,
@Body Map<String, Object> orderData
);
// 获取订单详情
@GET("/api/orders/{id}")
Call<BaseResponse<Map<String, Object>>> getOrderDetail(
@Header("Authorization") String token,
@Path("id") String orderId
);
// 通过订单号获取订单
@GET("/api/orders/order-no/{orderNo}")
Call<BaseResponse<Map<String, Object>>> getOrderByOrderNo(
@Header("Authorization") String token,
@Path("orderNo") String orderNo
);
// 获取买家订单列表
@GET("/api/orders/buyer/{buyerId}")
Call<BaseResponse<List<Map<String, Object>>>> getBuyerOrders(
@Header("Authorization") String token,
@Path("buyerId") String buyerId
);
// 获取卖家订单列表
@GET("/api/orders/seller/{sellerId}")
Call<BaseResponse<List<Map<String, Object>>>> getSellerOrders(
@Header("Authorization") String token,
@Path("sellerId") String sellerId
);
// 更新订单状态
@PUT("/api/orders/{id}/status")
Call<BaseResponse<Map<String, Object>>> updateOrderStatus(
@Header("Authorization") String token,
@Path("id") String orderId,
@Body Map<String, Object> statusData
);
// 更新订单支付信息
@PUT("/api/orders/{id}/payment")
Call<BaseResponse<Map<String, Object>>> updateOrderPayment(
@Header("Authorization") String token,
@Path("id") String orderId,
@Body Map<String, Object> paymentData
);
// 取消订单
@PUT("/api/orders/{id}/cancel")
Call<BaseResponse> cancelOrder(
@Header("Authorization") String token,
@Path("id") String orderId
);
// 获取买家指定状态的订单
@GET("/api/orders/buyer/{buyerId}/status/{status}")
Call<BaseResponse<List<Map<String, Object>>>> getBuyerOrdersByStatus(
@Header("Authorization") String token,
@Path("buyerId") String buyerId,
@Path("status") String status
);
// 获取卖家指定状态的订单
@GET("/api/orders/seller/{sellerId}/status/{status}")
Call<BaseResponse<List<Map<String, Object>>>> getSellerOrdersByStatus(
@Header("Authorization") String token,
@Path("sellerId") String sellerId,
@Path("status") String status
);
// 地址管理相关接口
// 获取用户地址列表
@GET("/api/addresses")
Call<BaseResponse<List<Address>>> getUserAddresses(
@Header("Authorization") String token
);
// 获取用户默认地址
@GET("/api/addresses/default")
Call<BaseResponse<Address>> getDefaultAddress(
@Header("Authorization") String token
);
// 添加新地址
@POST("/api/addresses")
Call<BaseResponse<Address>> addAddress(
@Header("Authorization") String token,
@Body Address address
);
// 更新地址
@PUT("/api/addresses/{id}")
Call<BaseResponse<Address>> updateAddress(
@Header("Authorization") String token,
@Path("id") String addressId,
@Body Address address
);
// 删除地址
@DELETE("/api/addresses/{id}")
Call<BaseResponse> deleteAddress(
@Header("Authorization") String token,
@Path("id") String addressId
);
// 设置默认地址
@PUT("/api/addresses/{id}/default")
Call<BaseResponse> setDefaultAddress(
@Header("Authorization") String token,
@Path("id") String addressId
);
}

@ -0,0 +1,69 @@
package com.startsmake.llrisetabbardemo.api;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class TestNetworkConnection extends AsyncTask<Void, Void, String> {
private Context context;
public TestNetworkConnection(Context context) {
this.context = context;
}
@Override
protected String doInBackground(Void... voids) {
String result = "";
HttpURLConnection urlConnection = null;
try {
// 测试服务器地址
URL url = new URL(ApiClient.BASE_URL);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(10000); // 10秒连接超时
urlConnection.setReadTimeout(10000); // 10秒读取超时
urlConnection.setRequestMethod("GET");
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应
InputStream inputStream = urlConnection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
result = stringBuilder.toString();
reader.close();
inputStream.close();
} else {
result = "连接失败,响应码: " + responseCode;
}
} catch (IOException e) {
result = "连接异常: " + e.getMessage();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return result;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Toast.makeText(context, "网络连接测试结果: " + result, Toast.LENGTH_LONG).show();
}
}

@ -0,0 +1,43 @@
package com.startsmake.llrisetabbardemo.api.response;
import com.google.gson.annotations.SerializedName;
public class BaseResponse<T> {
@SerializedName("status")
private String status;
@SerializedName("message")
private String message;
@SerializedName("data")
private T data;
// Getters and Setters
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public boolean isSuccess() {
return "success".equals(status);
}
}

@ -0,0 +1,105 @@
package com.startsmake.llrisetabbardemo.api.response;
import com.google.gson.annotations.SerializedName;
public class ChatMessageResponse {
@SerializedName("id")
private String id;
@SerializedName("senderId")
private String senderId;
@SerializedName("receiverId")
private String receiverId;
@SerializedName("content")
private String content;
@SerializedName("time")
private String time;
@SerializedName("read")
private boolean read;
@SerializedName("type")
private String type;
@SerializedName("senderName")
private String senderName;
@SerializedName("receiverName")
private String receiverName;
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSenderId() {
return senderId;
}
public void setSenderId(String senderId) {
this.senderId = senderId;
}
public String getReceiverId() {
return receiverId;
}
public void setReceiverId(String receiverId) {
this.receiverId = receiverId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSenderName() {
return senderName;
}
public void setSenderName(String senderName) {
this.senderName = senderName;
}
public String getReceiverName() {
return receiverName;
}
public void setReceiverName(String receiverName) {
this.receiverName = receiverName;
}
}

@ -0,0 +1,107 @@
package com.startsmake.llrisetabbardemo.api.response;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class ProductResponse {
@SerializedName("id")
private String id;
@SerializedName("title")
private String title;
@SerializedName("description")
private String description;
@SerializedName("category")
private String category;
@SerializedName("price")
private double price;
@SerializedName("image_urls")
private List<String> imageUrls;
@SerializedName("contact")
private String contact;
@SerializedName("publish_time")
private long publishTime;
@SerializedName("seller_id")
private String sellerId;
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public List<String> getImageUrls() {
return imageUrls;
}
public void setImageUrls(List<String> imageUrls) {
this.imageUrls = imageUrls;
}
public String getContact() {
return contact;
}
public void setContact(String contact) {
this.contact = contact;
}
public long getPublishTime() {
return publishTime;
}
public void setPublishTime(long publishTime) {
this.publishTime = publishTime;
}
public String getSellerId() {
return sellerId;
}
public void setSellerId(String sellerId) {
this.sellerId = sellerId;
}
}

@ -0,0 +1,140 @@
package com.startsmake.llrisetabbardemo.api.response;
import com.google.gson.annotations.SerializedName;
public class UserResponse extends BaseResponse<UserResponse.UserInfo> {
public static class UserInfo {
@SerializedName("id")
private String id;
@SerializedName("username")
private String username;
@SerializedName("phone")
private String phone;
@SerializedName("token")
private String token;
@SerializedName("avatar")
private String avatar;
@SerializedName("bio")
private String bio;
@SerializedName("creditScore")
private String creditScore;
@SerializedName("memberLevel")
private String memberLevel;
@SerializedName("wantCount")
private Integer wantCount;
@SerializedName("sellingCount")
private Integer sellingCount;
@SerializedName("soldCount")
private Integer soldCount;
@SerializedName("savedMoney")
private String savedMoney;
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getBio() {
return bio;
}
public void setBio(String bio) {
this.bio = bio;
}
public String getCreditScore() {
return creditScore;
}
public void setCreditScore(String creditScore) {
this.creditScore = creditScore;
}
public String getMemberLevel() {
return memberLevel;
}
public void setMemberLevel(String memberLevel) {
this.memberLevel = memberLevel;
}
public Integer getWantCount() {
return wantCount;
}
public void setWantCount(Integer wantCount) {
this.wantCount = wantCount;
}
public Integer getSellingCount() {
return sellingCount;
}
public void setSellingCount(Integer sellingCount) {
this.sellingCount = sellingCount;
}
public Integer getSoldCount() {
return soldCount;
}
public void setSoldCount(Integer soldCount) {
this.soldCount = soldCount;
}
public String getSavedMoney() {
return savedMoney;
}
public void setSavedMoney(String savedMoney) {
this.savedMoney = savedMoney;
}
}
}

@ -0,0 +1,249 @@
package com.startsmake.llrisetabbardemo.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.startsmake.llrisetabbardemo.model.RecognitionFeedback;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* OCR
*/
public class FeedbackDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "FeedbackDatabaseHelper";
// 数据库信息
private static final String DATABASE_NAME = "recognition_feedback.db";
private static final int DATABASE_VERSION = 1;
// 表名
private static final String TABLE_FEEDBACK = "feedback";
// 表字段
private static final String COLUMN_ID = "id";
private static final String COLUMN_ORIGINAL_TEXT = "original_text";
private static final String COLUMN_CORRECTED_TEXT = "corrected_text";
private static final String COLUMN_IMAGE_HASH = "image_hash";
private static final String COLUMN_TIMESTAMP = "timestamp";
private static final String COLUMN_IS_PROCESSED = "is_processed";
private static final String COLUMN_CONFIDENCE_SCORE = "confidence_score";
// 日期格式化
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// 创建表的SQL
private static final String CREATE_TABLE_FEEDBACK = "CREATE TABLE " + TABLE_FEEDBACK + " (" +
COLUMN_ID + " TEXT PRIMARY KEY, " +
COLUMN_ORIGINAL_TEXT + " TEXT NOT NULL, " +
COLUMN_CORRECTED_TEXT + " TEXT NOT NULL, " +
COLUMN_IMAGE_HASH + " TEXT, " +
COLUMN_TIMESTAMP + " TEXT NOT NULL, " +
COLUMN_IS_PROCESSED + " INTEGER DEFAULT 0, " +
COLUMN_CONFIDENCE_SCORE + " REAL DEFAULT 0.0" +
");";
public FeedbackDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_FEEDBACK);
Log.d(TAG, "创建反馈数据表成功");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 简单的升级策略,实际项目中应该更加完善
if (oldVersion < newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FEEDBACK);
onCreate(db);
}
}
/**
*
*/
public boolean saveFeedback(RecognitionFeedback feedback) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_ID, feedback.getId());
values.put(COLUMN_ORIGINAL_TEXT, feedback.getOriginalText());
values.put(COLUMN_CORRECTED_TEXT, feedback.getCorrectedText());
values.put(COLUMN_IMAGE_HASH, feedback.getImageHash());
values.put(COLUMN_TIMESTAMP, DATE_FORMAT.format(feedback.getTimestamp()));
values.put(COLUMN_IS_PROCESSED, feedback.isProcessed() ? 1 : 0);
values.put(COLUMN_CONFIDENCE_SCORE, feedback.getConfidenceScore());
try {
long result = db.insert(TABLE_FEEDBACK, null, values);
Log.d(TAG, "保存反馈成功: " + feedback.getId());
return result != -1;
} catch (Exception e) {
Log.e(TAG, "保存反馈失败: " + e.getMessage());
return false;
} finally {
db.close();
}
}
/**
*
*/
public List<RecognitionFeedback> getAllFeedback() {
List<RecognitionFeedback> feedbackList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT * FROM " + TABLE_FEEDBACK + " ORDER BY " + COLUMN_TIMESTAMP + " DESC";
try (Cursor cursor = db.rawQuery(query, null)) {
if (cursor.moveToFirst()) {
do {
RecognitionFeedback feedback = cursorToFeedback(cursor);
if (feedback != null) {
feedbackList.add(feedback);
}
} while (cursor.moveToNext());
}
} catch (Exception e) {
Log.e(TAG, "获取反馈列表失败: " + e.getMessage());
} finally {
db.close();
}
return feedbackList;
}
/**
*
*/
public List<RecognitionFeedback> getUnprocessedFeedback() {
List<RecognitionFeedback> feedbackList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT * FROM " + TABLE_FEEDBACK +
" WHERE " + COLUMN_IS_PROCESSED + " = 0 " +
" ORDER BY " + COLUMN_TIMESTAMP + " DESC";
try (Cursor cursor = db.rawQuery(query, null)) {
if (cursor.moveToFirst()) {
do {
RecognitionFeedback feedback = cursorToFeedback(cursor);
if (feedback != null) {
feedbackList.add(feedback);
}
} while (cursor.moveToNext());
}
} catch (Exception e) {
Log.e(TAG, "获取未处理反馈失败: " + e.getMessage());
} finally {
db.close();
}
return feedbackList;
}
/**
*
*/
public boolean markAsProcessed(String feedbackId) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_IS_PROCESSED, 1);
try {
int result = db.update(TABLE_FEEDBACK, values, COLUMN_ID + " = ?", new String[]{feedbackId});
Log.d(TAG, "标记反馈为已处理: " + feedbackId + ", 影响行数: " + result);
return result > 0;
} catch (Exception e) {
Log.e(TAG, "标记反馈为已处理失败: " + e.getMessage());
return false;
} finally {
db.close();
}
}
/**
*
*/
public boolean deleteFeedback(String feedbackId) {
SQLiteDatabase db = this.getWritableDatabase();
try {
int result = db.delete(TABLE_FEEDBACK, COLUMN_ID + " = ?", new String[]{feedbackId});
Log.d(TAG, "删除反馈: " + feedbackId + ", 影响行数: " + result);
return result > 0;
} catch (Exception e) {
Log.e(TAG, "删除反馈失败: " + e.getMessage());
return false;
} finally {
db.close();
}
}
/**
*
*/
public boolean clearAllFeedback() {
SQLiteDatabase db = this.getWritableDatabase();
try {
db.delete(TABLE_FEEDBACK, null, null);
Log.d(TAG, "清空所有反馈数据");
return true;
} catch (Exception e) {
Log.e(TAG, "清空反馈数据失败: " + e.getMessage());
return false;
} finally {
db.close();
}
}
/**
* CursorRecognitionFeedback
*/
private RecognitionFeedback cursorToFeedback(Cursor cursor) {
try {
RecognitionFeedback feedback = new RecognitionFeedback();
// 使用反射或直接创建对象的方式设置id
try {
Field idField = RecognitionFeedback.class.getDeclaredField("id");
idField.setAccessible(true);
idField.set(feedback, cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ID)));
} catch (Exception e) {
// 如果反射失败,至少记录错误但继续执行
Log.e(TAG, "Failed to set feedback ID", e);
}
feedback.setOriginalText(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ORIGINAL_TEXT)));
feedback.setCorrectedText(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_CORRECTED_TEXT)));
feedback.setImageHash(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_IMAGE_HASH)));
String timestampStr = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_TIMESTAMP));
try {
feedback.setTimestamp(DATE_FORMAT.parse(timestampStr));
} catch (ParseException e) {
feedback.setTimestamp(new Date());
}
feedback.setProcessed(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_IS_PROCESSED)) == 1);
feedback.setConfidenceScore(cursor.getFloat(cursor.getColumnIndexOrThrow(COLUMN_CONFIDENCE_SCORE)));
return feedback;
} catch (Exception e) {
Log.e(TAG, "Cursor转换失败: " + e.getMessage());
return null;
}
}
}

@ -0,0 +1,47 @@
package com.startsmake.llrisetabbardemo.decoration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class RoadItemDecoration extends RecyclerView.ItemDecoration {
private Paint roadPaint;
private int roadWidth;
public RoadItemDecoration(Context context) {
roadPaint = new Paint();
roadPaint.setColor(Color.GRAY);
roadPaint.setStyle(Paint.Style.FILL);
roadWidth = dpToPx(context, 2);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % 2;
// 设置道路间距
if (column == 0) {
outRect.right = roadWidth / 2;
} else {
outRect.left = roadWidth / 2;
}
// 行间道路
if (position >= 2) {
outRect.top = roadWidth;
}
}
private int dpToPx(Context context, int dp) {
float density = context.getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
}

@ -1,24 +1,141 @@
package com.startsmake.llrisetabbardemo.fragment;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.GridLayoutManager;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.activity.ShopDetailActivity;
import com.startsmake.llrisetabbardemo.adapter.ShopAdapter;
import com.startsmake.llrisetabbardemo.model.Shop;
import com.startsmake.llrisetabbardemo.decoration.RoadItemDecoration;
import java.util.ArrayList;
import java.util.List;
import java.io.InputStream;
import java.io.IOException;
/**
* User:Shine
* Date:2015-10-20
* Description:
*/
public class CityFragment extends Fragment {
@Nullable
private RecyclerView recyclerView;
private ShopAdapter shopAdapter;
public CityFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_city, container, false);
initViews(view);
setupRecyclerView();
return view;
}
private void initViews(View view) {
recyclerView = view.findViewById(R.id.recyclerView);
}
private void setupRecyclerView() {
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
recyclerView.setLayoutManager(layoutManager);
// 添加间距装饰
recyclerView.addItemDecoration(new RoadItemDecoration(getContext()));
List<Shop> shopList = createShopData(getContext());
shopAdapter = new ShopAdapter(shopList);
// 在 setupRecyclerView() 方法中修改点击事件
shopAdapter.setOnItemClickListener((shop, position) -> {
// 处理店铺点击事件
Log.d("CityFragment", "Clicked on shop: " + shop.getName());
// 跳转到店铺详情页,传递基本数据而不是整个对象
Intent intent = new Intent(getActivity(), ShopDetailActivity.class);
intent.putExtra(ShopDetailActivity.EXTRA_AREA_NAME, shop.getName());
intent.putExtra(ShopDetailActivity.EXTRA_AREA_DESCRIPTION, shop.getDescription());
// 如果有图片,可以转换为字节数组传递(可选)
if (shop.getImage() != null) {
// 这里可以选择传递图片或者不传递,因为 ShopDetailActivity 会从 assets 加载
}
startActivity(intent);
// 添加页面切换动画
if (getActivity() != null) {
getActivity().overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}
});
recyclerView.setAdapter(shopAdapter);
// 调试信息
Log.d("CityFragment", "Shop list size: " + shopList.size());
for (Shop shop : shopList) {
Log.d("CityFragment", "Shop: " + shop.getName() + ", Image: " + (shop.getImage() != null));
}
}
private List<Shop> createShopData(Context context) {
List<Shop> shops = new ArrayList<>();
String[] imagePaths = {
"shop/junmi.png",
"shop/game.png",
"shop/clothing.png",
"shop/food.png",
"shop/chaoliu.png",
"shop/sheying.png",
"shop/book.png",
};
String[] shopNames = {"军事迷", "游戏玩家", "服饰", "老吃家", "潮流玩艺", "影室","书虫"};
String[] shopDescriptions = {"不只是装备,更是信仰的延伸", "每个人都是游戏传奇", "用服饰表达态度,让日常成为秀场", "不是所有美味都值得老吃家的挑剔", "从经典到限量,满足你的收藏欲", "用镜头讲述属于你的独特故事","在这里,文字会说话,思想会发光"};
for (int i = 0; i < shopNames.length; i++) {
Bitmap bitmap = loadImageFromAssets(context, imagePaths[i]);
String shopId = "shop_" + i; // 为每个店铺生成唯一ID
shops.add(new Shop(shopId, shopNames[i], shopDescriptions[i], bitmap));
}
return shops;
}
private Bitmap loadImageFromAssets(Context context, String path) {
try {
InputStream is = context.getAssets().open(path);
// 添加图片压缩选项,避免内存溢出
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 缩小图片尺寸
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
if (bitmap == null) {
Log.e("CityFragment", "Failed to decode bitmap from assets: " + path);
}
return bitmap;
} catch (IOException e) {
Log.e("CityFragment", "Error loading image from assets: " + path, e);
return null;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_city, container, false);
public void onDestroy() {
super.onDestroy();
// 清理Bitmap资源避免内存泄漏
if (shopAdapter != null) {
// 可以在这里添加Bitmap回收逻辑
}
}
}
}

@ -0,0 +1,336 @@
package com.startsmake.llrisetabbardemo.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.fragment.CourseScheduleImportFragment.CourseInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Fragment
*/
public class CourseScheduleFragment extends Fragment {
private static final String TAG = "CourseScheduleFragment";
private Spinner spinnerWeekday;
private LinearLayout layoutCourses;
private List<CourseInfo> allCourses;
private Map<String, List<CourseInfo>> coursesByDay;
private Map<String, List<String>> subjectBooksMap; // 存储科目与推荐书籍的映射
private String[] weekdays = {"请选择星期", "周一", "周二", "周三", "周四", "周五", "周六", "周日"};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_course_schedule, container, false);
initViews(view);
loadCourseData();
return view;
}
private void initViews(View view) {
spinnerWeekday = view.findViewById(R.id.spinner_weekday);
layoutCourses = view.findViewById(R.id.layout_courses);
// 初始化科目-书籍映射
initSubjectBooksMap();
// 添加返回按钮
Button btnBack = view.findViewById(R.id.btn_back);
btnBack.setOnClickListener(v -> {
requireActivity().getSupportFragmentManager().popBackStack();
});
}
private void loadCourseData() {
allCourses = new ArrayList<>();
coursesByDay = new HashMap<>();
// 从Bundle中获取课程数据添加错误处理
if (getArguments() != null) {
try {
List<CourseInfo> importedCourses = (List<CourseInfo>) getArguments().getSerializable("COURSES_DATA");
if (importedCourses != null && !importedCourses.isEmpty()) {
allCourses.addAll(importedCourses);
organizeCoursesByDay();
setupSpinner();
showTotalCoursesInfo();
} else {
showNoDataMessage();
}
} catch (ClassCastException e) {
Log.e(TAG, "数据类型转换错误", e);
showErrorMessage("课程数据格式错误");
}
} else {
showNoDataMessage();
}
}
private void setupSpinner() {
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, weekdays);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerWeekday.setAdapter(adapter);
spinnerWeekday.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) { // 避免选中提示项
String selectedDay = weekdays[position];
displayCoursesForDay(selectedDay);
} else {
// 显示总体信息
layoutCourses.removeAllViews();
showTotalCoursesInfo();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 不需要实现
}
});
}
private void organizeCoursesByDay() {
for (CourseInfo course : allCourses) {
// 将整数类型的星期几转换为字符串
String day = convertDayOfWeekToString(course.getDayOfWeek());
if (!coursesByDay.containsKey(day)) {
coursesByDay.put(day, new ArrayList<>());
}
coursesByDay.get(day).add(course);
}
}
/**
*
*/
private String convertDayOfWeekToString(int dayOfWeek) {
String[] weekdays = {"", "周一", "周二", "周三", "周四", "周五", "周六", "周日"};
if (dayOfWeek >= 1 && dayOfWeek <= 7) {
return weekdays[dayOfWeek];
}
return "未知";
}
private void displayCoursesForDay(String dayOfWeek) {
layoutCourses.removeAllViews();
if (coursesByDay.containsKey(dayOfWeek)) {
List<CourseInfo> courses = coursesByDay.get(dayOfWeek);
for (CourseInfo course : courses) {
View courseItem = createCourseItemView(course);
layoutCourses.addView(courseItem);
}
} else {
TextView emptyText = new TextView(requireContext());
emptyText.setText(dayOfWeek + "暂无课程安排");
emptyText.setTextSize(16);
emptyText.setPadding(16, 16, 16, 16);
layoutCourses.addView(emptyText);
}
}
private View createCourseItemView(CourseInfo course) {
LinearLayout courseItemLayout = new LinearLayout(requireContext());
courseItemLayout.setOrientation(LinearLayout.VERTICAL);
courseItemLayout.setPadding(16, 16, 16, 16);
courseItemLayout.setBackgroundResource(R.drawable.course_item_background);
// 设置外边距
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, 12);
courseItemLayout.setLayoutParams(params);
// 课程名
TextView tvCourseName = new TextView(requireContext());
tvCourseName.setText(course.getCourseName());
tvCourseName.setTextSize(18);
tvCourseName.setTextColor(getResources().getColor(R.color.text_color_primary));
tvCourseName.setPadding(0, 0, 0, 8);
courseItemLayout.addView(tvCourseName);
// 时间段和地点
TextView tvTimeAndLocation = new TextView(requireContext());
tvTimeAndLocation.setText(course.getTimeSlot() + " | " + course.getLocation());
tvTimeAndLocation.setTextSize(14);
tvTimeAndLocation.setTextColor(getResources().getColor(R.color.text_color_secondary));
tvTimeAndLocation.setPadding(0, 0, 0, 8);
courseItemLayout.addView(tvTimeAndLocation);
// 添加推荐书籍按钮
Button btnRecommendBooks = new Button(requireContext());
btnRecommendBooks.setText("推荐书籍");
btnRecommendBooks.setTextSize(14);
btnRecommendBooks.setBackgroundResource(android.R.drawable.btn_default);
btnRecommendBooks.setTextColor(getResources().getColor(android.R.color.black));
// 设置按钮参数
LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
btnParams.gravity = Gravity.END;
btnRecommendBooks.setLayoutParams(btnParams);
// 设置按钮点击事件
btnRecommendBooks.setOnClickListener(v -> {
showRecommendedBooks(course.getCourseName());
});
courseItemLayout.addView(btnRecommendBooks);
return courseItemLayout;
}
private void showTotalCoursesInfo() {
TextView totalInfo = new TextView(requireContext());
if (allCourses.isEmpty()) {
totalInfo.setText("您还没有导入课程表,请点击左上角返回按钮返回课程表导入页面");
} else {
totalInfo.setText("已成功导入 " + allCourses.size() + " 门课程。请选择星期几查看对应课程安排。");
}
totalInfo.setTextSize(16);
totalInfo.setPadding(16, 16, 16, 16);
layoutCourses.addView(totalInfo);
}
private void showNoDataMessage() {
layoutCourses.removeAllViews();
TextView tvNoData = new TextView(requireContext());
tvNoData.setText("暂无课程数据,请重新导入课程表");
tvNoData.setTextSize(16);
tvNoData.setTextColor(getResources().getColor(R.color.tab_text_normal));
tvNoData.setGravity(Gravity.CENTER);
tvNoData.setPadding(0, 100, 0, 0);
layoutCourses.addView(tvNoData);
}
private void showErrorMessage(String message) {
layoutCourses.removeAllViews();
TextView tvError = new TextView(requireContext());
tvError.setText(message);
tvError.setTextSize(16);
tvError.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
tvError.setGravity(Gravity.CENTER);
tvError.setPadding(0, 100, 0, 0);
layoutCourses.addView(tvError);
}
/**
*
*/
private void initSubjectBooksMap() {
// 数学类科目推荐书籍
List<String> mathBooks = new ArrayList<>();
mathBooks.add("《高等数学》 - 同济大学数学系");
mathBooks.add("《数学分析》 - 华东师范大学数学系");
mathBooks.add("《线性代数》 - 同济大学出版社");
// 英语类科目推荐书籍
List<String> englishBooks = new ArrayList<>();
englishBooks.add("《大学英语精读》 - 上海外语教育出版社");
englishBooks.add("《新概念英语》 - 外语教学与研究出版社");
englishBooks.add("《英语语法新思维》 - 群言出版社");
// 计算机类科目推荐书籍
List<String> computerBooks = new ArrayList<>();
computerBooks.add("《程序设计基础》 - 清华大学出版社");
computerBooks.add("《数据结构与算法分析》 - 机械工业出版社");
computerBooks.add("《算法导论》 - 麻省理工学院出版社");
// 物理类科目推荐书籍
List<String> physicsBooks = new ArrayList<>();
physicsBooks.add("《大学物理》 - 高等教育出版社");
physicsBooks.add("《物理学》 - 清华大学出版社");
physicsBooks.add("《费曼物理学讲义》 - 上海科学技术出版社");
// 政治类科目推荐书籍
List<String> politicsBooks = new ArrayList<>();
politicsBooks.add("《毛泽东思想概论》 - 高等教育出版社");
politicsBooks.add("《中国近现代史纲要》 - 高等教育出版社");
politicsBooks.add("《马克思主义基本原理概论》 - 高等教育出版社");
// 通用推荐书籍(当无法识别具体科目时)
List<String> generalBooks = new ArrayList<>();
generalBooks.add("《学习之道》 - 芭芭拉·奥克利");
generalBooks.add("《如何阅读一本书》 - 莫提默·J.艾德勒");
generalBooks.add("《思考,快与慢》 - 丹尼尔·卡尼曼");
// 将书籍列表添加到映射中
subjectBooksMap.put("数学", mathBooks);
subjectBooksMap.put("高等数学", mathBooks);
subjectBooksMap.put("线性代数", mathBooks);
subjectBooksMap.put("英语", englishBooks);
subjectBooksMap.put("大学英语", englishBooks);
subjectBooksMap.put("程序", computerBooks);
subjectBooksMap.put("程序设计", computerBooks);
subjectBooksMap.put("数据结构", computerBooks);
subjectBooksMap.put("物理", physicsBooks);
subjectBooksMap.put("大学物理", physicsBooks);
subjectBooksMap.put("毛泽东", politicsBooks);
subjectBooksMap.put("政治", politicsBooks);
subjectBooksMap.put("体育", generalBooks);
}
/**
*
*/
private List<String> getRecommendedBooks(String courseName) {
// 返回通用推荐书籍
List<String> generalBooks = new ArrayList<>();
generalBooks.add("《学习之道》 - 芭芭拉·奥克利");
generalBooks.add("《如何阅读一本书》 - 莫提默·J.艾德勒");
generalBooks.add("《思考,快与慢》 - 丹尼尔·卡尼曼");
return generalBooks;
}
/**
*
*/
private void showRecommendedBooks(String courseName) {
List<String> books = getRecommendedBooks(courseName);
// 将书籍列表转换为数组
String[] bookArray = books.toArray(new String[0]);
// 创建并显示对话框
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setTitle(courseName + " 推荐书籍")
.setItems(bookArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 用户点击了某本书籍,可以添加相关逻辑,如跳转到详情页等
Toast.makeText(requireContext(), "您选择了: " + bookArray[which], Toast.LENGTH_SHORT).show();
}
})
.setPositiveButton("确定", null)
.show();
}
}

@ -0,0 +1,445 @@
package com.startsmake.llrisetabbardemo.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2;
import com.startsmake.llrisetabbardemo.activity.MainActivity;
import com.bumptech.glide.Glide;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.model.Item;
import com.startsmake.llrisetabbardemo.model.User;
import manager.DataManager;
import com.startsmake.llrisetabbardemo.manager.UserManager;
public class ItemDetailFragment extends Fragment {
private static final String ARG_ITEM = "item";
private Item item;
public static ItemDetailFragment newInstance(Item item) {
ItemDetailFragment fragment = new ItemDetailFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_ITEM, item);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
item = (Item) getArguments().getSerializable(ARG_ITEM);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_item_detail, container, false);
}
private ViewPager2 viewPagerItemImages;
private LinearLayout imageIndicator;
private boolean isLiked = false;
private boolean isFavorite = false;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (item == null) {
Toast.makeText(getContext(), "商品信息加载失败", Toast.LENGTH_SHORT).show();
return;
}
// 检查当前商品是否已经被用户点赞/收藏
// 获取Activity的Context如果不可用则创建空的UserManager引用
UserManager userManager = null;
if (getActivity() != null) {
userManager = UserManager.getInstance(getActivity());
User currentUser = userManager.getCurrentUser();
if (currentUser != null && currentUser.isItemLiked(item.getId())) {
isLiked = true;
isFavorite = true;
}
}
// 初始化视图
viewPagerItemImages = view.findViewById(R.id.viewPagerItemImages);
imageIndicator = view.findViewById(R.id.imageIndicator);
TextView tvTitle = view.findViewById(R.id.tvTitle);
TextView tvPrice = view.findViewById(R.id.tvPrice);
TextView tvDescription = view.findViewById(R.id.tvDescription);
TextView tvCategory = view.findViewById(R.id.tvCategory);
TextView tvLocation = view.findViewById(R.id.tvLocation);
TextView tvContact = view.findViewById(R.id.tvContact);
TextView tvPublishTime = view.findViewById(R.id.tvPublishTime);
ImageView btnLike = view.findViewById(R.id.btnLike);
TextView tvLikeCount = view.findViewById(R.id.tvLikeCount);
TextView tvViewCount = view.findViewById(R.id.tvViewCount);
Button btnFavorite = view.findViewById(R.id.btnFavorite);
Button btnContact = view.findViewById(R.id.btnContact);
Button btnDelete = view.findViewById(R.id.btnDelete);
// 设置商品信息
tvTitle.setText(item.getTitle());
tvPrice.setText(String.format("¥%.2f", item.getPrice()));
tvDescription.setText(item.getDescription());
tvCategory.setText("分类:" + item.getCategory());
tvLocation.setText("位置:" + item.getLocation());
tvContact.setText("联系方式:" + item.getContact());
// 设置发布时间
String time = android.text.format.DateFormat.format("yyyy-MM-dd HH:mm", item.getPublishTime()).toString();
tvPublishTime.setText("发布时间:" + time);
// 设置点赞和浏览数
tvLikeCount.setText(String.valueOf(item.getLikeCount()));
tvViewCount.setText(String.valueOf(item.getViewCount()));
// 初始化图片轮播
setupImageSlider();
// 点赞按钮点击事件
btnLike.setOnClickListener(v -> {
toggleLike(btnLike, tvLikeCount);
});
// 收藏按钮点击事件
btnFavorite.setOnClickListener(v -> {
toggleFavorite(btnFavorite);
});
// 联系卖家按钮点击事件
btnContact.setOnClickListener(v -> {
Toast.makeText(getContext(), "联系卖家:" + item.getContact(), Toast.LENGTH_SHORT).show();
});
// 添加购买按钮
Button btnBuyNow = view.findViewById(R.id.btnBuyNow);
btnBuyNow.setOnClickListener(v -> {
// 跳转到订单确认页面
Bundle bundle = new Bundle();
bundle.putSerializable("item", item);
OrderConfirmationFragment fragment = new OrderConfirmationFragment();
fragment.setArguments(bundle);
getParentFragmentManager().beginTransaction()
.replace(R.id.main_container, fragment)
.addToBackStack("order_confirmation")
.commit();
});
// 检查是否是当前用户发布的商品,如果是则显示删除按钮
checkAndShowDeleteButton(btnDelete);
// 删除按钮点击事件
btnDelete.setOnClickListener(v -> {
showDeleteConfirmationDialog();
});
// 增加浏览量
increaseViewCount(tvViewCount);
// 为底部导航栏添加点击事件
setupBottomNavigation(view);
}
// 初始化图片轮播
private void setupImageSlider() {
// 创建图片适配器
ImageSliderAdapter adapter = new ImageSliderAdapter();
viewPagerItemImages.setAdapter(adapter);
// 设置指示器
setupImageIndicators();
// 监听ViewPager页面变化更新指示器
viewPagerItemImages.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
updateIndicators(position);
}
});
}
// 设置图片指示器
private void setupImageIndicators() {
int imageCount = item.getImageUrls() != null ? item.getImageUrls().size() : 1;
imageIndicator.removeAllViews();
for (int i = 0; i < imageCount; i++) {
ImageView indicator = new ImageView(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(4, 0, 4, 0);
indicator.setLayoutParams(params);
indicator.setImageResource(i == 0 ? R.drawable.dot_selected : R.drawable.dot_unselected);
imageIndicator.addView(indicator);
}
}
// 更新指示器状态
private void updateIndicators(int position) {
for (int i = 0; i < imageIndicator.getChildCount(); i++) {
ImageView indicator = (ImageView) imageIndicator.getChildAt(i);
indicator.setImageResource(i == position ? R.drawable.dot_selected : R.drawable.dot_unselected);
}
}
// 切换点赞状态
private void toggleLike(ImageView btnLike, TextView tvLikeCount) {
isLiked = !isLiked;
int currentLikes = item.getLikeCount();
// 获取当前登录用户
if (getActivity() != null) {
UserManager userManager = UserManager.getInstance(getActivity());
User currentUser = userManager.getCurrentUser();
if (isLiked) {
item.setLikeCount(currentLikes + 1);
btnLike.setImageResource(R.drawable.ic_like_filled);
// 将商品添加到用户的想买列表
if (currentUser != null) {
currentUser.addLikedItem(item.getId());
userManager.saveCurrentUser(currentUser);
Toast.makeText(getContext(), "已添加到想买列表", Toast.LENGTH_SHORT).show();
}
} else {
item.setLikeCount(Math.max(0, currentLikes - 1));
btnLike.setImageResource(R.drawable.ic_like_unfilled);
// 从用户的想买列表中移除商品
if (currentUser != null) {
currentUser.removeLikedItem(item.getId());
userManager.saveCurrentUser(currentUser);
Toast.makeText(getContext(), "已从想买列表移除", Toast.LENGTH_SHORT).show();
}
}
}
tvLikeCount.setText(String.valueOf(item.getLikeCount()));
// 保存商品数据更新
DataManager.getInstance().updateItem(item);
}
// 切换收藏状态
private void toggleFavorite(Button btnFavorite) {
isFavorite = !isFavorite;
// 获取当前登录用户
if (getActivity() != null) {
UserManager userManager = UserManager.getInstance(getActivity());
User currentUser = userManager.getCurrentUser();
if (isFavorite) {
btnFavorite.setText("已收藏");
btnFavorite.setBackgroundResource(R.drawable.bg_button_primary);
btnFavorite.setTextColor(getResources().getColor(android.R.color.white));
// 将商品添加到用户的想买列表
if (currentUser != null) {
currentUser.addLikedItem(item.getId());
userManager.saveCurrentUser(currentUser);
Toast.makeText(getContext(), "已添加到想买列表", Toast.LENGTH_SHORT).show();
}
} else {
btnFavorite.setText("收藏");
btnFavorite.setBackgroundResource(R.drawable.bg_button_secondary);
btnFavorite.setTextColor(getResources().getColor(R.color.colorAccent));
// 从用户的想买列表中移除商品
if (currentUser != null) {
currentUser.removeLikedItem(item.getId());
userManager.saveCurrentUser(currentUser);
Toast.makeText(getContext(), "已从想买列表移除", Toast.LENGTH_SHORT).show();
}
}
}
// 保存商品数据更新
DataManager.getInstance().updateItem(item);
}
// 增加浏览量
private void increaseViewCount(TextView tvViewCount) {
int currentViews = item.getViewCount() + 1;
item.setViewCount(currentViews);
tvViewCount.setText(String.valueOf(currentViews));
}
// 检查是否是当前用户发布的商品,如果是则显示删除按钮
private void checkAndShowDeleteButton(Button btnDelete) {
// 获取Activity的Context如果不可用则创建空的UserManager引用
if (getActivity() != null) {
UserManager userManager = UserManager.getInstance(getActivity());
if (userManager != null && userManager.isLoggedIn() && item.getUserId() != null &&
item.getUserId().equals(userManager.getCurrentUserId())) {
// 是当前用户发布的商品,显示删除按钮
btnDelete.setVisibility(View.VISIBLE);
}
}
}
// 显示删除确认对话框
private void showDeleteConfirmationDialog() {
new AlertDialog.Builder(requireContext())
.setTitle("确认删除")
.setMessage("确定要删除这个商品吗?此操作不可撤销。")
.setPositiveButton("删除", (dialog, which) -> {
// 执行删除操作
deleteItem();
})
.setNegativeButton("取消", null)
.show();
}
// 删除商品
private void deleteItem() {
if (item == null) return;
boolean success = DataManager.getInstance().deleteItemById(item.getId());
if (success) {
Toast.makeText(getContext(), "商品删除成功", Toast.LENGTH_SHORT).show();
// 删除成功后返回上一个页面
getParentFragmentManager().popBackStack();
} else {
Toast.makeText(getContext(), "商品删除失败,请重试", Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param tabIndex 0-1-2-3-4-
*/
private void switchToTab(int tabIndex) {
try {
// 获取MainActivity实例
if (getActivity() instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) getActivity();
// 调用MainActivity中的switchToTab方法切换Tab
mainActivity.switchToTab(tabIndex);
Log.d("ItemDetailFragment", "切换到底部导航栏索引:" + tabIndex);
} else {
Log.e("ItemDetailFragment", "无法获取MainActivity实例");
}
} catch (Exception e) {
Log.e("ItemDetailFragment", "切换Tab失败", e);
}
}
/**
*
*/
private void setupBottomNavigation(View view) {
try {
// 不再尝试直接查找按钮因为底部导航栏是在MainActivity中管理的
// 通过switchToTab方法可以直接调用MainActivity的切换功能
Log.d("ItemDetailFragment", "底部导航栏功能已通过switchToTab方法实现");
} catch (Exception e) {
Log.e("ItemDetailFragment", "设置底部导航栏点击事件失败", e);
}
}
// 模拟更新后端数据
private void updateItemDataToBackend() {
// 在实际应用中这里应该调用API更新商品数据
// 暂时只做本地更新
}
// 图片轮播适配器
private class ImageSliderAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<ImageSliderAdapter.SliderViewHolder> {
@NonNull
@Override
public SliderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image_slider, parent, false);
return new SliderViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SliderViewHolder holder, int position) {
if (item.getImageUrls() != null && !item.getImageUrls().isEmpty() && position < item.getImageUrls().size()) {
String imageUrl = item.getImageUrls().get(position);
try {
// 检查是否是本地图片路径
if (imageUrl.startsWith("/")) {
// 对于本地图片创建File对象并检查是否存在
java.io.File file = new java.io.File(imageUrl);
if (file.exists() && file.isFile() && file.length() > 0) {
// 使用Glide加载本地图片
Glide.with(holder.imageView.getContext())
.load(file)
.centerCrop()
.into(holder.imageView);
} else {
// 文件不存在或无效,使用占位图
holder.imageView.setImageResource(R.mipmap.ic_launcher);
}
} else {
// 使用Glide加载网络图片
Glide.with(holder.imageView.getContext())
.load(imageUrl)
.centerCrop()
.into(holder.imageView);
}
} catch (Exception e) {
// 加载失败,使用占位图
holder.imageView.setImageResource(R.mipmap.ic_launcher);
}
} else {
// 如果没有图片,使用占位图
holder.imageView.setImageResource(R.mipmap.ic_launcher);
}
// 添加点击事件,跳转到图片预览页面
holder.imageView.setOnClickListener(v -> {
if (item.getImageUrls() != null && !item.getImageUrls().isEmpty() && getActivity() != null) {
try {
// 创建Intent跳转到图片预览Activity
android.content.Intent intent = new android.content.Intent(getActivity(), com.startsmake.llrisetabbardemo.activity.ImagePreviewActivity.class);
// 传递图片URL列表和当前位置
intent.putExtra(com.startsmake.llrisetabbardemo.activity.ImagePreviewActivity.EXTRA_IMAGE_URLS, new java.util.ArrayList<>(item.getImageUrls()));
intent.putExtra(com.startsmake.llrisetabbardemo.activity.ImagePreviewActivity.EXTRA_CURRENT_POSITION, position);
startActivity(intent);
} catch (Exception e) {
Log.e("ItemDetailFragment", "跳转图片预览失败", e);
}
}
});
}
@Override
public int getItemCount() {
// 如果没有图片返回1张占位图
return item.getImageUrls() != null && !item.getImageUrls().isEmpty() ?
item.getImageUrls().size() : 1;
}
class SliderViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
ImageView imageView;
public SliderViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.slider_image);
}
}
}
}

@ -1,24 +1,75 @@
package com.startsmake.llrisetabbardemo.fragment;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.activity.ChatActivity;
import com.startsmake.llrisetabbardemo.adapter.MessageAdapter;
import com.startsmake.llrisetabbardemo.model.MessageItem;
import java.util.ArrayList;
import java.util.List;
/**
* User:Shine
* Date:2015-10-20
* Description:
*/
public class MessageFragment extends Fragment {
private RecyclerView rvMessageList;
private MessageAdapter messageAdapter;
private List<MessageItem> messageList;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_message, container, false);
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_message, container, false);
initView(view);
initData();
return view;
}
private void initView(View view) {
rvMessageList = view.findViewById(R.id.rvMessageList);
rvMessageList.setLayoutManager(new LinearLayoutManager(getContext()));
}
private void initData() {
messageList = new ArrayList<>();
// 添加测试账号到消息列表
messageList.add(new MessageItem("测试账号1", "这是测试账号1的消息", "刚刚", 0, false));
messageList.add(new MessageItem("测试账号2", "这是测试账号2的消息", "1小时前", 1, false));
messageList.add(new MessageItem("测试账号3", "这是测试账号3的消息", "昨天", 0, false));
messageAdapter = new MessageAdapter(getContext(), messageList);
rvMessageList.setAdapter(messageAdapter);
// 添加点击监听
messageAdapter.setOnItemClickListener(new MessageAdapter.OnItemClickListener() {
@Override
public void onItemClick(MessageItem item) {
// 跳转到聊天页面
Intent intent = new Intent(getActivity(), ChatActivity.class);
intent.putExtra("chat_title", item.getTitle());
// 根据标题设置对应的用户ID
String userId = "";
if (item.getTitle().equals("测试账号1")) {
userId = "13800138001";
} else if (item.getTitle().equals("测试账号2")) {
userId = "13800138002";
} else if (item.getTitle().equals("测试账号3")) {
userId = "13800138003";
}
intent.putExtra("chat_user_id", userId);
startActivity(intent);
}
});
}
}
}

@ -0,0 +1,266 @@
package com.startsmake.llrisetabbardemo.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.startsmake.llrisetabbardemo.R;
import com.startsmake.llrisetabbardemo.adapter.SearchAdapter;
import com.startsmake.llrisetabbardemo.model.Product;
import com.startsmake.llrisetabbardemo.model.Item;
import com.startsmake.llrisetabbardemo.manager.UserManager;
import manager.DataManager;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment
*/
public class MyListingsFragment extends Fragment {
private static final String TAG = "MyListingsFragment";
private RecyclerView myListingsRecyclerView;
private LinearLayout noListingsText;
private SearchAdapter searchAdapter;
private List<Product> productList;
private Button btnClearAll;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView - 创建Fragment视图");
View view = inflater.inflate(R.layout.fragment_my_listings, container, false);
initViews(view);
setupRecyclerView();
return view;
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume - 刷新商品列表");
loadUserListings();
}
private void initViews(View view) {
Log.d(TAG, "initViews - 初始化视图");
myListingsRecyclerView = view.findViewById(R.id.my_listings_recycler_view);
noListingsText = view.findViewById(R.id.no_listings_text);
btnClearAll = view.findViewById(R.id.btn_clear_all);
// 为返回按钮设置点击事件监听器
view.findViewById(R.id.btn_back).setOnClickListener(v -> {
Log.d(TAG, "返回按钮被点击返回上一个Fragment");
getParentFragmentManager().popBackStack();
});
// 为清除所有按钮设置点击事件监听器
btnClearAll.setOnClickListener(v -> {
Log.d(TAG, "清除所有按钮被点击");
showClearAllConfirmationDialog();
});
}
private void setupRecyclerView() {
Log.d(TAG, "setupRecyclerView - 设置RecyclerView");
myListingsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
productList = new ArrayList<>();
searchAdapter = new SearchAdapter(productList);
searchAdapter.setOnItemClickListener(this::navigateToItemDetail);
// 设置长按监听器用于删除商品
searchAdapter.setOnItemLongClickListener(this::showDeleteConfirmationDialog);
myListingsRecyclerView.setAdapter(searchAdapter);
}
private void loadUserListings() {
Log.d(TAG, "loadUserListings - 加载用户发布的商品");
// 检查用户是否已登录需要Activity的Context
boolean isLoggedIn = false;
if (getActivity() != null) {
isLoggedIn = UserManager.getInstance(getActivity()).isLoggedIn();
}
if (!isLoggedIn) {
Log.w(TAG, "用户未登录");
showNoListings();
return;
}
// 获取当前用户ID需要Activity的Context
String userId = "";
if (getActivity() != null) {
userId = UserManager.getInstance(getActivity()).getCurrentUserId();
}
Log.d(TAG, "当前登录用户ID: " + userId);
// 从DataManager获取用户发布的商品
DataManager.getInstance().getItemsByUserIdWithCallback(userId, new DataManager.OnDataLoadedListener() {
@Override
public void onDataLoaded(List<Item> items) {
Log.d(TAG, "获取到用户商品数量: " + items.size());
if (items != null && !items.isEmpty()) {
// 将Item转换为Product
List<Product> products = new ArrayList<>();
for (Item item : items) {
Product product = new Product();
product.setId(item.getId());
product.setName(item.getTitle());
product.setDescription(item.getDescription());
product.setCategory(item.getCategory());
product.setPrice(item.getPrice());
// 获取第一张图片URL
if (item.getImageUrls() != null && !item.getImageUrls().isEmpty()) {
product.setImageUrl(item.getImageUrls().get(0));
}
products.add(product);
}
// 更新列表
productList.clear();
productList.addAll(products);
searchAdapter.updateData(productList);
// 显示列表,隐藏空状态
myListingsRecyclerView.setVisibility(View.VISIBLE);
noListingsText.setVisibility(View.GONE);
} else {
// 显示空状态
showNoListings();
}
}
@Override
public void onError(String message) {
Log.e(TAG, "加载用户商品失败: " + message);
Toast.makeText(requireContext(), "加载失败: " + message, Toast.LENGTH_SHORT).show();
showNoListings();
}
});
}
private void showNoListings() {
Log.d(TAG, "showNoListings - 显示空状态");
myListingsRecyclerView.setVisibility(View.GONE);
noListingsText.setVisibility(View.VISIBLE);
}
private void navigateToItemDetail(Product product) {
Log.d(TAG, "navigateToItemDetail - 跳转到商品详情页商品ID: " + product.getId());
// 从DataManager获取完整的Item对象
Item item = DataManager.getInstance().getItemById(product.getId());
// 如果通过ID找不到Item则回退到创建新Item对象的方式
if (item == null) {
Log.w(TAG, "通过ID未找到完整的Item对象创建新的Item对象");
item = new Item();
item.setId(product.getId());
item.setTitle(product.getName());
item.setDescription(product.getDescription());
item.setPrice(product.getPrice());
item.setCategory(product.getCategory());
// 设置默认值
item.setLocation("未知位置");
item.setContact("联系方式");
item.setPublishTime(System.currentTimeMillis());
} else {
Log.d(TAG, "成功获取到完整的Item对象包含位置: " + item.getLocation() + " 和联系方式: " + item.getContact());
}
// 使用静态工厂方法创建ItemDetailFragment实例这与应用其他部分的做法一致
ItemDetailFragment fragment = ItemDetailFragment.newInstance(item);
getParentFragmentManager().beginTransaction()
.replace(R.id.main_container, fragment)
.addToBackStack(null)
.commit();
}
/**
*
*/
private boolean showDeleteConfirmationDialog(Product product) {
Log.d(TAG, "showDeleteConfirmationDialog - 显示删除确认对话框商品ID: " + product.getId());
new AlertDialog.Builder(requireContext())
.setTitle("确认删除")
.setMessage("确定要删除这个商品吗?删除后无法恢复。")
.setPositiveButton("删除", (dialog, which) -> deleteItem(product))
.setNegativeButton("取消", null)
.show();
return true; // 消耗长按事件
}
private void showClearAllConfirmationDialog() {
Log.d(TAG, "showClearAllConfirmationDialog - 显示清除所有商品确认对话框");
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setTitle("确认清除所有商品")
.setMessage("确定要清除所有已发布的商品吗?此操作将删除所有商品记录,且无法恢复。")
.setPositiveButton("确定", (dialog, which) -> clearAllProducts())
.setNegativeButton("取消", null)
.show();
}
private void clearAllProducts() {
Log.d(TAG, "clearAllProducts - 开始清除所有商品");
try {
// 使用DataManager清除所有商品
DataManager.getInstance().clearAll();
Log.d(TAG, "所有商品已成功清除");
// 更新UI
productList.clear();
searchAdapter.updateData(productList);
showNoListings();
// 显示成功提示
Toast.makeText(requireContext(), "所有商品已成功清除", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "清除商品失败: " + e.getMessage());
Toast.makeText(requireContext(), "清除商品失败,请重试", Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
private void deleteItem(Product product) {
Log.d(TAG, "deleteItem - 删除商品商品ID: " + product.getId());
boolean success = DataManager.getInstance().deleteItemById(product.getId());
if (success) {
Log.d(TAG, "删除商品成功");
Toast.makeText(requireContext(), "商品已删除", Toast.LENGTH_SHORT).show();
// 从列表中移除并刷新
productList.remove(product);
searchAdapter.updateData(productList);
// 如果删除后列表为空,显示空状态
if (productList.isEmpty()) {
showNoListings();
}
} else {
Log.e(TAG, "删除商品失败");
Toast.makeText(requireContext(), "删除失败,请重试", Toast.LENGTH_SHORT).show();
}
}
// 用于外部调用刷新列表(例如删除商品后)
public void refreshListings() {
Log.d(TAG, "refreshListings - 外部调用刷新列表");
loadUserListings();
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save