diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml index 2fd4aef6..c81d5bfe 100644 --- a/android-app/app/src/main/AndroidManifest.xml +++ b/android-app/app/src/main/AndroidManifest.xml @@ -27,6 +27,14 @@ android:name="com.example.livestreaming.TabPlaceholderActivity" android:exported="false" /> + + + + diff --git a/android-app/app/src/main/java/com/example/livestreaming/BadgeItem.java b/android-app/app/src/main/java/com/example/livestreaming/BadgeItem.java new file mode 100644 index 00000000..2372c8c1 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/BadgeItem.java @@ -0,0 +1,44 @@ +package com.example.livestreaming; + +public class BadgeItem { + + private final String id; + private final String name; + private final String desc; + private final int iconRes; + private final boolean achieved; + private final boolean locked; + + public BadgeItem(String id, String name, String desc, int iconRes, boolean achieved, boolean locked) { + this.id = id; + this.name = name; + this.desc = desc; + this.iconRes = iconRes; + this.achieved = achieved; + this.locked = locked; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + public int getIconRes() { + return iconRes; + } + + public boolean isAchieved() { + return achieved; + } + + public boolean isLocked() { + return locked; + } +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/BadgesAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/BadgesAdapter.java new file mode 100644 index 00000000..43c1d3ee --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/BadgesAdapter.java @@ -0,0 +1,101 @@ +package com.example.livestreaming; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.livestreaming.databinding.ItemBadgeBinding; + +public class BadgesAdapter extends ListAdapter { + + public interface OnBadgeClickListener { + void onClick(BadgeItem item); + } + + private final OnBadgeClickListener onClick; + + public BadgesAdapter(OnBadgeClickListener onClick) { + super(DIFF); + this.onClick = onClick; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ItemBadgeBinding b = ItemBadgeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new VH(b, onClick); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + holder.bind(getItem(position)); + } + + static class VH extends RecyclerView.ViewHolder { + private final ItemBadgeBinding binding; + private final OnBadgeClickListener onClick; + + VH(ItemBadgeBinding binding, OnBadgeClickListener onClick) { + super(binding.getRoot()); + this.binding = binding; + this.onClick = onClick; + } + + void bind(BadgeItem item) { + if (item == null) return; + + binding.name.setText(item.getName() != null ? item.getName() : ""); + binding.icon.setImageResource(item.getIconRes()); + + if (item.isLocked()) { + binding.lockTag.setVisibility(View.VISIBLE); + binding.status.setText("未解锁"); + binding.status.setTextColor(0xFF888888); + binding.icon.setAlpha(0.35f); + } else if (item.isAchieved()) { + binding.lockTag.setVisibility(View.GONE); + binding.status.setText("已获得"); + binding.status.setTextColor(0xFF5B2CFF); + binding.icon.setAlpha(1.0f); + } else { + binding.lockTag.setVisibility(View.GONE); + binding.status.setText("可获取"); + binding.status.setTextColor(0xFF666666); + binding.icon.setAlpha(0.7f); + } + + binding.getRoot().setOnClickListener(v -> { + if (onClick != null) onClick.onClick(item); + }); + } + } + + private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull BadgeItem oldItem, @NonNull BadgeItem newItem) { + String o = oldItem.getId(); + String n = newItem.getId(); + return o != null && o.equals(n); + } + + @Override + public boolean areContentsTheSame(@NonNull BadgeItem oldItem, @NonNull BadgeItem newItem) { + return oldItem.getId().equals(newItem.getId()) + && safeEq(oldItem.getName(), newItem.getName()) + && safeEq(oldItem.getDesc(), newItem.getDesc()) + && oldItem.getIconRes() == newItem.getIconRes() + && oldItem.isAchieved() == newItem.isAchieved() + && oldItem.isLocked() == newItem.isLocked(); + } + + private boolean safeEq(String a, String b) { + if (a == null) return b == null; + return a.equals(b); + } + }; +} 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 2695f37d..cbfc00e1 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 @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.bumptech.glide.Glide; import com.example.livestreaming.databinding.ActivityMainBinding; import com.example.livestreaming.databinding.DialogCreateRoomBinding; import com.google.android.material.bottomnavigation.BottomNavigationView; @@ -61,6 +62,7 @@ public class MainActivity extends AppCompatActivity { // 立即显示缓存数据,提升启动速度 setupRecyclerView(); setupUI(); + loadAvatarFromPrefs(); // 异步加载资源文件,避免阻塞主线程 loadCoverAssetsAsync(); @@ -88,6 +90,11 @@ public class MainActivity extends AppCompatActivity { private void setupUI() { binding.swipeRefresh.setOnRefreshListener(this::fetchRooms); + binding.avatarButton.setOnClickListener(v -> { + ProfileActivity.start(this); + finish(); + }); + binding.topTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { @@ -190,12 +197,36 @@ public class MainActivity extends AppCompatActivity { }); } + private void loadAvatarFromPrefs() { + try { + String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE) + .getString("profile_avatar_uri", null); + if (!TextUtils.isEmpty(avatarUri)) { + Glide.with(this).load(avatarUri).into(binding.avatarButton); + return; + } + + int avatarRes = getSharedPreferences("profile_prefs", MODE_PRIVATE) + .getInt("profile_avatar_res", 0); + if (avatarRes != 0) { + binding.avatarButton.setImageResource(avatarRes); + } else { + binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24); + } + } catch (Exception ignored) { + if (binding != null) { + binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24); + } + } + } + @Override protected void onResume() { super.onResume(); if (binding != null) { BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation; bottomNavigation.setSelectedItemId(R.id.nav_home); + loadAvatarFromPrefs(); } } diff --git a/android-app/app/src/main/java/com/example/livestreaming/MoreAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/MoreAdapter.java new file mode 100644 index 00000000..f96ac948 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/MoreAdapter.java @@ -0,0 +1,104 @@ +package com.example.livestreaming; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.livestreaming.databinding.ItemMoreRowBinding; +import com.example.livestreaming.databinding.ItemMoreSectionBinding; + +import java.util.ArrayList; +import java.util.List; + +public class MoreAdapter extends RecyclerView.Adapter { + + public interface OnRowClickListener { + void onClick(MoreItem item); + } + + private static final int VT_SECTION = 1; + private static final int VT_ROW = 2; + + private final List items = new ArrayList<>(); + private final OnRowClickListener onRowClick; + + public MoreAdapter(OnRowClickListener onRowClick) { + this.onRowClick = onRowClick; + } + + public void submitList(List list) { + items.clear(); + if (list != null) items.addAll(list); + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + MoreItem item = items.get(position); + if (item == null) return VT_ROW; + return item.getType() == MoreItem.Type.SECTION ? VT_SECTION : VT_ROW; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == VT_SECTION) { + ItemMoreSectionBinding b = ItemMoreSectionBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new SectionVH(b); + } + ItemMoreRowBinding b = ItemMoreRowBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new RowVH(b, onRowClick); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + MoreItem item = items.get(position); + if (holder instanceof SectionVH) { + ((SectionVH) holder).bind(item); + } else if (holder instanceof RowVH) { + ((RowVH) holder).bind(item); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + static class SectionVH extends RecyclerView.ViewHolder { + private final ItemMoreSectionBinding binding; + + SectionVH(ItemMoreSectionBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + void bind(MoreItem item) { + binding.title.setText(item != null && item.getTitle() != null ? item.getTitle() : ""); + } + } + + static class RowVH extends RecyclerView.ViewHolder { + private final ItemMoreRowBinding binding; + private final OnRowClickListener onRowClick; + + RowVH(ItemMoreRowBinding binding, OnRowClickListener onRowClick) { + super(binding.getRoot()); + this.binding = binding; + this.onRowClick = onRowClick; + } + + void bind(MoreItem item) { + if (item == null) return; + binding.title.setText(item.getTitle() != null ? item.getTitle() : ""); + binding.subtitle.setText(item.getSubtitle() != null ? item.getSubtitle() : ""); + if (item.getIconRes() != 0) binding.icon.setImageResource(item.getIconRes()); + + binding.getRoot().setOnClickListener(v -> { + if (onRowClick != null) onRowClick.onClick(item); + }); + } + } +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/MoreItem.java b/android-app/app/src/main/java/com/example/livestreaming/MoreItem.java new file mode 100644 index 00000000..d61e16aa --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/MoreItem.java @@ -0,0 +1,46 @@ +package com.example.livestreaming; + +public class MoreItem { + + public enum Type { + SECTION, + ROW + } + + private final Type type; + + private final String title; + private final String subtitle; + private final int iconRes; + + public static MoreItem section(String title) { + return new MoreItem(Type.SECTION, title, null, 0); + } + + public static MoreItem row(String title, String subtitle, int iconRes) { + return new MoreItem(Type.ROW, title, subtitle, iconRes); + } + + private MoreItem(Type type, String title, String subtitle, int iconRes) { + this.type = type; + this.title = title; + this.subtitle = subtitle; + this.iconRes = iconRes; + } + + public Type getType() { + return type; + } + + public String getTitle() { + return title; + } + + public String getSubtitle() { + return subtitle; + } + + public int getIconRes() { + return iconRes; + } +} 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 dbabf52b..5f5549af 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 @@ -2,8 +2,11 @@ package com.example.livestreaming; import android.content.Context; import android.content.Intent; +import android.content.ClipData; +import android.content.ClipboardManager; import android.os.Bundle; import android.text.TextUtils; +import android.view.View; import android.widget.EditText; import android.widget.Toast; @@ -44,6 +47,7 @@ public class ProfileActivity extends AppCompatActivity { loadProfileFromPrefs(); setupEditableAreas(); setupNavigationClicks(); + setupProfileTabs(); BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation; bottomNavigation.setSelectedItemId(R.id.nav_profile); @@ -117,10 +121,22 @@ public class ProfileActivity extends AppCompatActivity { } private void setupNavigationClicks() { - binding.topActionSearch.setOnClickListener(v -> TabPlaceholderActivity.start(this, "搜索")); - binding.topActionClock.setOnClickListener(v -> TabPlaceholderActivity.start(this, "定位/发现")); + binding.topActionSearch.setOnClickListener(v -> TabPlaceholderActivity.start(this, "定位/发现")); + binding.topActionClock.setOnClickListener(v -> WatchHistoryActivity.start(this)); binding.topActionMore.setOnClickListener(v -> TabPlaceholderActivity.start(this, "更多")); + binding.copyIdBtn.setOnClickListener(v -> { + String idText = binding.idLine.getText() != null ? binding.idLine.getText().toString() : ""; + if (TextUtils.isEmpty(idText)) return; + String digits = idText.replaceAll("\\D+", ""); + if (TextUtils.isEmpty(digits)) return; + ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + if (cm != null) { + cm.setPrimaryClip(ClipData.newPlainText("id", digits)); + Toast.makeText(this, "已复制:" + digits, Toast.LENGTH_SHORT).show(); + } + }); + binding.following.setOnClickListener(v -> FollowingListActivity.start(this)); binding.followers.setOnClickListener(v -> FansListActivity.start(this)); binding.likes.setOnClickListener(v -> LikesListActivity.start(this)); @@ -136,6 +152,40 @@ public class ProfileActivity extends AppCompatActivity { binding.addFriendBtn.setOnClickListener(v -> TabPlaceholderActivity.start(this, "加好友")); } + private void setupProfileTabs() { + showTab(0); + + binding.profileTabs.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) { + if (tab == null) return; + showTab(tab.getPosition()); + } + + @Override + public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) { + } + + @Override + public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) { + if (tab == null) return; + showTab(tab.getPosition()); + } + }); + + 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)); + } + + private void showTab(int index) { + 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); + } + private interface OnTextSaved { void onSaved(String text); } 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 new file mode 100644 index 00000000..68e13fcc --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java @@ -0,0 +1,112 @@ +package com.example.livestreaming; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +import com.example.livestreaming.databinding.ActivitySearchBinding; +import com.example.livestreaming.net.Room; + +import java.util.ArrayList; +import java.util.List; + +public class SearchActivity extends AppCompatActivity { + + private ActivitySearchBinding binding; + private RoomsAdapter adapter; + + private final List all = new ArrayList<>(); + + public static void start(Context context) { + Intent intent = new Intent(context, SearchActivity.class); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivitySearchBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + setupList(); + setupInput(); + + binding.backButton.setOnClickListener(v -> finish()); + binding.cancelBtn.setOnClickListener(v -> finish()); + + binding.searchInput.requestFocus(); + } + + private void setupList() { + adapter = new RoomsAdapter(room -> { + if (room == null) return; + Intent intent = new Intent(SearchActivity.this, RoomDetailActivity.class); + intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId()); + startActivity(intent); + }); + + StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); + glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); + binding.resultsRecyclerView.setLayoutManager(glm); + binding.resultsRecyclerView.setAdapter(adapter); + + all.clear(); + all.addAll(buildDemoRooms(24)); + adapter.submitList(new ArrayList<>(all)); + } + + private void setupInput() { + binding.searchInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH); + binding.searchInput.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) { + applyFilter(s != null ? s.toString() : ""); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + private void applyFilter(String q) { + String query = q != null ? q.trim() : ""; + if (query.isEmpty()) { + adapter.submitList(new ArrayList<>(all)); + return; + } + + List filtered = new ArrayList<>(); + for (Room r : all) { + if (r == null) continue; + String title = r.getTitle() != null ? r.getTitle() : ""; + String streamer = r.getStreamerName() != null ? r.getStreamerName() : ""; + if (title.contains(query) || streamer.contains(query)) { + filtered.add(r); + } + } + adapter.submitList(filtered); + } + + private List buildDemoRooms(int count) { + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String id = "search-" + i; + String title = "热门直播间 " + (i + 1); + String streamer = "主播" + (i + 1); + boolean live = i % 5 != 0; + list.add(new Room(id, title, streamer, live)); + } + return list; + } +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/SearchSuggestionsAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/SearchSuggestionsAdapter.java new file mode 100644 index 00000000..911068b1 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/SearchSuggestionsAdapter.java @@ -0,0 +1,79 @@ +package com.example.livestreaming; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.livestreaming.databinding.ItemSearchSuggestionBinding; +import com.example.livestreaming.net.Room; + +public class SearchSuggestionsAdapter extends ListAdapter { + + public interface OnSuggestionClickListener { + void onClick(Room room); + } + + private final OnSuggestionClickListener onClick; + + public SearchSuggestionsAdapter(OnSuggestionClickListener onClick) { + super(DIFF); + this.onClick = onClick; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ItemSearchSuggestionBinding b = ItemSearchSuggestionBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new VH(b, onClick); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + holder.bind(getItem(position)); + } + + static class VH extends RecyclerView.ViewHolder { + private final ItemSearchSuggestionBinding binding; + private final OnSuggestionClickListener onClick; + + VH(ItemSearchSuggestionBinding binding, OnSuggestionClickListener onClick) { + super(binding.getRoot()); + this.binding = binding; + this.onClick = onClick; + } + + void bind(Room room) { + String title = room != null && room.getTitle() != null ? room.getTitle() : ""; + String sub = room != null && room.getStreamerName() != null ? room.getStreamerName() : ""; + binding.title.setText(title); + binding.subtitle.setText(sub); + + boolean live = room != null && room.isLive(); + binding.badge.setVisibility(live ? View.VISIBLE : View.GONE); + + binding.getRoot().setOnClickListener(v -> { + if (room == null) return; + if (onClick != null) onClick.onClick(room); + }); + } + } + + private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull Room oldItem, @NonNull Room newItem) { + String o = oldItem.getId(); + String n = newItem.getId(); + return o != null && o.equals(n); + } + + @Override + public boolean areContentsTheSame(@NonNull Room oldItem, @NonNull Room newItem) { + return oldItem.equals(newItem); + } + }; +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java b/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java new file mode 100644 index 00000000..5addcd72 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java @@ -0,0 +1,133 @@ +package com.example.livestreaming; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.livestreaming.databinding.ActivitySettingsPageBinding; + +import java.util.ArrayList; +import java.util.List; + +public class SettingsPageActivity extends AppCompatActivity { + + public static final String EXTRA_PAGE = "extra_page"; + + public static final String PAGE_ACCOUNT_SECURITY = "account_security"; + public static final String PAGE_PRIVACY = "privacy"; + public static final String PAGE_NOTIFICATIONS = "notifications"; + public static final String PAGE_CLEAR_CACHE = "clear_cache"; + public static final String PAGE_HELP = "help"; + public static final String PAGE_ABOUT = "about"; + + private ActivitySettingsPageBinding binding; + + public static void start(Context context, String page) { + Intent intent = new Intent(context, SettingsPageActivity.class); + intent.putExtra(EXTRA_PAGE, page); + context.startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivitySettingsPageBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + binding.backButton.setOnClickListener(v -> finish()); + + String page = getIntent() != null ? getIntent().getStringExtra(EXTRA_PAGE) : null; + if (page == null) page = ""; + + String title = resolveTitle(page); + binding.titleText.setText(title); + + MoreAdapter adapter = new MoreAdapter(item -> { + if (item == null) return; + if (item.getType() != MoreItem.Type.ROW) return; + String t = item.getTitle() != null ? item.getTitle() : ""; + Toast.makeText(this, "点击:" + t, Toast.LENGTH_SHORT).show(); + }); + + binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); + binding.recyclerView.setAdapter(adapter); + + adapter.submitList(buildItems(page)); + } + + private String resolveTitle(String page) { + switch (page) { + case PAGE_ACCOUNT_SECURITY: + return "账号与安全"; + case PAGE_PRIVACY: + return "隐私"; + case PAGE_NOTIFICATIONS: + return "通知"; + case PAGE_CLEAR_CACHE: + return "清理缓存"; + case PAGE_HELP: + return "帮助与反馈"; + case PAGE_ABOUT: + return "关于"; + default: + return "设置"; + } + } + + private List buildItems(String page) { + List list = new ArrayList<>(); + + if (PAGE_ACCOUNT_SECURITY.equals(page)) { + list.add(MoreItem.section("登录与账号")); + list.add(MoreItem.row("修改密码", "设置登录密码", R.drawable.ic_person_24)); + list.add(MoreItem.row("绑定手机号", "用于登录与找回", R.drawable.ic_people_24)); + list.add(MoreItem.row("登录设备管理", "查看并管理已登录设备", R.drawable.ic_grid_24)); + return list; + } + + if (PAGE_PRIVACY.equals(page)) { + list.add(MoreItem.section("权限与安全")); + list.add(MoreItem.row("黑名单", "管理你屏蔽的用户", R.drawable.ic_people_24)); + list.add(MoreItem.row("权限管理", "相机、麦克风、定位等", R.drawable.ic_mic_24)); + list.add(MoreItem.row("隐私政策", "了解我们如何保护你的数据", R.drawable.ic_globe_24)); + return list; + } + + if (PAGE_NOTIFICATIONS.equals(page)) { + list.add(MoreItem.section("消息提醒")); + list.add(MoreItem.row("系统通知", "关注、评论、私信提醒", R.drawable.ic_notifications_24)); + list.add(MoreItem.row("免打扰", "设置勿扰时段", R.drawable.ic_notifications_24)); + return list; + } + + if (PAGE_CLEAR_CACHE.equals(page)) { + list.add(MoreItem.section("存储")); + list.add(MoreItem.row("缓存大小", "点击清理缓存(演示)", R.drawable.ic_grid_24)); + list.add(MoreItem.row("图片缓存", "清理封面/头像缓存", R.drawable.ic_palette_24)); + return list; + } + + if (PAGE_HELP.equals(page)) { + list.add(MoreItem.section("帮助")); + list.add(MoreItem.row("常见问题", "问题解答与使用指南", R.drawable.ic_chat_24)); + list.add(MoreItem.row("意见反馈", "提交你的建议与问题", R.drawable.ic_chat_24)); + list.add(MoreItem.row("联系客服", "在线客服(演示)", R.drawable.ic_chat_24)); + return list; + } + + if (PAGE_ABOUT.equals(page)) { + list.add(MoreItem.section("应用信息")); + list.add(MoreItem.row("版本", "Live Streaming 1.0", R.drawable.ic_menu_24)); + list.add(MoreItem.row("用户协议", "服务条款与规则", R.drawable.ic_menu_24)); + list.add(MoreItem.row("隐私政策", "隐私保护说明", R.drawable.ic_menu_24)); + return list; + } + + list.add(MoreItem.row("返回", "", R.drawable.ic_arrow_back_24)); + return list; + } +} 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 69d971c9..4f0d4754 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 @@ -3,10 +3,17 @@ package com.example.livestreaming; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import com.example.livestreaming.databinding.ActivityTabPlaceholderBinding; @@ -21,6 +28,16 @@ public class TabPlaceholderActivity extends AppCompatActivity { private ActivityTabPlaceholderBinding binding; + private SearchSuggestionsAdapter suggestionsAdapter; + private final List discoverAllRooms = new ArrayList<>(); + + private BadgesAdapter badgesAdapter; + + private MoreAdapter moreAdapter; + + private NearbyUsersAdapter addFriendAdapter; + private final List addFriendAllUsers = new ArrayList<>(); + public static void start(Context context, String title) { Intent intent = new Intent(context, TabPlaceholderActivity.class); intent.putExtra(EXTRA_TITLE, title); @@ -51,12 +68,138 @@ public class TabPlaceholderActivity extends AppCompatActivity { showNearbyUsers(); return; } + if ("发现".equals(title)) { + showDiscover(); + return; + } + if ("定位/发现".equals(title)) { + showLocationDiscover(); + return; + } + if ("加好友".equals(title)) { + showAddFriends(); + return; + } + if ("公园勋章".equals(title)) { + showParkBadges(); + return; + } + if ("更多".equals(title)) { + showMore(); + return; + } binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.GONE); + binding.genericPlaceholderContainer.setVisibility(View.VISIBLE); binding.followRecyclerView.setVisibility(View.GONE); binding.nearbyRecyclerView.setVisibility(View.GONE); } + private void showDiscover() { + binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.VISIBLE); + binding.parkBadgeContainer.setVisibility(View.GONE); + binding.moreContainer.setVisibility(View.GONE); + binding.locationDiscoverContainer.setVisibility(View.GONE); + binding.addFriendContainer.setVisibility(View.GONE); + binding.genericPlaceholderContainer.setVisibility(View.GONE); + binding.followRecyclerView.setVisibility(View.GONE); + binding.nearbyRecyclerView.setVisibility(View.GONE); + + ensureDiscoverSuggestions(); + binding.discoverSearchInput.requestFocus(); + showKeyboard(binding.discoverSearchInput); + + binding.discoverSearch.setOnClickListener(v -> { + binding.discoverSearchInput.requestFocus(); + showKeyboard(binding.discoverSearchInput); + }); + + binding.discoverSearchCancel.setOnClickListener(v -> { + binding.discoverSearchInput.setText(""); + binding.discoverSuggestionsRecyclerView.setVisibility(View.GONE); + hideKeyboard(binding.discoverSearchInput); + }); + + binding.discoverShortcutLive.setOnClickListener(v -> Toast.makeText(this, "进入直播模块", Toast.LENGTH_SHORT).show()); + binding.discoverShortcutNearby.setOnClickListener(v -> TabPlaceholderActivity.start(this, "附近")); + binding.discoverShortcutRank.setOnClickListener(v -> Toast.makeText(this, "榜单功能待接入", Toast.LENGTH_SHORT).show()); + binding.discoverShortcutTopic.setOnClickListener(v -> Toast.makeText(this, "话题功能待接入", Toast.LENGTH_SHORT).show()); + } + + private void ensureDiscoverSuggestions() { + if (suggestionsAdapter != null) return; + + suggestionsAdapter = new SearchSuggestionsAdapter(room -> { + if (room == null) return; + Intent intent = new Intent(TabPlaceholderActivity.this, RoomDetailActivity.class); + intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId()); + startActivity(intent); + }); + + binding.discoverSuggestionsRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + binding.discoverSuggestionsRecyclerView.setAdapter(suggestionsAdapter); + + discoverAllRooms.clear(); + discoverAllRooms.addAll(buildDiscoverDemoRooms(30)); + suggestionsAdapter.submitList(new ArrayList<>()); + + binding.discoverSearchInput.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) { + applyDiscoverFilter(s != null ? s.toString() : ""); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + private void applyDiscoverFilter(String q) { + String query = q != null ? q.trim() : ""; + if (TextUtils.isEmpty(query)) { + binding.discoverSuggestionsRecyclerView.setVisibility(View.GONE); + suggestionsAdapter.submitList(new ArrayList<>()); + return; + } + + List filtered = new ArrayList<>(); + for (Room r : discoverAllRooms) { + if (r == null) continue; + String title = r.getTitle() != null ? r.getTitle() : ""; + String streamer = r.getStreamerName() != null ? r.getStreamerName() : ""; + if (title.contains(query) || streamer.contains(query)) { + filtered.add(r); + if (filtered.size() >= 8) break; + } + } + + binding.discoverSuggestionsRecyclerView.setVisibility(filtered.isEmpty() ? View.GONE : View.VISIBLE); + suggestionsAdapter.submitList(filtered); + } + + private void showKeyboard(View target) { + try { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) imm.showSoftInput(target, InputMethodManager.SHOW_IMPLICIT); + } catch (Exception ignored) { + } + } + + private void hideKeyboard(View target) { + try { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) imm.hideSoftInputFromWindow(target.getWindowToken(), 0); + } catch (Exception ignored) { + } + } + private void showFollowRooms() { binding.genericScroll.setVisibility(View.GONE); binding.followRecyclerView.setVisibility(View.VISIBLE); @@ -124,4 +267,249 @@ public class TabPlaceholderActivity extends AppCompatActivity { } return list; } + + private List buildDiscoverDemoRooms(int count) { + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String id = "discover-" + i; + String title = "推荐直播间 " + (i + 1); + String streamer = "推荐主播" + (i + 1); + boolean live = i % 4 != 0; + list.add(new Room(id, title, streamer, live)); + } + return list; + } + + private void showParkBadges() { + binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.GONE); + binding.parkBadgeContainer.setVisibility(View.VISIBLE); + binding.moreContainer.setVisibility(View.GONE); + binding.locationDiscoverContainer.setVisibility(View.GONE); + binding.addFriendContainer.setVisibility(View.GONE); + binding.genericPlaceholderContainer.setVisibility(View.GONE); + binding.followRecyclerView.setVisibility(View.GONE); + binding.nearbyRecyclerView.setVisibility(View.GONE); + + ensureParkBadges(); + } + + private void ensureParkBadges() { + if (badgesAdapter != null) return; + + badgesAdapter = new BadgesAdapter(item -> { + if (item == null) return; + String name = item.getName() != null ? item.getName() : ""; + String desc = item.getDesc() != null ? item.getDesc() : ""; + if (item.isLocked()) { + Toast.makeText(this, "未解锁:" + name, Toast.LENGTH_SHORT).show(); + } else if (item.isAchieved()) { + Toast.makeText(this, "已获得:" + name + "\n" + desc, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "可获取:" + name + "\n" + desc, Toast.LENGTH_SHORT).show(); + } + }); + + binding.parkBadgeRecyclerView.setLayoutManager(new GridLayoutManager(this, 3)); + binding.parkBadgeRecyclerView.setAdapter(badgesAdapter); + + badgesAdapter.submitList(buildDemoBadges()); + } + + private List buildDemoBadges() { + List list = new ArrayList<>(); + list.add(new BadgeItem("b-1", "新人报道", "首次完善个人资料", R.drawable.ic_person_24, true, false)); + list.add(new BadgeItem("b-2", "热度新星", "累计获得100次点赞", R.drawable.ic_heart_24, false, false)); + list.add(new BadgeItem("b-3", "连续签到", "连续签到7天", R.drawable.ic_grid_24, false, true)); + list.add(new BadgeItem("b-4", "分享达人", "分享主页3次", R.drawable.ic_copy_24, false, false)); + list.add(new BadgeItem("b-5", "探索者", "进入发现页10次", R.drawable.ic_globe_24, true, false)); + list.add(new BadgeItem("b-6", "公园守护", "完成公园任务5次", R.drawable.ic_tree_24, false, true)); + list.add(new BadgeItem("b-7", "话题参与", "发布话题内容1次", R.drawable.ic_palette_24, false, false)); + list.add(new BadgeItem("b-8", "社交达人", "添加好友5人", R.drawable.ic_people_24, false, true)); + list.add(new BadgeItem("b-9", "开播尝鲜", "创建直播间1次", R.drawable.ic_mic_24, false, false)); + return list; + } + + private void showMore() { + binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.GONE); + binding.parkBadgeContainer.setVisibility(View.GONE); + binding.moreContainer.setVisibility(View.VISIBLE); + binding.locationDiscoverContainer.setVisibility(View.GONE); + binding.addFriendContainer.setVisibility(View.GONE); + binding.genericPlaceholderContainer.setVisibility(View.GONE); + binding.followRecyclerView.setVisibility(View.GONE); + binding.nearbyRecyclerView.setVisibility(View.GONE); + + ensureMore(); + } + + private void ensureMore() { + if (moreAdapter != null) return; + + moreAdapter = new MoreAdapter(item -> { + if (item == null) return; + if (item.getType() != MoreItem.Type.ROW) return; + String t = item.getTitle() != null ? item.getTitle() : ""; + + if ("账号与安全".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_ACCOUNT_SECURITY); + return; + } + if ("隐私".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_PRIVACY); + return; + } + if ("通知".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_NOTIFICATIONS); + return; + } + if ("清理缓存".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_CLEAR_CACHE); + return; + } + if ("帮助与反馈".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_HELP); + return; + } + if ("关于".equals(t)) { + SettingsPageActivity.start(this, SettingsPageActivity.PAGE_ABOUT); + return; + } + if ("退出登录".equals(t)) { + showLogoutConfirm(); + return; + } + + Toast.makeText(this, "暂未接入:" + t, Toast.LENGTH_SHORT).show(); + }); + + binding.moreRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + binding.moreRecyclerView.setAdapter(moreAdapter); + + binding.moreEdit.setOnClickListener(v -> EditProfileActivity.start(this)); + + List items = new ArrayList<>(); + items.add(MoreItem.section("账号")); + items.add(MoreItem.row("账号与安全", "密码、手机号、登录设备", R.drawable.ic_person_24)); + items.add(MoreItem.row("隐私", "黑名单、权限管理", R.drawable.ic_globe_24)); + items.add(MoreItem.row("通知", "消息提醒、勿扰", R.drawable.ic_notifications_24)); + + items.add(MoreItem.section("通用")); + items.add(MoreItem.row("清理缓存", "释放存储空间", R.drawable.ic_grid_24)); + items.add(MoreItem.row("帮助与反馈", "常见问题、意见反馈", R.drawable.ic_chat_24)); + items.add(MoreItem.row("关于", "版本信息、协议", R.drawable.ic_menu_24)); + + items.add(MoreItem.section("其他")); + items.add(MoreItem.row("退出登录", "切换账号", R.drawable.ic_arrow_back_24)); + + moreAdapter.submitList(items); + } + + private void showLogoutConfirm() { + new AlertDialog.Builder(this) + .setTitle("退出登录") + .setMessage("确定要退出登录吗?") + .setNegativeButton("取消", null) + .setPositiveButton("退出", (d, w) -> { + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + }) + .show(); + } + + private void showLocationDiscover() { + binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.GONE); + binding.parkBadgeContainer.setVisibility(View.GONE); + binding.moreContainer.setVisibility(View.GONE); + binding.locationDiscoverContainer.setVisibility(View.VISIBLE); + binding.addFriendContainer.setVisibility(View.GONE); + binding.genericPlaceholderContainer.setVisibility(View.GONE); + binding.followRecyclerView.setVisibility(View.GONE); + binding.nearbyRecyclerView.setVisibility(View.GONE); + + binding.locationRefresh.setOnClickListener(v -> Toast.makeText(this, "刷新定位(待接入)", Toast.LENGTH_SHORT).show()); + binding.nearbyEntryUsers.setOnClickListener(v -> TabPlaceholderActivity.start(this, "附近")); + binding.nearbyEntryLive.setOnClickListener(v -> Toast.makeText(this, "附近直播(待接入)", Toast.LENGTH_SHORT).show()); + binding.nearbyEntryPlaces.setOnClickListener(v -> Toast.makeText(this, "热门地点(待接入)", Toast.LENGTH_SHORT).show()); + } + + private void showAddFriends() { + binding.genericScroll.setVisibility(View.VISIBLE); + binding.discoverContainer.setVisibility(View.GONE); + binding.parkBadgeContainer.setVisibility(View.GONE); + binding.moreContainer.setVisibility(View.GONE); + binding.locationDiscoverContainer.setVisibility(View.GONE); + binding.addFriendContainer.setVisibility(View.VISIBLE); + binding.genericPlaceholderContainer.setVisibility(View.GONE); + binding.followRecyclerView.setVisibility(View.GONE); + binding.nearbyRecyclerView.setVisibility(View.GONE); + + ensureAddFriends(); + } + + private void ensureAddFriends() { + if (addFriendAdapter != null) return; + + addFriendAdapter = new NearbyUsersAdapter(user -> { + if (user == null) return; + 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.setAdapter(addFriendAdapter); + + addFriendAllUsers.clear(); + addFriendAllUsers.addAll(buildNearbyDemoUsers(18)); + addFriendAdapter.submitList(new ArrayList<>(addFriendAllUsers)); + binding.addFriendEmpty.setVisibility(View.GONE); + + binding.addFriendSearchClear.setOnClickListener(v -> { + binding.addFriendSearchInput.setText(""); + addFriendAdapter.submitList(new ArrayList<>(addFriendAllUsers)); + binding.addFriendEmpty.setVisibility(View.GONE); + }); + + binding.addFriendSearchInput.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) { + applyAddFriendFilter(s != null ? s.toString() : ""); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + private void applyAddFriendFilter(String q) { + String query = q != null ? q.trim() : ""; + if (TextUtils.isEmpty(query)) { + addFriendAdapter.submitList(new ArrayList<>(addFriendAllUsers)); + binding.addFriendEmpty.setVisibility(View.GONE); + return; + } + + List filtered = new ArrayList<>(); + for (NearbyUser u : addFriendAllUsers) { + if (u == null) continue; + String name = u.getName() != null ? u.getName() : ""; + String id = u.getId() != null ? u.getId() : ""; + if (name.contains(query) || id.contains(query)) { + filtered.add(u); + } + } + + addFriendAdapter.submitList(filtered); + binding.addFriendEmpty.setVisibility(filtered.isEmpty() ? View.VISIBLE : View.GONE); + } } diff --git a/android-app/app/src/main/java/com/example/livestreaming/WishTreeActivity.java b/android-app/app/src/main/java/com/example/livestreaming/WishTreeActivity.java index 00f4c47c..c728f773 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/WishTreeActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/WishTreeActivity.java @@ -3,27 +3,100 @@ package com.example.livestreaming; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import androidx.appcompat.app.AppCompatActivity; import com.example.livestreaming.databinding.ActivityWishTreeBinding; import com.google.android.material.bottomnavigation.BottomNavigationView; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + public class WishTreeActivity extends AppCompatActivity { private ActivityWishTreeBinding binding; + private final Handler handler = new Handler(Looper.getMainLooper()); + private Runnable timerRunnable; + public static void start(Context context) { Intent intent = new Intent(context, WishTreeActivity.class); context.startActivity(intent); } + @Override + protected void onStart() { + super.onStart(); + startBannerCountdown(); + } + + @Override + protected void onStop() { + super.onStop(); + stopBannerCountdown(); + } + + private void startBannerCountdown() { + stopBannerCountdown(); + timerRunnable = new Runnable() { + @Override + public void run() { + updateBannerTimer(); + handler.postDelayed(this, 1000); + } + }; + handler.post(timerRunnable); + } + + private void stopBannerCountdown() { + if (timerRunnable != null) { + handler.removeCallbacks(timerRunnable); + timerRunnable = null; + } + } + + private void updateBannerTimer() { + if (binding == null) return; + try { + long now = System.currentTimeMillis(); + TimeZone tz = TimeZone.getDefault(); + Calendar c = Calendar.getInstance(tz); + c.setTimeInMillis(now); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + c.add(Calendar.DAY_OF_MONTH, 1); + long nextMidnight = c.getTimeInMillis(); + long diff = Math.max(0, nextMidnight - now); + + long totalSeconds = diff / 1000; + long hours = totalSeconds / 3600; + long minutes = (totalSeconds % 3600) / 60; + long seconds = totalSeconds % 60; + + SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + fmt.setTimeZone(tz); + String current = fmt.format(new Date(now)); + String remain = String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds); + binding.bannerTimer.setText(current + " " + remain); + } catch (Exception ignored) { + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityWishTreeBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + startBannerCountdown(); + BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation; bottomNavigation.setSelectedItemId(R.id.nav_wish_tree); bottomNavigation.setOnItemSelectedListener(item -> { diff --git a/android-app/app/src/main/res/drawable/bg_icon_button_12.xml b/android-app/app/src/main/res/drawable/bg_icon_button_12.xml new file mode 100644 index 00000000..3b24737a --- /dev/null +++ b/android-app/app/src/main/res/drawable/bg_icon_button_12.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android-app/app/src/main/res/drawable/ic_clock_24.xml b/android-app/app/src/main/res/drawable/ic_clock_24.xml new file mode 100644 index 00000000..e57000bf --- /dev/null +++ b/android-app/app/src/main/res/drawable/ic_clock_24.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/android-app/app/src/main/res/drawable/wish_tree_title.png b/android-app/app/src/main/res/drawable/wish_tree_title.png new file mode 100644 index 00000000..5fdcf56c Binary files /dev/null and b/android-app/app/src/main/res/drawable/wish_tree_title.png differ diff --git a/android-app/app/src/main/res/drawable/yuan_chi_title.png b/android-app/app/src/main/res/drawable/yuan_chi_title.png new file mode 100644 index 00000000..6fe65268 Binary files /dev/null and b/android-app/app/src/main/res/drawable/yuan_chi_title.png differ 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 5613a4fd..bc50bf38 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 @@ -6,14 +6,14 @@ android:background="@drawable/bg_fishpond_gradient" android:paddingTop="18dp"> - 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 a594e783..5e559cca 100644 --- a/android-app/app/src/main/res/layout/activity_profile.xml +++ b/android-app/app/src/main/res/layout/activity_profile.xml @@ -159,6 +159,7 @@ android:textSize="13sp" /> @@ -202,7 +203,7 @@ android:layout_marginEnd="10dp" android:background="@drawable/bg_circle_white_60" android:padding="8dp" - android:src="@drawable/ic_search_24" + android:src="@drawable/ic_crosshair_24" app:layout_constraintEnd_toStartOf="@id/topActionClock" app:layout_constraintTop_toTopOf="@id/topActionMore" /> @@ -545,7 +546,7 @@ android:layout_height="44dp" android:background="@drawable/bg_gray_12" android:gravity="center" - android:text="编辑资料 40%" + android:text="编辑资料" android:textColor="#111111" app:layout_constraintEnd_toStartOf="@id/shareHome" app:layout_constraintStart_toStartOf="parent" @@ -569,9 +570,10 @@ android:layout_width="44dp" android:layout_height="44dp" android:layout_marginStart="12dp" - android:background="@drawable/bg_gray_12" - android:padding="10dp" + android:background="@drawable/bg_icon_button_12" + android:padding="11dp" android:src="@drawable/ic_person_24" + android:tint="@color/purple_500" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/shareHome" app:layout_constraintTop_toTopOf="parent" /> @@ -584,6 +586,9 @@ android:layout_height="wrap_content" android:layout_marginTop="12dp" app:tabIndicatorColor="@color/purple_500" + app:tabIndicatorFullWidth="false" + app:tabIndicatorHeight="3dp" + app:tabRippleColor="@android:color/transparent" app:tabSelectedTextColor="#111111" app:tabTextColor="#666666" app:layout_constraintEnd_toEndOf="parent" @@ -612,52 +617,219 @@ - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:orientation="vertical" + android:visibility="gone"> - + - + + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/activity_search.xml b/android-app/app/src/main/res/layout/activity_search.xml new file mode 100644 index 00000000..9ce5fd31 --- /dev/null +++ b/android-app/app/src/main/res/layout/activity_search.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/activity_settings_page.xml b/android-app/app/src/main/res/layout/activity_settings_page.xml new file mode 100644 index 00000000..c591b0e7 --- /dev/null +++ b/android-app/app/src/main/res/layout/activity_settings_page.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/activity_tab_placeholder.xml b/android-app/app/src/main/res/layout/activity_tab_placeholder.xml index 55cc6c39..d75b5118 100644 --- a/android-app/app/src/main/res/layout/activity_tab_placeholder.xml +++ b/android-app/app/src/main/res/layout/activity_tab_placeholder.xml @@ -68,6 +68,845 @@ android:paddingTop="12dp" android:paddingBottom="24dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/activity_wish_tree.xml b/android-app/app/src/main/res/layout/activity_wish_tree.xml index 26b095fa..cc4a16fc 100644 --- a/android-app/app/src/main/res/layout/activity_wish_tree.xml +++ b/android-app/app/src/main/res/layout/activity_wish_tree.xml @@ -23,14 +23,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - - diff --git a/android-app/app/src/main/res/layout/item_badge.xml b/android-app/app/src/main/res/layout/item_badge.xml new file mode 100644 index 00000000..a8204136 --- /dev/null +++ b/android-app/app/src/main/res/layout/item_badge.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/item_more_row.xml b/android-app/app/src/main/res/layout/item_more_row.xml new file mode 100644 index 00000000..321bffa3 --- /dev/null +++ b/android-app/app/src/main/res/layout/item_more_row.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + diff --git a/android-app/app/src/main/res/layout/item_more_section.xml b/android-app/app/src/main/res/layout/item_more_section.xml new file mode 100644 index 00000000..69c4ffb6 --- /dev/null +++ b/android-app/app/src/main/res/layout/item_more_section.xml @@ -0,0 +1,11 @@ + + diff --git a/android-app/app/src/main/res/layout/item_search_suggestion.xml b/android-app/app/src/main/res/layout/item_search_suggestion.xml new file mode 100644 index 00000000..a3b10dc3 --- /dev/null +++ b/android-app/app/src/main/res/layout/item_search_suggestion.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + +