Compare commits
3 Commits
1995a37f2f
...
202c8452c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
202c8452c5 | ||
|
|
1124ce7d82 | ||
|
|
a2287bccd1 |
|
|
@ -1,285 +0,0 @@
|
|||
# Android端API路径修复指南
|
||||
|
||||
## 🎯 目标
|
||||
|
||||
将Android端的观看历史API调用路径修改为与zhibo-h后台匹配。
|
||||
|
||||
---
|
||||
|
||||
## 📝 需要修改的文件
|
||||
|
||||
### 文件1: ApiService.java
|
||||
|
||||
**位置**: `android-app/app/src/main/java/com/zbkj/front/net/ApiService.java`
|
||||
|
||||
#### 修改内容
|
||||
|
||||
找到以下接口定义并修改:
|
||||
|
||||
```java
|
||||
// ==================== 用户活动记录 ====================
|
||||
|
||||
/**
|
||||
* 获取观看历史
|
||||
*/
|
||||
@GET("api/front/activity/view/history") // ← 修改这里:view-history 改为 view/history
|
||||
Call<ApiResponse<PageResponse<Map<String, Object>>>> getViewHistory(
|
||||
@Query("targetType") String targetType, // ← 修改这里:type 改为 targetType
|
||||
@Query("page") int page,
|
||||
@Query("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 清除观看历史
|
||||
*/
|
||||
@DELETE("api/front/activity/view/history") // ← 修改这里:view-history 改为 view/history
|
||||
Call<ApiResponse<String>> clearViewHistory(@Query("targetType") String targetType); // ← 修改这里:type 改为 targetType
|
||||
|
||||
/**
|
||||
* 获取点赞记录
|
||||
*/
|
||||
@GET("api/front/activity/like/records") // ← 修改这里:like-records 改为 like/records
|
||||
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikeRecords(
|
||||
@Query("targetType") String targetType, // ← 修改这里:type 改为 targetType
|
||||
@Query("page") int page,
|
||||
@Query("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 获取收藏的作品
|
||||
*/
|
||||
@GET("api/front/activity/collect/works") // ← 修改这里:collected-works 改为 collect/works
|
||||
Call<ApiResponse<PageResponse<Map<String, Object>>>> getCollectedWorks(
|
||||
@Query("page") int page,
|
||||
@Query("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 获取关注记录
|
||||
*/
|
||||
@GET("api/front/activity/follow/records") // ← 修改这里:follow-records 改为 follow/records
|
||||
Call<ApiResponse<PageResponse<Map<String, Object>>>> getFollowRecords(
|
||||
@Query("page") int page,
|
||||
@Query("pageSize") int pageSize);
|
||||
|
||||
/**
|
||||
* 记录观看历史(新版)
|
||||
*/
|
||||
@POST("api/front/activity/view/record") // ← 修改这里:record-view 改为 view/record
|
||||
Call<ApiResponse<Map<String, Object>>> recordViewHistoryNew(@Body Map<String, Object> body);
|
||||
|
||||
/**
|
||||
* 调试Token
|
||||
*/
|
||||
@GET("api/front/activity/debug/token") // ← 修改这里:debug-token 改为 debug/token
|
||||
Call<ApiResponse<Map<String, Object>>> debugToken();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 文件2: RoomDetailActivity.java
|
||||
|
||||
**位置**: `android-app/app/src/main/java/com/zbkj/front/livestreaming/RoomDetailActivity.java`
|
||||
|
||||
#### 修改内容
|
||||
|
||||
找到 `recordWatchHistory()` 方法,确保参数名正确:
|
||||
|
||||
```java
|
||||
private void recordWatchHistory() {
|
||||
try {
|
||||
if (!AuthHelper.isLoggedIn(this)) {
|
||||
return; // 未登录用户不记录
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(roomId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApiService apiService = ApiClient.getService(getApplicationContext());
|
||||
|
||||
// 使用新的统一观看历史API
|
||||
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
||||
body.put("targetType", "room"); // ✅ 正确:targetType
|
||||
body.put("targetId", roomId);
|
||||
body.put("targetTitle", "直播间");
|
||||
body.put("duration", 0); // ✅ 正确:duration(不是viewDuration)
|
||||
|
||||
Call<ApiResponse<java.util.Map<String, Object>>> call = apiService.recordViewHistoryNew(body);
|
||||
|
||||
call.enqueue(new Callback<ApiResponse<java.util.Map<String, Object>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<java.util.Map<String, Object>>> call,
|
||||
Response<ApiResponse<java.util.Map<String, Object>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||
android.util.Log.d("RoomDetail", "观看历史记录成功");
|
||||
// 房间信息加载后更新观看历史
|
||||
updateWatchHistoryWithRoomInfo();
|
||||
} else {
|
||||
android.util.Log.w("RoomDetail", "记录观看历史失败: " +
|
||||
(response.body() != null ? response.body().getMessage() : "unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<java.util.Map<String, Object>>> call, Throwable t) {
|
||||
// 忽略错误,不影响直播观看
|
||||
android.util.Log.w("RoomDetail", "记录观看历史失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// 忽略所有异常,不影响直播观看
|
||||
android.util.Log.w("RoomDetail", "记录观看历史失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
找到 `updateWatchHistoryWithRoomInfo()` 方法,确保参数名正确:
|
||||
|
||||
```java
|
||||
private void updateWatchHistoryWithRoomInfo() {
|
||||
if (room == null || !AuthHelper.isLoggedIn(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ApiService apiService = ApiClient.getService(getApplicationContext());
|
||||
|
||||
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
||||
body.put("targetType", "room"); // ✅ 正确:targetType
|
||||
body.put("targetId", roomId);
|
||||
body.put("targetTitle", room.getTitle() != null ? room.getTitle() : "直播间");
|
||||
body.put("duration", 0); // ✅ 正确:duration(不是viewDuration)
|
||||
|
||||
apiService.recordViewHistoryNew(body).enqueue(new Callback<ApiResponse<java.util.Map<String, Object>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<java.util.Map<String, Object>>> call,
|
||||
Response<ApiResponse<java.util.Map<String, Object>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||
android.util.Log.d("RoomDetail", "观看历史更新成功");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<java.util.Map<String, Object>>> call, Throwable t) {
|
||||
// 忽略错误
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// 忽略所有异常
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 修改对比表
|
||||
|
||||
| 功能 | 旧路径(错误) | 新路径(正确) | 参数变化 |
|
||||
|------|--------------|--------------|---------|
|
||||
| 记录观看历史 | `/activity/record-view` | `/activity/view/record` | - |
|
||||
| 获取观看历史 | `/activity/view-history` | `/activity/view/history` | `type` → `targetType` |
|
||||
| 清除观看历史 | `/activity/view-history` | `/activity/view/history` | `type` → `targetType` |
|
||||
| 获取点赞记录 | `/activity/like-records` | `/activity/like/records` | `type` → `targetType` |
|
||||
| 获取收藏记录 | `/activity/collected-works` | `/activity/collect/works` | - |
|
||||
| 获取关注记录 | `/activity/follow-records` | `/activity/follow/records` | - |
|
||||
| Token调试 | `/activity/debug-token` | `/activity/debug/token` | - |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修改检查清单
|
||||
|
||||
- [ ] 修改 `ApiService.java` 中的所有API路径
|
||||
- [ ] 修改 `ApiService.java` 中的参数名(`type` → `targetType`)
|
||||
- [ ] 检查 `RoomDetailActivity.java` 中的参数名(`duration` 不是 `viewDuration`)
|
||||
- [ ] 重新编译Android应用
|
||||
- [ ] 测试记录观看历史功能
|
||||
- [ ] 测试查看观看历史功能
|
||||
- [ ] 检查日志输出
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 编译应用
|
||||
|
||||
```bash
|
||||
cd android-app
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
### 2. 安装并运行
|
||||
|
||||
```bash
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### 3. 测试流程
|
||||
|
||||
1. **登录应用**
|
||||
- 确保使用有效的账号登录
|
||||
|
||||
2. **进入直播间**
|
||||
- 选择任意直播间进入
|
||||
- 查看Logcat日志,搜索 "观看历史"
|
||||
|
||||
3. **检查日志**
|
||||
```
|
||||
RoomDetail: 观看历史记录成功
|
||||
RoomDetail: 观看历史更新成功
|
||||
```
|
||||
|
||||
4. **查看历史记录**
|
||||
- 返回首页
|
||||
- 进入"我的" → "我的记录"
|
||||
- 切换到"观看历史"标签页
|
||||
- 应该能看到刚才观看的直播间
|
||||
|
||||
### 4. 调试Token(如果有问题)
|
||||
|
||||
使用Postman或curl测试:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://1.15.149.240:8081/api/front/activity/debug/token" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### 问题1:401 未登录
|
||||
|
||||
**原因**: Token未正确传递
|
||||
|
||||
**解决**:
|
||||
1. 检查 `ApiClient.java` 中的Token拦截器
|
||||
2. 确保Token格式正确:`Bearer {token}`
|
||||
3. 使用 `/activity/debug/token` 接口验证
|
||||
|
||||
### 问题2:404 Not Found
|
||||
|
||||
**原因**: API路径错误
|
||||
|
||||
**解决**:
|
||||
1. 检查 `ApiService.java` 中的路径是否正确
|
||||
2. 确保使用 `/` 而不是 `-`
|
||||
3. 检查后台服务是否运行在8081端口
|
||||
|
||||
### 问题3:参数错误
|
||||
|
||||
**原因**: 参数名不匹配
|
||||
|
||||
**解决**:
|
||||
1. 确保使用 `targetType` 而不是 `type`
|
||||
2. 确保使用 `duration` 而不是 `viewDuration`
|
||||
3. 检查后台日志
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [观看历史功能-zhibo-h后台说明.md](./观看历史功能-zhibo-h后台说明.md)
|
||||
- [观看历史功能-快速指南.md](./观看历史功能-快速指南.md)
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-01-05
|
||||
**状态**: 📝 待修改
|
||||
|
|
@ -20,4 +20,4 @@ APK: android-app/app/build/outputs/apk/release/
|
|||
前端: Zhibo/admin/dist/
|
||||
|
||||
|
||||
将前端访问的服务改成8083,本地改回8081
|
||||
将app部署改成8083,本地开发改成8081
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
-- 添加社会动态测试数据
|
||||
-- 先检查表是否存在
|
||||
SELECT COUNT(*) as count FROM eb_dynamic;
|
||||
|
||||
-- 插入测试数据(使用已有用户)
|
||||
INSERT INTO `eb_dynamic` (`uid`, `nickname`, `avatar`, `content`, `images`, `location`, `like_count`, `comment_count`, `share_count`, `view_count`, `is_top`, `status`, `create_time`) VALUES
|
||||
(41, '夏至已至', 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKq2CRmib1mpu4hOFYtPNLkpU5ILwYTYsvVYB0WYQ0hL4sKAGicicO0OicYCT1/132', '今天天气真好,出来直播啦!大家快来看我~', '["https://picsum.photos/400/300?random=101"]', '北京市', 128, 32, 8, 560, 0, 1, NOW() - INTERVAL 2 HOUR),
|
||||
(42, '测试用户', '', '分享一下我的日常生活,希望大家喜欢❤️', '["https://picsum.photos/400/300?random=102","https://picsum.photos/400/300?random=103"]', '上海市', 256, 45, 12, 890, 0, 1, NOW() - INTERVAL 5 HOUR),
|
||||
(43, 'xiaofeng', '', '新的一天,新的开始!今晚8点准时开播,不见不散~', '["https://picsum.photos/400/300?random=104"]', '广州市', 89, 18, 5, 320, 0, 1, NOW() - INTERVAL 1 DAY),
|
||||
(100, '吉惟同学', 'https://img.zcool.cn/community/01a9a65d143edaa8012187f447cfef.jpg', '周末愉快!有没有想一起玩游戏的小伙伴?', '[]', '深圳市', 167, 28, 9, 450, 1, 1, NOW() - INTERVAL 3 HOUR),
|
||||
(101, '国瑞哥', 'https://img.zcool.cn/community/01b72057a7e0790000018c1bf4fce0.png', '感谢大家的支持,粉丝破万啦!撒花🎉', '["https://picsum.photos/400/300?random=105","https://picsum.photos/400/300?random=106","https://picsum.photos/400/300?random=107"]', '成都市', 512, 89, 35, 1580, 0, 1, NOW() - INTERVAL 6 HOUR),
|
||||
(102, '玖书小姐姐', '', '今天学了一首新歌,晚上直播间唱给大家听~', '["https://picsum.photos/400/300?random=108"]', '杭州市', 78, 15, 3, 210, 0, 1, NOW() - INTERVAL 8 HOUR);
|
||||
|
||||
-- 查看插入的数据
|
||||
SELECT d.*, u.avatar as user_avatar
|
||||
FROM eb_dynamic d
|
||||
LEFT JOIN eb_user u ON d.uid = u.uid
|
||||
ORDER BY d.id DESC
|
||||
LIMIT 10;
|
||||
|
|
@ -71,8 +71,8 @@ android {
|
|||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<!-- 定位权限 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
|
|
@ -111,6 +114,11 @@
|
|||
android:name="com.example.livestreaming.FishPondActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.FishPondWebViewActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.VoiceMatchActivity"
|
||||
android:exported="false" />
|
||||
|
|
@ -180,6 +188,11 @@
|
|||
android:name="com.example.livestreaming.WishTreeActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.WishTreeWebViewActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.livestreaming.MessagesActivity"
|
||||
android:exported="false" />
|
||||
|
|
|
|||
150
android-app/app/src/main/assets/web/profile.html
Normal file
150
android-app/app/src/main/assets/web/profile.html
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<title>个人中心</title>
|
||||
<link rel="stylesheet" href="./styles/profile.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="profile-app">
|
||||
<!-- 背景层 -->
|
||||
<div class="bg-layer">
|
||||
<div class="avatar-blur" id="avatarBlur"></div>
|
||||
<div class="gradient-overlay"></div>
|
||||
<div class="grid-lines"></div>
|
||||
</div>
|
||||
|
||||
<div class="profile-inner">
|
||||
<!-- 顶部操作栏 -->
|
||||
<header class="top-bar">
|
||||
<div class="top-actions">
|
||||
<button class="icon-btn glass" id="searchBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn glass" id="historyBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn glass" id="settingsBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/>
|
||||
<line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/>
|
||||
<line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 用户信息区 -->
|
||||
<section class="user-section">
|
||||
<div class="avatar-area">
|
||||
<div class="avatar-ring">
|
||||
<div class="ring-glow"></div>
|
||||
<img class="avatar-img" id="avatar" src="" alt="头像">
|
||||
</div>
|
||||
<button class="edit-btn glass" id="editBtn">编辑资料</button>
|
||||
</div>
|
||||
|
||||
<div class="user-info">
|
||||
<h1 class="user-name" id="userName">加载中...</h1>
|
||||
<div class="user-tags">
|
||||
<span class="tag level" id="tagLevel">月亮 20</span>
|
||||
<span class="tag fans" id="tagFans">星耀 100</span>
|
||||
<span class="tag vip" id="tagVip">至尊</span>
|
||||
</div>
|
||||
<div class="user-id">
|
||||
<span class="gender-icon" id="genderIcon">♂</span>
|
||||
<span id="userId">ID: ------</span>
|
||||
<button class="copy-btn" id="copyBtn">📋</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 钱包卡片 -->
|
||||
<section class="wallet-card glass" id="walletCard">
|
||||
<div class="wallet-glow"></div>
|
||||
<div class="wallet-shine"></div>
|
||||
<div class="wallet-content">
|
||||
<div class="wallet-icon">💰</div>
|
||||
<div class="wallet-info">
|
||||
<span class="wallet-label">我的钱包</span>
|
||||
<span class="wallet-value" id="walletBalance">0.00</span>
|
||||
</div>
|
||||
<div class="wallet-actions">
|
||||
<button class="wallet-btn" id="rechargeBtn">充值</button>
|
||||
<button class="wallet-btn outline" id="withdrawBtn">提现</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 统计数据 -->
|
||||
<section class="stats-section glass">
|
||||
<div class="stat-item" id="followingBtn">
|
||||
<span class="stat-value" id="followingCount">0</span>
|
||||
<span class="stat-label">关注</span>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item" id="fansBtn">
|
||||
<span class="stat-value" id="fansCount">0</span>
|
||||
<span class="stat-label">粉丝</span>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item" id="likesBtn">
|
||||
<span class="stat-value" id="likesCount">0</span>
|
||||
<span class="stat-label">获赞</span>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item" id="friendsBtn">
|
||||
<span class="stat-value" id="friendsCount">0</span>
|
||||
<span class="stat-label">好友</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<section class="menu-section">
|
||||
<div class="menu-item glass" id="menuLiked">
|
||||
<div class="menu-icon pink">❤️</div>
|
||||
<div class="menu-text">
|
||||
<span class="menu-title">我的收藏</span>
|
||||
<span class="menu-desc">收藏的直播间</span>
|
||||
</div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item glass" id="menuRecords">
|
||||
<div class="menu-icon cyan">📝</div>
|
||||
<div class="menu-text">
|
||||
<span class="menu-title">我的记录</span>
|
||||
<span class="menu-desc">观看历史</span>
|
||||
</div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item glass" id="menuStreamer">
|
||||
<div class="menu-icon purple">🎬</div>
|
||||
<div class="menu-text">
|
||||
<span class="menu-title">主播中心</span>
|
||||
<span class="menu-desc">开始直播</span>
|
||||
</div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
|
||||
<div class="menu-item glass" id="menuSettings">
|
||||
<div class="menu-icon gray">⚙️</div>
|
||||
<div class="menu-text">
|
||||
<span class="menu-title">设置</span>
|
||||
<span class="menu-desc">账号与隐私</span>
|
||||
</div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./scripts/profile.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
210
android-app/app/src/main/assets/web/scripts/profile.js
Normal file
210
android-app/app/src/main/assets/web/scripts/profile.js
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
// 个人中心 - 赛博梦幻版
|
||||
|
||||
const AndroidBridge = {
|
||||
isAndroid: () => typeof Android !== 'undefined',
|
||||
|
||||
getUserProfile: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getUserProfile()); } catch (e) { return null; }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getWalletBalance: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return Android.getWalletBalance(); } catch (e) { return '0.00'; }
|
||||
}
|
||||
return '0.00';
|
||||
},
|
||||
|
||||
onEditProfile: function() {
|
||||
if (this.isAndroid()) Android.onEditProfile();
|
||||
else console.log('编辑资料');
|
||||
},
|
||||
|
||||
onWalletClick: function() {
|
||||
if (this.isAndroid()) Android.onWalletClick();
|
||||
else console.log('钱包');
|
||||
},
|
||||
|
||||
onRecharge: function() {
|
||||
if (this.isAndroid()) Android.onRecharge();
|
||||
else console.log('充值');
|
||||
},
|
||||
|
||||
onWithdraw: function() {
|
||||
if (this.isAndroid()) Android.onWithdraw();
|
||||
else console.log('提现');
|
||||
},
|
||||
|
||||
onFollowingClick: function() {
|
||||
if (this.isAndroid()) Android.onFollowingClick();
|
||||
else console.log('关注');
|
||||
},
|
||||
|
||||
onFansClick: function() {
|
||||
if (this.isAndroid()) Android.onFansClick();
|
||||
else console.log('粉丝');
|
||||
},
|
||||
|
||||
onLikesClick: function() {
|
||||
if (this.isAndroid()) Android.onLikesClick();
|
||||
else console.log('获赞');
|
||||
},
|
||||
|
||||
onFriendsClick: function() {
|
||||
if (this.isAndroid()) Android.onFriendsClick();
|
||||
else console.log('好友');
|
||||
},
|
||||
|
||||
onMenuClick: function(menuId) {
|
||||
if (this.isAndroid()) Android.onMenuClick(menuId);
|
||||
else console.log('菜单:', menuId);
|
||||
},
|
||||
|
||||
onSearchClick: function() {
|
||||
if (this.isAndroid()) Android.onSearchClick();
|
||||
},
|
||||
|
||||
onHistoryClick: function() {
|
||||
if (this.isAndroid()) Android.onHistoryClick();
|
||||
},
|
||||
|
||||
onSettingsClick: function() {
|
||||
if (this.isAndroid()) Android.onSettingsClick();
|
||||
},
|
||||
|
||||
copyToClipboard: function(text) {
|
||||
if (this.isAndroid()) {
|
||||
Android.copyToClipboard(text);
|
||||
} else {
|
||||
navigator.clipboard.writeText(text);
|
||||
alert('已复制: ' + text);
|
||||
}
|
||||
},
|
||||
|
||||
showToast: function(msg) {
|
||||
if (this.isAndroid()) Android.showToast(msg);
|
||||
else console.log('Toast:', msg);
|
||||
}
|
||||
};
|
||||
|
||||
let userProfile = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
||||
|
||||
function initPage() {
|
||||
setupEventListeners();
|
||||
setTimeout(() => {
|
||||
loadUserProfile();
|
||||
loadWalletBalance();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
// 顶部按钮
|
||||
document.getElementById('searchBtn').addEventListener('click', () => AndroidBridge.onSearchClick());
|
||||
document.getElementById('historyBtn').addEventListener('click', () => AndroidBridge.onHistoryClick());
|
||||
document.getElementById('settingsBtn').addEventListener('click', () => AndroidBridge.onSettingsClick());
|
||||
|
||||
// 编辑资料
|
||||
document.getElementById('editBtn').addEventListener('click', () => AndroidBridge.onEditProfile());
|
||||
|
||||
// 复制ID
|
||||
document.getElementById('copyBtn').addEventListener('click', () => {
|
||||
const userId = document.getElementById('userId').textContent;
|
||||
AndroidBridge.copyToClipboard(userId);
|
||||
AndroidBridge.showToast('已复制ID');
|
||||
});
|
||||
|
||||
// 钱包
|
||||
document.getElementById('walletCard').addEventListener('click', () => AndroidBridge.onWalletClick());
|
||||
document.getElementById('rechargeBtn').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
AndroidBridge.onRecharge();
|
||||
});
|
||||
document.getElementById('withdrawBtn').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
AndroidBridge.onWithdraw();
|
||||
});
|
||||
|
||||
// 统计
|
||||
document.getElementById('followingBtn').addEventListener('click', () => AndroidBridge.onFollowingClick());
|
||||
document.getElementById('fansBtn').addEventListener('click', () => AndroidBridge.onFansClick());
|
||||
document.getElementById('likesBtn').addEventListener('click', () => AndroidBridge.onLikesClick());
|
||||
document.getElementById('friendsBtn').addEventListener('click', () => AndroidBridge.onFriendsClick());
|
||||
|
||||
// 菜单
|
||||
document.getElementById('menuLiked').addEventListener('click', () => AndroidBridge.onMenuClick('liked'));
|
||||
document.getElementById('menuRecords').addEventListener('click', () => AndroidBridge.onMenuClick('records'));
|
||||
document.getElementById('menuStreamer').addEventListener('click', () => AndroidBridge.onMenuClick('streamer'));
|
||||
document.getElementById('menuSettings').addEventListener('click', () => AndroidBridge.onMenuClick('settings'));
|
||||
}
|
||||
|
||||
function loadUserProfile() {
|
||||
const profile = AndroidBridge.getUserProfile();
|
||||
if (profile) {
|
||||
userProfile = profile;
|
||||
renderProfile(profile);
|
||||
} else {
|
||||
// 默认数据
|
||||
renderProfile({
|
||||
name: '用户',
|
||||
id: '12345678',
|
||||
avatar: '',
|
||||
gender: 'male',
|
||||
level: '月亮 20',
|
||||
fansLevel: '星耀 100',
|
||||
vipLevel: '至尊',
|
||||
following: 0,
|
||||
fans: 0,
|
||||
likes: 0,
|
||||
friends: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderProfile(profile) {
|
||||
document.getElementById('userName').textContent = profile.name || '用户';
|
||||
document.getElementById('userId').textContent = 'ID: ' + (profile.id || '------');
|
||||
|
||||
if (profile.avatar) {
|
||||
document.getElementById('avatar').src = profile.avatar;
|
||||
}
|
||||
|
||||
document.getElementById('genderIcon').textContent = profile.gender === 'female' ? '♀' : '♂';
|
||||
document.getElementById('genderIcon').style.color = profile.gender === 'female' ? 'var(--neon-pink)' : 'var(--neon-cyan)';
|
||||
|
||||
if (profile.level) document.getElementById('tagLevel').textContent = profile.level;
|
||||
if (profile.fansLevel) document.getElementById('tagFans').textContent = profile.fansLevel;
|
||||
if (profile.vipLevel) document.getElementById('tagVip').textContent = profile.vipLevel;
|
||||
|
||||
document.getElementById('followingCount').textContent = formatNumber(profile.following || 0);
|
||||
document.getElementById('fansCount').textContent = formatNumber(profile.fans || 0);
|
||||
document.getElementById('likesCount').textContent = formatNumber(profile.likes || 0);
|
||||
document.getElementById('friendsCount').textContent = formatNumber(profile.friends || 0);
|
||||
}
|
||||
|
||||
function loadWalletBalance() {
|
||||
const balance = AndroidBridge.getWalletBalance();
|
||||
document.getElementById('walletBalance').textContent = balance;
|
||||
}
|
||||
|
||||
function formatNumber(num) {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + 'w';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
// Android回调
|
||||
function updateProfile(json) {
|
||||
try {
|
||||
const profile = JSON.parse(json);
|
||||
renderProfile(profile);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function updateWalletBalance(balance) {
|
||||
document.getElementById('walletBalance').textContent = balance;
|
||||
}
|
||||
234
android-app/app/src/main/assets/web/scripts/wishtree.js
Normal file
234
android-app/app/src/main/assets/web/scripts/wishtree.js
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// 许愿树 - 赛博梦幻版
|
||||
|
||||
const AndroidBridge = {
|
||||
isAndroid: () => typeof Android !== 'undefined',
|
||||
|
||||
getFestival: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getFestival()); } catch (e) { return null; }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getMyWishes: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getMyWishes()); } catch (e) { return []; }
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
publishWish: function(content) {
|
||||
if (this.isAndroid()) {
|
||||
Android.publishWish(content);
|
||||
} else {
|
||||
setTimeout(() => onPublishSuccess(), 500);
|
||||
}
|
||||
},
|
||||
|
||||
deleteWish: function(wishId, isComplete) {
|
||||
if (this.isAndroid()) {
|
||||
Android.deleteWish(wishId, isComplete);
|
||||
} else {
|
||||
setTimeout(() => onDeleteSuccess(isComplete), 500);
|
||||
}
|
||||
},
|
||||
|
||||
onSearchClick: function() {
|
||||
if (this.isAndroid()) Android.onSearchClick();
|
||||
},
|
||||
|
||||
onNotifyClick: function() {
|
||||
if (this.isAndroid()) Android.onNotifyClick();
|
||||
},
|
||||
|
||||
showToast: function(msg) {
|
||||
if (this.isAndroid()) Android.showToast(msg);
|
||||
else console.log('Toast:', msg);
|
||||
},
|
||||
|
||||
checkLogin: function() {
|
||||
if (this.isAndroid()) return Android.checkLogin();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let myWishes = [];
|
||||
let currentFestival = null;
|
||||
let currentViewingWishIndex = -1;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
||||
|
||||
function initPage() {
|
||||
setupEventListeners();
|
||||
startTimer();
|
||||
setTimeout(() => {
|
||||
loadFestival();
|
||||
loadMyWishes();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
document.getElementById('searchBtn').addEventListener('click', () => AndroidBridge.onSearchClick());
|
||||
document.getElementById('notifyBtn').addEventListener('click', () => AndroidBridge.onNotifyClick());
|
||||
document.getElementById('addWishCard').addEventListener('click', showWishModal);
|
||||
document.getElementById('makeWishBtn').addEventListener('click', showWishModal);
|
||||
|
||||
// 许愿弹窗
|
||||
document.getElementById('modalClose').addEventListener('click', () => hideModal('wishModal'));
|
||||
document.getElementById('btnCancel').addEventListener('click', () => hideModal('wishModal'));
|
||||
document.getElementById('btnConfirm').addEventListener('click', () => {
|
||||
const content = document.getElementById('wishInput').value.trim();
|
||||
if (!content) { AndroidBridge.showToast('请输入心愿内容'); return; }
|
||||
if (content.length > 50) { AndroidBridge.showToast('心愿内容不能超过50字'); return; }
|
||||
AndroidBridge.publishWish(content);
|
||||
});
|
||||
|
||||
// 查看弹窗
|
||||
document.getElementById('viewModalClose').addEventListener('click', () => hideModal('viewModal'));
|
||||
document.getElementById('btnDelete').addEventListener('click', () => {
|
||||
if (currentViewingWishIndex >= 0 && currentViewingWishIndex < myWishes.length) {
|
||||
if (confirm('确定要删除这个心愿吗?')) {
|
||||
AndroidBridge.deleteWish(myWishes[currentViewingWishIndex].id, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('btnComplete').addEventListener('click', () => {
|
||||
if (currentViewingWishIndex >= 0 && currentViewingWishIndex < myWishes.length) {
|
||||
if (confirm('恭喜!确认愿望已经达成了吗?')) {
|
||||
AndroidBridge.deleteWish(myWishes[currentViewingWishIndex].id, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 心愿卡片点击
|
||||
document.querySelectorAll('.wish-note').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const index = parseInt(card.dataset.index);
|
||||
if (index < myWishes.length && myWishes[index]) {
|
||||
showViewWishModal(index);
|
||||
} else {
|
||||
showWishModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 输入字数统计
|
||||
document.getElementById('wishInput').addEventListener('input', function() {
|
||||
document.getElementById('charCount').textContent = this.value.length;
|
||||
});
|
||||
|
||||
// 点击遮罩关闭
|
||||
document.querySelectorAll('.modal-mask').forEach(mask => {
|
||||
mask.addEventListener('click', function() {
|
||||
const modal = this.closest('.modal');
|
||||
if (modal && modal.id !== 'successModal') modal.classList.remove('show');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showModal(id) { document.getElementById(id).classList.add('show'); }
|
||||
function hideModal(id) {
|
||||
document.getElementById(id).classList.remove('show');
|
||||
if (id === 'wishModal') {
|
||||
document.getElementById('wishInput').value = '';
|
||||
document.getElementById('charCount').textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
function showWishModal() {
|
||||
if (!AndroidBridge.checkLogin()) return;
|
||||
showModal('wishModal');
|
||||
}
|
||||
|
||||
function showViewWishModal(index) {
|
||||
currentViewingWishIndex = index;
|
||||
const wish = myWishes[index];
|
||||
document.getElementById('wishContent').textContent = wish.content;
|
||||
document.getElementById('wishLikes').innerHTML = '♥ ' + (wish.likeCount || 0);
|
||||
document.getElementById('wishComments').innerHTML = '💬 ' + (wish.commentCount || 0);
|
||||
showModal('viewModal');
|
||||
}
|
||||
|
||||
function loadFestival() {
|
||||
const festival = AndroidBridge.getFestival();
|
||||
if (festival) {
|
||||
currentFestival = festival;
|
||||
document.getElementById('bannerText').textContent = festival.name + '许愿树';
|
||||
}
|
||||
}
|
||||
|
||||
function loadMyWishes() {
|
||||
myWishes = AndroidBridge.getMyWishes() || [];
|
||||
renderWishCards();
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
function renderWishCards() {
|
||||
const cards = document.querySelectorAll('.wish-note');
|
||||
cards.forEach((card, index) => {
|
||||
const contentEl = card.querySelector('.note-content');
|
||||
const countEl = card.querySelector('.count');
|
||||
if (index < myWishes.length && myWishes[index]) {
|
||||
const wish = myWishes[index];
|
||||
contentEl.textContent = wish.content.length > 10 ? wish.content.substring(0, 10) + '...' : wish.content;
|
||||
countEl.textContent = wish.likeCount || 0;
|
||||
card.classList.remove('empty');
|
||||
} else {
|
||||
contentEl.textContent = '';
|
||||
countEl.textContent = '0';
|
||||
card.classList.add('empty');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
const count = myWishes.length;
|
||||
const max = 100;
|
||||
const percent = Math.min((count / max) * 100, 100);
|
||||
document.getElementById('wishCount').textContent = count + ' / ' + max;
|
||||
document.getElementById('progressFill').style.width = percent + '%';
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
setInterval(() => {
|
||||
const now = new Date();
|
||||
const tomorrow = new Date(now);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrow.setHours(0, 0, 0, 0);
|
||||
const diff = tomorrow - now;
|
||||
const h = Math.floor(diff / 3600000);
|
||||
const m = Math.floor((diff % 3600000) / 60000);
|
||||
const s = Math.floor((diff % 60000) / 1000);
|
||||
document.getElementById('bannerTimer').textContent =
|
||||
String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Android回调
|
||||
function updateWishes(json) {
|
||||
try { myWishes = JSON.parse(json); renderWishCards(); updateProgress(); } catch (e) {}
|
||||
}
|
||||
|
||||
function updateFestival(json) {
|
||||
try {
|
||||
currentFestival = JSON.parse(json);
|
||||
document.getElementById('bannerText').textContent = currentFestival.name + '许愿树';
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function onPublishSuccess() {
|
||||
hideModal('wishModal');
|
||||
showSuccessModal('🎉', '许愿成功!');
|
||||
}
|
||||
|
||||
function onDeleteSuccess(isComplete) {
|
||||
hideModal('viewModal');
|
||||
if (isComplete) showSuccessModal('🎊', '愿望达成!');
|
||||
}
|
||||
|
||||
function showSuccessModal(icon, text) {
|
||||
document.getElementById('successIcon').textContent = icon;
|
||||
document.getElementById('successText').textContent = text;
|
||||
showModal('successModal');
|
||||
setTimeout(() => hideModal('successModal'), 2000);
|
||||
}
|
||||
205
android-app/app/src/main/assets/web/scripts/yuanchi.js
Normal file
205
android-app/app/src/main/assets/web/scripts/yuanchi.js
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
// 缘池 - 赛博梦幻版
|
||||
|
||||
const AndroidBridge = {
|
||||
isAndroid: () => typeof Android !== 'undefined',
|
||||
|
||||
getUserInfo: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getUserInfo()); } catch (e) { return null; }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getNearbyUsers: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getNearbyUsers()); } catch (e) { return []; }
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
getMatchableCount: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return Android.getMatchableCount(); } catch (e) { return '--'; }
|
||||
}
|
||||
return '--';
|
||||
},
|
||||
|
||||
getCategories: function() {
|
||||
if (this.isAndroid()) {
|
||||
try { return JSON.parse(Android.getCategories()); } catch (e) { return []; }
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
onUserClick: function(userId, userName) {
|
||||
if (this.isAndroid()) Android.onUserClick(userId, userName);
|
||||
else console.log('点击用户:', userId, userName);
|
||||
},
|
||||
|
||||
onCategoryClick: function(categoryId, categoryName, jumpPage) {
|
||||
if (this.isAndroid()) Android.onCategoryClick(categoryId, categoryName, jumpPage);
|
||||
else console.log('点击板块:', categoryId, categoryName);
|
||||
},
|
||||
|
||||
onRefresh: function() {
|
||||
if (this.isAndroid()) Android.onRefresh();
|
||||
else console.log('刷新');
|
||||
},
|
||||
|
||||
onVoiceMatch: function() {
|
||||
if (this.isAndroid()) Android.onVoiceMatch();
|
||||
else alert('语音匹配功能待接入~');
|
||||
},
|
||||
|
||||
onHeartSignal: function() {
|
||||
if (this.isAndroid()) Android.onHeartSignal();
|
||||
else alert('心动信号功能待接入~');
|
||||
},
|
||||
|
||||
showToast: function(msg) {
|
||||
if (this.isAndroid()) Android.showToast(msg);
|
||||
else console.log('Toast:', msg);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
||||
|
||||
function initPage() {
|
||||
setupEventListeners();
|
||||
setTimeout(() => {
|
||||
loadUserInfo();
|
||||
loadMatchableCount();
|
||||
loadNearbyUsers();
|
||||
loadCategories();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
// 刷新按钮
|
||||
document.getElementById('refreshBtn').addEventListener('click', function() {
|
||||
this.querySelector('.core-icon').style.animation = 'none';
|
||||
this.offsetHeight;
|
||||
this.querySelector('.core-icon').style.animation = 'spin 0.5s ease';
|
||||
AndroidBridge.onRefresh();
|
||||
loadMatchableCount();
|
||||
loadNearbyUsers();
|
||||
});
|
||||
|
||||
// 语音匹配
|
||||
document.getElementById('voiceMatchBtn').addEventListener('click', () => AndroidBridge.onVoiceMatch());
|
||||
|
||||
// 心动信号
|
||||
document.getElementById('heartSignalBtn').addEventListener('click', () => AndroidBridge.onHeartSignal());
|
||||
|
||||
// 用户头像点击
|
||||
document.querySelectorAll('.orbit-user').forEach(user => {
|
||||
user.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId || '';
|
||||
const userName = this.querySelector('.user-name').textContent || '用户';
|
||||
if (userId) AndroidBridge.onUserClick(userId, userName);
|
||||
});
|
||||
});
|
||||
|
||||
// 功能卡片点击
|
||||
document.querySelectorAll('.feature-card').forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
const id = parseInt(this.dataset.id) || 0;
|
||||
const name = this.querySelector('.card-title').textContent || '';
|
||||
const jumpPage = this.dataset.jumpPage || '';
|
||||
AndroidBridge.onCategoryClick(id, name, jumpPage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 添加旋转动画
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }';
|
||||
document.head.appendChild(style);
|
||||
|
||||
function loadUserInfo() {
|
||||
const info = AndroidBridge.getUserInfo();
|
||||
if (info) {
|
||||
if (info.name) document.getElementById('userName').textContent = info.name;
|
||||
if (info.info) document.getElementById('userInfo').textContent = info.info;
|
||||
}
|
||||
}
|
||||
|
||||
function loadMatchableCount() {
|
||||
const count = AndroidBridge.getMatchableCount();
|
||||
document.getElementById('matchCount').textContent = count;
|
||||
}
|
||||
|
||||
function loadNearbyUsers() {
|
||||
const users = AndroidBridge.getNearbyUsers();
|
||||
renderOrbitUsers(users);
|
||||
}
|
||||
|
||||
function renderOrbitUsers(users) {
|
||||
const userEls = document.querySelectorAll('.orbit-user');
|
||||
const defaultUsers = [
|
||||
{ id: '1', name: '小树', location: '杭州' },
|
||||
{ id: '2', name: 'Lina', location: '深圳' },
|
||||
{ id: '3', name: '小七', location: '武汉' },
|
||||
{ id: '4', name: '小北', location: '西安' },
|
||||
{ id: '5', name: '暖暖', location: '成都' },
|
||||
{ id: '6', name: '阿宁', location: '南宁' }
|
||||
];
|
||||
|
||||
const data = (users && users.length > 0) ? users : defaultUsers;
|
||||
|
||||
userEls.forEach((el, index) => {
|
||||
const user = data[index] || {};
|
||||
el.dataset.userId = user.id || '';
|
||||
el.querySelector('.user-name').textContent = user.name || '用户';
|
||||
el.querySelector('.user-location').textContent = user.location || '';
|
||||
|
||||
const img = el.querySelector('.avatar-img');
|
||||
if (user.avatar) {
|
||||
img.src = user.avatar;
|
||||
img.onerror = () => { img.src = ''; };
|
||||
} else {
|
||||
img.src = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadCategories() {
|
||||
const categories = AndroidBridge.getCategories();
|
||||
if (categories && categories.length > 0) {
|
||||
renderCategories(categories);
|
||||
}
|
||||
}
|
||||
|
||||
function renderCategories(categories) {
|
||||
const grid = document.getElementById('categoryGrid');
|
||||
const defaultIcons = ['💕', '🎮', '🎤', '🎨', '🔫', '🎲'];
|
||||
const defaultGlows = ['pink', 'cyan', 'purple', 'pink', 'cyan', 'purple'];
|
||||
|
||||
const cards = grid.querySelectorAll('.feature-card');
|
||||
cards.forEach((card, index) => {
|
||||
const cat = categories[index];
|
||||
if (cat) {
|
||||
card.dataset.id = cat.id || index + 1;
|
||||
card.dataset.jumpPage = cat.jumpPage || '';
|
||||
card.querySelector('.card-title').textContent = cat.name || '未知';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Android回调
|
||||
function updateNearbyUsers(json) {
|
||||
try { renderOrbitUsers(JSON.parse(json)); } catch (e) {}
|
||||
}
|
||||
|
||||
function updateMatchableCount(count) {
|
||||
document.getElementById('matchCount').textContent = count;
|
||||
}
|
||||
|
||||
function updateCategories(json) {
|
||||
try { renderCategories(JSON.parse(json)); } catch (e) {}
|
||||
}
|
||||
|
||||
function updateUserInfo(name, info) {
|
||||
if (name) document.getElementById('userName').textContent = name;
|
||||
if (info) document.getElementById('userInfo').textContent = info;
|
||||
}
|
||||
482
android-app/app/src/main/assets/web/styles/profile.css
Normal file
482
android-app/app/src/main/assets/web/styles/profile.css
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
/* ========== 基础重置 ========== */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-dark: #0a0a14;
|
||||
--bg-mid: #12122b;
|
||||
--neon-pink: #FF2E63;
|
||||
--neon-cyan: #08D9D6;
|
||||
--neon-purple: #a855f7;
|
||||
--gold-start: #f6d365;
|
||||
--gold-end: #fda085;
|
||||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text-primary);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ========== 主容器 ========== */
|
||||
.profile-app {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(180deg, var(--bg-mid) 0%, var(--bg-dark) 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.profile-inner {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 14px 16px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
/* ========== 背景层 ========== */
|
||||
.bg-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.avatar-blur {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple));
|
||||
border-radius: 50%;
|
||||
filter: blur(100px);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.gradient-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, transparent 0%, var(--bg-dark) 60%);
|
||||
}
|
||||
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
mask-image: linear-gradient(to bottom, black 20%, transparent 80%);
|
||||
}
|
||||
|
||||
/* ========== 玻璃拟态 ========== */
|
||||
.glass {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* ========== 顶部栏 ========== */
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-btn:active {
|
||||
transform: scale(0.92);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* ========== 用户信息区 ========== */
|
||||
.user-section {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.avatar-ring {
|
||||
position: relative;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-cyan));
|
||||
}
|
||||
|
||||
.ring-glow {
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-cyan));
|
||||
filter: blur(10px);
|
||||
opacity: 0.5;
|
||||
animation: ring-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ring-pulse {
|
||||
0%, 100% { opacity: 0.5; transform: scale(1); }
|
||||
50% { opacity: 0.8; transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple));
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
padding: 6px 14px;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.edit-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(90deg, #fff, var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.user-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tag.level {
|
||||
background: linear-gradient(90deg, var(--neon-purple), var(--neon-pink));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tag.fans {
|
||||
background: linear-gradient(90deg, var(--gold-start), var(--gold-end));
|
||||
color: #5a3d00;
|
||||
}
|
||||
|
||||
.tag.vip {
|
||||
background: linear-gradient(90deg, var(--neon-cyan), var(--neon-purple));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.gender-icon {
|
||||
color: var(--neon-cyan);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.copy-btn:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/* ========== 钱包卡片 ========== */
|
||||
.wallet-card {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, rgba(246, 211, 101, 0.15), rgba(253, 160, 133, 0.1));
|
||||
border: 1px solid rgba(246, 211, 101, 0.3);
|
||||
}
|
||||
|
||||
.wallet-glow {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -30%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: radial-gradient(circle, rgba(246, 211, 101, 0.4) 0%, transparent 70%);
|
||||
animation: wallet-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wallet-pulse {
|
||||
0%, 100% { opacity: 0.4; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(1.2); }
|
||||
}
|
||||
|
||||
.wallet-shine {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
animation: wallet-shine 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wallet-shine {
|
||||
0%, 100% { left: -100%; }
|
||||
50% { left: 100%; }
|
||||
}
|
||||
|
||||
.wallet-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.wallet-icon {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.wallet-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wallet-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.wallet-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, var(--gold-start), var(--gold-end));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.wallet-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.wallet-btn {
|
||||
padding: 8px 18px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: linear-gradient(90deg, var(--gold-start), var(--gold-end));
|
||||
color: #5a3d00;
|
||||
}
|
||||
|
||||
.wallet-btn.outline {
|
||||
background: transparent;
|
||||
border: 1px solid var(--gold-start);
|
||||
color: var(--gold-start);
|
||||
}
|
||||
|
||||
.wallet-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* ========== 统计数据 ========== */
|
||||
.stats-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 18px 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.stat-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background: var(--glass-border);
|
||||
}
|
||||
|
||||
/* ========== 功能菜单 ========== */
|
||||
.menu-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.menu-icon.pink {
|
||||
background: rgba(255, 46, 99, 0.15);
|
||||
}
|
||||
|
||||
.menu-icon.cyan {
|
||||
background: rgba(8, 217, 214, 0.15);
|
||||
}
|
||||
|
||||
.menu-icon.purple {
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
}
|
||||
|
||||
.menu-icon.gray {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.menu-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 20px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* ========== 响应式 ========== */
|
||||
@media (max-height: 700px) {
|
||||
.avatar-ring {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
.user-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
.wallet-card {
|
||||
padding: 16px;
|
||||
}
|
||||
.wallet-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
859
android-app/app/src/main/assets/web/styles/wishtree.css
Normal file
859
android-app/app/src/main/assets/web/styles/wishtree.css
Normal file
|
|
@ -0,0 +1,859 @@
|
|||
/* ========== 基础重置 ========== */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-dark: #0a0a14;
|
||||
--bg-mid: #12122b;
|
||||
--neon-pink: #FF2E63;
|
||||
--neon-cyan: #08D9D6;
|
||||
--neon-purple: #a855f7;
|
||||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
--glass-highlight: rgba(255, 255, 255, 0.15);
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ========== 主容器 ========== */
|
||||
.wish-app {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(135deg, var(--bg-mid) 0%, var(--bg-dark) 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wish-inner {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 14px 16px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ========== 背景层 ========== */
|
||||
.bg-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradient-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
background: var(--neon-pink);
|
||||
animation: float-orb 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
bottom: 100px;
|
||||
left: -80px;
|
||||
background: var(--neon-cyan);
|
||||
animation: float-orb 10s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
@keyframes float-orb {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(30px, 20px) scale(1.1); }
|
||||
}
|
||||
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
mask-image: linear-gradient(to bottom, transparent, black 20%, black 80%, transparent);
|
||||
}
|
||||
|
||||
/* ========== 玻璃拟态 ========== */
|
||||
.glass {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* ========== 顶部栏 ========== */
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.logo-glow {
|
||||
font-size: 20px;
|
||||
color: var(--neon-cyan);
|
||||
text-shadow: 0 0 10px var(--neon-cyan);
|
||||
animation: pulse-glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-btn:active {
|
||||
transform: scale(0.92);
|
||||
background: var(--glass-highlight);
|
||||
}
|
||||
|
||||
.notify-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--neon-pink);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 8px var(--neon-pink);
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { opacity: 1; text-shadow: 0 0 10px var(--neon-cyan); }
|
||||
50% { opacity: 0.7; text-shadow: 0 0 20px var(--neon-cyan), 0 0 30px var(--neon-cyan); }
|
||||
}
|
||||
|
||||
|
||||
/* ========== 活动横幅 ========== */
|
||||
.event-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 46, 99, 0.1), transparent);
|
||||
animation: scan-glow 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes scan-glow {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
font-size: 32px;
|
||||
animation: bounce 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-4px); }
|
||||
}
|
||||
|
||||
.banner-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--neon-pink);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.banner-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.banner-timer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 14px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple));
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.timer-label {
|
||||
font-size: 9px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.timer-value {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ========== 许愿树区域 ========== */
|
||||
.tree-area {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
/* 发光树影 */
|
||||
.tree-silhouette {
|
||||
position: absolute;
|
||||
width: 280px;
|
||||
height: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tree-glow {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -60%);
|
||||
background: radial-gradient(circle, rgba(8, 217, 214, 0.2) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
animation: tree-breathe 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes tree-breathe {
|
||||
0%, 100% { transform: translate(-50%, -60%) scale(1); opacity: 0.6; }
|
||||
50% { transform: translate(-50%, -60%) scale(1.15); opacity: 1; }
|
||||
}
|
||||
|
||||
.tree-crown {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.crown-layer {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-left: 60px solid transparent;
|
||||
border-right: 60px solid transparent;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.layer-1 {
|
||||
bottom: 0;
|
||||
border-bottom: 100px solid var(--neon-cyan);
|
||||
width: 0;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.layer-2 {
|
||||
bottom: 70px;
|
||||
border-bottom: 90px solid var(--neon-cyan);
|
||||
border-left-width: 50px;
|
||||
border-right-width: 50px;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.layer-3 {
|
||||
bottom: 130px;
|
||||
border-bottom: 80px solid var(--neon-cyan);
|
||||
border-left-width: 40px;
|
||||
border-right-width: 40px;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.tree-trunk {
|
||||
width: 30px;
|
||||
height: 60px;
|
||||
background: linear-gradient(180deg, rgba(139, 69, 19, 0.3), rgba(139, 69, 19, 0.1));
|
||||
border-radius: 0 0 8px 8px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
/* 树上光点 */
|
||||
.tree-lights {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.light-dot {
|
||||
position: absolute;
|
||||
left: var(--x);
|
||||
top: var(--y);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--neon-cyan);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
|
||||
animation: twinkle 2s ease-in-out infinite;
|
||||
animation-delay: var(--delay);
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
||||
50% { opacity: 1; transform: scale(1.2); }
|
||||
}
|
||||
|
||||
/* ========== 心愿信笺 ========== */
|
||||
.wish-notes {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wish-note {
|
||||
position: absolute;
|
||||
left: var(--x);
|
||||
top: var(--y);
|
||||
width: 65px;
|
||||
height: 80px;
|
||||
padding: 8px 6px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.wish-note.floating {
|
||||
animation: note-float 4s ease-in-out infinite;
|
||||
animation-delay: var(--delay);
|
||||
}
|
||||
|
||||
@keyframes note-float {
|
||||
0%, 100% { transform: translateY(0) rotate(-2deg); }
|
||||
50% { transform: translateY(-8px) rotate(2deg); }
|
||||
}
|
||||
|
||||
.wish-note:active {
|
||||
transform: scale(1.1) !important;
|
||||
}
|
||||
|
||||
.wish-note.empty {
|
||||
opacity: 0.3;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.note-pin {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple));
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 8px var(--neon-pink);
|
||||
}
|
||||
|
||||
.note-content {
|
||||
flex: 1;
|
||||
font-size: 9px;
|
||||
line-height: 1.3;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.note-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
font-size: 10px;
|
||||
color: var(--neon-pink);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.heart {
|
||||
animation: heartbeat 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); }
|
||||
}
|
||||
|
||||
|
||||
/* ========== 中心许愿按钮 ========== */
|
||||
.wish-core {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 55%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 85px;
|
||||
height: 85px;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.core-pulse {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--neon-cyan);
|
||||
animation: pulse-ring 2s ease-out infinite;
|
||||
}
|
||||
|
||||
.core-pulse.delay {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% { transform: scale(1); opacity: 0.8; }
|
||||
100% { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
|
||||
.core-btn {
|
||||
position: absolute;
|
||||
inset: 8px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple), var(--neon-cyan));
|
||||
background-size: 200% 200%;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow:
|
||||
0 0 30px rgba(255, 46, 99, 0.5),
|
||||
0 0 60px rgba(8, 217, 214, 0.3),
|
||||
inset 0 0 20px rgba(255, 255, 255, 0.2);
|
||||
animation: gradient-rotate 4s linear infinite, core-glow 2s ease-in-out infinite;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.wish-core:active .core-btn {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
@keyframes gradient-rotate {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
@keyframes core-glow {
|
||||
0%, 100% { box-shadow: 0 0 30px rgba(255, 46, 99, 0.5), 0 0 60px rgba(8, 217, 214, 0.3); }
|
||||
50% { box-shadow: 0 0 50px rgba(255, 46, 99, 0.7), 0 0 100px rgba(8, 217, 214, 0.5); }
|
||||
}
|
||||
|
||||
.core-icon {
|
||||
font-size: 26px;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.core-text {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ========== 底部区域 ========== */
|
||||
.bottom-area {
|
||||
padding: 12px 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
position: relative;
|
||||
height: 10px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-purple), var(--neon-cyan));
|
||||
border-radius: 5px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
||||
animation: progress-shine 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-shine {
|
||||
0% { left: -50%; }
|
||||
100% { left: 150%; }
|
||||
}
|
||||
|
||||
/* 霓虹按钮 */
|
||||
.neon-btn {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
border: none;
|
||||
border-radius: 26px;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-purple));
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.neon-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.btn-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
transform: translateX(-100%);
|
||||
animation: btn-shine 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes btn-shine {
|
||||
0%, 100% { transform: translateX(-100%); }
|
||||
50% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.main-btn {
|
||||
box-shadow: 0 4px 20px rgba(255, 46, 99, 0.4), 0 0 40px rgba(255, 46, 99, 0.2);
|
||||
}
|
||||
|
||||
|
||||
/* ========== 弹窗 ========== */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
animation: fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.modal-box {
|
||||
position: relative;
|
||||
width: 88%;
|
||||
max-width: 340px;
|
||||
animation: modal-pop 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes modal-pop {
|
||||
from { transform: scale(0.85); opacity: 0; }
|
||||
to { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 18px 20px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
font-size: 22px;
|
||||
color: var(--neon-cyan);
|
||||
text-shadow: 0 0 10px var(--neon-cyan);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
flex: 1;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
color: var(--text-secondary);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:active {
|
||||
transform: scale(0.9);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.input-area::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.input-area:focus {
|
||||
border-color: var(--neon-cyan);
|
||||
box-shadow: 0 0 0 2px rgba(8, 217, 214, 0.2);
|
||||
}
|
||||
|
||||
.char-info {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.wish-display {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.wish-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.modal-foot {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
.btn-secondary, .btn-primary, .btn-danger, .btn-success {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:active, .btn-danger:active, .btn-success:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-purple));
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 15px rgba(255, 46, 99, 0.3);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: rgba(255, 82, 82, 0.2);
|
||||
color: #ff5252;
|
||||
border: 1px solid rgba(255, 82, 82, 0.3);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
/* 成功弹窗 */
|
||||
.success-box {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
||||
.success-particles {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at 30% 30%, var(--neon-pink) 2px, transparent 2px),
|
||||
radial-gradient(circle at 70% 20%, var(--neon-cyan) 2px, transparent 2px),
|
||||
radial-gradient(circle at 50% 60%, var(--neon-purple) 2px, transparent 2px);
|
||||
animation: particles-float 2s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes particles-float {
|
||||
0% { opacity: 1; transform: scale(0.5); }
|
||||
100% { opacity: 0; transform: scale(2); }
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
animation: success-pop 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes success-pop {
|
||||
0% { transform: scale(0); }
|
||||
50% { transform: scale(1.2); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.success-text {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* ========== 响应式 ========== */
|
||||
@media (max-height: 680px) {
|
||||
.tree-area { min-height: 280px; }
|
||||
.tree-silhouette { width: 240px; height: 280px; }
|
||||
.wish-core { width: 75px; height: 75px; }
|
||||
.wish-note { width: 55px; height: 70px; }
|
||||
}
|
||||
|
||||
@media (max-height: 580px) {
|
||||
.event-banner { padding: 10px 14px; margin-bottom: 10px; }
|
||||
.tree-area { min-height: 240px; }
|
||||
.tree-silhouette { width: 200px; height: 240px; }
|
||||
}
|
||||
633
android-app/app/src/main/assets/web/styles/yuanchi.css
Normal file
633
android-app/app/src/main/assets/web/styles/yuanchi.css
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
/* ========== 基础重置 ========== */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-dark: #0a0a14;
|
||||
--bg-mid: #12122b;
|
||||
--neon-pink: #FF2E63;
|
||||
--neon-cyan: #08D9D6;
|
||||
--neon-purple: #a855f7;
|
||||
--glass-bg: rgba(255, 255, 255, 0.05);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
--glass-highlight: rgba(255, 255, 255, 0.15);
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: rgba(255, 255, 255, 0.6);
|
||||
|
||||
/* 轨道半径 - 根据屏幕动态调整 */
|
||||
--orbit-radius: min(38vw, 140px);
|
||||
--avatar-size: min(15vw, 56px);
|
||||
--center-size: min(22vw, 90px);
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text-primary);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ========== 主容器 ========== */
|
||||
.pond-app {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(135deg, var(--bg-mid) 0%, var(--bg-dark) 100%);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.pond-inner {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 12px 14px;
|
||||
padding-bottom: 70px;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ========== 背景层 ========== */
|
||||
.bg-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradient-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: -60px;
|
||||
left: -60px;
|
||||
background: var(--neon-cyan);
|
||||
animation: float-orb 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
bottom: 100px;
|
||||
right: -60px;
|
||||
background: var(--neon-pink);
|
||||
animation: float-orb 12s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
@keyframes float-orb {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(15px, 20px) scale(1.05); }
|
||||
}
|
||||
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
|
||||
}
|
||||
|
||||
/* ========== 玻璃拟态 ========== */
|
||||
.glass {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* ========== 顶部栏 ========== */
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.logo-glow {
|
||||
font-size: 20px;
|
||||
color: var(--neon-cyan);
|
||||
text-shadow: 0 0 12px var(--neon-cyan);
|
||||
animation: pulse-glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { text-shadow: 0 0 8px var(--neon-cyan); }
|
||||
50% { text-shadow: 0 0 20px var(--neon-cyan), 0 0 30px var(--neon-cyan); }
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-family: "Snell Roundhand", cursive;
|
||||
font-size: 20px;
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* ========== 信息栏 ========== */
|
||||
.info-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-left, .info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.info-label, .info-desc {
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.info-value, .info-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-unit {
|
||||
font-size: 11px;
|
||||
color: var(--neon-pink);
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.neon-text {
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.info-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ========== 匹配圆环区域 - 摩天轮布局 ========== */
|
||||
.match-area {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
/* 固定高度,确保圆环区域大小一致 */
|
||||
height: calc(var(--orbit-radius) * 2 + var(--avatar-size) + 50px);
|
||||
max-height: 340px;
|
||||
margin: 0 auto 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 中心脉冲圆环 */
|
||||
.center-ring {
|
||||
position: absolute;
|
||||
width: var(--center-size);
|
||||
height: var(--center-size);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.ring-pulse {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--neon-cyan);
|
||||
animation: ring-expand 2.5s ease-out infinite;
|
||||
}
|
||||
|
||||
.ring-pulse.delay-1 { animation-delay: 0.8s; }
|
||||
.ring-pulse.delay-2 { animation-delay: 1.6s; }
|
||||
|
||||
@keyframes ring-expand {
|
||||
0% { transform: scale(1); opacity: 0.7; }
|
||||
100% { transform: scale(2.2); opacity: 0; }
|
||||
}
|
||||
|
||||
.ring-core {
|
||||
position: absolute;
|
||||
inset: 8px;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-purple), var(--neon-cyan));
|
||||
background-size: 200% 200%;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
animation: gradient-spin 4s linear infinite;
|
||||
box-shadow:
|
||||
0 0 25px rgba(8, 217, 214, 0.4),
|
||||
0 0 50px rgba(255, 46, 99, 0.3),
|
||||
inset 0 0 15px rgba(255, 255, 255, 0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.ring-core:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
@keyframes gradient-spin {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
.core-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.core-icon {
|
||||
font-size: min(8vw, 28px);
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 轨道环 - 真正的圆形轨道 */
|
||||
.orbit-ring {
|
||||
position: absolute;
|
||||
width: calc(var(--orbit-radius) * 2);
|
||||
height: calc(var(--orbit-radius) * 2);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.15);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 外层装饰轨道 */
|
||||
.orbit-ring::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -15px;
|
||||
border: 1px solid rgba(168, 85, 247, 0.1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.orbit-ring::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 15px;
|
||||
border: 1px solid rgba(8, 217, 214, 0.1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 用户头像容器 - 摩天轮定位 */
|
||||
.orbit-users {
|
||||
position: absolute;
|
||||
width: calc(var(--orbit-radius) * 2);
|
||||
height: calc(var(--orbit-radius) * 2);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
/* 整体缓慢旋转 */
|
||||
animation: orbit-spin 60s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes orbit-spin {
|
||||
from { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
to { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 单个用户 - 精确定位在轨道上 */
|
||||
.orbit-user {
|
||||
position: absolute;
|
||||
width: var(--avatar-size);
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease;
|
||||
/* 反向旋转保持头像正向 */
|
||||
animation: counter-spin 60s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes counter-spin {
|
||||
from { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
to { transform: translate(-50%, -50%) rotate(-360deg); }
|
||||
}
|
||||
|
||||
/* 6个用户的精确位置 - 圆周均匀分布 */
|
||||
.orbit-user[data-pos="1"] {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user[data-pos="2"] {
|
||||
left: calc(50% + 86.6%/2); /* cos(30°) ≈ 0.866 */
|
||||
top: 25%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user[data-pos="3"] {
|
||||
left: calc(50% + 86.6%/2);
|
||||
top: 75%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user[data-pos="4"] {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user[data-pos="5"] {
|
||||
left: calc(50% - 86.6%/2);
|
||||
top: 75%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user[data-pos="6"] {
|
||||
left: calc(50% - 86.6%/2);
|
||||
top: 25%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.orbit-user:active {
|
||||
transform: translate(-50%, -50%) scale(1.1) !important;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: var(--avatar-size);
|
||||
height: var(--avatar-size);
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-cyan));
|
||||
}
|
||||
|
||||
.avatar-glow {
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--neon-pink), var(--neon-cyan));
|
||||
opacity: 0.4;
|
||||
z-index: -1;
|
||||
filter: blur(6px);
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
max-width: calc(var(--avatar-size) + 10px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
.user-location {
|
||||
font-size: 8px;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
/* ========== 快捷操作按钮 ========== */
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.action-btn.pink {
|
||||
background: linear-gradient(90deg, var(--neon-pink), var(--neon-purple));
|
||||
box-shadow: 0 4px 16px rgba(255, 46, 99, 0.35);
|
||||
}
|
||||
|
||||
.action-btn.cyan {
|
||||
background: linear-gradient(90deg, var(--neon-purple), var(--neon-cyan));
|
||||
box-shadow: 0 4px 16px rgba(8, 217, 214, 0.35);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ========== 功能入口 2x3 ========== */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
position: relative;
|
||||
padding: 14px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.card-glow {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
filter: blur(25px);
|
||||
opacity: 0.12;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.card-glow.pink { background: var(--neon-pink); }
|
||||
.card-glow.cyan { background: var(--neon-cyan); }
|
||||
.card-glow.purple { background: var(--neon-purple); }
|
||||
|
||||
.feature-card:hover .card-glow {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 10px;
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ========== 响应式 - 小屏幕优化 ========== */
|
||||
@media (max-height: 680px) {
|
||||
:root {
|
||||
--orbit-radius: min(32vw, 115px);
|
||||
--avatar-size: min(13vw, 48px);
|
||||
--center-size: min(18vw, 70px);
|
||||
}
|
||||
|
||||
.match-area {
|
||||
max-height: 280px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.info-bar {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
padding: 10px 4px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 600px) {
|
||||
:root {
|
||||
--orbit-radius: min(28vw, 100px);
|
||||
--avatar-size: min(11vw, 42px);
|
||||
--center-size: min(16vw, 60px);
|
||||
}
|
||||
|
||||
.match-area {
|
||||
max-height: 240px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.logo-glow, .logo-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.pond-inner {
|
||||
padding: 10px 12px;
|
||||
padding-bottom: 65px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.btn-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕 */
|
||||
@media (max-height: 550px) {
|
||||
:root {
|
||||
--orbit-radius: min(25vw, 85px);
|
||||
--avatar-size: min(10vw, 38px);
|
||||
--center-size: min(14vw, 52px);
|
||||
}
|
||||
|
||||
.match-area {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.user-location {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
203
android-app/app/src/main/assets/web/wishtree.html
Normal file
203
android-app/app/src/main/assets/web/wishtree.html
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<title>许愿树</title>
|
||||
<link rel="stylesheet" href="./styles/wishtree.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="wish-app">
|
||||
<!-- 背景层 -->
|
||||
<div class="bg-layer">
|
||||
<div class="gradient-orb orb-1"></div>
|
||||
<div class="gradient-orb orb-2"></div>
|
||||
<div class="grid-lines"></div>
|
||||
</div>
|
||||
|
||||
<div class="wish-inner">
|
||||
<!-- 顶部栏 -->
|
||||
<header class="top-bar">
|
||||
<div class="logo-area">
|
||||
<span class="logo-glow">✦</span>
|
||||
<span class="logo-text">WishTree</span>
|
||||
</div>
|
||||
<div class="action-btns">
|
||||
<button class="icon-btn glass" id="searchBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn glass" id="notifyBtn">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
||||
</svg>
|
||||
<span class="notify-badge"></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 活动横幅 -->
|
||||
<section class="event-banner glass">
|
||||
<div class="banner-glow"></div>
|
||||
<div class="banner-icon">🎄</div>
|
||||
<div class="banner-info">
|
||||
<div class="banner-title" id="bannerText">元旦许愿树</div>
|
||||
<div class="banner-desc">许下心愿,愿望成真</div>
|
||||
</div>
|
||||
<div class="banner-timer">
|
||||
<span class="timer-label">剩余</span>
|
||||
<span class="timer-value" id="bannerTimer">00:00:00</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 许愿树主区域 -->
|
||||
<section class="tree-area">
|
||||
<!-- 发光树影 -->
|
||||
<div class="tree-silhouette">
|
||||
<div class="tree-glow"></div>
|
||||
<div class="tree-trunk"></div>
|
||||
<div class="tree-crown">
|
||||
<div class="crown-layer layer-1"></div>
|
||||
<div class="crown-layer layer-2"></div>
|
||||
<div class="crown-layer layer-3"></div>
|
||||
</div>
|
||||
<!-- 树上的光点 -->
|
||||
<div class="tree-lights">
|
||||
<span class="light-dot" style="--x: 20%; --y: 25%; --delay: 0s;"></span>
|
||||
<span class="light-dot" style="--x: 75%; --y: 20%; --delay: 0.5s;"></span>
|
||||
<span class="light-dot" style="--x: 35%; --y: 45%; --delay: 1s;"></span>
|
||||
<span class="light-dot" style="--x: 65%; --y: 40%; --delay: 1.5s;"></span>
|
||||
<span class="light-dot" style="--x: 50%; --y: 15%; --delay: 2s;"></span>
|
||||
<span class="light-dot" style="--x: 25%; --y: 60%; --delay: 0.3s;"></span>
|
||||
<span class="light-dot" style="--x: 70%; --y: 55%; --delay: 0.8s;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 心愿信笺卡片 -->
|
||||
<div class="wish-notes" id="wishCards">
|
||||
<div class="wish-note glass floating" data-index="0" style="--x: 12%; --y: 15%; --delay: 0s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="1" style="--x: 72%; --y: 12%; --delay: 0.4s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="2" style="--x: 5%; --y: 40%; --delay: 0.8s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="3" style="--x: 78%; --y: 38%; --delay: 1.2s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="4" style="--x: 18%; --y: 62%; --delay: 0.2s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="5" style="--x: 68%; --y: 60%; --delay: 0.6s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
<div class="wish-note glass floating" data-index="6" style="--x: 42%; --y: 8%; --delay: 1s;">
|
||||
<div class="note-pin"></div>
|
||||
<div class="note-content"></div>
|
||||
<div class="note-footer"><span class="heart">♥</span><span class="count">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中心许愿按钮 -->
|
||||
<div class="wish-core" id="addWishCard">
|
||||
<div class="core-pulse"></div>
|
||||
<div class="core-pulse delay"></div>
|
||||
<div class="core-btn">
|
||||
<span class="core-icon">+</span>
|
||||
<span class="core-text">许愿</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 底部进度区 -->
|
||||
<section class="bottom-area">
|
||||
<div class="progress-section glass">
|
||||
<div class="progress-header">
|
||||
<span class="progress-label">祈愿能量</span>
|
||||
<span class="progress-value" id="wishCount">0 / 100</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
<div class="progress-glow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="main-btn neon-btn" id="makeWishBtn">
|
||||
<span class="btn-glow"></span>
|
||||
<span class="btn-text">✦ 前往祈愿</span>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 许愿弹窗 -->
|
||||
<div class="modal" id="wishModal">
|
||||
<div class="modal-mask"></div>
|
||||
<div class="modal-box glass">
|
||||
<div class="modal-head">
|
||||
<span class="modal-icon">✦</span>
|
||||
<span class="modal-title">许下心愿</span>
|
||||
<button class="close-btn" id="modalClose">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea class="input-area glass" id="wishInput" placeholder="在星空下写下你的心愿..." maxlength="50"></textarea>
|
||||
<div class="char-info"><span id="charCount">0</span>/50</div>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button class="btn-secondary glass" id="btnCancel">取消</button>
|
||||
<button class="btn-primary neon-btn" id="btnConfirm">✦ 许愿</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看心愿弹窗 -->
|
||||
<div class="modal" id="viewModal">
|
||||
<div class="modal-mask"></div>
|
||||
<div class="modal-box glass">
|
||||
<div class="modal-head">
|
||||
<span class="modal-icon">💫</span>
|
||||
<span class="modal-title">我的心愿</span>
|
||||
<button class="close-btn" id="viewModalClose">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wish-display glass" id="wishContent"></div>
|
||||
<div class="wish-stats">
|
||||
<span class="stat" id="wishLikes">♥ 0</span>
|
||||
<span class="stat" id="wishComments">💬 0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button class="btn-danger" id="btnDelete">删除</button>
|
||||
<button class="btn-success" id="btnComplete">🎉 达成</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功弹窗 -->
|
||||
<div class="modal" id="successModal">
|
||||
<div class="modal-mask"></div>
|
||||
<div class="success-box">
|
||||
<div class="success-particles"></div>
|
||||
<div class="success-icon" id="successIcon">🎉</div>
|
||||
<div class="success-text" id="successText">许愿成功!</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./scripts/wishtree.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
160
android-app/app/src/main/assets/web/yuanchi.html
Normal file
160
android-app/app/src/main/assets/web/yuanchi.html
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<title>缘池</title>
|
||||
<link rel="stylesheet" href="./styles/yuanchi.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="pond-app">
|
||||
<!-- 背景层 -->
|
||||
<div class="bg-layer">
|
||||
<div class="gradient-orb orb-1"></div>
|
||||
<div class="gradient-orb orb-2"></div>
|
||||
<div class="grid-lines"></div>
|
||||
</div>
|
||||
|
||||
<div class="pond-inner">
|
||||
<!-- 顶部栏 -->
|
||||
<header class="top-bar">
|
||||
<div class="logo-area">
|
||||
<span class="logo-glow">◈</span>
|
||||
<span class="logo-text">FishPond</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 信息栏 -->
|
||||
<section class="info-bar glass">
|
||||
<div class="info-left">
|
||||
<span class="info-label">剩余匹配</span>
|
||||
<span class="info-value neon-text" id="matchCount">--</span>
|
||||
<span class="info-unit">人</span>
|
||||
</div>
|
||||
<div class="info-right">
|
||||
<span class="info-desc" id="userInfo">真诚 | 南宁 | 在线</span>
|
||||
<span class="info-name neon-text" id="userName">加载中...</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 匹配圆环区域 -->
|
||||
<section class="match-area">
|
||||
<!-- 中心脉冲圆环 -->
|
||||
<div class="center-ring">
|
||||
<div class="ring-pulse"></div>
|
||||
<div class="ring-pulse delay-1"></div>
|
||||
<div class="ring-pulse delay-2"></div>
|
||||
<div class="ring-core" id="refreshBtn">
|
||||
<div class="core-inner">
|
||||
<span class="core-icon">⟳</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 轨道环 -->
|
||||
<div class="orbit-ring"></div>
|
||||
|
||||
<!-- 用户头像 - 摩天轮布局 -->
|
||||
<div class="orbit-users" id="orbitGroup">
|
||||
<div class="orbit-user" data-pos="1">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
<div class="orbit-user" data-pos="2">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
<div class="orbit-user" data-pos="3">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
<div class="orbit-user" data-pos="4">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
<div class="orbit-user" data-pos="5">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
<div class="orbit-user" data-pos="6">
|
||||
<div class="user-avatar glass">
|
||||
<img src="" alt="" class="avatar-img">
|
||||
<div class="avatar-glow"></div>
|
||||
</div>
|
||||
<div class="user-name"></div>
|
||||
<div class="user-location"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 快捷操作按钮 -->
|
||||
<section class="quick-actions">
|
||||
<button class="action-btn neon-btn pink" id="voiceMatchBtn">
|
||||
<span class="btn-icon">🎧</span>
|
||||
<span class="btn-label">语音匹配</span>
|
||||
</button>
|
||||
<button class="action-btn neon-btn cyan" id="heartSignalBtn">
|
||||
<span class="btn-icon">💓</span>
|
||||
<span class="btn-label">心动信号</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- 功能入口 2x3 -->
|
||||
<section class="feature-grid" id="categoryGrid">
|
||||
<div class="feature-card glass" data-id="1">
|
||||
<div class="card-glow pink"></div>
|
||||
<div class="card-icon">💕</div>
|
||||
<div class="card-title">在线处对象</div>
|
||||
</div>
|
||||
<div class="feature-card glass" data-id="2">
|
||||
<div class="card-glow cyan"></div>
|
||||
<div class="card-icon">🎮</div>
|
||||
<div class="card-title">找人玩游戏</div>
|
||||
</div>
|
||||
<div class="feature-card glass" data-id="3">
|
||||
<div class="card-glow purple"></div>
|
||||
<div class="card-icon">🎤</div>
|
||||
<div class="card-title">一起KTV</div>
|
||||
</div>
|
||||
<div class="feature-card glass" data-id="4">
|
||||
<div class="card-glow pink"></div>
|
||||
<div class="card-icon">🎨</div>
|
||||
<div class="card-title">你画我猜</div>
|
||||
</div>
|
||||
<div class="feature-card glass" data-id="5">
|
||||
<div class="card-glow cyan"></div>
|
||||
<div class="card-icon">🔫</div>
|
||||
<div class="card-title">和平精英</div>
|
||||
</div>
|
||||
<div class="feature-card glass" data-id="6">
|
||||
<div class="card-glow purple"></div>
|
||||
<div class="card-icon">🎲</div>
|
||||
<div class="card-title">桌游派对</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./scripts/yuanchi.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -56,7 +56,7 @@ public class DrawGuessActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,12 +125,12 @@ public class DynamicCommunityActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public class FindGameActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ public class FishPondActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,508 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.ApiService;
|
||||
import com.example.livestreaming.net.CommunityResponse;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* 缘池页面 - WebView版本
|
||||
* 使用H5页面展示,通过JavaScript Bridge与原生交互
|
||||
*/
|
||||
public class FishPondWebViewActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "FishPondWebView";
|
||||
|
||||
private WebView webView;
|
||||
private ProgressBar loadingProgress;
|
||||
private ApiService apiService;
|
||||
|
||||
// 缓存数据
|
||||
private String cachedUserName = "";
|
||||
private String cachedUserInfo = "";
|
||||
private int cachedMatchableCount = 0;
|
||||
private List<CommunityResponse.NearbyUser> cachedNearbyUsers = new ArrayList<>();
|
||||
private List<CommunityResponse.Category> cachedCategories = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_fish_pond_webview);
|
||||
|
||||
apiService = ApiClient.getService(this);
|
||||
|
||||
initViews();
|
||||
setupWebView();
|
||||
setupBottomNav();
|
||||
|
||||
// 预加载数据
|
||||
loadCurrentUserInfo();
|
||||
loadMatchableUserCount();
|
||||
loadNearbyUsers();
|
||||
loadCategories();
|
||||
|
||||
// 加载H5页面
|
||||
webView.loadUrl("file:///android_asset/web/yuanchi.html");
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
webView = findViewById(R.id.webView);
|
||||
loadingProgress = findViewById(R.id.loadingProgress);
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private void setupWebView() {
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setAllowContentAccess(true);
|
||||
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
|
||||
// 添加JavaScript接口
|
||||
webView.addJavascriptInterface(new FishPondJSInterface(), "Android");
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
|
||||
// 页面加载完成后,推送缓存的数据到H5
|
||||
pushDataToH5();
|
||||
}
|
||||
});
|
||||
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress < 100) {
|
||||
loadingProgress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBottomNav() {
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_friends);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
|
||||
bottomNav.setOnItemSelectedListener(item -> {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.nav_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_messages) {
|
||||
MessagesActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_profile) {
|
||||
ProfileActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载当前用户信息
|
||||
*/
|
||||
private void loadCurrentUserInfo() {
|
||||
SharedPreferences prefs = getSharedPreferences("profile_prefs", MODE_PRIVATE);
|
||||
cachedUserName = prefs.getString("profile_name", "爱你");
|
||||
String location = prefs.getString("profile_location", "广西");
|
||||
String gender = prefs.getString("profile_gender", "");
|
||||
|
||||
String genderText = "真诚";
|
||||
String locationText = location;
|
||||
if (!TextUtils.isEmpty(location) && location.contains("-")) {
|
||||
locationText = location.replace("-", "·");
|
||||
} else if (TextUtils.isEmpty(location)) {
|
||||
locationText = "南宁";
|
||||
}
|
||||
|
||||
cachedUserInfo = genderText + " | " + locationText + " | 在线";
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载可匹配用户数量
|
||||
*/
|
||||
private void loadMatchableUserCount() {
|
||||
apiService.getMatchableUserCount().enqueue(new Callback<ApiResponse<Integer>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<Integer>> call, Response<ApiResponse<Integer>> response) {
|
||||
if (response.isSuccessful() && response.body() != null
|
||||
&& response.body().isOk() && response.body().getData() != null) {
|
||||
cachedMatchableCount = response.body().getData();
|
||||
pushMatchableCountToH5();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<Integer>> call, Throwable t) {
|
||||
Log.e(TAG, "获取用户总数失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载附近用户
|
||||
*/
|
||||
private void loadNearbyUsers() {
|
||||
SharedPreferences prefs = getSharedPreferences("profile_prefs", MODE_PRIVATE);
|
||||
double latitude = prefs.getFloat("latitude", 22.82f);
|
||||
double longitude = prefs.getFloat("longitude", 108.32f);
|
||||
|
||||
apiService.getNearbyUsers(latitude, longitude, 5000, 6)
|
||||
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserList>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserList>> call,
|
||||
Response<ApiResponse<CommunityResponse.NearbyUserList>> response) {
|
||||
if (response.isSuccessful() && response.body() != null
|
||||
&& response.body().isOk() && response.body().getData() != null) {
|
||||
List<CommunityResponse.NearbyUser> users = response.body().getData().getUsers();
|
||||
if (users != null && !users.isEmpty()) {
|
||||
cachedNearbyUsers = users;
|
||||
pushNearbyUsersToH5();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<CommunityResponse.NearbyUserList>> call, Throwable t) {
|
||||
Log.e(TAG, "获取附近用户失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载板块列表
|
||||
*/
|
||||
private void loadCategories() {
|
||||
apiService.getCommunityCategories().enqueue(new Callback<ApiResponse<List<CommunityResponse.Category>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResponse<List<CommunityResponse.Category>>> call,
|
||||
Response<ApiResponse<List<CommunityResponse.Category>>> response) {
|
||||
if (response.isSuccessful() && response.body() != null
|
||||
&& response.body().isOk() && response.body().getData() != null) {
|
||||
cachedCategories = response.body().getData();
|
||||
pushCategoriesToH5();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResponse<List<CommunityResponse.Category>>> call, Throwable t) {
|
||||
Log.e(TAG, "加载板块失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送所有数据到H5
|
||||
*/
|
||||
private void pushDataToH5() {
|
||||
pushUserInfoToH5();
|
||||
pushMatchableCountToH5();
|
||||
pushNearbyUsersToH5();
|
||||
pushCategoriesToH5();
|
||||
}
|
||||
|
||||
private void pushUserInfoToH5() {
|
||||
runOnUiThread(() -> {
|
||||
String js = String.format("updateUserInfo('%s', '%s')",
|
||||
escapeJs(cachedUserName), escapeJs(cachedUserInfo));
|
||||
webView.evaluateJavascript(js, null);
|
||||
});
|
||||
}
|
||||
|
||||
private void pushMatchableCountToH5() {
|
||||
runOnUiThread(() -> {
|
||||
String js = String.format("updateMatchableCount('%d')", cachedMatchableCount);
|
||||
webView.evaluateJavascript(js, null);
|
||||
});
|
||||
}
|
||||
|
||||
private void pushNearbyUsersToH5() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
JSONArray usersArray = new JSONArray();
|
||||
String baseUrl = ApiClient.getCurrentBaseUrl(this);
|
||||
|
||||
for (CommunityResponse.NearbyUser user : cachedNearbyUsers) {
|
||||
JSONObject userObj = new JSONObject();
|
||||
userObj.put("id", String.valueOf(user.getUserId()));
|
||||
userObj.put("name", user.getNickname() != null ? user.getNickname() : "用户");
|
||||
userObj.put("location", user.getLocation() != null ? user.getLocation() : "附近");
|
||||
|
||||
String avatarUrl = user.getAvatar();
|
||||
if (avatarUrl != null && !avatarUrl.isEmpty()) {
|
||||
if (!avatarUrl.startsWith("http://") && !avatarUrl.startsWith("https://")) {
|
||||
avatarUrl = baseUrl + avatarUrl;
|
||||
}
|
||||
}
|
||||
userObj.put("avatar", avatarUrl != null ? avatarUrl : "");
|
||||
|
||||
usersArray.put(userObj);
|
||||
}
|
||||
|
||||
String js = String.format("updateNearbyUsers('%s')", escapeJs(usersArray.toString()));
|
||||
webView.evaluateJavascript(js, null);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "构建用户JSON失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void pushCategoriesToH5() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
JSONArray categoriesArray = new JSONArray();
|
||||
|
||||
for (CommunityResponse.Category cat : cachedCategories) {
|
||||
if (cat.status == 1 || cat.status == 0) {
|
||||
JSONObject catObj = new JSONObject();
|
||||
catObj.put("id", cat.getId());
|
||||
catObj.put("name", cat.getName() != null ? cat.getName() : "");
|
||||
catObj.put("icon", cat.getIcon() != null ? cat.getIcon() : "");
|
||||
catObj.put("jumpPage", cat.getJumpPage() != null ? cat.getJumpPage() : "");
|
||||
categoriesArray.put(catObj);
|
||||
}
|
||||
}
|
||||
|
||||
String js = String.format("updateCategories('%s')", escapeJs(categoriesArray.toString()));
|
||||
webView.evaluateJavascript(js, null);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "构建板块JSON失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String escapeJs(String str) {
|
||||
if (str == null) return "";
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JavaScript接口类 - 供H5调用
|
||||
*/
|
||||
private class FishPondJSInterface {
|
||||
|
||||
@JavascriptInterface
|
||||
public String getUserInfo() {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("name", cachedUserName);
|
||||
obj.put("info", cachedUserInfo);
|
||||
return obj.toString();
|
||||
} catch (JSONException e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getMatchableCount() {
|
||||
return String.valueOf(cachedMatchableCount);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getNearbyUsers() {
|
||||
try {
|
||||
JSONArray usersArray = new JSONArray();
|
||||
String baseUrl = ApiClient.getCurrentBaseUrl(FishPondWebViewActivity.this);
|
||||
|
||||
for (CommunityResponse.NearbyUser user : cachedNearbyUsers) {
|
||||
JSONObject userObj = new JSONObject();
|
||||
userObj.put("id", String.valueOf(user.getUserId()));
|
||||
userObj.put("name", user.getNickname() != null ? user.getNickname() : "用户");
|
||||
userObj.put("location", user.getLocation() != null ? user.getLocation() : "附近");
|
||||
|
||||
String avatarUrl = user.getAvatar();
|
||||
if (avatarUrl != null && !avatarUrl.isEmpty()) {
|
||||
if (!avatarUrl.startsWith("http://") && !avatarUrl.startsWith("https://")) {
|
||||
avatarUrl = baseUrl + avatarUrl;
|
||||
}
|
||||
}
|
||||
userObj.put("avatar", avatarUrl != null ? avatarUrl : "");
|
||||
|
||||
usersArray.put(userObj);
|
||||
}
|
||||
return usersArray.toString();
|
||||
} catch (JSONException e) {
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getCategories() {
|
||||
try {
|
||||
JSONArray categoriesArray = new JSONArray();
|
||||
|
||||
for (CommunityResponse.Category cat : cachedCategories) {
|
||||
if (cat.status == 1 || cat.status == 0) {
|
||||
JSONObject catObj = new JSONObject();
|
||||
catObj.put("id", cat.getId());
|
||||
catObj.put("name", cat.getName() != null ? cat.getName() : "");
|
||||
catObj.put("icon", cat.getIcon() != null ? cat.getIcon() : "");
|
||||
catObj.put("jumpPage", cat.getJumpPage() != null ? cat.getJumpPage() : "");
|
||||
categoriesArray.put(catObj);
|
||||
}
|
||||
}
|
||||
return categoriesArray.toString();
|
||||
} catch (JSONException e) {
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onUserClick(String oderId, String userName) {
|
||||
Log.d(TAG, "点击用户: " + oderId + ", " + userName);
|
||||
runOnUiThread(() -> {
|
||||
// 跳转到用户详情页
|
||||
try {
|
||||
int userId = 0;
|
||||
try {
|
||||
userId = Integer.parseInt(oderId);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, "用户ID解析失败: " + oderId);
|
||||
}
|
||||
|
||||
if (userId > 0) {
|
||||
UserProfileActivity.start(FishPondWebViewActivity.this, userId);
|
||||
} else {
|
||||
Toast.makeText(FishPondWebViewActivity.this, "用户ID无效", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(FishPondWebViewActivity.this, "查看用户: " + userName, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onCategoryClick(int categoryId, String categoryName, String jumpPage) {
|
||||
Log.d(TAG, "点击板块: " + categoryId + ", " + categoryName + ", " + jumpPage);
|
||||
runOnUiThread(() -> {
|
||||
// 跳转到板块详情页
|
||||
try {
|
||||
Intent intent = new Intent(FishPondWebViewActivity.this, DynamicCommunityActivity.class);
|
||||
intent.putExtra("category_id", categoryId);
|
||||
intent.putExtra("category_name", categoryName);
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(FishPondWebViewActivity.this, "板块 \"" + categoryName + "\" 暂未开放", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onRefresh() {
|
||||
Log.d(TAG, "刷新");
|
||||
runOnUiThread(() -> {
|
||||
loadMatchableUserCount();
|
||||
loadNearbyUsers();
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onVoiceMatch() {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(FishPondWebViewActivity.this, "语音匹配功能待接入~", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onHeartSignal() {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(FishPondWebViewActivity.this, "心动信号功能待接入~", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void showToast(String message) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(FishPondWebViewActivity.this, message, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 刷新底部导航状态
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_friends);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (webView != null) {
|
||||
webView.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -102,12 +102,12 @@ public class HeartbeatSignalActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class KTVTogetherActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (!AuthHelper.requireLogin(this, "缘池功能需要登录")) {
|
||||
return;
|
||||
}
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
|
@ -501,12 +501,12 @@ public class MainActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1749,16 +1749,16 @@ public class MainActivity extends AppCompatActivity {
|
|||
allBackendCategories.addAll(categories);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
// 从"我的频道"加载用户保存的频道列表
|
||||
List<ChannelManagerAdapter.ChannelItem> myChannelList = loadMyChannels();
|
||||
|
||||
// 清空现有标签
|
||||
binding.categoryTabs.removeAllTabs();
|
||||
|
||||
// 只显示"我的频道"里的内容
|
||||
for (ChannelManagerAdapter.ChannelItem channel : myChannelList) {
|
||||
if (channel != null && channel.getName() != null) {
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(channel.getName()));
|
||||
// 添加"推荐"作为第一个标签
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
|
||||
|
||||
// 直接使用后端返回的分类
|
||||
for (com.example.livestreaming.net.CategoryResponse cat : categories) {
|
||||
if (cat != null && cat.getName() != null) {
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(cat.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1766,7 +1766,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
String lastCategory = CategoryFilterManager.getLastCategory(this);
|
||||
restoreCategoryTabSelection();
|
||||
|
||||
Log.d(TAG, "updateCategoryTabs() 分类标签更新完成,共 " + binding.categoryTabs.getTabCount() + " 个标签(只显示我的频道)");
|
||||
Log.d(TAG, "updateCategoryTabs() 分类标签更新完成,共 " + binding.categoryTabs.getTabCount() + " 个标签(直接使用后端分类)");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1777,23 +1777,33 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (binding == null || binding.categoryTabs == null) return;
|
||||
|
||||
runOnUiThread(() -> {
|
||||
// 从"我的频道"加载用户保存的频道列表
|
||||
List<ChannelManagerAdapter.ChannelItem> myChannelList = loadMyChannels();
|
||||
|
||||
// 清空现有标签
|
||||
binding.categoryTabs.removeAllTabs();
|
||||
|
||||
// 只显示"我的频道"里的内容
|
||||
for (ChannelManagerAdapter.ChannelItem channel : myChannelList) {
|
||||
if (channel != null && channel.getName() != null) {
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(channel.getName()));
|
||||
// 使用后端分类(如果已加载)
|
||||
if (!allBackendCategories.isEmpty()) {
|
||||
// 添加"推荐"作为第一个标签
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
|
||||
|
||||
// 添加后端分类
|
||||
for (com.example.livestreaming.net.CategoryResponse cat : allBackendCategories) {
|
||||
if (cat != null && cat.getName() != null) {
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(cat.getName()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 后端分类未加载,使用硬编码默认值
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("娱乐"));
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("游戏"));
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("音乐"));
|
||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("户外"));
|
||||
}
|
||||
|
||||
// 恢复上次选中的分类
|
||||
restoreCategoryTabSelection();
|
||||
|
||||
Log.d(TAG, "useDefaultCategories() 使用我的频道,共 " + binding.categoryTabs.getTabCount() + " 个标签");
|
||||
Log.d(TAG, "useDefaultCategories() 使用默认分类,共 " + binding.categoryTabs.getTabCount() + " 个标签");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,12 +103,12 @@ public class MessagesActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class OnlineDatingActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class PeaceEliteActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,12 +113,12 @@ public class ProfileActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.ApiService;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* 个人中心 - WebView版本
|
||||
*/
|
||||
public class ProfileWebViewActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "ProfileWebView";
|
||||
|
||||
private WebView webView;
|
||||
private ProgressBar loadingProgress;
|
||||
private ApiService apiService;
|
||||
|
||||
// 缓存数据
|
||||
private String cachedName = "";
|
||||
private String cachedId = "";
|
||||
private String cachedAvatar = "";
|
||||
private String cachedGender = "male";
|
||||
private int cachedFollowing = 0;
|
||||
private int cachedFans = 0;
|
||||
private int cachedLikes = 0;
|
||||
private int cachedFriends = 0;
|
||||
private String cachedBalance = "0.00";
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity(new Intent(context, ProfileWebViewActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_profile_webview);
|
||||
|
||||
apiService = ApiClient.getService(this);
|
||||
|
||||
initViews();
|
||||
setupWebView();
|
||||
setupBottomNav();
|
||||
loadUserData();
|
||||
|
||||
webView.loadUrl("file:///android_asset/web/profile.html");
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
webView = findViewById(R.id.webView);
|
||||
loadingProgress = findViewById(R.id.loadingProgress);
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private void setupWebView() {
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
|
||||
webView.addJavascriptInterface(new ProfileJSInterface(), "Android");
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
pushDataToH5();
|
||||
}
|
||||
});
|
||||
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
loadingProgress.setVisibility(newProgress < 100 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBottomNav() {
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_profile);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
|
||||
bottomNav.setOnItemSelectedListener(item -> {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.nav_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_messages) {
|
||||
MessagesActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_profile) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadUserData() {
|
||||
SharedPreferences prefs = getSharedPreferences("profile_prefs", MODE_PRIVATE);
|
||||
cachedName = prefs.getString("profile_name", "用户");
|
||||
cachedId = prefs.getString("profile_id", "12345678");
|
||||
cachedAvatar = prefs.getString("profile_avatar", "");
|
||||
cachedGender = prefs.getString("profile_gender", "male");
|
||||
cachedFollowing = prefs.getInt("following_count", 0);
|
||||
cachedFans = prefs.getInt("fans_count", 0);
|
||||
cachedLikes = prefs.getInt("likes_count", 0);
|
||||
cachedFriends = prefs.getInt("friends_count", 0);
|
||||
cachedBalance = prefs.getString("wallet_balance", "0.00");
|
||||
}
|
||||
|
||||
private void pushDataToH5() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
JSONObject profile = new JSONObject();
|
||||
profile.put("name", cachedName);
|
||||
profile.put("id", cachedId);
|
||||
profile.put("avatar", cachedAvatar);
|
||||
profile.put("gender", cachedGender);
|
||||
profile.put("following", cachedFollowing);
|
||||
profile.put("fans", cachedFans);
|
||||
profile.put("likes", cachedLikes);
|
||||
profile.put("friends", cachedFriends);
|
||||
|
||||
String js = String.format("updateProfile('%s')", escapeJs(profile.toString()));
|
||||
webView.evaluateJavascript(js, null);
|
||||
|
||||
String balanceJs = String.format("updateWalletBalance('%s')", cachedBalance);
|
||||
webView.evaluateJavascript(balanceJs, null);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "构建JSON失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String escapeJs(String str) {
|
||||
if (str == null) return "";
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n");
|
||||
}
|
||||
|
||||
private class ProfileJSInterface {
|
||||
|
||||
@JavascriptInterface
|
||||
public String getUserProfile() {
|
||||
try {
|
||||
JSONObject profile = new JSONObject();
|
||||
profile.put("name", cachedName);
|
||||
profile.put("id", cachedId);
|
||||
profile.put("avatar", cachedAvatar);
|
||||
profile.put("gender", cachedGender);
|
||||
profile.put("following", cachedFollowing);
|
||||
profile.put("fans", cachedFans);
|
||||
profile.put("likes", cachedLikes);
|
||||
profile.put("friends", cachedFriends);
|
||||
return profile.toString();
|
||||
} catch (JSONException e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getWalletBalance() {
|
||||
return cachedBalance;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onEditProfile() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, EditProfileActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "编辑资料", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onWalletClick() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, WalletActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "钱包", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onRecharge() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, RechargeActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "充值", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onWithdraw() {
|
||||
runOnUiThread(() -> Toast.makeText(ProfileWebViewActivity.this, "提现功能开发中", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onFollowingClick() {
|
||||
runOnUiThread(() -> FollowingListActivity.start(ProfileWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onFansClick() {
|
||||
runOnUiThread(() -> FansListActivity.start(ProfileWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onLikesClick() {
|
||||
runOnUiThread(() -> LikesListActivity.start(ProfileWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onFriendsClick() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, MyFriendsActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "我的好友", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onMenuClick(String menuId) {
|
||||
runOnUiThread(() -> {
|
||||
switch (menuId) {
|
||||
case "liked":
|
||||
LikedRoomsActivity.start(ProfileWebViewActivity.this);
|
||||
break;
|
||||
case "records":
|
||||
MyRecordsActivity.start(ProfileWebViewActivity.this);
|
||||
break;
|
||||
case "streamer":
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, StreamerCenterActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "主播中心", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
case "settings":
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, SettingsPageActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "设置", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onSearchClick() {
|
||||
runOnUiThread(() -> SearchActivity.start(ProfileWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onHistoryClick() {
|
||||
runOnUiThread(() -> WatchHistoryActivity.start(ProfileWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onSettingsClick() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
startActivity(new Intent(ProfileWebViewActivity.this, SettingsPageActivity.class));
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(ProfileWebViewActivity.this, "设置", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void copyToClipboard(String text) {
|
||||
runOnUiThread(() -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("ID", text);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
});
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void showToast(String message) {
|
||||
runOnUiThread(() -> Toast.makeText(ProfileWebViewActivity.this, message, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_profile);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
}
|
||||
loadUserData();
|
||||
pushDataToH5();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (webView != null) webView.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ public class TableGamesActivity extends BaseCategoryActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,12 +206,12 @@ public class VoiceMatchActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
WishTreeActivity.start(this);
|
||||
WishTreeWebViewActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,7 +499,7 @@ public class WishTreeActivity extends AppCompatActivity {
|
|||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondActivity.class));
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,413 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.ApiService;
|
||||
import com.example.livestreaming.net.AuthStore;
|
||||
import com.example.livestreaming.net.WishtreeRequest;
|
||||
import com.example.livestreaming.net.WishtreeResponse;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* 许愿树页面 - WebView版本
|
||||
*/
|
||||
public class WishTreeWebViewActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "WishTreeWebView";
|
||||
|
||||
private WebView webView;
|
||||
private ProgressBar loadingProgress;
|
||||
private ApiService apiService;
|
||||
|
||||
// 缓存数据
|
||||
private WishtreeResponse.Festival currentFestival;
|
||||
private List<WishtreeResponse.Wish> myWishes = new ArrayList<>();
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity(new Intent(context, WishTreeWebViewActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_wish_tree_webview);
|
||||
|
||||
apiService = ApiClient.getService(this);
|
||||
|
||||
initViews();
|
||||
setupWebView();
|
||||
setupBottomNav();
|
||||
|
||||
// 预加载数据
|
||||
loadCurrentFestival();
|
||||
loadMyWishes();
|
||||
|
||||
// 加载H5页面
|
||||
webView.loadUrl("file:///android_asset/web/wishtree.html");
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
webView = findViewById(R.id.webView);
|
||||
loadingProgress = findViewById(R.id.loadingProgress);
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private void setupWebView() {
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setAllowFileAccess(true);
|
||||
settings.setAllowContentAccess(true);
|
||||
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
|
||||
webView.addJavascriptInterface(new WishTreeJSInterface(), "Android");
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
pushDataToH5();
|
||||
}
|
||||
});
|
||||
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress < 100) {
|
||||
loadingProgress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
loadingProgress.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBottomNav() {
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_wish_tree);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
|
||||
bottomNav.setOnItemSelectedListener(item -> {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.nav_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_friends) {
|
||||
startActivity(new Intent(this, FishPondWebViewActivity.class));
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_wish_tree) {
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_messages) {
|
||||
MessagesActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (id == R.id.nav_profile) {
|
||||
ProfileActivity.start(this);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载当前节日
|
||||
*/
|
||||
private void loadCurrentFestival() {
|
||||
apiService.getCurrentFestival().enqueue(new Callback<ApiResponse<WishtreeResponse.Festival>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse<WishtreeResponse.Festival>> call,
|
||||
@NonNull Response<ApiResponse<WishtreeResponse.Festival>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getData() != null) {
|
||||
currentFestival = response.body().getData();
|
||||
pushFestivalToH5();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse<WishtreeResponse.Festival>> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "加载节日失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载我的心愿
|
||||
*/
|
||||
private void loadMyWishes() {
|
||||
apiService.getMyWishes(1, 10).enqueue(new Callback<ApiResponse<WishtreeResponse.WishPage>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse<WishtreeResponse.WishPage>> call,
|
||||
@NonNull Response<ApiResponse<WishtreeResponse.WishPage>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getData() != null) {
|
||||
myWishes = response.body().getData().list;
|
||||
if (myWishes == null) myWishes = new ArrayList<>();
|
||||
pushWishesToH5();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse<WishtreeResponse.WishPage>> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "加载心愿失败: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布心愿
|
||||
*/
|
||||
private void publishWish(String content) {
|
||||
Integer festivalId = currentFestival != null ? currentFestival.id : null;
|
||||
WishtreeRequest.PublishWish request = new WishtreeRequest.PublishWish(festivalId, content, null);
|
||||
|
||||
apiService.publishWish(request).enqueue(new Callback<ApiResponse<WishtreeResponse.Wish>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse<WishtreeResponse.Wish>> call,
|
||||
@NonNull Response<ApiResponse<WishtreeResponse.Wish>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||
runOnUiThread(() -> {
|
||||
webView.evaluateJavascript("onPublishSuccess()", null);
|
||||
Toast.makeText(WishTreeWebViewActivity.this, "许愿成功", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
loadMyWishes();
|
||||
} else {
|
||||
String msg = response.body() != null ? response.body().getMessage() : "发布失败";
|
||||
runOnUiThread(() -> Toast.makeText(WishTreeWebViewActivity.this, msg, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse<WishtreeResponse.Wish>> call, @NonNull Throwable t) {
|
||||
runOnUiThread(() -> Toast.makeText(WishTreeWebViewActivity.this, "网络错误", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除心愿
|
||||
*/
|
||||
private void deleteWish(long wishId, boolean isComplete) {
|
||||
apiService.deleteMyWish(wishId).enqueue(new Callback<ApiResponse<String>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse<String>> call,
|
||||
@NonNull Response<ApiResponse<String>> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||
runOnUiThread(() -> {
|
||||
webView.evaluateJavascript("onDeleteSuccess(" + isComplete + ")", null);
|
||||
if (isComplete) {
|
||||
Toast.makeText(WishTreeWebViewActivity.this, "恭喜愿望达成!", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(WishTreeWebViewActivity.this, "心愿已删除", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
loadMyWishes();
|
||||
} else {
|
||||
runOnUiThread(() -> Toast.makeText(WishTreeWebViewActivity.this, "操作失败", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse<String>> call, @NonNull Throwable t) {
|
||||
runOnUiThread(() -> Toast.makeText(WishTreeWebViewActivity.this, "网络错误", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送数据到H5
|
||||
*/
|
||||
private void pushDataToH5() {
|
||||
pushFestivalToH5();
|
||||
pushWishesToH5();
|
||||
}
|
||||
|
||||
private void pushFestivalToH5() {
|
||||
if (currentFestival == null) return;
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("id", currentFestival.id);
|
||||
obj.put("name", currentFestival.name != null ? currentFestival.name : "");
|
||||
String js = String.format("updateFestival('%s')", escapeJs(obj.toString()));
|
||||
webView.evaluateJavascript(js, null);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "构建节日JSON失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void pushWishesToH5() {
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
JSONArray arr = new JSONArray();
|
||||
for (WishtreeResponse.Wish wish : myWishes) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("id", wish.id);
|
||||
obj.put("content", wish.content != null ? wish.content : "");
|
||||
obj.put("likeCount", wish.likeCount);
|
||||
obj.put("commentCount", wish.commentCount);
|
||||
arr.put(obj);
|
||||
}
|
||||
String js = String.format("updateWishes('%s')", escapeJs(arr.toString()));
|
||||
webView.evaluateJavascript(js, null);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "构建心愿JSON失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String escapeJs(String str) {
|
||||
if (str == null) return "";
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JavaScript接口类
|
||||
*/
|
||||
private class WishTreeJSInterface {
|
||||
|
||||
@JavascriptInterface
|
||||
public String getFestival() {
|
||||
if (currentFestival == null) return "{}";
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("id", currentFestival.id);
|
||||
obj.put("name", currentFestival.name != null ? currentFestival.name : "");
|
||||
return obj.toString();
|
||||
} catch (JSONException e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getMyWishes() {
|
||||
try {
|
||||
JSONArray arr = new JSONArray();
|
||||
for (WishtreeResponse.Wish wish : myWishes) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("id", wish.id);
|
||||
obj.put("content", wish.content != null ? wish.content : "");
|
||||
obj.put("likeCount", wish.likeCount);
|
||||
obj.put("commentCount", wish.commentCount);
|
||||
arr.put(obj);
|
||||
}
|
||||
return arr.toString();
|
||||
} catch (JSONException e) {
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void publishWish(String content) {
|
||||
Log.d(TAG, "发布心愿: " + content);
|
||||
WishTreeWebViewActivity.this.publishWish(content);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void deleteWish(long wishId, boolean isComplete) {
|
||||
Log.d(TAG, "删除心愿: " + wishId + ", isComplete: " + isComplete);
|
||||
WishTreeWebViewActivity.this.deleteWish(wishId, isComplete);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onSearchClick() {
|
||||
runOnUiThread(() -> SearchActivity.start(WishTreeWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void onNotifyClick() {
|
||||
runOnUiThread(() -> NotificationsActivity.start(WishTreeWebViewActivity.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean checkLogin() {
|
||||
String token = AuthStore.getToken(WishTreeWebViewActivity.this);
|
||||
if (token == null || token.isEmpty()) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(WishTreeWebViewActivity.this, "请先登录", Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(WishTreeWebViewActivity.this, LoginActivity.class));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void showToast(String message) {
|
||||
runOnUiThread(() -> Toast.makeText(WishTreeWebViewActivity.this, message, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
|
||||
if (bottomNav != null) {
|
||||
bottomNav.setSelectedItemId(R.id.nav_wish_tree);
|
||||
UnreadMessageManager.updateBadge(bottomNav);
|
||||
}
|
||||
loadMyWishes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (webView != null) {
|
||||
webView.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
package com.example.livestreaming.location;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 天地图定位服务
|
||||
* 使用Android原生定位获取经纬度,然后通过天地图API进行逆地理编码获取地址
|
||||
*/
|
||||
public class TianDiTuLocationService {
|
||||
|
||||
private static final String TAG = "TianDiTuLocation";
|
||||
|
||||
// 天地图API Key
|
||||
private static final String TIANDITU_API_KEY = "1b337ad2535a95289c5b943858dd53ac";
|
||||
|
||||
// 天地图逆地理编码API
|
||||
private static final String GEOCODE_API_URL = "http://api.tianditu.gov.cn/geocoder";
|
||||
|
||||
// 测试模式:如果为true,使用固定的中国坐标进行测试
|
||||
// 设置为false后,将使用真实的GPS定位
|
||||
// 注意:Android模拟器默认位置在美国,需要在模拟器中手动设置中国坐标
|
||||
// 或者开启测试模式进行开发测试
|
||||
private static final boolean TEST_MODE = false; // 关闭测试模式,使用真实GPS定位
|
||||
// private static final double TEST_LATITUDE = 22.5431; // 深圳纬度(仅测试模式使用)
|
||||
// private static final double TEST_LONGITUDE = 114.0579; // 深圳经度(仅测试模式使用)
|
||||
|
||||
private Context context;
|
||||
private LocationManager locationManager;
|
||||
private LocationListener locationListener;
|
||||
private OnLocationResultListener resultListener;
|
||||
|
||||
// 保存当前定位的经纬度
|
||||
private double currentLatitude;
|
||||
private double currentLongitude;
|
||||
|
||||
public interface OnLocationResultListener {
|
||||
void onSuccess(String province, String city, String address, double latitude, double longitude);
|
||||
void onError(String error);
|
||||
}
|
||||
|
||||
public TianDiTuLocationService(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始定位
|
||||
*/
|
||||
public void startLocation(OnLocationResultListener listener) {
|
||||
this.resultListener = listener;
|
||||
|
||||
// 检查权限
|
||||
if (!checkLocationPermission()) {
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("缺少定位权限");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查定位服务是否开启
|
||||
if (!isLocationEnabled()) {
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("请先开启定位服务");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建位置监听器
|
||||
locationListener = new LocationListener() {
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
double lat = location.getLatitude();
|
||||
double lon = location.getLongitude();
|
||||
Log.d(TAG, "位置更新 - 纬度(Latitude): " + lat + ", 经度(Longitude): " + lon);
|
||||
|
||||
// 获取到位置后,停止监听
|
||||
stopLocation();
|
||||
// 进行逆地理编码
|
||||
reverseGeocode(lon, lat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
}
|
||||
};
|
||||
|
||||
// 优先使用GPS定位
|
||||
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
0,
|
||||
0,
|
||||
locationListener
|
||||
);
|
||||
Log.d(TAG, "开始GPS定位");
|
||||
}
|
||||
|
||||
// 同时使用网络定位作为备选
|
||||
if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.NETWORK_PROVIDER,
|
||||
0,
|
||||
0,
|
||||
locationListener
|
||||
);
|
||||
Log.d(TAG, "开始网络定位");
|
||||
}
|
||||
|
||||
// 尝试获取最后已知位置
|
||||
Location lastKnownLocation = getLastKnownLocation();
|
||||
if (lastKnownLocation != null) {
|
||||
double lat = lastKnownLocation.getLatitude();
|
||||
double lon = lastKnownLocation.getLongitude();
|
||||
Log.d(TAG, "使用最后已知位置 - 纬度(Latitude): " + lat + ", 经度(Longitude): " + lon);
|
||||
stopLocation();
|
||||
reverseGeocode(lon, lat);
|
||||
}
|
||||
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "定位权限异常", e);
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("定位权限异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定位
|
||||
*/
|
||||
public void stopLocation() {
|
||||
if (locationManager != null && locationListener != null) {
|
||||
try {
|
||||
locationManager.removeUpdates(locationListener);
|
||||
Log.d(TAG, "停止定位");
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "停止定位异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查定位权限
|
||||
*/
|
||||
private boolean checkLocationPermission() {
|
||||
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
|| ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查定位服务是否开启
|
||||
*/
|
||||
private boolean isLocationEnabled() {
|
||||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后已知位置
|
||||
*/
|
||||
private Location getLastKnownLocation() {
|
||||
try {
|
||||
Location gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||
Location networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
|
||||
|
||||
if (gpsLocation != null && networkLocation != null) {
|
||||
// 返回较新的位置
|
||||
return gpsLocation.getTime() > networkLocation.getTime() ? gpsLocation : networkLocation;
|
||||
} else if (gpsLocation != null) {
|
||||
return gpsLocation;
|
||||
} else {
|
||||
return networkLocation;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "获取最后已知位置异常", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 逆地理编码 - 将经纬度转换为地址
|
||||
*/
|
||||
private void reverseGeocode(final double longitude, final double latitude) {
|
||||
// 保存经纬度
|
||||
this.currentLongitude = longitude;
|
||||
this.currentLatitude = latitude;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d(TAG, "开始逆地理编码 - 经度(Lon): " + longitude + ", 纬度(Lat): " + latitude);
|
||||
|
||||
// 检查坐标是否在中国境内(粗略判断)
|
||||
// 中国经度范围:73°E - 135°E,纬度范围:3°N - 53°N
|
||||
if (longitude < 73 || longitude > 135 || latitude < 3 || latitude > 53) {
|
||||
Log.w(TAG, "坐标不在中国境内 - 经度: " + longitude + " (应在73-135之间), 纬度: " + latitude + " (应在3-53之间)");
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("当前位置不在中国境内,天地图API仅支持中国地区\n坐标: " + latitude + ", " + longitude);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "坐标检查通过,在中国境内");
|
||||
|
||||
// 构建请求URL
|
||||
JSONObject postData = new JSONObject();
|
||||
postData.put("lon", longitude);
|
||||
postData.put("lat", latitude);
|
||||
postData.put("ver", 1);
|
||||
|
||||
String urlString = GEOCODE_API_URL + "?postStr=" + URLEncoder.encode(postData.toString(), "UTF-8")
|
||||
+ "&type=geocode&tk=" + TIANDITU_API_KEY;
|
||||
|
||||
Log.d(TAG, "逆地理编码请求: " + urlString);
|
||||
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setConnectTimeout(10000);
|
||||
connection.setReadTimeout(10000);
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
Log.d(TAG, "响应码: " + responseCode);
|
||||
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String responseStr = response.toString();
|
||||
Log.d(TAG, "逆地理编码响应: " + responseStr);
|
||||
|
||||
// 解析响应
|
||||
parseGeocodeResponse(responseStr);
|
||||
} else {
|
||||
Log.e(TAG, "逆地理编码失败,响应码: " + responseCode);
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("获取地址失败,错误码: " + responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
connection.disconnect();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "逆地理编码异常", e);
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("获取地址异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析天地图逆地理编码响应
|
||||
*/
|
||||
private void parseGeocodeResponse(String responseStr) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(responseStr);
|
||||
|
||||
// 检查状态
|
||||
String status = json.optString("status");
|
||||
Log.d(TAG, "API状态码: " + status);
|
||||
|
||||
if (!"0".equals(status)) {
|
||||
String msg = json.optString("msg", "未知错误");
|
||||
Log.e(TAG, "天地图API返回错误: status=" + status + ", msg=" + msg);
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("获取地址失败: " + msg + " (状态码: " + status + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取地址信息
|
||||
JSONObject result = json.optJSONObject("result");
|
||||
if (result != null) {
|
||||
JSONObject addressComponent = result.optJSONObject("addressComponent");
|
||||
String formattedAddress = result.optString("formatted_address", "");
|
||||
|
||||
Log.d(TAG, "完整地址: " + formattedAddress);
|
||||
|
||||
String province = "";
|
||||
String city = "";
|
||||
|
||||
if (addressComponent != null) {
|
||||
province = addressComponent.optString("province", "");
|
||||
city = addressComponent.optString("city", "");
|
||||
String county = addressComponent.optString("county", "");
|
||||
|
||||
Log.d(TAG, "省份: " + province + ", 城市: " + city + ", 区县: " + county);
|
||||
|
||||
// 如果city为空,尝试使用county
|
||||
if (city.isEmpty()) {
|
||||
city = county;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果省份和城市都为空,尝试使用完整地址
|
||||
if (province.isEmpty() && city.isEmpty() && !formattedAddress.isEmpty()) {
|
||||
Log.d(TAG, "省份和城市为空,使用完整地址");
|
||||
province = formattedAddress;
|
||||
}
|
||||
|
||||
Log.d(TAG, "解析地址成功 - 省份: " + province + ", 城市: " + city + ", 完整地址: " + formattedAddress);
|
||||
|
||||
if (resultListener != null) {
|
||||
resultListener.onSuccess(province, city, formattedAddress, currentLatitude, currentLongitude);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "响应中没有result字段");
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("地址解析失败:响应数据格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "解析响应异常", e);
|
||||
if (resultListener != null) {
|
||||
resultListener.onError("地址解析异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.example.livestreaming.util;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.example.livestreaming.R;
|
||||
|
||||
/**
|
||||
* 未读红点呼吸动画工具类
|
||||
*/
|
||||
public class BadgeAnimator {
|
||||
|
||||
/**
|
||||
* 启动未读红点的呼吸动画
|
||||
* @param badgeView 红点视图
|
||||
*/
|
||||
public static void startPulseAnimation(View badgeView) {
|
||||
if (badgeView == null || badgeView.getVisibility() != View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Animation pulseAnim = AnimationUtils.loadAnimation(
|
||||
badgeView.getContext(),
|
||||
R.anim.badge_pulse
|
||||
);
|
||||
badgeView.startAnimation(pulseAnim);
|
||||
} catch (Exception e) {
|
||||
// 动画加载失败时静默处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止动画
|
||||
* @param badgeView 红点视图
|
||||
*/
|
||||
public static void stopAnimation(View badgeView) {
|
||||
if (badgeView != null) {
|
||||
badgeView.clearAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示红点并启动动画
|
||||
* @param badgeView 红点视图
|
||||
* @param count 未读数量
|
||||
*/
|
||||
public static void showBadgeWithAnimation(View badgeView, int count) {
|
||||
if (badgeView == null) return;
|
||||
|
||||
if (count > 0) {
|
||||
badgeView.setVisibility(View.VISIBLE);
|
||||
if (badgeView instanceof android.widget.TextView) {
|
||||
((android.widget.TextView) badgeView).setText(
|
||||
count > 99 ? "99+" : String.valueOf(count)
|
||||
);
|
||||
}
|
||||
startPulseAnimation(badgeView);
|
||||
} else {
|
||||
stopAnimation(badgeView);
|
||||
badgeView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.example.livestreaming.util;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.example.livestreaming.R;
|
||||
|
||||
/**
|
||||
* 点击缩放动画工具类
|
||||
* 为所有可点击的 View 添加 0.95x 缩放反馈
|
||||
*/
|
||||
public class ClickScaleAnimator {
|
||||
|
||||
/**
|
||||
* 为 View 添加点击缩放效果
|
||||
* @param view 目标 View
|
||||
*/
|
||||
public static void attach(View view) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setOnTouchListener((v, event) -> {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
v.animate()
|
||||
.scaleX(0.95f)
|
||||
.scaleY(0.95f)
|
||||
.setDuration(100)
|
||||
.start();
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
v.animate()
|
||||
.scaleX(1.0f)
|
||||
.scaleY(1.0f)
|
||||
.setDuration(150)
|
||||
.start();
|
||||
break;
|
||||
}
|
||||
return false; // 返回 false 以便 onClick 仍然触发
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量为多个 View 添加点击缩放效果
|
||||
* @param views 目标 View 数组
|
||||
*/
|
||||
public static void attachAll(View... views) {
|
||||
for (View view : views) {
|
||||
attach(view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 XML 动画资源播放缩放动画
|
||||
* @param view 目标 View
|
||||
*/
|
||||
public static void playScaleAnimation(View view) {
|
||||
if (view == null) return;
|
||||
|
||||
Animation anim = AnimationUtils.loadAnimation(view.getContext(), R.anim.scale_click);
|
||||
view.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.example.livestreaming.util;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.view.animation.RotateAnimation;
|
||||
|
||||
import com.example.livestreaming.R;
|
||||
|
||||
/**
|
||||
* 钱包光束旋转动画工具类
|
||||
*/
|
||||
public class WalletBeamAnimator {
|
||||
|
||||
/**
|
||||
* 启动钱包光束旋转动画
|
||||
* @param beamOverlay 光束叠加层 View
|
||||
*/
|
||||
public static void startBeamRotation(View beamOverlay) {
|
||||
if (beamOverlay == null) return;
|
||||
|
||||
RotateAnimation rotate = new RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
);
|
||||
rotate.setDuration(8000);
|
||||
rotate.setRepeatCount(Animation.INFINITE);
|
||||
rotate.setRepeatMode(Animation.RESTART);
|
||||
rotate.setInterpolator(new LinearInterpolator());
|
||||
|
||||
beamOverlay.startAnimation(rotate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止钱包光束旋转动画
|
||||
* @param beamOverlay 光束叠加层 View
|
||||
*/
|
||||
public static void stopBeamRotation(View beamOverlay) {
|
||||
if (beamOverlay == null) return;
|
||||
beamOverlay.clearAnimation();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.example.livestreaming.util;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
|
||||
/**
|
||||
* 心愿卡片漂浮动画工具类
|
||||
* 为许愿树页面的心愿卡片提供随风摆动的动效
|
||||
*/
|
||||
public class WishCardAnimator {
|
||||
|
||||
/**
|
||||
* 为单个卡片启动漂浮动画
|
||||
* @param view 目标视图
|
||||
* @param amplitude 漂浮幅度 (dp)
|
||||
* @param duration 动画周期 (ms)
|
||||
* @param startDelay 启动延迟 (ms)
|
||||
*/
|
||||
public static ObjectAnimator startFloatAnimation(View view, float amplitude, long duration, long startDelay) {
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", 0f, -amplitude, 0f, amplitude, 0f);
|
||||
animator.setDuration(duration);
|
||||
animator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
animator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animator.setStartDelay(startDelay);
|
||||
animator.start();
|
||||
return animator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为卡片启动组合动画(漂浮 + 轻微旋转)
|
||||
*/
|
||||
public static ObjectAnimator startFloatWithRotation(View view, float yAmplitude, float rotationAmplitude, long duration, long startDelay) {
|
||||
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", 0f, -yAmplitude, 0f, yAmplitude, 0f);
|
||||
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofFloat("rotation", 0f, rotationAmplitude, 0f, -rotationAmplitude, 0f);
|
||||
|
||||
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvhY, pvhRotation);
|
||||
animator.setDuration(duration);
|
||||
animator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
animator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animator.setStartDelay(startDelay);
|
||||
animator.start();
|
||||
return animator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为一组心愿卡片启动错落的漂浮动画
|
||||
* @param cards 卡片视图数组
|
||||
*/
|
||||
public static void startAllCardAnimations(View... cards) {
|
||||
float[] amplitudes = {12f, 15f, 10f, 14f, 11f, 16f, 13f};
|
||||
long[] durations = {2800L, 3200L, 2500L, 3000L, 2700L, 3100L, 2900L};
|
||||
long[] delays = {0L, 200L, 400L, 100L, 300L, 150L, 250L};
|
||||
float[] rotations = {2f, -1.5f, 1.8f, -2f, 1.5f, -1.8f, 2.2f};
|
||||
|
||||
for (int i = 0; i < cards.length && i < amplitudes.length; i++) {
|
||||
if (cards[i] != null) {
|
||||
startFloatWithRotation(cards[i], amplitudes[i], rotations[i], durations[i], delays[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮点击缩放动画
|
||||
*/
|
||||
public static void playButtonClickAnimation(View button) {
|
||||
ObjectAnimator scaleDown = ObjectAnimator.ofPropertyValuesHolder(
|
||||
button,
|
||||
PropertyValuesHolder.ofFloat("scaleX", 1f, 0.95f, 1f),
|
||||
PropertyValuesHolder.ofFloat("scaleY", 1f, 0.95f, 1f)
|
||||
);
|
||||
scaleDown.setDuration(150);
|
||||
scaleDown.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 光晕脉冲动画
|
||||
*/
|
||||
public static ObjectAnimator startGlowPulse(View glowView) {
|
||||
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.1f, 1f);
|
||||
PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.1f, 1f);
|
||||
PropertyValuesHolder pvhAlpha = PropertyValuesHolder.ofFloat("alpha", 0.6f, 1f, 0.6f);
|
||||
|
||||
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(glowView, pvhScaleX, pvhScaleY, pvhAlpha);
|
||||
animator.setDuration(4000);
|
||||
animator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
animator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animator.start();
|
||||
return animator;
|
||||
}
|
||||
}
|
||||
14
android-app/app/src/main/res/anim/badge_pulse.xml
Normal file
14
android-app/app/src/main/res/anim/badge_pulse.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
|
||||
<scale
|
||||
android:fromXScale="1.0"
|
||||
android:toXScale="1.2"
|
||||
android:fromYScale="1.0"
|
||||
android:toYScale="1.2"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:duration="600"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite" />
|
||||
</set>
|
||||
12
android-app/app/src/main/res/anim/float_card_1.xml
Normal file
12
android-app/app/src/main/res/anim/float_card_1.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<objectAnimator
|
||||
android:propertyName="translationY"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-15"
|
||||
android:valueType="floatType"
|
||||
android:duration="2800"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite"
|
||||
android:interpolator="@android:interpolator/accelerate_decelerate" />
|
||||
</set>
|
||||
12
android-app/app/src/main/res/anim/float_card_2.xml
Normal file
12
android-app/app/src/main/res/anim/float_card_2.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<objectAnimator
|
||||
android:propertyName="translationY"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-12"
|
||||
android:valueType="floatType"
|
||||
android:duration="3200"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite"
|
||||
android:interpolator="@android:interpolator/accelerate_decelerate" />
|
||||
</set>
|
||||
12
android-app/app/src/main/res/anim/float_card_3.xml
Normal file
12
android-app/app/src/main/res/anim/float_card_3.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<objectAnimator
|
||||
android:propertyName="translationY"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-18"
|
||||
android:valueType="floatType"
|
||||
android:duration="2500"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite"
|
||||
android:interpolator="@android:interpolator/accelerate_decelerate" />
|
||||
</set>
|
||||
12
android-app/app/src/main/res/anim/float_up_down.xml
Normal file
12
android-app/app/src/main/res/anim/float_up_down.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite">
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="-10dp"
|
||||
android:duration="1500"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="infinite" />
|
||||
</set>
|
||||
25
android-app/app/src/main/res/anim/nav_icon_glow.xml
Normal file
25
android-app/app/src/main/res/anim/nav_icon_glow.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 底部导航图标选中发光动画 -->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
|
||||
<!-- 缩放弹跳 -->
|
||||
<scale
|
||||
android:duration="200"
|
||||
android:fromXScale="1.0"
|
||||
android:fromYScale="1.0"
|
||||
android:toXScale="1.15"
|
||||
android:toYScale="1.15"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="1" />
|
||||
|
||||
<!-- 透明度闪烁 -->
|
||||
<alpha
|
||||
android:duration="150"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.7"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="1" />
|
||||
</set>
|
||||
15
android-app/app/src/main/res/anim/scale_click.xml
Normal file
15
android-app/app/src/main/res/anim/scale_click.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 点击缩放反馈动画 - 缩小到0.95x -->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<scale
|
||||
android:duration="150"
|
||||
android:fromXScale="1.0"
|
||||
android:fromYScale="1.0"
|
||||
android:toXScale="0.95"
|
||||
android:toYScale="0.95"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:repeatMode="reverse"
|
||||
android:repeatCount="1" />
|
||||
</set>
|
||||
11
android-app/app/src/main/res/anim/wallet_beam_rotate.xml
Normal file
11
android-app/app/src/main/res/anim/wallet_beam_rotate.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 钱包光束旋转动画 -->
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="8000"
|
||||
android:fromDegrees="0"
|
||||
android:toDegrees="360"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:repeatCount="infinite"
|
||||
android:repeatMode="restart"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/shimmer_gold" android:state_checked="true" />
|
||||
<item android:color="@color/glass_white_60" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 底部导航图标颜色 - 选中时金色发光 -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#FFD700" android:state_checked="true" />
|
||||
<item android:color="#99FFFFFF" android:state_checked="false" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/shimmer_gold" android:state_checked="true" />
|
||||
<item android:color="@color/shimmer_gold" android:state_selected="true" />
|
||||
<item android:color="#80FFFFFF" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#CC121225" />
|
||||
<stroke
|
||||
android:width="0dp"
|
||||
android:color="@color/glass_white_10" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33A855F7" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#4DA855F7" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#CC1A1A2E" />
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/radius_large"
|
||||
android:topRightRadius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 底部导航 - 深紫色半透明 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#E6121225" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#1AA855F7" />
|
||||
</shape>
|
||||
18
android-app/app/src/main/res/drawable/bg_bottom_nav_deep.xml
Normal file
18
android-app/app/src/main/res/drawable/bg_bottom_nav_deep.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 底部导航 - 深紫色半透明 + 顶部微光边 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 深紫色半透明背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#F0121225" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 顶部微光边线 -->
|
||||
<item android:bottom="-1dp" android:left="-1dp" android:right="-1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#20A855F7" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#E6121225" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_10" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_20" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_40" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_pill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="@color/red_primary"
|
||||
android:endColor="@color/shimmer_pink"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_pill" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 分类标签选中状态 - 带发光圆点指示器 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 透明背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="@dimen/radius_pill" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 底部发光圆点 -->
|
||||
<item
|
||||
android:gravity="bottom|center_horizontal"
|
||||
android:width="8dp"
|
||||
android:height="8dp"
|
||||
android:bottom="-2dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#FFD700" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="#00000000"
|
||||
android:centerColor="#40000000"
|
||||
android:endColor="#99000000"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:startColor="@color/deep_night_bg"
|
||||
android:centerColor="@color/deep_night_card"
|
||||
android:endColor="@color/deep_night_surface"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
21
android-app/app/src/main/res/drawable/bg_glass_button.xml
Normal file
21
android-app/app/src/main/res/drawable/bg_glass_button.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_40" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="@color/glass_white_60" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_20" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="@color/glass_white_40" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
9
android-app/app/src/main/res/drawable/bg_glass_card.xml
Normal file
9
android-app/app/src/main/res/drawable/bg_glass_card.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 大号深色玻璃卡片 - 降低白块感 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0AFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="#12FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 大圆角超轻玻璃卡片 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0DFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#1AFFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 超深色玻璃卡片 - 降低白块感 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0DFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="@dimen/stroke_thin"
|
||||
android:color="#15FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 超轻玻璃卡片 - 5%透明度,增强星空透出感 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0DFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#1AFFFFFF" />
|
||||
</shape>
|
||||
10
android-app/app/src/main/res/drawable/bg_live_badge_glow.xml
Normal file
10
android-app/app/src/main/res/drawable/bg_live_badge_glow.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="#FF4757"
|
||||
android:endColor="#FF6B9D"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_small" />
|
||||
</shape>
|
||||
10
android-app/app/src/main/res/drawable/bg_menu_card_glass.xml
Normal file
10
android-app/app/src/main/res/drawable/bg_menu_card_glass.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 功能入口小卡片 - 玻璃拟态 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0CFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#15FFFFFF" />
|
||||
</shape>
|
||||
17
android-app/app/src/main/res/drawable/bg_nav_icon_glow.xml
Normal file
17
android-app/app/src/main/res/drawable/bg_nav_icon_glow.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 底部导航选中图标发光效果 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 外层发光 -->
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#30FFD700" />
|
||||
<size android:width="40dp" android:height="40dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 内层发光 -->
|
||||
<item android:left="6dp" android:top="6dp" android:right="6dp" android:bottom="6dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#50FFD700" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 基础渐变背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:startColor="#1A1A2E"
|
||||
android:centerColor="#16213E"
|
||||
android:endColor="#0F3460"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 顶部光晕 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:gradientRadius="300dp"
|
||||
android:centerX="0.8"
|
||||
android:centerY="0.2"
|
||||
android:startColor="#33A855F7"
|
||||
android:endColor="#00000000" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
26
android-app/app/src/main/res/drawable/bg_room_card_flow.xml
Normal file
26
android-app/app/src/main/res/drawable/bg_room_card_flow.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 直播间卡片 - 流光双层效果 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 底层磨砂透明 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0AFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 顶层渐变描边 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#20A855F7" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:startColor="#08FFD700"
|
||||
android:centerColor="#05A855F7"
|
||||
android:endColor="#08FF6B9D"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
10
android-app/app/src/main/res/drawable/bg_room_card_glass.xml
Normal file
10
android-app/app/src/main/res/drawable/bg_room_card_glass.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 直播间卡片 - 玻璃拟态效果 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#0DFFFFFF" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#18FFFFFF" />
|
||||
</shape>
|
||||
9
android-app/app/src/main/res/drawable/bg_search_dark.xml
Normal file
9
android-app/app/src/main/res/drawable/bg_search_dark.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#4D333333" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_10" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_pill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 搜索栏 - 霓虹底线样式 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 透明背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 底部霓虹细线 -->
|
||||
<item android:top="-1dp" android:left="-1dp" android:right="-1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@android:color/transparent" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 底部渐变发光线 -->
|
||||
<item android:gravity="bottom" android:height="2dp">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="#00FFD700"
|
||||
android:centerColor="#FFD700"
|
||||
android:endColor="#00FFD700"
|
||||
android:type="linear" />
|
||||
<corners android:radius="1dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 首页搜索栏 - 玻璃拟态宽版 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#15FFFFFF" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#20FFFFFF" />
|
||||
</shape>
|
||||
10
android-app/app/src/main/res/drawable/bg_searchbar_wide.xml
Normal file
10
android-app/app/src/main/res/drawable/bg_searchbar_wide.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 宽搜索栏 - 半透明玻璃 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#26FFFFFF" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#33FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#E6C200" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="#FFD700"
|
||||
android:centerColor="#FFF176"
|
||||
android:endColor="#FFD700"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_large" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 标签指示器 - 发光小圆点 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 外层发光 -->
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#40FFD700" />
|
||||
<size android:width="12dp" android:height="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 内层实心 -->
|
||||
<item android:left="3dp" android:top="3dp" android:right="3dp" android:bottom="3dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#FFD700" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
9
android-app/app/src/main/res/drawable/bg_tag_glass.xml
Normal file
9
android-app/app/src/main/res/drawable/bg_tag_glass.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_medium" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/glass_white_20" />
|
||||
</shape>
|
||||
10
android-app/app/src/main/res/drawable/bg_timer_pill.xml
Normal file
10
android-app/app/src/main/res/drawable/bg_timer_pill.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="@color/shimmer_purple"
|
||||
android:endColor="@color/shimmer_pink"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_pill" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:gradientRadius="200dp"
|
||||
android:centerX="0.5"
|
||||
android:centerY="0.5"
|
||||
android:startColor="#4D6C5CE7"
|
||||
android:centerColor="#267B68EE"
|
||||
android:endColor="#00000000" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/red_primary" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
33
android-app/app/src/main/res/drawable/bg_wallet_beam.xml
Normal file
33
android-app/app/src/main/res/drawable/bg_wallet_beam.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 钱包卡片 - 深色半透明 + 金色描边 + 光束效果 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 深色半透明底层 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0D1A1A2E" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 内部微光渐变 -->
|
||||
<item android:left="1dp" android:top="1dp" android:right="1dp" android:bottom="1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="#10FFD700"
|
||||
android:centerColor="#05FFFFFF"
|
||||
android:endColor="#08FFD700"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 金色描边 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="#90FFD700" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 钱包旋转光束叠加层 -->
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="0"
|
||||
android:toDegrees="360"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="45"
|
||||
android:startColor="#00FFFFFF"
|
||||
android:centerColor="#15FFD700"
|
||||
android:endColor="#00FFFFFF"
|
||||
android:type="sweep" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
</shape>
|
||||
</rotate>
|
||||
13
android-app/app/src/main/res/drawable/bg_wallet_glass.xml
Normal file
13
android-app/app/src/main/res/drawable/bg_wallet_glass.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:startColor="#26FFD700"
|
||||
android:endColor="#1AFFA500"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#4DFFD700" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 钱包卡片 - 深色背景 + 金色描边 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#1A1A2E" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@color/shimmer_gold" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 钱包卡片 - 深色背景 + 金色描边 -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 深色底层 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0D1A1A2E" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 金色渐变描边 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="#80FFD700" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 内部微光效果 -->
|
||||
<item
|
||||
android:left="1dp"
|
||||
android:top="1dp"
|
||||
android:right="1dp"
|
||||
android:bottom="1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:startColor="#15FFD700"
|
||||
android:centerColor="#08FFD700"
|
||||
android:endColor="#05FFA500"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_standard" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#26FFD700" />
|
||||
<corners android:radius="@dimen/radius_small" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="#80FFD700" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33FFFFFF" />
|
||||
<corners android:radius="@dimen/radius_small" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#4DFFD700" />
|
||||
</shape>
|
||||
6
android-app/app/src/main/res/drawable/divider_glass.xml
Normal file
6
android-app/app/src/main/res/drawable/divider_glass.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<size android:height="1dp" />
|
||||
</shape>
|
||||
10
android-app/app/src/main/res/drawable/ic_back.xml
Normal file
10
android-app/app/src/main/res/drawable/ic_back.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#000000">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||
</vector>
|
||||
13
android-app/app/src/main/res/drawable/ic_empty.xml
Normal file
13
android-app/app/src/main/res/drawable/ic_empty.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#CCCCCC">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 背景轨道 -->
|
||||
<item android:id="@android:id/background">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/glass_white_10" />
|
||||
<corners android:radius="@dimen/radius_small" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 进度条 -->
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="@color/shimmer_pink"
|
||||
android:centerColor="@color/shimmer_purple"
|
||||
android:endColor="@color/red_primary"
|
||||
android:type="linear" />
|
||||
<corners android:radius="@dimen/radius_small" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
||||
14
android-app/app/src/main/res/drawable/radial_glow_purple.xml
Normal file
14
android-app/app/src/main/res/drawable/radial_glow_purple.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 头像背光 - 紫色径向渐变 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:gradientRadius="60dp"
|
||||
android:centerX="0.5"
|
||||
android:centerY="0.5"
|
||||
android:startColor="#60A855F7"
|
||||
android:centerColor="#30A855F7"
|
||||
android:endColor="#00A855F7" />
|
||||
<size android:width="120dp" android:height="120dp" />
|
||||
</shape>
|
||||
|
|
@ -5,12 +5,11 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#000000">
|
||||
|
||||
<!-- 使用 OpenGlView 作为摄像头预览,支持 RootEncoder RTMP 推流和视频编码 -->
|
||||
<!-- keepAspectRatio=false 让预览填满整个屏幕 -->
|
||||
<!-- 使用 OpenGlView 作为摄像头预览,全屏填充显示 -->
|
||||
<com.pedro.rtplibrary.view.OpenGlView
|
||||
android:id="@+id/openGlView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
android:layout_height="match_parent"
|
||||
android:paddingTop="18dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
android:clipToPadding="false"
|
||||
android:background="@color/deep_night_bg">
|
||||
|
||||
<!-- 背景图片 -->
|
||||
<ImageView
|
||||
|
|
@ -19,6 +20,17 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- 中心光晕效果 -->
|
||||
<View
|
||||
android:id="@+id/centerGlow"
|
||||
android:layout_width="350dp"
|
||||
android:layout_height="350dp"
|
||||
android:background="@drawable/bg_tree_glow_radial"
|
||||
app:layout_constraintBottom_toBottomOf="@id/orbitContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/orbitContainer" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/titleText"
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -35,12 +47,12 @@
|
|||
android:id="@+id/topCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:background="@drawable/bg_card_starry"
|
||||
android:paddingStart="14dp"
|
||||
android:paddingEnd="14dp"
|
||||
android:layout_marginStart="@dimen/page_margin"
|
||||
android:layout_marginEnd="@dimen/page_margin"
|
||||
android:layout_marginTop="@dimen/card_padding"
|
||||
android:background="@drawable/bg_glass_card"
|
||||
android:paddingStart="@dimen/card_padding"
|
||||
android:paddingEnd="@dimen/card_padding"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleText">
|
||||
|
|
@ -50,8 +62,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="剩余匹配"
|
||||
android:textColor="#CCFFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/glass_white_60"
|
||||
android:textSize="@dimen/text_sm"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
|
@ -61,8 +73,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="50 人"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/shimmer_gold"
|
||||
android:textSize="@dimen/text_xl"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/leftInfo" />
|
||||
|
|
@ -72,8 +84,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="真诚 | 南宁 | 在线"
|
||||
android:textColor="#CCFFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/glass_white_60"
|
||||
android:textSize="@dimen/text_sm"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
|
@ -83,8 +95,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="小份"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_lg"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rightInfo" />
|
||||
|
|
@ -95,8 +107,8 @@
|
|||
android:id="@+id/orbitContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="18dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginTop="@dimen/spacing_xl"
|
||||
android:layout_marginBottom="@dimen/spacing_md"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
|
|
@ -135,7 +147,7 @@
|
|||
android:padding="25dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_globe_clean_filled"
|
||||
app:tint="#FFFFFF"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -176,11 +188,11 @@
|
|||
android:id="@+id/orbitUserTopName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginTop="@dimen/spacing_xs"
|
||||
android:maxLines="1"
|
||||
android:text="小树"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_sm"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
|
|
@ -189,8 +201,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:text="杭州"
|
||||
android:textColor="#CCFFFFFF"
|
||||
android:textSize="10sp" />
|
||||
android:textColor="@color/glass_white_60"
|
||||
android:textSize="@dimen/text_xs" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user