From a6b56bceb182641cc9e85fce64f1da2565030372 Mon Sep 17 00:00:00 2001
From: ShiQi <3572915148@qq.com>
Date: Mon, 22 Dec 2025 16:31:46 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E7=95=8C=E9=9D=A2?=
=?UTF-8?q?=E4=B8=AD=E5=90=84=E4=B8=AA=E7=BB=86=E8=8A=82=E4=B8=AD=E7=9A=84?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
android-app/app/src/main/AndroidManifest.xml | 33 +
.../app/src/main/assets/location_data.json | 657 ++++++++++++++++++
.../livestreaming/AvatarViewerDialog.java | 162 +++++
.../livestreaming/ConversationActivity.java | 18 +
.../ConversationMessagesAdapter.java | 118 ++++
.../livestreaming/ConversationsAdapter.java | 50 ++
.../livestreaming/DrawGuessActivity.java | 61 ++
.../livestreaming/EditProfileActivity.java | 308 ++++++++
.../livestreaming/FindGameActivity.java | 61 ++
.../livestreaming/FishPondActivity.java | 100 ++-
.../HeartbeatSignalActivity.java | 62 ++
.../livestreaming/KTVTogetherActivity.java | 61 ++
.../livestreaming/LocationDataManager.java | 142 ++++
.../example/livestreaming/MainActivity.java | 316 ++++++++-
.../livestreaming/MessagesActivity.java | 182 ++++-
.../livestreaming/NearbyUsersAdapter.java | 45 +-
.../livestreaming/OnlineDatingActivity.java | 61 ++
.../livestreaming/PeaceEliteActivity.java | 61 ++
.../livestreaming/ProfileActivity.java | 223 +++++-
.../example/livestreaming/SearchActivity.java | 16 +
.../livestreaming/TabPlaceholderActivity.java | 17 +-
.../livestreaming/TableGamesActivity.java | 61 ++
.../livestreaming/UnreadMessageManager.java | 74 ++
.../UserProfileReadOnlyActivity.java | 16 +-
.../livestreaming/VoiceMatchActivity.java | 62 ++
.../app/src/main/res/drawable/ic_globe_24.xml | 2 +-
.../main/res/layout/activity_draw_guess.xml | 56 ++
.../main/res/layout/activity_edit_profile.xml | 24 +-
.../main/res/layout/activity_find_game.xml | 56 ++
.../main/res/layout/activity_fish_pond.xml | 10 +-
.../res/layout/activity_heartbeat_signal.xml | 56 ++
.../main/res/layout/activity_ktv_together.xml | 56 ++
.../app/src/main/res/layout/activity_main.xml | 20 +-
.../res/layout/activity_online_dating.xml | 56 ++
.../main/res/layout/activity_peace_elite.xml | 56 ++
.../src/main/res/layout/activity_profile.xml | 33 +-
.../main/res/layout/activity_table_games.xml | 56 ++
.../activity_user_profile_read_only.xml | 274 ++++++--
.../main/res/layout/activity_voice_match.xml | 56 ++
.../res/layout/bottom_sheet_date_picker.xml | 107 +++
.../layout/bottom_sheet_location_picker.xml | 80 +++
.../bottom_sheet_location_picker_spinner.xml | 100 +++
.../main/res/layout/dialog_avatar_viewer.xml | 20 +
.../main/res/layout/include_bottom_nav.xml | 38 +-
.../src/main/res/layout/item_conversation.xml | 5 +-
.../item_conversation_message_incoming.xml | 5 +-
.../item_conversation_message_outgoing.xml | 5 +-
.../src/main/res/layout/item_nearby_user.xml | 138 ++--
.../app/src/main/res/values/themes.xml | 5 +
49 files changed, 4017 insertions(+), 264 deletions(-)
create mode 100644 android-app/app/src/main/assets/location_data.json
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/AvatarViewerDialog.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/DrawGuessActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/FindGameActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/HeartbeatSignalActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/KTVTogetherActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/LocationDataManager.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/OnlineDatingActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/PeaceEliteActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/TableGamesActivity.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/UnreadMessageManager.java
create mode 100644 android-app/app/src/main/java/com/example/livestreaming/VoiceMatchActivity.java
create mode 100644 android-app/app/src/main/res/layout/activity_draw_guess.xml
create mode 100644 android-app/app/src/main/res/layout/activity_find_game.xml
create mode 100644 android-app/app/src/main/res/layout/activity_heartbeat_signal.xml
create mode 100644 android-app/app/src/main/res/layout/activity_ktv_together.xml
create mode 100644 android-app/app/src/main/res/layout/activity_online_dating.xml
create mode 100644 android-app/app/src/main/res/layout/activity_peace_elite.xml
create mode 100644 android-app/app/src/main/res/layout/activity_table_games.xml
create mode 100644 android-app/app/src/main/res/layout/activity_voice_match.xml
create mode 100644 android-app/app/src/main/res/layout/bottom_sheet_date_picker.xml
create mode 100644 android-app/app/src/main/res/layout/bottom_sheet_location_picker.xml
create mode 100644 android-app/app/src/main/res/layout/bottom_sheet_location_picker_spinner.xml
create mode 100644 android-app/app/src/main/res/layout/dialog_avatar_viewer.xml
diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml
index d77b2ef8..01deb8dd 100644
--- a/android-app/app/src/main/AndroidManifest.xml
+++ b/android-app/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/assets/location_data.json b/android-app/app/src/main/assets/location_data.json
new file mode 100644
index 00000000..01b2a7ff
--- /dev/null
+++ b/android-app/app/src/main/assets/location_data.json
@@ -0,0 +1,657 @@
+{
+ "provinces": [
+ {
+ "name": "北京",
+ "cities": [
+ {"name": "东城区"},
+ {"name": "西城区"},
+ {"name": "朝阳区"},
+ {"name": "丰台区"},
+ {"name": "石景山区"},
+ {"name": "海淀区"},
+ {"name": "门头沟区"},
+ {"name": "房山区"},
+ {"name": "通州区"},
+ {"name": "顺义区"},
+ {"name": "昌平区"},
+ {"name": "大兴区"},
+ {"name": "怀柔区"},
+ {"name": "平谷区"},
+ {"name": "密云区"},
+ {"name": "延庆区"}
+ ]
+ },
+ {
+ "name": "上海",
+ "cities": [
+ {"name": "黄浦区"},
+ {"name": "徐汇区"},
+ {"name": "长宁区"},
+ {"name": "静安区"},
+ {"name": "普陀区"},
+ {"name": "虹口区"},
+ {"name": "杨浦区"},
+ {"name": "闵行区"},
+ {"name": "宝山区"},
+ {"name": "嘉定区"},
+ {"name": "浦东新区"},
+ {"name": "金山区"},
+ {"name": "松江区"},
+ {"name": "青浦区"},
+ {"name": "奉贤区"},
+ {"name": "崇明区"}
+ ]
+ },
+ {
+ "name": "天津",
+ "cities": [
+ {"name": "和平区"},
+ {"name": "河东区"},
+ {"name": "河西区"},
+ {"name": "南开区"},
+ {"name": "河北区"},
+ {"name": "红桥区"},
+ {"name": "东丽区"},
+ {"name": "西青区"},
+ {"name": "津南区"},
+ {"name": "北辰区"},
+ {"name": "武清区"},
+ {"name": "宝坻区"},
+ {"name": "滨海新区"},
+ {"name": "宁河区"},
+ {"name": "静海区"},
+ {"name": "蓟州区"}
+ ]
+ },
+ {
+ "name": "重庆",
+ "cities": [
+ {"name": "万州区"},
+ {"name": "涪陵区"},
+ {"name": "渝中区"},
+ {"name": "大渡口区"},
+ {"name": "江北区"},
+ {"name": "沙坪坝区"},
+ {"name": "九龙坡区"},
+ {"name": "南岸区"},
+ {"name": "北碚区"},
+ {"name": "綦江区"},
+ {"name": "大足区"},
+ {"name": "渝北区"},
+ {"name": "巴南区"},
+ {"name": "黔江区"},
+ {"name": "长寿区"},
+ {"name": "江津区"},
+ {"name": "合川区"},
+ {"name": "永川区"},
+ {"name": "南川区"},
+ {"name": "璧山区"},
+ {"name": "铜梁区"},
+ {"name": "潼南区"},
+ {"name": "荣昌区"},
+ {"name": "开州区"},
+ {"name": "梁平区"},
+ {"name": "武隆区"}
+ ]
+ },
+ {
+ "name": "河北",
+ "cities": [
+ {"name": "石家庄"},
+ {"name": "唐山"},
+ {"name": "秦皇岛"},
+ {"name": "邯郸"},
+ {"name": "邢台"},
+ {"name": "保定"},
+ {"name": "张家口"},
+ {"name": "承德"},
+ {"name": "沧州"},
+ {"name": "廊坊"},
+ {"name": "衡水"}
+ ]
+ },
+ {
+ "name": "山西",
+ "cities": [
+ {"name": "太原"},
+ {"name": "大同"},
+ {"name": "阳泉"},
+ {"name": "长治"},
+ {"name": "晋城"},
+ {"name": "朔州"},
+ {"name": "晋中"},
+ {"name": "运城"},
+ {"name": "忻州"},
+ {"name": "临汾"},
+ {"name": "吕梁"}
+ ]
+ },
+ {
+ "name": "内蒙古",
+ "cities": [
+ {"name": "呼和浩特"},
+ {"name": "包头"},
+ {"name": "乌海"},
+ {"name": "赤峰"},
+ {"name": "通辽"},
+ {"name": "鄂尔多斯"},
+ {"name": "呼伦贝尔"},
+ {"name": "巴彦淖尔"},
+ {"name": "乌兰察布"},
+ {"name": "兴安盟"},
+ {"name": "锡林郭勒盟"},
+ {"name": "阿拉善盟"}
+ ]
+ },
+ {
+ "name": "辽宁",
+ "cities": [
+ {"name": "沈阳"},
+ {"name": "大连"},
+ {"name": "鞍山"},
+ {"name": "抚顺"},
+ {"name": "本溪"},
+ {"name": "丹东"},
+ {"name": "锦州"},
+ {"name": "营口"},
+ {"name": "阜新"},
+ {"name": "辽阳"},
+ {"name": "盘锦"},
+ {"name": "铁岭"},
+ {"name": "朝阳"},
+ {"name": "葫芦岛"}
+ ]
+ },
+ {
+ "name": "吉林",
+ "cities": [
+ {"name": "长春"},
+ {"name": "吉林"},
+ {"name": "四平"},
+ {"name": "辽源"},
+ {"name": "通化"},
+ {"name": "白山"},
+ {"name": "松原"},
+ {"name": "白城"},
+ {"name": "延边朝鲜族自治州"}
+ ]
+ },
+ {
+ "name": "黑龙江",
+ "cities": [
+ {"name": "哈尔滨"},
+ {"name": "齐齐哈尔"},
+ {"name": "鸡西"},
+ {"name": "鹤岗"},
+ {"name": "双鸭山"},
+ {"name": "大庆"},
+ {"name": "伊春"},
+ {"name": "佳木斯"},
+ {"name": "七台河"},
+ {"name": "牡丹江"},
+ {"name": "黑河"},
+ {"name": "绥化"},
+ {"name": "大兴安岭"}
+ ]
+ },
+ {
+ "name": "江苏",
+ "cities": [
+ {"name": "南京"},
+ {"name": "无锡"},
+ {"name": "徐州"},
+ {"name": "常州"},
+ {"name": "苏州"},
+ {"name": "南通"},
+ {"name": "连云港"},
+ {"name": "淮安"},
+ {"name": "盐城"},
+ {"name": "扬州"},
+ {"name": "镇江"},
+ {"name": "泰州"},
+ {"name": "宿迁"}
+ ]
+ },
+ {
+ "name": "浙江",
+ "cities": [
+ {"name": "杭州"},
+ {"name": "宁波"},
+ {"name": "温州"},
+ {"name": "嘉兴"},
+ {"name": "湖州"},
+ {"name": "绍兴"},
+ {"name": "金华"},
+ {"name": "衢州"},
+ {"name": "舟山"},
+ {"name": "台州"},
+ {"name": "丽水"}
+ ]
+ },
+ {
+ "name": "安徽",
+ "cities": [
+ {"name": "合肥"},
+ {"name": "芜湖"},
+ {"name": "蚌埠"},
+ {"name": "淮南"},
+ {"name": "马鞍山"},
+ {"name": "淮北"},
+ {"name": "铜陵"},
+ {"name": "安庆"},
+ {"name": "黄山"},
+ {"name": "滁州"},
+ {"name": "阜阳"},
+ {"name": "宿州"},
+ {"name": "六安"},
+ {"name": "亳州"},
+ {"name": "池州"},
+ {"name": "宣城"}
+ ]
+ },
+ {
+ "name": "福建",
+ "cities": [
+ {"name": "福州"},
+ {"name": "厦门"},
+ {"name": "莆田"},
+ {"name": "三明"},
+ {"name": "泉州"},
+ {"name": "漳州"},
+ {"name": "南平"},
+ {"name": "龙岩"},
+ {"name": "宁德"}
+ ]
+ },
+ {
+ "name": "江西",
+ "cities": [
+ {"name": "南昌"},
+ {"name": "景德镇"},
+ {"name": "萍乡"},
+ {"name": "九江"},
+ {"name": "新余"},
+ {"name": "鹰潭"},
+ {"name": "赣州"},
+ {"name": "吉安"},
+ {"name": "宜春"},
+ {"name": "抚州"},
+ {"name": "上饶"}
+ ]
+ },
+ {
+ "name": "山东",
+ "cities": [
+ {"name": "济南"},
+ {"name": "青岛"},
+ {"name": "淄博"},
+ {"name": "枣庄"},
+ {"name": "东营"},
+ {"name": "烟台"},
+ {"name": "潍坊"},
+ {"name": "济宁"},
+ {"name": "泰安"},
+ {"name": "威海"},
+ {"name": "日照"},
+ {"name": "临沂"},
+ {"name": "德州"},
+ {"name": "聊城"},
+ {"name": "滨州"},
+ {"name": "菏泽"}
+ ]
+ },
+ {
+ "name": "河南",
+ "cities": [
+ {"name": "郑州"},
+ {"name": "开封"},
+ {"name": "洛阳"},
+ {"name": "平顶山"},
+ {"name": "安阳"},
+ {"name": "鹤壁"},
+ {"name": "新乡"},
+ {"name": "焦作"},
+ {"name": "濮阳"},
+ {"name": "许昌"},
+ {"name": "漯河"},
+ {"name": "三门峡"},
+ {"name": "南阳"},
+ {"name": "商丘"},
+ {"name": "信阳"},
+ {"name": "周口"},
+ {"name": "驻马店"},
+ {"name": "济源"}
+ ]
+ },
+ {
+ "name": "湖北",
+ "cities": [
+ {"name": "武汉"},
+ {"name": "黄石"},
+ {"name": "十堰"},
+ {"name": "宜昌"},
+ {"name": "襄阳"},
+ {"name": "鄂州"},
+ {"name": "荆门"},
+ {"name": "孝感"},
+ {"name": "荆州"},
+ {"name": "黄冈"},
+ {"name": "咸宁"},
+ {"name": "随州"},
+ {"name": "恩施"},
+ {"name": "仙桃"},
+ {"name": "潜江"},
+ {"name": "天门"},
+ {"name": "神农架"}
+ ]
+ },
+ {
+ "name": "湖南",
+ "cities": [
+ {"name": "长沙"},
+ {"name": "株洲"},
+ {"name": "湘潭"},
+ {"name": "衡阳"},
+ {"name": "邵阳"},
+ {"name": "岳阳"},
+ {"name": "常德"},
+ {"name": "张家界"},
+ {"name": "益阳"},
+ {"name": "郴州"},
+ {"name": "永州"},
+ {"name": "怀化"},
+ {"name": "娄底"},
+ {"name": "湘西"}
+ ]
+ },
+ {
+ "name": "广东",
+ "cities": [
+ {"name": "广州"},
+ {"name": "深圳"},
+ {"name": "珠海"},
+ {"name": "汕头"},
+ {"name": "佛山"},
+ {"name": "韶关"},
+ {"name": "湛江"},
+ {"name": "肇庆"},
+ {"name": "江门"},
+ {"name": "茂名"},
+ {"name": "惠州"},
+ {"name": "梅州"},
+ {"name": "汕尾"},
+ {"name": "河源"},
+ {"name": "阳江"},
+ {"name": "清远"},
+ {"name": "东莞"},
+ {"name": "中山"},
+ {"name": "潮州"},
+ {"name": "揭阳"},
+ {"name": "云浮"}
+ ]
+ },
+ {
+ "name": "广西",
+ "cities": [
+ {"name": "南宁"},
+ {"name": "柳州"},
+ {"name": "桂林"},
+ {"name": "梧州"},
+ {"name": "北海"},
+ {"name": "防城港"},
+ {"name": "钦州"},
+ {"name": "贵港"},
+ {"name": "玉林"},
+ {"name": "百色"},
+ {"name": "贺州"},
+ {"name": "河池"},
+ {"name": "来宾"},
+ {"name": "崇左"}
+ ]
+ },
+ {
+ "name": "海南",
+ "cities": [
+ {"name": "海口"},
+ {"name": "三亚"},
+ {"name": "三沙"},
+ {"name": "儋州"},
+ {"name": "五指山"},
+ {"name": "琼海"},
+ {"name": "文昌"},
+ {"name": "万宁"},
+ {"name": "东方"},
+ {"name": "定安"},
+ {"name": "屯昌"},
+ {"name": "澄迈"},
+ {"name": "临高"},
+ {"name": "白沙"},
+ {"name": "昌江"},
+ {"name": "乐东"},
+ {"name": "陵水"},
+ {"name": "保亭"},
+ {"name": "琼中"}
+ ]
+ },
+ {
+ "name": "四川",
+ "cities": [
+ {"name": "成都"},
+ {"name": "自贡"},
+ {"name": "攀枝花"},
+ {"name": "泸州"},
+ {"name": "德阳"},
+ {"name": "绵阳"},
+ {"name": "广元"},
+ {"name": "遂宁"},
+ {"name": "内江"},
+ {"name": "乐山"},
+ {"name": "南充"},
+ {"name": "眉山"},
+ {"name": "宜宾"},
+ {"name": "广安"},
+ {"name": "达州"},
+ {"name": "雅安"},
+ {"name": "巴中"},
+ {"name": "资阳"},
+ {"name": "阿坝"},
+ {"name": "甘孜"},
+ {"name": "凉山"}
+ ]
+ },
+ {
+ "name": "贵州",
+ "cities": [
+ {"name": "贵阳"},
+ {"name": "六盘水"},
+ {"name": "遵义"},
+ {"name": "安顺"},
+ {"name": "毕节"},
+ {"name": "铜仁"},
+ {"name": "黔西南"},
+ {"name": "黔东南"},
+ {"name": "黔南"}
+ ]
+ },
+ {
+ "name": "云南",
+ "cities": [
+ {"name": "昆明"},
+ {"name": "曲靖"},
+ {"name": "玉溪"},
+ {"name": "保山"},
+ {"name": "昭通"},
+ {"name": "丽江"},
+ {"name": "普洱"},
+ {"name": "临沧"},
+ {"name": "楚雄"},
+ {"name": "红河"},
+ {"name": "文山"},
+ {"name": "西双版纳"},
+ {"name": "大理"},
+ {"name": "德宏"},
+ {"name": "怒江"},
+ {"name": "迪庆"}
+ ]
+ },
+ {
+ "name": "西藏",
+ "cities": [
+ {"name": "拉萨"},
+ {"name": "日喀则"},
+ {"name": "昌都"},
+ {"name": "林芝"},
+ {"name": "山南"},
+ {"name": "那曲"},
+ {"name": "阿里"}
+ ]
+ },
+ {
+ "name": "陕西",
+ "cities": [
+ {"name": "西安"},
+ {"name": "铜川"},
+ {"name": "宝鸡"},
+ {"name": "咸阳"},
+ {"name": "渭南"},
+ {"name": "延安"},
+ {"name": "汉中"},
+ {"name": "榆林"},
+ {"name": "安康"},
+ {"name": "商洛"}
+ ]
+ },
+ {
+ "name": "甘肃",
+ "cities": [
+ {"name": "兰州"},
+ {"name": "嘉峪关"},
+ {"name": "金昌"},
+ {"name": "白银"},
+ {"name": "天水"},
+ {"name": "武威"},
+ {"name": "张掖"},
+ {"name": "平凉"},
+ {"name": "酒泉"},
+ {"name": "庆阳"},
+ {"name": "定西"},
+ {"name": "陇南"},
+ {"name": "临夏"},
+ {"name": "甘南"}
+ ]
+ },
+ {
+ "name": "青海",
+ "cities": [
+ {"name": "西宁"},
+ {"name": "海东"},
+ {"name": "海北"},
+ {"name": "黄南"},
+ {"name": "海南"},
+ {"name": "果洛"},
+ {"name": "玉树"},
+ {"name": "海西"}
+ ]
+ },
+ {
+ "name": "宁夏",
+ "cities": [
+ {"name": "银川"},
+ {"name": "石嘴山"},
+ {"name": "吴忠"},
+ {"name": "固原"},
+ {"name": "中卫"}
+ ]
+ },
+ {
+ "name": "新疆",
+ "cities": [
+ {"name": "乌鲁木齐"},
+ {"name": "克拉玛依"},
+ {"name": "吐鲁番"},
+ {"name": "哈密"},
+ {"name": "昌吉"},
+ {"name": "博尔塔拉"},
+ {"name": "巴音郭楞"},
+ {"name": "阿克苏"},
+ {"name": "克孜勒苏"},
+ {"name": "喀什"},
+ {"name": "和田"},
+ {"name": "伊犁"},
+ {"name": "塔城"},
+ {"name": "阿勒泰"},
+ {"name": "石河子"},
+ {"name": "阿拉尔"},
+ {"name": "图木舒克"},
+ {"name": "五家渠"},
+ {"name": "北屯"},
+ {"name": "铁门关"},
+ {"name": "双河"},
+ {"name": "可克达拉"},
+ {"name": "昆玉"},
+ {"name": "胡杨河"}
+ ]
+ },
+ {
+ "name": "台湾",
+ "cities": [
+ {"name": "台北"},
+ {"name": "新北"},
+ {"name": "桃园"},
+ {"name": "台中"},
+ {"name": "台南"},
+ {"name": "高雄"},
+ {"name": "基隆"},
+ {"name": "新竹"},
+ {"name": "嘉义"},
+ {"name": "宜兰"},
+ {"name": "苗栗"},
+ {"name": "彰化"},
+ {"name": "南投"},
+ {"name": "云林"},
+ {"name": "屏东"},
+ {"name": "台东"},
+ {"name": "花莲"},
+ {"name": "澎湖"},
+ {"name": "金门"},
+ {"name": "连江"}
+ ]
+ },
+ {
+ "name": "香港",
+ "cities": [
+ {"name": "中西区"},
+ {"name": "湾仔区"},
+ {"name": "东区"},
+ {"name": "南区"},
+ {"name": "深水埗区"},
+ {"name": "油尖旺区"},
+ {"name": "九龙城区"},
+ {"name": "黄大仙区"},
+ {"name": "观塘区"},
+ {"name": "荃湾区"},
+ {"name": "屯门区"},
+ {"name": "元朗区"},
+ {"name": "北区"},
+ {"name": "大埔区"},
+ {"name": "沙田区"},
+ {"name": "西贡区"},
+ {"name": "葵青区"},
+ {"name": "离岛区"}
+ ]
+ },
+ {
+ "name": "澳门",
+ "cities": [
+ {"name": "花地玛堂区"},
+ {"name": "圣安多尼堂区"},
+ {"name": "望德堂区"},
+ {"name": "大堂区"},
+ {"name": "风顺堂区"},
+ {"name": "嘉模堂区"},
+ {"name": "路凼填海区"},
+ {"name": "圣方济各堂区"}
+ ]
+ }
+ ]
+}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/AvatarViewerDialog.java b/android-app/app/src/main/java/com/example/livestreaming/AvatarViewerDialog.java
new file mode 100644
index 00000000..6a7e5355
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/AvatarViewerDialog.java
@@ -0,0 +1,162 @@
+package com.example.livestreaming;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
+public class AvatarViewerDialog extends Dialog {
+
+ private ImageView avatarImageView;
+ private Uri avatarUri;
+ private Integer avatarResId;
+ private Drawable avatarDrawable;
+ private Context activityContext;
+
+ public AvatarViewerDialog(@NonNull Context context) {
+ super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
+ this.activityContext = context;
+ }
+
+ public static AvatarViewerDialog create(Context context) {
+ return new AvatarViewerDialog(context);
+ }
+
+ public AvatarViewerDialog setAvatarUri(Uri uri) {
+ this.avatarUri = uri;
+ this.avatarResId = null;
+ this.avatarDrawable = null;
+ return this;
+ }
+
+ public AvatarViewerDialog setAvatarResId(int resId) {
+ this.avatarResId = resId;
+ this.avatarUri = null;
+ this.avatarDrawable = null;
+ return this;
+ }
+
+ public AvatarViewerDialog setAvatarDrawable(Drawable drawable) {
+ this.avatarDrawable = drawable;
+ this.avatarUri = null;
+ this.avatarResId = null;
+ return this;
+ }
+
+ public AvatarViewerDialog setAvatarFromImageView(ImageView imageView) {
+ if (imageView == null) return this;
+
+ // 优先从ImageView的Drawable获取
+ Drawable drawable = imageView.getDrawable();
+ if (drawable != null) {
+ return setAvatarDrawable(drawable);
+ }
+
+ // 如果Drawable为空,尝试从Tag获取URI或资源ID
+ Object tag = imageView.getTag();
+ if (tag instanceof Uri) {
+ return setAvatarUri((Uri) tag);
+ } else if (tag instanceof Integer) {
+ return setAvatarResId((Integer) tag);
+ }
+
+ return this;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.dialog_avatar_viewer);
+
+ Window window = getWindow();
+ if (window != null) {
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ window.setAttributes(params);
+ window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ window.setBackgroundDrawableResource(android.R.color.black);
+ }
+
+ avatarImageView = findViewById(R.id.avatarImageView);
+
+ if (avatarImageView == null) {
+ dismiss();
+ return;
+ }
+
+ // 点击任意位置关闭
+ View rootLayout = findViewById(R.id.rootLayout);
+ if (rootLayout != null) {
+ rootLayout.setOnClickListener(v -> dismiss());
+ }
+
+ // 也允许点击ImageView关闭
+ avatarImageView.setOnClickListener(v -> dismiss());
+
+ loadAvatar();
+ }
+
+ private void loadAvatar() {
+ if (avatarImageView == null) return;
+
+ // 使用Activity的Context而不是Dialog的Context,确保Glide能正常工作
+ Context context = activityContext;
+ if (context == null) {
+ context = getContext();
+ }
+ if (context == null) {
+ avatarImageView.setImageResource(R.drawable.ic_account_circle_24);
+ return;
+ }
+
+ try {
+ if (avatarUri != null) {
+ Glide.with(context)
+ .load(avatarUri)
+ .diskCacheStrategy(DiskCacheStrategy.ALL)
+ .placeholder(R.drawable.ic_account_circle_24)
+ .error(R.drawable.ic_account_circle_24)
+ .into(avatarImageView);
+ } else if (avatarResId != null && avatarResId != 0) {
+ Glide.with(context)
+ .load(avatarResId)
+ .diskCacheStrategy(DiskCacheStrategy.ALL)
+ .placeholder(R.drawable.ic_account_circle_24)
+ .error(R.drawable.ic_account_circle_24)
+ .into(avatarImageView);
+ } else if (avatarDrawable != null) {
+ // 直接设置Drawable,但如果是Glide的Drawable,可能需要特殊处理
+ try {
+ avatarImageView.setImageDrawable(avatarDrawable);
+ } catch (Exception e) {
+ // 如果设置Drawable失败,尝试使用Glide加载
+ // 对于Glide加载的Drawable,我们需要重新加载原始资源
+ avatarImageView.setImageResource(R.drawable.ic_account_circle_24);
+ }
+ } else {
+ avatarImageView.setImageResource(R.drawable.ic_account_circle_24);
+ }
+ } catch (Exception e) {
+ // 如果Glide加载失败,直接使用setImageResource
+ try {
+ avatarImageView.setImageResource(R.drawable.ic_account_circle_24);
+ } catch (Exception ex) {
+ // 最后的fallback
+ }
+ }
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java
index 09d5a707..e7676a30 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java
@@ -33,6 +33,10 @@ public class ConversationActivity extends AppCompatActivity {
context.startActivity(intent);
}
+ private static final String EXTRA_UNREAD_COUNT = "extra_unread_count";
+
+ private int initialUnreadCount = 0;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -41,11 +45,25 @@ public class ConversationActivity extends AppCompatActivity {
String title = getIntent() != null ? getIntent().getStringExtra(EXTRA_CONVERSATION_TITLE) : null;
binding.titleText.setText(title != null ? title : "会话");
+
+ // 获取该会话的初始未读数量
+ initialUnreadCount = getIntent() != null ? getIntent().getIntExtra(EXTRA_UNREAD_COUNT, 0) : 0;
binding.backButton.setOnClickListener(v -> finish());
setupMessages();
setupInput();
+
+ // 用户进入会话时,标记该会话的消息为已读,减少未读数量
+ if (initialUnreadCount > 0) {
+ UnreadMessageManager.decrementUnreadCount(this, initialUnreadCount);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // 当用户离开会话时,更新总未读数量(如果会话未读数量已减少)
}
private void setupMessages() {
diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java
index 6593f8d4..b611abea 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java
@@ -1,8 +1,13 @@
package com.example.livestreaming;
+import android.graphics.Outline;
+import android.net.Uri;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -10,6 +15,8 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -56,15 +63,35 @@ public class ConversationMessagesAdapter extends ListAdapter {
+ if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) {
+ avatarView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setOval(0, 0, view.getWidth(), view.getHeight());
+ }
+ });
+ avatarView.setClipToOutline(true);
+ }
+ });
}
void bind(ChatMessage message) {
@@ -72,23 +99,114 @@ public class ConversationMessagesAdapter extends ListAdapter {
+ if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) {
+ avatarView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setOval(0, 0, view.getWidth(), view.getHeight());
+ }
+ });
+ avatarView.setClipToOutline(true);
+ }
+ });
}
void bind(ChatMessage message) {
if (message == null) return;
msgText.setText(message.getMessage() != null ? message.getMessage() : "");
timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp())));
+
+ // 确保头像圆形裁剪设置正确
+ setupAvatarOutline();
+ // 加载用户头像
+ loadAvatar();
+ }
+
+ private void loadAvatar() {
+ if (avatarView == null) return;
+
+ try {
+ String avatarUri = avatarView.getContext()
+ .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
+ .getString("profile_avatar_uri", null);
+
+ if (!TextUtils.isEmpty(avatarUri)) {
+ Glide.with(avatarView)
+ .load(Uri.parse(avatarUri))
+ .circleCrop()
+ .error(R.drawable.ic_account_circle_24)
+ .into(avatarView);
+ return;
+ }
+
+ int avatarRes = avatarView.getContext()
+ .getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
+ .getInt("profile_avatar_res", 0);
+
+ if (avatarRes != 0) {
+ Glide.with(avatarView)
+ .load(avatarRes)
+ .circleCrop()
+ .error(R.drawable.ic_account_circle_24)
+ .into(avatarView);
+ } else {
+ Glide.with(avatarView)
+ .load(R.drawable.ic_account_circle_24)
+ .circleCrop()
+ .into(avatarView);
+ }
+ } catch (Exception e) {
+ Glide.with(avatarView)
+ .load(R.drawable.ic_account_circle_24)
+ .circleCrop()
+ .into(avatarView);
+ }
}
}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationsAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationsAdapter.java
index 61633e78..c9f18959 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/ConversationsAdapter.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationsAdapter.java
@@ -1,14 +1,19 @@
package com.example.livestreaming;
+import android.graphics.Outline;
+import android.net.Uri;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ItemConversationBinding;
public class ConversationsAdapter extends ListAdapter {
@@ -66,11 +71,56 @@ public class ConversationsAdapter extends ListAdapter {
if (item == null) return;
if (onConversationClickListener != null) onConversationClickListener.onConversationClick(item);
});
}
+
+ private void loadAvatar(ConversationItem item) {
+ if (binding.avatar == null) return;
+
+ // 设置圆形裁剪
+ setupAvatarOutline();
+
+ // 根据会话ID或标题加载对应的头像
+ // 这里可以根据实际需求从服务器或本地加载
+ // 暂时使用默认头像,或者根据会话类型加载不同头像
+ try {
+ String conversationId = item != null ? item.getId() : null;
+
+ // 可以根据conversationId从SharedPreferences或其他地方加载头像
+ // 这里先使用默认头像,使用Glide确保圆形裁剪
+ Glide.with(binding.avatar)
+ .load(R.drawable.ic_account_circle_24)
+ .circleCrop()
+ .into(binding.avatar);
+ } catch (Exception e) {
+ Glide.with(binding.avatar)
+ .load(R.drawable.ic_account_circle_24)
+ .circleCrop()
+ .into(binding.avatar);
+ }
+ }
+
+ private void setupAvatarOutline() {
+ if (binding.avatar == null) return;
+ // 设置圆形裁剪,确保在模拟器上也能正常工作
+ binding.avatar.post(() -> {
+ if (binding.avatar.getWidth() > 0 && binding.avatar.getHeight() > 0) {
+ binding.avatar.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setOval(0, 0, view.getWidth(), view.getHeight());
+ }
+ });
+ binding.avatar.setClipToOutline(true);
+ }
+ });
+ }
}
private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() {
diff --git a/android-app/app/src/main/java/com/example/livestreaming/DrawGuessActivity.java b/android-app/app/src/main/java/com/example/livestreaming/DrawGuessActivity.java
new file mode 100644
index 00000000..b1248e03
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/DrawGuessActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityDrawGuessBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class DrawGuessActivity extends AppCompatActivity {
+
+ private ActivityDrawGuessBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, DrawGuessActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityDrawGuessBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java
index 346b4248..f227a942 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java
@@ -4,25 +4,41 @@ import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Spinner;
+import android.widget.TextView;
import android.widget.Toast;
+import android.widget.AdapterView;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityEditProfileBinding;
import com.google.android.material.bottomsheet.BottomSheetDialog;
+import android.widget.NumberPicker;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
public class EditProfileActivity extends AppCompatActivity {
@@ -84,8 +100,40 @@ public class EditProfileActivity extends AppCompatActivity {
binding.cancelButton.setOnClickListener(v -> finish());
loadFromPrefs();
+ setupGenderDropdown();
+ setupLocationDropdown();
+ setupBirthdayPicker();
binding.avatarRow.setOnClickListener(v -> showAvatarBottomSheet());
+
+ // 头像预览点击放大
+ binding.avatarPreview.setOnClickListener(v -> {
+ AvatarViewerDialog dialog = AvatarViewerDialog.create(this);
+
+ // 优先使用selectedAvatarUri(当前选择但未保存的头像)
+ if (selectedAvatarUri != null) {
+ dialog.setAvatarUri(selectedAvatarUri);
+ } else {
+ // 如果没有选择新头像,从SharedPreferences读取
+ String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
+ int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
+
+ if (!TextUtils.isEmpty(avatarUri)) {
+ dialog.setAvatarUri(Uri.parse(avatarUri));
+ } else if (avatarRes != 0) {
+ dialog.setAvatarResId(avatarRes);
+ } else {
+ // 如果都没有,尝试从ImageView获取Drawable
+ Drawable drawable = binding.avatarPreview.getDrawable();
+ if (drawable != null) {
+ dialog.setAvatarDrawable(drawable);
+ } else {
+ dialog.setAvatarResId(R.drawable.ic_account_circle_24);
+ }
+ }
+ }
+ dialog.show();
+ });
binding.saveButton.setOnClickListener(v -> {
String name = binding.inputName.getText() != null ? binding.inputName.getText().toString().trim() : "";
@@ -121,9 +169,12 @@ public class EditProfileActivity extends AppCompatActivity {
}
}
+ // 生日已经在日期选择器中实时保存,这里只需要确保同步
+ // 如果输入框为空,则清除
if (TextUtils.isEmpty(birthday)) {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().remove(KEY_BIRTHDAY).apply();
} else {
+ // 确保使用输入框中的最新值
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_BIRTHDAY, birthday).apply();
}
@@ -139,6 +190,8 @@ public class EditProfileActivity extends AppCompatActivity {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putString(KEY_LOCATION, location).apply();
}
+ // 设置结果,通知ProfileActivity需要刷新
+ setResult(RESULT_OK);
finish();
});
}
@@ -200,6 +253,11 @@ public class EditProfileActivity extends AppCompatActivity {
if (!TextUtils.isEmpty(avatarUri)) {
selectedAvatarUri = Uri.parse(avatarUri);
Glide.with(this).load(selectedAvatarUri).circleCrop().into(binding.avatarPreview);
+ } else {
+ int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
+ if (avatarRes != 0) {
+ Glide.with(this).load(avatarRes).circleCrop().into(binding.avatarPreview);
+ }
}
}
@@ -250,4 +308,254 @@ public class EditProfileActivity extends AppCompatActivity {
return null;
}
}
+
+ private void setupGenderDropdown() {
+ String[] genders = {"男", "女", "保密"};
+ ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, genders);
+
+ binding.inputGender.setOnClickListener(v -> {
+ android.widget.PopupMenu popupMenu = new android.widget.PopupMenu(this, binding.inputGender);
+ for (int i = 0; i < genders.length; i++) {
+ popupMenu.getMenu().add(android.view.Menu.NONE, i, i, genders[i]);
+ }
+ popupMenu.setOnMenuItemClickListener(item -> {
+ String selectedGender = genders[item.getItemId()];
+ binding.inputGender.setText(selectedGender);
+ return true;
+ });
+ popupMenu.show();
+ });
+ }
+
+ private void setupLocationDropdown() {
+ binding.inputLocation.setOnClickListener(v -> showLocationPicker());
+ }
+
+ private void showLocationPicker() {
+ BottomSheetDialog dialog = new BottomSheetDialog(this);
+ View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_location_picker_spinner, null);
+ dialog.setContentView(view);
+
+ Spinner provinceSpinner = view.findViewById(R.id.provinceSpinner);
+ Spinner citySpinner = view.findViewById(R.id.citySpinner);
+ TextView confirmButton = view.findViewById(R.id.confirmButton);
+ TextView cancelButton = view.findViewById(R.id.cancelButton);
+
+ // 加载地址数据
+ LocationDataManager locationManager = LocationDataManager.getInstance();
+ locationManager.loadData(this);
+
+ // 解析当前地址
+ String currentLocation = binding.inputLocation.getText() != null ? binding.inputLocation.getText().toString() : "";
+ String[] parsed = locationManager.parseLocation(currentLocation);
+ String currentProvince = parsed[0];
+ String currentCity = parsed[1];
+
+ // 初始化省份Spinner
+ List provinceNames = locationManager.getProvinceNames();
+ ArrayAdapter provinceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, provinceNames);
+ provinceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ provinceSpinner.setAdapter(provinceAdapter);
+
+ // 设置当前选中的省份
+ if (!TextUtils.isEmpty(currentProvince)) {
+ int provinceIndex = locationManager.findProvinceIndex(currentProvince);
+ if (provinceIndex >= 0 && provinceIndex < provinceNames.size()) {
+ provinceSpinner.setSelection(provinceIndex);
+ }
+ }
+
+ // 初始化城市Spinner
+ final String[] selectedProvince = {currentProvince};
+ final String[] selectedCity = {currentCity};
+
+ updateCitySpinner(citySpinner, locationManager, selectedProvince[0], selectedCity[0]);
+
+ // 省份选择监听
+ provinceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ selectedProvince[0] = provinceNames.get(position);
+ selectedCity[0] = "";
+ updateCitySpinner(citySpinner, locationManager, selectedProvince[0], "");
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ });
+
+ // 城市选择监听
+ citySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ List cityNames = locationManager.getCityNames(selectedProvince[0]);
+ if (position >= 0 && position < cityNames.size()) {
+ selectedCity[0] = cityNames.get(position);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ });
+
+ confirmButton.setOnClickListener(v -> {
+ String location = locationManager.formatLocation(selectedProvince[0], selectedCity[0]);
+ binding.inputLocation.setText(location);
+ dialog.dismiss();
+ });
+
+ cancelButton.setOnClickListener(v -> dialog.dismiss());
+
+ dialog.show();
+ }
+
+ private void updateCitySpinner(Spinner citySpinner, LocationDataManager locationManager, String province, String selectedCity) {
+ List cityNames = locationManager.getCityNames(province);
+ ArrayAdapter cityAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, cityNames);
+ cityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ citySpinner.setAdapter(cityAdapter);
+
+ if (!TextUtils.isEmpty(selectedCity) && cityNames.contains(selectedCity)) {
+ int cityIndex = locationManager.findCityIndex(province, selectedCity);
+ if (cityIndex >= 0 && cityIndex < cityNames.size()) {
+ citySpinner.setSelection(cityIndex);
+ }
+ } else if (!cityNames.isEmpty()) {
+ citySpinner.setSelection(0);
+ }
+ }
+
+ private void setupBirthdayPicker() {
+ binding.inputBirthday.setOnClickListener(v -> showDatePicker());
+ }
+
+ private void showDatePicker() {
+ BottomSheetDialog dialog = new BottomSheetDialog(this);
+ View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_date_picker, null);
+ dialog.setContentView(view);
+
+ NumberPicker yearPicker = view.findViewById(R.id.yearPicker);
+ NumberPicker monthPicker = view.findViewById(R.id.monthPicker);
+ NumberPicker dayPicker = view.findViewById(R.id.dayPicker);
+ TextView confirmButton = view.findViewById(R.id.confirmButton);
+ TextView cancelButton = view.findViewById(R.id.cancelButton);
+
+ // 获取当前日期
+ Calendar today = Calendar.getInstance();
+ int currentYear = today.get(Calendar.YEAR);
+ int currentMonth = today.get(Calendar.MONTH) + 1; // Calendar.MONTH 从0开始
+ int currentDay = today.get(Calendar.DAY_OF_MONTH);
+
+ // 解析当前生日
+ String currentBirthday = binding.inputBirthday.getText() != null ? binding.inputBirthday.getText().toString() : "";
+ final int[] selectedYear = {currentYear - 25}; // 默认25岁
+ final int[] selectedMonth = {currentMonth};
+ final int[] selectedDay = {currentDay};
+
+ if (!TextUtils.isEmpty(currentBirthday)) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+ java.util.Date date = sdf.parse(currentBirthday);
+ if (date != null) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ selectedYear[0] = cal.get(Calendar.YEAR);
+ selectedMonth[0] = cal.get(Calendar.MONTH) + 1;
+ selectedDay[0] = cal.get(Calendar.DAY_OF_MONTH);
+ }
+ } catch (Exception e) {
+ // 解析失败,使用默认值
+ }
+ }
+
+ // 设置年份范围:100年前到今天
+ int minYear = currentYear - 100;
+ int maxYear = currentYear;
+ yearPicker.setMinValue(minYear);
+ yearPicker.setMaxValue(maxYear);
+ yearPicker.setValue(selectedYear[0]);
+ yearPicker.setWrapSelectorWheel(false);
+
+ // 设置月份范围:1-12
+ monthPicker.setMinValue(1);
+ monthPicker.setMaxValue(12);
+ monthPicker.setValue(selectedMonth[0]);
+ monthPicker.setWrapSelectorWheel(false);
+ monthPicker.setDisplayedValues(new String[]{"1月", "2月", "3月", "4月", "5月", "6月",
+ "7月", "8月", "9月", "10月", "11月", "12月"});
+
+ // 更新日期选择器的最大值
+ updateDayPicker(dayPicker, selectedYear[0], selectedMonth[0], selectedDay[0]);
+
+ // 年份和月份变化时,更新日期选择器
+ yearPicker.setOnValueChangedListener((picker, oldVal, newVal) -> {
+ selectedYear[0] = newVal;
+ int maxDay = getDaysInMonth(selectedYear[0], selectedMonth[0]);
+ if (selectedDay[0] > maxDay) {
+ selectedDay[0] = maxDay;
+ }
+ updateDayPicker(dayPicker, selectedYear[0], selectedMonth[0], selectedDay[0]);
+ });
+
+ monthPicker.setOnValueChangedListener((picker, oldVal, newVal) -> {
+ selectedMonth[0] = newVal;
+ int maxDay = getDaysInMonth(selectedYear[0], selectedMonth[0]);
+ if (selectedDay[0] > maxDay) {
+ selectedDay[0] = maxDay;
+ }
+ updateDayPicker(dayPicker, selectedYear[0], selectedMonth[0], selectedDay[0]);
+ });
+
+ // 确认按钮
+ confirmButton.setOnClickListener(v -> {
+ int year = yearPicker.getValue();
+ int month = monthPicker.getValue();
+ int day = dayPicker.getValue();
+
+ // 格式化日期
+ Calendar selectedDate = Calendar.getInstance();
+ selectedDate.set(year, month - 1, day); // Calendar.MONTH 从0开始
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+ String formattedDate = sdf.format(selectedDate.getTime());
+
+ // 更新输入框显示
+ binding.inputBirthday.setText(formattedDate);
+
+ // 立即保存到SharedPreferences
+ getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
+ .edit()
+ .putString(KEY_BIRTHDAY, formattedDate)
+ .apply();
+
+ dialog.dismiss();
+ });
+
+ // 取消按钮
+ cancelButton.setOnClickListener(v -> dialog.dismiss());
+
+ dialog.show();
+ }
+
+ private void updateDayPicker(NumberPicker dayPicker, int year, int month, int selectedDay) {
+ int maxDay = getDaysInMonth(year, month);
+ dayPicker.setMinValue(1);
+ dayPicker.setMaxValue(maxDay);
+ dayPicker.setWrapSelectorWheel(false);
+
+ // 确保选中的日期不超过最大值
+ if (selectedDay > maxDay) {
+ selectedDay = maxDay;
+ }
+ dayPicker.setValue(selectedDay);
+ }
+
+ private int getDaysInMonth(int year, int month) {
+ Calendar cal = Calendar.getInstance();
+ cal.set(year, month - 1, 1); // Calendar.MONTH 从0开始
+ return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+ }
}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/FindGameActivity.java b/android-app/app/src/main/java/com/example/livestreaming/FindGameActivity.java
new file mode 100644
index 00000000..2b7e09e1
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/FindGameActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityFindGameBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class FindGameActivity extends AppCompatActivity {
+
+ private ActivityFindGameBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, FindGameActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityFindGameBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/FishPondActivity.java b/android-app/app/src/main/java/com/example/livestreaming/FishPondActivity.java
index 8bd453de..ec8bd96f 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/FishPondActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/FishPondActivity.java
@@ -1,10 +1,12 @@
package com.example.livestreaming;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.animation.ValueAnimator;
import android.os.Handler;
import android.os.Looper;
+import android.text.TextUtils;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.animation.LinearInterpolator;
@@ -70,12 +72,18 @@ public class FishPondActivity extends AppCompatActivity {
setupOrbitUserInteractions();
setupCenterRefresh();
+ setupQuickActions();
+ loadCurrentUserInfo();
binding.orbitContainer.post(this::updateOrbitLayout);
refreshUsers();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_friends);
+
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNavigation);
+
bottomNavigation.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
@@ -116,6 +124,54 @@ public class FishPondActivity extends AppCompatActivity {
});
}
+ private void setupQuickActions() {
+ if (binding == null) return;
+
+ // 语音匹配按钮
+ binding.actionLeft.setOnClickListener(v -> {
+ VoiceMatchActivity.start(this);
+ });
+
+ // 心动信号按钮
+ binding.actionRight.setOnClickListener(v -> {
+ HeartbeatSignalActivity.start(this);
+ });
+
+ // 获取GridLayout中的卡片并设置点击事件
+ android.widget.GridLayout grid = binding.grid;
+ if (grid != null) {
+ int childCount = grid.getChildCount();
+ for (int i = 0; i < childCount && i < 6; i++) {
+ View card = grid.getChildAt(i);
+ if (card != null) {
+ final int index = i;
+ card.setOnClickListener(v -> {
+ switch (index) {
+ case 0: // 在线处对象
+ OnlineDatingActivity.start(this);
+ break;
+ case 1: // 找人玩游戏
+ FindGameActivity.start(this);
+ break;
+ case 2: // 一起KTV
+ KTVTogetherActivity.start(this);
+ break;
+ case 3: // 你画我猜
+ DrawGuessActivity.start(this);
+ break;
+ case 4: // 和平精英
+ PeaceEliteActivity.start(this);
+ break;
+ case 5: // 桌子游
+ TableGamesActivity.start(this);
+ break;
+ }
+ });
+ }
+ }
+ }
+ }
+
private void updateOrbitLayout() {
if (binding == null) return;
@@ -227,6 +283,42 @@ public class FishPondActivity extends AppCompatActivity {
uiHandler.removeCallbacks(pulseRunnable);
}
+ private void loadCurrentUserInfo() {
+ if (binding == null) return;
+
+ SharedPreferences prefs = getSharedPreferences("profile_prefs", MODE_PRIVATE);
+ String name = prefs.getString("profile_name", "爱你");
+ String location = prefs.getString("profile_location", "广西");
+ String gender = prefs.getString("profile_gender", "");
+
+ // 设置右侧名称
+ binding.rightName.setText(name);
+
+ // 设置右侧信息:性别 | 地址 | 在线
+ String genderText = "";
+ if (!TextUtils.isEmpty(gender)) {
+ if (gender.contains("男")) {
+ genderText = "真诚";
+ } else if (gender.contains("女")) {
+ genderText = "真诚";
+ } else {
+ genderText = "真诚";
+ }
+ } else {
+ genderText = "真诚";
+ }
+
+ // 处理地址格式:如果是"省份-城市"格式,转换为"省份·城市"
+ String locationText = location;
+ if (!TextUtils.isEmpty(location) && location.contains("-")) {
+ locationText = location.replace("-", "·");
+ } else if (TextUtils.isEmpty(location)) {
+ locationText = "南宁";
+ }
+
+ binding.rightInfo.setText(genderText + " | " + locationText + " | 在线");
+ }
+
private void refreshUsers() {
if (binding == null) return;
@@ -330,9 +422,15 @@ public class FishPondActivity extends AppCompatActivity {
protected void onResume() {
super.onResume();
if (binding != null) {
- binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ BottomNavigationView bottomNav = binding.bottomNavInclude.bottomNavigation;
+ bottomNav.setSelectedItemId(R.id.nav_friends);
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNav);
}
+ // 刷新当前用户信息(可能从编辑资料页面返回)
+ loadCurrentUserInfo();
+
// ensure radius computed before starting
if (binding != null) {
binding.orbitContainer.post(() -> {
diff --git a/android-app/app/src/main/java/com/example/livestreaming/HeartbeatSignalActivity.java b/android-app/app/src/main/java/com/example/livestreaming/HeartbeatSignalActivity.java
new file mode 100644
index 00000000..fbe1e118
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/HeartbeatSignalActivity.java
@@ -0,0 +1,62 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityHeartbeatSignalBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class HeartbeatSignalActivity extends AppCompatActivity {
+
+ private ActivityHeartbeatSignalBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, HeartbeatSignalActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityHeartbeatSignalBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/KTVTogetherActivity.java b/android-app/app/src/main/java/com/example/livestreaming/KTVTogetherActivity.java
new file mode 100644
index 00000000..1765b46b
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/KTVTogetherActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityKtvTogetherBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class KTVTogetherActivity extends AppCompatActivity {
+
+ private ActivityKtvTogetherBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, KTVTogetherActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityKtvTogetherBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/LocationDataManager.java b/android-app/app/src/main/java/com/example/livestreaming/LocationDataManager.java
new file mode 100644
index 00000000..353f24b8
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/LocationDataManager.java
@@ -0,0 +1,142 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LocationDataManager {
+ private static LocationDataManager instance;
+ private List provinces;
+ private Map provinceMap;
+
+ private LocationDataManager() {
+ provinces = new ArrayList<>();
+ provinceMap = new HashMap<>();
+ }
+
+ public static synchronized LocationDataManager getInstance() {
+ if (instance == null) {
+ instance = new LocationDataManager();
+ }
+ return instance;
+ }
+
+ public void loadData(Context context) {
+ if (provinces != null && !provinces.isEmpty()) {
+ return; // 已经加载过
+ }
+
+ try {
+ InputStream is = context.getAssets().open("location_data.json");
+ int size = is.available();
+ byte[] buffer = new byte[size];
+ is.read(buffer);
+ is.close();
+
+ String json = new String(buffer, "UTF-8");
+ JSONObject jsonObject = new JSONObject(json);
+ JSONArray provincesArray = jsonObject.getJSONArray("provinces");
+
+ provinces.clear();
+ provinceMap.clear();
+
+ for (int i = 0; i < provincesArray.length(); i++) {
+ JSONObject provinceObj = provincesArray.getJSONObject(i);
+ Province province = new Province();
+ province.name = provinceObj.getString("name");
+
+ JSONArray citiesArray = provinceObj.getJSONArray("cities");
+ province.cities = new ArrayList<>();
+ for (int j = 0; j < citiesArray.length(); j++) {
+ JSONObject cityObj = citiesArray.getJSONObject(j);
+ City city = new City();
+ city.name = cityObj.getString("name");
+ province.cities.add(city);
+ }
+
+ provinces.add(province);
+ provinceMap.put(province.name, province);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public List getProvinceNames() {
+ List names = new ArrayList<>();
+ for (Province province : provinces) {
+ names.add(province.name);
+ }
+ return names;
+ }
+
+ public List getCityNames(String provinceName) {
+ List names = new ArrayList<>();
+ Province province = provinceMap.get(provinceName);
+ if (province != null && province.cities != null) {
+ for (City city : province.cities) {
+ names.add(city.name);
+ }
+ }
+ return names;
+ }
+
+ public String formatLocation(String province, String city) {
+ if (TextUtils.isEmpty(province)) {
+ return "";
+ }
+ if (TextUtils.isEmpty(city)) {
+ return province;
+ }
+ return province + "-" + city;
+ }
+
+ public String[] parseLocation(String location) {
+ if (TextUtils.isEmpty(location)) {
+ return new String[]{"", ""};
+ }
+ if (location.contains("-")) {
+ String[] parts = location.split("-", 2);
+ return new String[]{parts[0], parts.length > 1 ? parts[1] : ""};
+ }
+ return new String[]{location, ""};
+ }
+
+ public int findProvinceIndex(String provinceName) {
+ for (int i = 0; i < provinces.size(); i++) {
+ if (provinces.get(i).name.equals(provinceName)) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ public int findCityIndex(String provinceName, String cityName) {
+ Province province = provinceMap.get(provinceName);
+ if (province != null && province.cities != null) {
+ for (int i = 0; i < province.cities.size(); i++) {
+ if (province.cities.get(i).name.equals(cityName)) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
+ static class Province {
+ String name;
+ List cities;
+ }
+
+ static class City {
+ String name;
+ }
+}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java b/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java
index da676ef5..22f08039 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java
@@ -1,14 +1,21 @@
package com.example.livestreaming;
+import android.Manifest;
import android.content.res.AssetManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.speech.RecognitionListener;
+import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
+import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
@@ -16,6 +23,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -56,6 +65,11 @@ public class MainActivity extends AppCompatActivity {
private boolean isFetching;
private long lastFetchMs;
+ private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
+ private SpeechRecognizer speechRecognizer;
+ private Intent speechRecognizerIntent;
+ private boolean isListening = false;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -66,6 +80,29 @@ public class MainActivity extends AppCompatActivity {
setupRecyclerView();
setupUI();
loadAvatarFromPrefs();
+ setupSpeechRecognizer();
+
+ // 初始化未读消息数量(演示数据)
+ if (UnreadMessageManager.getUnreadCount(this) == 0) {
+ // 从消息列表计算总未读数量
+ UnreadMessageManager.setUnreadCount(this, calculateTotalUnreadCount());
+ }
+
+ // 清除默认选中状态,让所有标签页初始显示为未选中样式
+ if (binding != null && binding.topTabs != null) {
+ // 在布局完成后清除默认选中状态
+ binding.topTabs.post(() -> {
+ // 清除所有选中状态
+ for (int i = 0; i < binding.topTabs.getTabCount(); i++) {
+ TabLayout.Tab tab = binding.topTabs.getTabAt(i);
+ if (tab != null && tab.isSelected()) {
+ // 取消选中,但不触发监听器
+ binding.topTabs.selectTab(null, false);
+ break;
+ }
+ }
+ });
+ }
// 异步加载资源文件,避免阻塞主线程
loadCoverAssetsAsync();
@@ -91,7 +128,7 @@ public class MainActivity extends AppCompatActivity {
items.add(new DrawerCardItem(DrawerCardItem.ACTION_PROFILE, "个人主页", "资料、作品、粉丝", R.drawable.ic_person_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_MESSAGES, "消息", "私信、互动通知", R.drawable.ic_chat_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_MY_FRIENDS, "我的好友", "通讯录与挚友", R.drawable.ic_people_24));
- items.add(new DrawerCardItem(DrawerCardItem.ACTION_FISH_POND, "鱼塘", "附近与社交圈", R.drawable.ic_globe_24));
+ items.add(new DrawerCardItem(DrawerCardItem.ACTION_FISH_POND, "缘池", "附近与社交圈", R.drawable.ic_people_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_FOLLOWING, "我的关注", "你关注的主播", R.drawable.ic_people_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_FANS, "粉丝", "关注你的人", R.drawable.ic_people_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_LIKES, "获赞", "收到的点赞", R.drawable.ic_heart_24));
@@ -204,12 +241,20 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab == null) return;
+ // 用户点击后,显示选中样式(显示指示器,改变文字颜色)
+ binding.topTabs.setSelectedTabIndicatorHeight(2); // 显示指示器
+ // 更新布局属性以显示选中样式
+ binding.topTabs.setTabTextColors(
+ getResources().getColor(android.R.color.darker_gray, null), // 未选中颜色
+ getResources().getColor(R.color.purple_500, null) // 选中颜色
+ );
CharSequence title = tab.getText();
TabPlaceholderActivity.start(MainActivity.this, title != null ? title.toString() : "");
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
+ // 取消选中时,恢复默认样式
}
@Override
@@ -271,8 +316,15 @@ public class MainActivity extends AppCompatActivity {
// 设置添加直播按钮点击事件
binding.fabAddLive.setOnClickListener(v -> showCreateRoomDialog());
+ // 设置搜索框文本监听和图标切换
+ setupSearchBox();
+
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_home);
+
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNavigation);
+
bottomNavigation.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
@@ -302,6 +354,245 @@ public class MainActivity extends AppCompatActivity {
});
}
+ private void setupSearchBox() {
+ // 监听搜索框文本变化
+ binding.searchEdit.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // 根据文本是否为空切换图标
+ String text = s != null ? s.toString().trim() : "";
+ if (text.isEmpty()) {
+ // 文本为空,显示麦克风图标
+ binding.micIcon.setImageResource(R.drawable.ic_mic_24);
+ binding.micIcon.setContentDescription("mic");
+ } else {
+ // 文本不为空,显示搜索图标
+ binding.micIcon.setImageResource(R.drawable.ic_search_24);
+ binding.micIcon.setContentDescription("search");
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ // 设置麦克风/搜索图标点击事件
+ binding.micIcon.setOnClickListener(v -> {
+ String searchText = binding.searchEdit.getText() != null ?
+ binding.searchEdit.getText().toString().trim() : "";
+
+ if (searchText.isEmpty()) {
+ // 如果文本为空,启动语音识别
+ startVoiceRecognition();
+ } else {
+ // 如果文本不为空,执行搜索
+ // 跳转到搜索页面并传递搜索关键词
+ SearchActivity.start(MainActivity.this, searchText);
+ }
+ });
+ }
+
+ private void setupSpeechRecognizer() {
+ // 检查设备是否支持语音识别
+ if (!SpeechRecognizer.isRecognitionAvailable(this)) {
+ return;
+ }
+
+ speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
+ speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+ speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "zh-CN"); // 设置为中文
+ speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
+ speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
+
+ speechRecognizer.setRecognitionListener(new RecognitionListener() {
+ @Override
+ public void onReadyForSpeech(Bundle params) {
+ isListening = true;
+ // 可以在这里显示"正在聆听..."的提示
+ binding.micIcon.setAlpha(0.5f);
+ }
+
+ @Override
+ public void onBeginningOfSpeech() {
+ // 开始说话
+ }
+
+ @Override
+ public void onRmsChanged(float rmsdB) {
+ // 音量变化,可以用来显示音量动画
+ }
+
+ @Override
+ public void onBufferReceived(byte[] buffer) {
+ // 接收音频数据
+ }
+
+ @Override
+ public void onEndOfSpeech() {
+ isListening = false;
+ binding.micIcon.setAlpha(1.0f);
+ }
+
+ @Override
+ public void onError(int error) {
+ isListening = false;
+ binding.micIcon.setAlpha(1.0f);
+
+ String errorMessage;
+ switch (error) {
+ case SpeechRecognizer.ERROR_AUDIO:
+ errorMessage = "音频错误";
+ break;
+ case SpeechRecognizer.ERROR_CLIENT:
+ errorMessage = "客户端错误";
+ break;
+ case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
+ errorMessage = "权限不足";
+ requestAudioPermission();
+ return;
+ case SpeechRecognizer.ERROR_NETWORK:
+ errorMessage = "网络错误";
+ break;
+ case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
+ errorMessage = "网络超时";
+ break;
+ case SpeechRecognizer.ERROR_NO_MATCH:
+ errorMessage = "未识别到语音";
+ break;
+ case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
+ errorMessage = "识别器忙碌";
+ break;
+ case SpeechRecognizer.ERROR_SERVER:
+ errorMessage = "服务器错误";
+ break;
+ case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
+ errorMessage = "未检测到语音";
+ break;
+ default:
+ errorMessage = "未知错误";
+ break;
+ }
+
+ // 只在非用户取消的情况下显示错误提示
+ if (error != SpeechRecognizer.ERROR_CLIENT) {
+ Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void onResults(Bundle results) {
+ isListening = false;
+ binding.micIcon.setAlpha(1.0f);
+
+ ArrayList matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
+ if (matches != null && !matches.isEmpty()) {
+ String spokenText = matches.get(0);
+ // 将识别结果填充到搜索框
+ binding.searchEdit.setText(spokenText);
+ binding.searchEdit.setSelection(spokenText.length());
+ }
+ }
+
+ @Override
+ public void onPartialResults(Bundle partialResults) {
+ // 部分结果,可以实时显示
+ ArrayList matches = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
+ if (matches != null && !matches.isEmpty()) {
+ String partialText = matches.get(0);
+ // 可以在这里实时更新搜索框(可选)
+ // binding.searchEdit.setText(partialText);
+ }
+ }
+
+ @Override
+ public void onEvent(int eventType, Bundle params) {
+ // 事件回调
+ }
+ });
+ }
+
+ private void startVoiceRecognition() {
+ // 检查权限
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestAudioPermission();
+ return;
+ }
+
+ // 检查是否正在监听
+ if (isListening) {
+ stopVoiceRecognition();
+ return;
+ }
+
+ // 检查SpeechRecognizer是否可用
+ if (speechRecognizer == null || speechRecognizerIntent == null) {
+ Toast.makeText(this, "语音识别功能不可用", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ try {
+ speechRecognizer.startListening(speechRecognizerIntent);
+ } catch (Exception e) {
+ Toast.makeText(this, "启动语音识别失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void stopVoiceRecognition() {
+ if (speechRecognizer != null && isListening) {
+ speechRecognizer.stopListening();
+ isListening = false;
+ binding.micIcon.setAlpha(1.0f);
+ }
+ }
+
+ private void requestAudioPermission() {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
+ new AlertDialog.Builder(this)
+ .setTitle("需要麦克风权限")
+ .setMessage("语音搜索需要访问麦克风权限,请在设置中允许")
+ .setPositiveButton("确定", (dialog, which) -> {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO},
+ REQUEST_RECORD_AUDIO_PERMISSION);
+ })
+ .setNegativeButton("取消", null)
+ .show();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO},
+ REQUEST_RECORD_AUDIO_PERMISSION);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // 权限已授予,可以开始语音识别
+ startVoiceRecognition();
+ } else {
+ Toast.makeText(this, "需要麦克风权限才能使用语音搜索", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (speechRecognizer != null) {
+ speechRecognizer.destroy();
+ speechRecognizer = null;
+ }
+ }
+
private void loadAvatarFromPrefs() {
try {
String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE)
@@ -340,8 +631,31 @@ public class MainActivity extends AppCompatActivity {
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_home);
loadAvatarFromPrefs();
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNavigation);
}
}
+
+ /**
+ * 计算总未读消息数量(从演示数据中计算)
+ */
+ private int calculateTotalUnreadCount() {
+ // 模拟从消息列表计算总未读数量
+ // 这里使用 MessagesActivity 中的演示数据
+ int total = 0;
+ total += 2; // 系统通知
+ total += 5; // 附近的人
+ total += 19; // 直播间群聊
+ total += 1; // 客服
+ return total;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // 暂停时停止语音识别
+ stopVoiceRecognition();
+ }
@Override
protected void onStart() {
diff --git a/android-app/app/src/main/java/com/example/livestreaming/MessagesActivity.java b/android-app/app/src/main/java/com/example/livestreaming/MessagesActivity.java
index 206ce2c8..7124c021 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/MessagesActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/MessagesActivity.java
@@ -33,8 +33,11 @@ public class MessagesActivity extends AppCompatActivity {
private int swipedPosition = RecyclerView.NO_POSITION;
private RectF deleteButtonRect;
+ private RectF markReadButtonRect;
private final Paint deleteBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint deleteTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint markReadBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint markReadTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int lastSwipePosition = RecyclerView.NO_POSITION;
private float lastSwipeDx;
@@ -89,14 +92,35 @@ public class MessagesActivity extends AppCompatActivity {
}
private void setupConversationList() {
+ // 删除按钮的背景颜色(红色)
deleteBackgroundPaint.setColor(0xFFE53935);
deleteTextPaint.setColor(Color.WHITE);
deleteTextPaint.setTextAlign(Paint.Align.CENTER);
deleteTextPaint.setTextSize(sp(14));
+
+ // 标记已读按钮的背景颜色(蓝色)
+ markReadBackgroundPaint.setColor(0xFF2196F3);
+ markReadTextPaint.setColor(Color.WHITE);
+ markReadTextPaint.setTextAlign(Paint.Align.CENTER);
+ markReadTextPaint.setTextSize(sp(14));
conversationsAdapter = new ConversationsAdapter(item -> {
if (item == null) return;
- ConversationActivity.start(this, item.getId(), item.getTitle());
+ // 启动会话页面,传递未读数量
+ Intent intent = new Intent(this, ConversationActivity.class);
+ intent.putExtra("extra_conversation_id", item.getId());
+ intent.putExtra("extra_conversation_title", item.getTitle());
+ intent.putExtra("extra_unread_count", item.getUnreadCount());
+ startActivity(intent);
+
+ // 用户点击会话时,减少该会话的未读数量
+ if (item.getUnreadCount() > 0) {
+ // 更新该会话的未读数量为0(在实际应用中,这里应该更新数据源)
+ // 然后更新总未读数量
+ UnreadMessageManager.decrementUnreadCount(this, item.getUnreadCount());
+ // 更新列表中的未读数量显示
+ updateConversationUnreadCount(item.getId(), 0);
+ }
});
binding.conversationsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.conversationsRecyclerView.setAdapter(conversationsAdapter);
@@ -109,7 +133,9 @@ public class MessagesActivity extends AppCompatActivity {
}
private void attachSwipeToDelete(RecyclerView recyclerView) {
- final float actionWidth = dp(96);
+ // 两个按钮的总宽度:删除按钮 + 标记已读按钮
+ final float buttonWidth = dp(96);
+ final float actionWidth = buttonWidth * 2; // 两个按钮并排显示
touchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
@@ -140,6 +166,7 @@ public class MessagesActivity extends AppCompatActivity {
int old = swipedPosition;
swipedPosition = RecyclerView.NO_POSITION;
deleteButtonRect = null;
+ markReadButtonRect = null;
conversationsAdapter.notifyItemChanged(old);
}
swipedPosition = pos;
@@ -148,6 +175,7 @@ public class MessagesActivity extends AppCompatActivity {
if (swipedPosition == pos) {
swipedPosition = RecyclerView.NO_POSITION;
deleteButtonRect = null;
+ markReadButtonRect = null;
}
viewHolder.itemView.setTranslationX(0f);
}
@@ -207,20 +235,37 @@ public class MessagesActivity extends AppCompatActivity {
}
View itemView = viewHolder.itemView;
- float left;
float top = itemView.getTop();
float bottom = itemView.getBottom();
float right = itemView.getRight();
- left = right + clampedDx;
+ float left = right + clampedDx;
- deleteButtonRect = new RectF(left, top, right, bottom);
+ // 绘制两个按钮:标记已读(左侧)和删除(右侧)
+ final float buttonWidth = dp(96);
+
+ // 标记已读按钮(左侧,蓝色)
+ float markReadLeft = left;
+ float markReadRight = left + buttonWidth;
+ markReadButtonRect = new RectF(markReadLeft, top, markReadRight, bottom);
+ c.drawRect(markReadButtonRect, markReadBackgroundPaint);
+
+ float markReadCenterX = markReadButtonRect.centerX();
+ float markReadCenterY = markReadButtonRect.centerY();
+ Paint.FontMetrics markReadFm = markReadTextPaint.getFontMetrics();
+ float markReadTextY = markReadCenterY - (markReadFm.ascent + markReadFm.descent) / 2f;
+ c.drawText("标记已读", markReadCenterX, markReadTextY, markReadTextPaint);
+
+ // 删除按钮(右侧,红色)
+ float deleteLeft = markReadRight;
+ float deleteRight = right;
+ deleteButtonRect = new RectF(deleteLeft, top, deleteRight, bottom);
c.drawRect(deleteButtonRect, deleteBackgroundPaint);
-
- float centerX = deleteButtonRect.centerX();
- float centerY = deleteButtonRect.centerY();
- Paint.FontMetrics fm = deleteTextPaint.getFontMetrics();
- float textY = centerY - (fm.ascent + fm.descent) / 2f;
- c.drawText("删除", centerX, textY, deleteTextPaint);
+
+ float deleteCenterX = deleteButtonRect.centerX();
+ float deleteCenterY = deleteButtonRect.centerY();
+ Paint.FontMetrics deleteFm = deleteTextPaint.getFontMetrics();
+ float deleteTextY = deleteCenterY - (deleteFm.ascent + deleteFm.descent) / 2f;
+ c.drawText("删除", deleteCenterX, deleteTextY, deleteTextPaint);
super.onChildDraw(c, recyclerView, viewHolder, clampedDx, dY, actionState, isCurrentlyActive);
}
@@ -229,7 +274,7 @@ public class MessagesActivity extends AppCompatActivity {
new ItemTouchHelper(callback).attachToRecyclerView(recyclerView);
recyclerView.setOnTouchListener((v, event) -> {
- if (swipedPosition == RecyclerView.NO_POSITION || deleteButtonRect == null) return false;
+ if (swipedPosition == RecyclerView.NO_POSITION || (deleteButtonRect == null && markReadButtonRect == null)) return false;
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
@@ -251,10 +296,20 @@ public class MessagesActivity extends AppCompatActivity {
if (action == MotionEvent.ACTION_UP) {
if (!touchIsClick) return false;
- boolean hit = deleteButtonRect.contains(event.getX(), event.getY());
int pos = swipedPosition;
+ float x = event.getX();
+ float y = event.getY();
+
+ // 检查点击的是哪个按钮
+ boolean hitMarkRead = markReadButtonRect != null && markReadButtonRect.contains(x, y);
+ boolean hitDelete = deleteButtonRect != null && deleteButtonRect.contains(x, y);
+
recoverSwipedItem();
- if (hit) {
+
+ if (hitMarkRead) {
+ markAsReadAt(pos);
+ return true;
+ } else if (hitDelete) {
deleteConversationAt(pos);
return true;
}
@@ -267,17 +322,56 @@ public class MessagesActivity extends AppCompatActivity {
int pos = swipedPosition;
swipedPosition = RecyclerView.NO_POSITION;
deleteButtonRect = null;
+ markReadButtonRect = null;
if (pos != RecyclerView.NO_POSITION && conversationsAdapter != null) {
conversationsAdapter.notifyItemChanged(pos);
}
}
+ /**
+ * 标记会话为已读
+ */
+ private void markAsReadAt(int position) {
+ if (position < 0 || position >= conversations.size()) return;
+
+ ConversationItem item = conversations.get(position);
+ if (item == null) return;
+
+ // 获取该会话的未读数量
+ int unreadCount = item.getUnreadCount();
+
+ // 如果已经有未读消息,才需要更新
+ if (unreadCount > 0) {
+ // 将该会话的未读数量设为0
+ // updateConversationUnreadCount 方法会自动重新计算总未读数量并更新徽章
+ updateConversationUnreadCount(item.getId(), 0);
+ }
+ }
+
+ /**
+ * 删除会话
+ */
private void deleteConversationAt(int position) {
if (position < 0 || position >= conversations.size()) return;
+
+ // 获取要删除的会话的未读数量
+ ConversationItem itemToDelete = conversations.get(position);
+ int unreadCountToRemove = itemToDelete != null ? itemToDelete.getUnreadCount() : 0;
+
+ // 删除会话
conversations.remove(position);
if (conversationsAdapter != null) {
conversationsAdapter.submitList(new ArrayList<>(conversations));
}
+
+ // 更新总未读数量:减少被删除会话的未读数量
+ if (unreadCountToRemove > 0) {
+ UnreadMessageManager.decrementUnreadCount(this, unreadCountToRemove);
+ // 更新底部导航栏徽章
+ if (binding != null) {
+ UnreadMessageManager.updateBadge(binding.bottomNavInclude.bottomNavigation);
+ }
+ }
}
private float dp(float value) {
@@ -304,7 +398,65 @@ public class MessagesActivity extends AppCompatActivity {
protected void onResume() {
super.onResume();
if (binding != null) {
- binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_messages);
+ BottomNavigationView bottomNav = binding.bottomNavInclude.bottomNavigation;
+ bottomNav.setSelectedItemId(R.id.nav_messages);
+
+ // 用户进入消息页面时,计算并更新总未读数量
+ int totalUnread = calculateTotalUnreadCount();
+ UnreadMessageManager.setUnreadCount(this, totalUnread);
+ UnreadMessageManager.updateBadge(bottomNav);
+
+ // 刷新列表以更新未读数量显示
+ if (conversationsAdapter != null) {
+ conversationsAdapter.submitList(new ArrayList<>(conversations));
+ }
+ }
+ }
+
+ /**
+ * 计算总未读消息数量
+ */
+ private int calculateTotalUnreadCount() {
+ int total = 0;
+ for (ConversationItem item : conversations) {
+ if (item != null) {
+ total += item.getUnreadCount();
+ }
+ }
+ return total;
+ }
+
+ /**
+ * 更新会话的未读数量
+ */
+ private void updateConversationUnreadCount(String conversationId, int newUnreadCount) {
+ for (int i = 0; i < conversations.size(); i++) {
+ ConversationItem item = conversations.get(i);
+ if (item != null && item.getId().equals(conversationId)) {
+ // 创建新的 ConversationItem,更新未读数量
+ conversations.set(i, new ConversationItem(
+ item.getId(),
+ item.getTitle(),
+ item.getLastMessage(),
+ item.getTimeText(),
+ newUnreadCount,
+ item.isMuted()
+ ));
+ break;
+ }
+ }
+
+ // 通知适配器更新列表显示
+ if (conversationsAdapter != null) {
+ conversationsAdapter.submitList(new ArrayList<>(conversations));
+ }
+
+ // 更新总未读数量
+ int totalUnread = calculateTotalUnreadCount();
+ UnreadMessageManager.setUnreadCount(this, totalUnread);
+ // 更新底部导航栏徽章
+ if (binding != null) {
+ UnreadMessageManager.updateBadge(binding.bottomNavInclude.bottomNavigation);
}
}
}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/NearbyUsersAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/NearbyUsersAdapter.java
index 8d77549d..8880c12f 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/NearbyUsersAdapter.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/NearbyUsersAdapter.java
@@ -5,7 +5,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
@@ -50,37 +49,25 @@ public class NearbyUsersAdapter extends ListAdapter {
+ if (user == null) return;
+ if (onUserClickListener != null) onUserClickListener.onUserClick(user);
+ });
+
+ // 整个item点击也可以触发添加
binding.getRoot().setOnClickListener(v -> {
if (user == null) return;
if (onUserClickListener != null) onUserClickListener.onUserClick(user);
diff --git a/android-app/app/src/main/java/com/example/livestreaming/OnlineDatingActivity.java b/android-app/app/src/main/java/com/example/livestreaming/OnlineDatingActivity.java
new file mode 100644
index 00000000..3ed05067
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/OnlineDatingActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityOnlineDatingBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class OnlineDatingActivity extends AppCompatActivity {
+
+ private ActivityOnlineDatingBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, OnlineDatingActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityOnlineDatingBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/PeaceEliteActivity.java b/android-app/app/src/main/java/com/example/livestreaming/PeaceEliteActivity.java
new file mode 100644
index 00000000..77f4b6a1
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/PeaceEliteActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityPeaceEliteBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class PeaceEliteActivity extends AppCompatActivity {
+
+ private ActivityPeaceEliteBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, PeaceEliteActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityPeaceEliteBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java
index 7ae09465..12322d9d 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.net.Uri;
import android.text.TextUtils;
@@ -11,6 +12,9 @@ import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog;
@@ -18,6 +22,12 @@ import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityProfileBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
public class ProfileActivity extends AppCompatActivity {
private ActivityProfileBinding binding;
@@ -35,6 +45,8 @@ public class ProfileActivity extends AppCompatActivity {
private static final String KEY_LOCATION = "profile_location";
private static final String BIO_HINT_TEXT = "填写个人签名更容易获得关注,点击此处添加";
+
+ private ActivityResultLauncher editProfileLauncher;
public static void start(Context context) {
Intent intent = new Intent(context, ProfileActivity.class);
@@ -47,13 +59,31 @@ public class ProfileActivity extends AppCompatActivity {
binding = ActivityProfileBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ // 注册编辑资料页面的结果监听
+ editProfileLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ // 当从EditProfileActivity返回时,立即刷新所有数据
+ loadProfileFromPrefs();
+ loadAndDisplayTags();
+ loadProfileInfo();
+ }
+ );
+
loadProfileFromPrefs();
+ loadAndDisplayTags();
+ loadProfileInfo();
setupEditableAreas();
+ setupAvatarClick();
setupNavigationClicks();
setupProfileTabs();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_profile);
+
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNavigation);
+
bottomNavigation.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
@@ -140,6 +170,32 @@ public class ProfileActivity extends AppCompatActivity {
}
+ private void setupAvatarClick() {
+ binding.avatar.setOnClickListener(v -> {
+ AvatarViewerDialog dialog = AvatarViewerDialog.create(this);
+
+ // 优先从SharedPreferences读取最新的头像信息(因为ImageView可能还在加载中)
+ String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
+ int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
+
+ if (!TextUtils.isEmpty(avatarUri)) {
+ // 使用URI加载,确保能正确显示
+ dialog.setAvatarUri(Uri.parse(avatarUri));
+ } else if (avatarRes != 0) {
+ dialog.setAvatarResId(avatarRes);
+ } else {
+ // 如果都没有,尝试从ImageView获取Drawable
+ Drawable drawable = binding.avatar.getDrawable();
+ if (drawable != null) {
+ dialog.setAvatarDrawable(drawable);
+ } else {
+ dialog.setAvatarResId(R.drawable.ic_account_circle_24);
+ }
+ }
+ dialog.show();
+ });
+ }
+
private void setupNavigationClicks() {
binding.topActionSearch.setOnClickListener(v -> TabPlaceholderActivity.start(this, "定位/发现"));
binding.topActionClock.setOnClickListener(v -> WatchHistoryActivity.start(this));
@@ -161,13 +217,14 @@ public class ProfileActivity extends AppCompatActivity {
binding.followers.setOnClickListener(v -> FansListActivity.start(this));
binding.likes.setOnClickListener(v -> LikesListActivity.start(this));
- binding.recordBtn.setOnClickListener(v -> Toast.makeText(this, "录制声音(待实现)", Toast.LENGTH_SHORT).show());
-
binding.action1.setOnClickListener(v -> TabPlaceholderActivity.start(this, "公园勋章"));
binding.action2.setOnClickListener(v -> WatchHistoryActivity.start(this));
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
- binding.editProfile.setOnClickListener(v -> EditProfileActivity.start(this));
+ binding.editProfile.setOnClickListener(v -> {
+ Intent intent = new Intent(this, EditProfileActivity.class);
+ editProfileLauncher.launch(intent);
+ });
binding.shareHome.setOnClickListener(v -> {
// TabPlaceholderActivity.start(this, "分享主页");
String idText = binding.idLine.getText() != null ? binding.idLine.getText().toString() : "";
@@ -209,14 +266,18 @@ public class ProfileActivity extends AppCompatActivity {
binding.worksPublishBtn.setOnClickListener(v -> Toast.makeText(this, "发布功能待接入", Toast.LENGTH_SHORT).show());
binding.likedGoBrowseBtn.setOnClickListener(v -> startActivity(new Intent(this, MainActivity.class)));
binding.favGoBrowseBtn.setOnClickListener(v -> startActivity(new Intent(this, MainActivity.class)));
- binding.profileEditFromTab.setOnClickListener(v -> EditProfileActivity.start(this));
+ binding.profileEditFromTab.setOnClickListener(v -> {
+ Intent intent = new Intent(this, EditProfileActivity.class);
+ editProfileLauncher.launch(intent);
+ });
}
private void showTab(int index) {
+ // 标签页顺序:0-作品, 1-收藏, 2-赞过
binding.tabWorks.setVisibility(index == 0 ? View.VISIBLE : View.GONE);
- binding.tabLiked.setVisibility(index == 1 ? View.VISIBLE : View.GONE);
- binding.tabFavorites.setVisibility(index == 2 ? View.VISIBLE : View.GONE);
- binding.tabProfile.setVisibility(index == 3 ? View.VISIBLE : View.GONE);
+ binding.tabFavorites.setVisibility(index == 1 ? View.VISIBLE : View.GONE);
+ binding.tabLiked.setVisibility(index == 2 ? View.VISIBLE : View.GONE);
+ // "资料"标签页已移除
}
private interface OnTextSaved {
@@ -251,7 +312,153 @@ public class ProfileActivity extends AppCompatActivity {
super.onResume();
if (binding != null) {
loadProfileFromPrefs();
- binding.bottomNavInclude.bottomNavigation.setSelectedItemId(R.id.nav_profile);
+ loadAndDisplayTags();
+ loadProfileInfo();
+ BottomNavigationView bottomNav = binding.bottomNavInclude.bottomNavigation;
+ bottomNav.setSelectedItemId(R.id.nav_profile);
+ // 更新未读消息徽章
+ UnreadMessageManager.updateBadge(bottomNav);
+ }
+ }
+
+ private void loadAndDisplayTags() {
+ String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, "");
+ String gender = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_GENDER, "");
+ String birthday = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIRTHDAY, "");
+
+ // 设置所在地标签 - 支持"省份-城市"格式
+ if (!TextUtils.isEmpty(location)) {
+ // 将"省份-城市"格式转换为"省份·城市"显示
+ String displayLocation = location.replace("-", "·");
+ binding.tagLocation.setText("IP:" + displayLocation);
+ binding.tagLocation.setVisibility(View.VISIBLE);
+ } else {
+ binding.tagLocation.setText("IP:广西");
+ binding.tagLocation.setVisibility(View.VISIBLE);
+ }
+
+ // 设置性别标签
+ if (!TextUtils.isEmpty(gender)) {
+ if (gender.contains("男")) {
+ binding.tagGender.setText("男");
+ } else if (gender.contains("女")) {
+ binding.tagGender.setText("女");
+ } else {
+ binding.tagGender.setText("H");
+ }
+ binding.tagGender.setVisibility(View.VISIBLE);
+ } else {
+ binding.tagGender.setText("H");
+ binding.tagGender.setVisibility(View.VISIBLE);
+ }
+
+ // 计算并设置年龄标签
+ if (!TextUtils.isEmpty(birthday)) {
+ int age = calculateAge(birthday);
+ if (age > 0) {
+ binding.tagAge.setText(age + "岁");
+ binding.tagAge.setVisibility(View.VISIBLE);
+ } else {
+ binding.tagAge.setVisibility(View.GONE);
+ }
+ } else {
+ binding.tagAge.setVisibility(View.GONE);
+ }
+
+ // 计算并设置星座标签
+ if (!TextUtils.isEmpty(birthday)) {
+ String constellation = calculateConstellation(birthday);
+ if (!TextUtils.isEmpty(constellation)) {
+ binding.tagConstellation.setText(constellation);
+ binding.tagConstellation.setVisibility(View.VISIBLE);
+ } else {
+ binding.tagConstellation.setVisibility(View.GONE);
+ }
+ } else {
+ binding.tagConstellation.setVisibility(View.GONE);
+ }
+ }
+
+ private void loadProfileInfo() {
+ String name = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_NAME, "爱你");
+ String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, "广西");
+ String bio = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_BIO, null);
+
+ if (binding.profileInfoLine1 != null) {
+ binding.profileInfoLine1.setText("昵称:" + name);
+ }
+
+ if (binding.profileInfoLine2 != null) {
+ String locationText = !TextUtils.isEmpty(location) ? location : "广西";
+ binding.profileInfoLine2.setText("地区:" + locationText);
+ }
+
+ if (binding.profileInfoLine3 != null) {
+ String bioText = (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) ? bio : "填写个人签名更容易获得关注";
+ binding.profileInfoLine3.setText("签名:" + bioText);
+ }
+ }
+
+ private int calculateAge(String birthdayStr) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+ Date birthDate = sdf.parse(birthdayStr);
+ if (birthDate == null) return 0;
+
+ Calendar birth = Calendar.getInstance();
+ birth.setTime(birthDate);
+ Calendar now = Calendar.getInstance();
+
+ int age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR);
+ if (now.get(Calendar.DAY_OF_YEAR) < birth.get(Calendar.DAY_OF_YEAR)) {
+ age--;
+ }
+ return age;
+ } catch (ParseException e) {
+ return 0;
+ }
+ }
+
+ private String calculateConstellation(String birthdayStr) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+ Date birthDate = sdf.parse(birthdayStr);
+ if (birthDate == null) return "";
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(birthDate);
+ int month = cal.get(Calendar.MONTH) + 1; // Calendar.MONTH 从0开始
+ int day = cal.get(Calendar.DAY_OF_MONTH);
+
+ // 星座计算
+ if ((month == 3 && day >= 21) || (month == 4 && day <= 19)) {
+ return "白羊座";
+ } else if ((month == 4 && day >= 20) || (month == 5 && day <= 20)) {
+ return "金牛座";
+ } else if ((month == 5 && day >= 21) || (month == 6 && day <= 21)) {
+ return "双子座";
+ } else if ((month == 6 && day >= 22) || (month == 7 && day <= 22)) {
+ return "巨蟹座";
+ } else if ((month == 7 && day >= 23) || (month == 8 && day <= 22)) {
+ return "狮子座";
+ } else if ((month == 8 && day >= 23) || (month == 9 && day <= 22)) {
+ return "处女座";
+ } else if ((month == 9 && day >= 23) || (month == 10 && day <= 23)) {
+ return "天秤座";
+ } else if ((month == 10 && day >= 24) || (month == 11 && day <= 22)) {
+ return "天蝎座";
+ } else if ((month == 11 && day >= 23) || (month == 12 && day <= 21)) {
+ return "射手座";
+ } else if ((month == 12 && day >= 22) || (month == 1 && day <= 19)) {
+ return "摩羯座";
+ } else if ((month == 1 && day >= 20) || (month == 2 && day <= 18)) {
+ return "水瓶座";
+ } else if ((month == 2 && day >= 19) || (month == 3 && day <= 20)) {
+ return "双鱼座";
+ }
+ return "";
+ } catch (ParseException e) {
+ return "";
}
}
}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java b/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java
index 68e13fcc..0f750b92 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java
@@ -23,8 +23,17 @@ public class SearchActivity extends AppCompatActivity {
private final List all = new ArrayList<>();
+ private static final String EXTRA_SEARCH_QUERY = "search_query";
+
public static void start(Context context) {
+ start(context, null);
+ }
+
+ public static void start(Context context, String searchQuery) {
Intent intent = new Intent(context, SearchActivity.class);
+ if (searchQuery != null && !searchQuery.trim().isEmpty()) {
+ intent.putExtra(EXTRA_SEARCH_QUERY, searchQuery);
+ }
context.startActivity(intent);
}
@@ -40,6 +49,13 @@ public class SearchActivity extends AppCompatActivity {
binding.backButton.setOnClickListener(v -> finish());
binding.cancelBtn.setOnClickListener(v -> finish());
+ // 如果从Intent中获取到搜索关键词,自动填充到搜索框
+ String searchQuery = getIntent().getStringExtra(EXTRA_SEARCH_QUERY);
+ if (searchQuery != null && !searchQuery.trim().isEmpty()) {
+ binding.searchInput.setText(searchQuery);
+ binding.searchInput.setSelection(searchQuery.length());
+ }
+
binding.searchInput.requestFocus();
}
diff --git a/android-app/app/src/main/java/com/example/livestreaming/TabPlaceholderActivity.java b/android-app/app/src/main/java/com/example/livestreaming/TabPlaceholderActivity.java
index 7457f197..283f548b 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/TabPlaceholderActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/TabPlaceholderActivity.java
@@ -232,9 +232,8 @@ public class TabPlaceholderActivity extends AppCompatActivity {
Toast.makeText(this, "点击:" + user.getName(), Toast.LENGTH_SHORT).show();
});
- StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
- glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
- binding.nearbyRecyclerView.setLayoutManager(glm);
+ LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+ binding.nearbyRecyclerView.setLayoutManager(layoutManager);
binding.nearbyRecyclerView.setAdapter(adapter);
adapter.submitList(buildNearbyDemoUsers(18));
@@ -254,10 +253,12 @@ public class TabPlaceholderActivity extends AppCompatActivity {
private List buildNearbyDemoUsers(int count) {
List list = new ArrayList<>();
+ String[] names = {"小王", "小李", "安安", "小陈", "小美", "老张", "小七", "阿杰",
+ "小雨", "阿宁", "小星", "小林", "小杨", "小刘", "小赵", "小孙", "小周", "小吴"};
for (int i = 0; i < count; i++) {
- String id = "nearby-" + i;
- String name = "附近主播" + (i + 1);
- boolean live = i % 3 == 0;
+ String id = "user-" + i;
+ String name = i < names.length ? names[i] : "用户" + (i + 1);
+ boolean live = false; // 不再显示直播状态
String distanceText;
if (i < 3) {
distanceText = (300 + i * 120) + "m";
@@ -491,9 +492,7 @@ public class TabPlaceholderActivity extends AppCompatActivity {
Toast.makeText(this, "已发送好友请求:" + user.getName(), Toast.LENGTH_SHORT).show();
});
- StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
- glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
- binding.addFriendRecyclerView.setLayoutManager(glm);
+ binding.addFriendRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.addFriendRecyclerView.setAdapter(addFriendAdapter);
addFriendAllUsers.clear();
diff --git a/android-app/app/src/main/java/com/example/livestreaming/TableGamesActivity.java b/android-app/app/src/main/java/com/example/livestreaming/TableGamesActivity.java
new file mode 100644
index 00000000..38123d51
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/TableGamesActivity.java
@@ -0,0 +1,61 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityTableGamesBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class TableGamesActivity extends AppCompatActivity {
+
+ private ActivityTableGamesBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, TableGamesActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityTableGamesBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/UnreadMessageManager.java b/android-app/app/src/main/java/com/example/livestreaming/UnreadMessageManager.java
new file mode 100644
index 00000000..f86d9d52
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/UnreadMessageManager.java
@@ -0,0 +1,74 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.google.android.material.badge.BadgeDrawable;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class UnreadMessageManager {
+
+ private static final String PREFS_NAME = "unread_messages";
+ private static final String KEY_UNREAD_COUNT = "unread_count";
+
+ /**
+ * 获取总未读消息数量
+ */
+ public static int getUnreadCount(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getInt(KEY_UNREAD_COUNT, 0);
+ }
+
+ /**
+ * 设置总未读消息数量
+ */
+ public static void setUnreadCount(Context context, int count) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putInt(KEY_UNREAD_COUNT, Math.max(0, count)).apply();
+ }
+
+ /**
+ * 增加未读消息数量
+ */
+ public static void incrementUnreadCount(Context context, int increment) {
+ int current = getUnreadCount(context);
+ setUnreadCount(context, current + increment);
+ }
+
+ /**
+ * 减少未读消息数量
+ */
+ public static void decrementUnreadCount(Context context, int decrement) {
+ int current = getUnreadCount(context);
+ setUnreadCount(context, Math.max(0, current - decrement));
+ }
+
+ /**
+ * 更新底部导航栏的消息徽章
+ */
+ public static void updateBadge(BottomNavigationView bottomNav) {
+ if (bottomNav == null) return;
+
+ Context context = bottomNav.getContext();
+ int unreadCount = getUnreadCount(context);
+
+ BadgeDrawable badge = bottomNav.getOrCreateBadge(R.id.nav_messages);
+
+ if (unreadCount > 0) {
+ badge.setVisible(true);
+ badge.setNumber(unreadCount);
+ // BadgeDrawable 会自动处理超过 99 的情况,显示 "99+"
+ } else {
+ badge.setVisible(false);
+ badge.clearNumber();
+ }
+ }
+
+ /**
+ * 清除未读消息数量(当用户进入消息页面时)
+ */
+ public static void clearUnreadCount(Context context) {
+ setUnreadCount(context, 0);
+ }
+}
+
diff --git a/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java b/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java
index 5b8c8553..372cd49f 100644
--- a/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java
+++ b/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java
@@ -58,6 +58,13 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
binding.bio.setText(bio != null ? bio : "");
binding.avatar.setImageResource(avatarRes);
+ // 设置头像点击放大功能
+ binding.avatar.setOnClickListener(v -> {
+ AvatarViewerDialog dialog = AvatarViewerDialog.create(this);
+ dialog.setAvatarResId(avatarRes);
+ dialog.show();
+ });
+
setupTabsAndWorks();
bindDemoStatsAndWorks(userId);
@@ -86,8 +93,8 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
if (binding.tabs.getTabCount() == 0) {
binding.tabs.addTab(binding.tabs.newTab().setText("作品"));
- binding.tabs.addTab(binding.tabs.newTab().setText("动态"));
- binding.tabs.addTab(binding.tabs.newTab().setText("资料"));
+ binding.tabs.addTab(binding.tabs.newTab().setText("收藏"));
+ binding.tabs.addTab(binding.tabs.newTab().setText("赞过"));
}
if (worksAdapter == null) {
@@ -119,9 +126,10 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
private void showTab(int index) {
if (binding == null) return;
+ // 标签页顺序:0-作品, 1-收藏, 2-赞过
binding.worksRecycler.setVisibility(index == 0 ? android.view.View.VISIBLE : android.view.View.GONE);
- binding.dynamicsPlaceholder.setVisibility(index == 1 ? android.view.View.VISIBLE : android.view.View.GONE);
- binding.profilePlaceholder.setVisibility(index == 2 ? android.view.View.VISIBLE : android.view.View.GONE);
+ binding.favoritesPlaceholder.setVisibility(index == 1 ? android.view.View.VISIBLE : android.view.View.GONE);
+ binding.likedPlaceholder.setVisibility(index == 2 ? android.view.View.VISIBLE : android.view.View.GONE);
}
private void bindDemoStatsAndWorks(String userId) {
diff --git a/android-app/app/src/main/java/com/example/livestreaming/VoiceMatchActivity.java b/android-app/app/src/main/java/com/example/livestreaming/VoiceMatchActivity.java
new file mode 100644
index 00000000..fb4ff2f8
--- /dev/null
+++ b/android-app/app/src/main/java/com/example/livestreaming/VoiceMatchActivity.java
@@ -0,0 +1,62 @@
+package com.example.livestreaming;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.livestreaming.databinding.ActivityVoiceMatchBinding;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+
+public class VoiceMatchActivity extends AppCompatActivity {
+
+ private ActivityVoiceMatchBinding binding;
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, VoiceMatchActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityVoiceMatchBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ setupUI();
+ }
+
+ private void setupUI() {
+ binding.backButton.setOnClickListener(v -> finish());
+
+ BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
+ bottomNavigation.setSelectedItemId(R.id.nav_friends);
+ bottomNavigation.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_wish_tree) {
+ WishTreeActivity.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;
+ });
+ }
+}
+
diff --git a/android-app/app/src/main/res/drawable/ic_globe_24.xml b/android-app/app/src/main/res/drawable/ic_globe_24.xml
index 8eb416ed..0e696bda 100644
--- a/android-app/app/src/main/res/drawable/ic_globe_24.xml
+++ b/android-app/app/src/main/res/drawable/ic_globe_24.xml
@@ -6,7 +6,7 @@
android:viewportHeight="24">
diff --git a/android-app/app/src/main/res/layout/activity_draw_guess.xml b/android-app/app/src/main/res/layout/activity_draw_guess.xml
new file mode 100644
index 00000000..ae501c1e
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_draw_guess.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_edit_profile.xml b/android-app/app/src/main/res/layout/activity_edit_profile.xml
index e0957dee..33a45faf 100644
--- a/android-app/app/src/main/res/layout/activity_edit_profile.xml
+++ b/android-app/app/src/main/res/layout/activity_edit_profile.xml
@@ -186,7 +186,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:hint="生日(例如 2001-08-08)"
+ android:hint="生日"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
@@ -202,7 +202,13 @@
android:id="@+id/inputBirthday"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:maxLines="1" />
+ android:maxLines="1"
+ android:focusable="false"
+ android:focusableInTouchMode="false"
+ android:clickable="true"
+ android:cursorVisible="false"
+ android:enabled="true"
+ android:inputType="none" />
@@ -211,7 +217,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:hint="性别(男/女/保密)"
+ android:hint="性别"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
@@ -227,7 +233,10 @@
android:id="@+id/inputGender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:maxLines="1" />
+ android:maxLines="1"
+ android:focusable="false"
+ android:clickable="true"
+ android:cursorVisible="false" />
@@ -236,7 +245,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
- android:hint="所在地(例如 北京·朝阳)"
+ android:hint="所在地"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
@@ -252,7 +261,10 @@
android:id="@+id/inputLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:maxLines="1" />
+ android:maxLines="1"
+ android:focusable="false"
+ android:clickable="true"
+ android:cursorVisible="false" />
diff --git a/android-app/app/src/main/res/layout/activity_find_game.xml b/android-app/app/src/main/res/layout/activity_find_game.xml
new file mode 100644
index 00000000..40fbe118
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_find_game.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_fish_pond.xml b/android-app/app/src/main/res/layout/activity_fish_pond.xml
index ca931c8e..841d718c 100644
--- a/android-app/app/src/main/res/layout/activity_fish_pond.xml
+++ b/android-app/app/src/main/res/layout/activity_fish_pond.xml
@@ -4,7 +4,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_fishpond_gradient"
- android:paddingTop="18dp">
+ android:paddingTop="18dp"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
@@ -472,7 +476,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/orbitContainer"
- app:layout_constraintBottom_toTopOf="@id/bottomAppBar">
+ app:layout_constraintBottom_toTopOf="@id/bottomNavInclude">
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_ktv_together.xml b/android-app/app/src/main/res/layout/activity_ktv_together.xml
new file mode 100644
index 00000000..e71a3274
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_ktv_together.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_main.xml b/android-app/app/src/main/res/layout/activity_main.xml
index be266f43..fa8abccb 100644
--- a/android-app/app/src/main/res/layout/activity_main.xml
+++ b/android-app/app/src/main/res/layout/activity_main.xml
@@ -7,7 +7,8 @@
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="false">
@@ -61,17 +64,6 @@
android:text="附近" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_peace_elite.xml b/android-app/app/src/main/res/layout/activity_peace_elite.xml
new file mode 100644
index 00000000..50d1c44b
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_peace_elite.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_profile.xml b/android-app/app/src/main/res/layout/activity_profile.xml
index 9713e7b6..37e47456 100644
--- a/android-app/app/src/main/res/layout/activity_profile.xml
+++ b/android-app/app/src/main/res/layout/activity_profile.xml
@@ -167,13 +167,6 @@
android:src="@drawable/ic_copy_24"
android:tint="#E6FFFFFF" />
-
-
@@ -264,18 +257,6 @@
-
-
-
-
+ android:text="赞过" />
diff --git a/android-app/app/src/main/res/layout/activity_table_games.xml b/android-app/app/src/main/res/layout/activity_table_games.xml
new file mode 100644
index 00000000..29b06eee
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_table_games.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/activity_user_profile_read_only.xml b/android-app/app/src/main/res/layout/activity_user_profile_read_only.xml
index 85f2c408..7e6bdb72 100644
--- a/android-app/app/src/main/res/layout/activity_user_profile_read_only.xml
+++ b/android-app/app/src/main/res/layout/activity_user_profile_read_only.xml
@@ -18,20 +18,109 @@
android:paddingTop="14dp"
android:paddingBottom="16dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/name"
+ app:layout_constraintBottom_toBottomOf="@id/name" />
+
+
+ app:layout_constraintStart_toEndOf="@id/space_location_margin_start"
+ app:layout_constraintTop_toBottomOf="@id/space_location_margin_top" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/space_bio_margin_top" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/space_addFriend_margin_top" />
-
@@ -140,10 +288,9 @@
android:id="@+id/statsRow"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginTop="18dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/readOnlyHint">
+ app:layout_constraintTop_toBottomOf="@id/space_statsRow_margin_top">
+
+
+ app:layout_constraintTop_toBottomOf="@id/space_tabs_margin_top" />
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/space_likedPlaceholder_margin_top" />
diff --git a/android-app/app/src/main/res/layout/activity_voice_match.xml b/android-app/app/src/main/res/layout/activity_voice_match.xml
new file mode 100644
index 00000000..ed954b4b
--- /dev/null
+++ b/android-app/app/src/main/res/layout/activity_voice_match.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/bottom_sheet_date_picker.xml b/android-app/app/src/main/res/layout/bottom_sheet_date_picker.xml
new file mode 100644
index 00000000..66d13a05
--- /dev/null
+++ b/android-app/app/src/main/res/layout/bottom_sheet_date_picker.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/bottom_sheet_location_picker.xml b/android-app/app/src/main/res/layout/bottom_sheet_location_picker.xml
new file mode 100644
index 00000000..9e55f346
--- /dev/null
+++ b/android-app/app/src/main/res/layout/bottom_sheet_location_picker.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/bottom_sheet_location_picker_spinner.xml b/android-app/app/src/main/res/layout/bottom_sheet_location_picker_spinner.xml
new file mode 100644
index 00000000..5ec00079
--- /dev/null
+++ b/android-app/app/src/main/res/layout/bottom_sheet_location_picker_spinner.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/dialog_avatar_viewer.xml b/android-app/app/src/main/res/layout/dialog_avatar_viewer.xml
new file mode 100644
index 00000000..663baae4
--- /dev/null
+++ b/android-app/app/src/main/res/layout/dialog_avatar_viewer.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/android-app/app/src/main/res/layout/include_bottom_nav.xml b/android-app/app/src/main/res/layout/include_bottom_nav.xml
index da3ef4e5..38d9501f 100644
--- a/android-app/app/src/main/res/layout/include_bottom_nav.xml
+++ b/android-app/app/src/main/res/layout/include_bottom_nav.xml
@@ -1,26 +1,20 @@
-
-
-
-
-
+ android:layout_height="48dp"
+ android:minHeight="48dp"
+ android:background="@android:color/white"
+ android:paddingTop="2dp"
+ android:paddingBottom="2dp"
+ android:fitsSystemWindows="false"
+ android:elevation="8dp"
+ app:itemIconTint="@color/bottom_nav_item_color"
+ app:itemTextColor="@color/bottom_nav_item_color"
+ app:itemIconSize="22dp"
+ app:itemTextAppearanceActive="@style/BottomNavigationView.TextAppearance"
+ app:itemTextAppearanceInactive="@style/BottomNavigationView.TextAppearance"
+ app:labelVisibilityMode="labeled"
+ app:menu="@menu/bottom_nav_main" />
diff --git a/android-app/app/src/main/res/layout/item_conversation.xml b/android-app/app/src/main/res/layout/item_conversation.xml
index 623c30a0..7de6a357 100644
--- a/android-app/app/src/main/res/layout/item_conversation.xml
+++ b/android-app/app/src/main/res/layout/item_conversation.xml
@@ -11,8 +11,9 @@
android:id="@+id/avatar"
android:layout_width="44dp"
android:layout_height="44dp"
- android:background="@drawable/bg_avatar_circle"
- android:padding="6dp"
+ android:background="@drawable/bg_avatar_circle_transparent"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/android-app/app/src/main/res/layout/item_conversation_message_incoming.xml b/android-app/app/src/main/res/layout/item_conversation_message_incoming.xml
index 56153e6a..6e97d6f0 100644
--- a/android-app/app/src/main/res/layout/item_conversation_message_incoming.xml
+++ b/android-app/app/src/main/res/layout/item_conversation_message_incoming.xml
@@ -19,8 +19,9 @@
android:id="@+id/avatarView"
android:layout_width="34dp"
android:layout_height="34dp"
- android:background="@drawable/bg_avatar_circle"
- android:padding="5dp"
+ android:background="@drawable/bg_avatar_circle_transparent"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/android-app/app/src/main/res/layout/item_conversation_message_outgoing.xml b/android-app/app/src/main/res/layout/item_conversation_message_outgoing.xml
index 5e8a35cc..21031661 100644
--- a/android-app/app/src/main/res/layout/item_conversation_message_outgoing.xml
+++ b/android-app/app/src/main/res/layout/item_conversation_message_outgoing.xml
@@ -19,8 +19,9 @@
android:id="@+id/avatarView"
android:layout_width="34dp"
android:layout_height="34dp"
- android:background="@drawable/bg_avatar_circle"
- android:padding="5dp"
+ android:background="@drawable/bg_avatar_circle_transparent"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
android:src="@drawable/ic_account_circle_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/android-app/app/src/main/res/layout/item_nearby_user.xml b/android-app/app/src/main/res/layout/item_nearby_user.xml
index 1723e6a5..c6d0bd9a 100644
--- a/android-app/app/src/main/res/layout/item_nearby_user.xml
+++ b/android-app/app/src/main/res/layout/item_nearby_user.xml
@@ -1,78 +1,78 @@
-
+ android:layout_height="72dp"
+ android:background="@android:color/white"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
-
+
+
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:text="用户昵称"
+ android:textColor="#111111"
+ android:textSize="15sp"
+ android:textStyle="bold"
+ app:layout_constraintEnd_toStartOf="@id/addButton"
+ app:layout_constraintStart_toEndOf="@id/avatarImage"
+ app:layout_constraintTop_toTopOf="@id/avatarImage" />
-
+
-
+
-
+
-
-
-
-
-
+
diff --git a/android-app/app/src/main/res/values/themes.xml b/android-app/app/src/main/res/values/themes.xml
index 1204dd61..18c0168f 100644
--- a/android-app/app/src/main/res/values/themes.xml
+++ b/android-app/app/src/main/res/values/themes.xml
@@ -34,4 +34,9 @@
- 50%
+
+
+