feat: use pager-based root tab navigation
This commit is contained in:
@@ -6,9 +6,10 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -19,7 +20,9 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -60,6 +63,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private Button tabConversations;
|
||||
private Button tabDevices;
|
||||
private Button tabMe;
|
||||
private ViewPager2 rootPager;
|
||||
private SwipeRefreshLayout screenRefresh;
|
||||
private ScrollView screenScroll;
|
||||
private LinearLayout screenContent;
|
||||
@@ -81,7 +85,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private boolean conversationAutoRefreshArmed = false;
|
||||
private boolean conversationAutoRefreshEnabled = false;
|
||||
private final Set<String> selectedConversationProjectIds = new LinkedHashSet<>();
|
||||
private @Nullable GestureDetector conversationPagerGestureDetector;
|
||||
private @Nullable RootPagerAdapter rootPagerAdapter;
|
||||
private boolean syncingRootPagerSelection = false;
|
||||
private final Runnable conversationAutoRefreshRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -175,9 +180,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
tabConversations = findViewById(R.id.tab_conversations);
|
||||
tabDevices = findViewById(R.id.tab_devices);
|
||||
tabMe = findViewById(R.id.tab_me);
|
||||
screenRefresh = findViewById(R.id.screen_refresh);
|
||||
screenScroll = findViewById(R.id.screen_scroll);
|
||||
screenContent = findViewById(R.id.screen_content);
|
||||
rootPager = findViewById(R.id.root_pager);
|
||||
|
||||
String[] rootTabs = WechatSurfaceMapper.rootTabLabels();
|
||||
tabConversations.setText(rootTabs[0]);
|
||||
@@ -197,47 +200,23 @@ public class MainActivity extends AppCompatActivity {
|
||||
tabConversations.setOnClickListener(v -> setActiveTab("conversations", true));
|
||||
tabDevices.setOnClickListener(v -> setActiveTab("devices", true));
|
||||
tabMe.setOnClickListener(v -> setActiveTab("me", true));
|
||||
screenRefresh.setOnRefreshListener(this::refreshCurrentTab);
|
||||
conversationPagerGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
|
||||
private static final int SWIPE_DISTANCE_THRESHOLD = 120;
|
||||
private static final int SWIPE_VELOCITY_THRESHOLD = 120;
|
||||
|
||||
rootPagerAdapter = new RootPagerAdapter();
|
||||
rootPager.setAdapter(rootPagerAdapter);
|
||||
rootPager.setOffscreenPageLimit(3);
|
||||
rootPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
if (e1 == null || e2 == null) {
|
||||
return false;
|
||||
public void onPageSelected(int position) {
|
||||
if (syncingRootPagerSelection) {
|
||||
return;
|
||||
}
|
||||
float deltaX = e2.getX() - e1.getX();
|
||||
float deltaY = e2.getY() - e1.getY();
|
||||
if (Math.abs(deltaX) < Math.abs(deltaY)) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(deltaX) < SWIPE_DISTANCE_THRESHOLD || Math.abs(velocityX) < SWIPE_VELOCITY_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
handleHorizontalPageSwipe(deltaX < 0 ? 1 : -1);
|
||||
return true;
|
||||
handleRootPagerSelection(position);
|
||||
}
|
||||
});
|
||||
screenRefresh.setOnTouchListener((v, event) -> {
|
||||
if (conversationPagerGestureDetector != null) {
|
||||
conversationPagerGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
return false;
|
||||
rootPager.post(() -> {
|
||||
syncActivePageViews(activeTab);
|
||||
renderCurrentTab();
|
||||
updateConversationAutoRefresh();
|
||||
});
|
||||
if (screenScroll != null) {
|
||||
screenScroll.setOnTouchListener((v, event) -> {
|
||||
if (conversationPagerGestureDetector != null) {
|
||||
conversationPagerGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void applyInitialTab(@Nullable Intent intent) {
|
||||
@@ -462,6 +441,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
lastRootBackPressedAt = 0L;
|
||||
updateTabStyles();
|
||||
syncRootPager(tab, fromUser);
|
||||
syncActivePageViews(tab);
|
||||
renderCurrentTab();
|
||||
updateConversationAutoRefresh();
|
||||
}
|
||||
@@ -552,6 +533,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void renderConversationsRoot() {
|
||||
if (screenContent == null) {
|
||||
return;
|
||||
}
|
||||
screenContent.removeAllViews();
|
||||
screenContent.addView(buildConversationSearchInput());
|
||||
if (conversationSelectionMode) {
|
||||
@@ -756,20 +740,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private void handleHorizontalPageSwipe(int direction) {
|
||||
String[] order = new String[] {"conversations", "devices", "me"};
|
||||
int currentIndex = 0;
|
||||
for (int i = 0; i < order.length; i++) {
|
||||
if (order[i].equals(activeTab)) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int nextIndex = currentIndex + direction;
|
||||
if (nextIndex < 0 || nextIndex >= order.length) {
|
||||
private void handleRootPagerSelection(int position) {
|
||||
String tab = tabForIndex(position);
|
||||
if (tab.equals(activeTab)) {
|
||||
syncActivePageViews(tab);
|
||||
updateConversationAutoRefresh();
|
||||
return;
|
||||
}
|
||||
setActiveTab(order[nextIndex], true);
|
||||
setActiveTab(tab, true);
|
||||
}
|
||||
|
||||
private EditText buildConversationSearchInput() {
|
||||
@@ -848,6 +826,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void renderDevicesRoot() {
|
||||
if (screenContent == null) {
|
||||
return;
|
||||
}
|
||||
screenContent.removeAllViews();
|
||||
if (devicesData == null || devicesData.length() == 0) {
|
||||
screenContent.addView(BossUi.buildEmptyCard(this, "当前没有接入设备。"));
|
||||
@@ -875,6 +856,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void renderMeRoot() {
|
||||
if (screenContent == null) {
|
||||
return;
|
||||
}
|
||||
screenContent.removeAllViews();
|
||||
String displayName = sessionData == null
|
||||
? apiClient.getDisplayName()
|
||||
@@ -945,6 +929,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void startRefreshing(boolean refreshing) {
|
||||
if (screenRefresh == null) {
|
||||
return;
|
||||
}
|
||||
screenRefresh.setRefreshing(refreshing);
|
||||
syncTopActionVisualState(refreshing);
|
||||
if (!refreshing) {
|
||||
@@ -1113,4 +1100,144 @@ public class MainActivity extends AppCompatActivity {
|
||||
return getSharedPreferences(UI_PREFS, MODE_PRIVATE)
|
||||
.getString(KEY_LAST_ROOT_TAB, null);
|
||||
}
|
||||
|
||||
private void syncRootPager(String tab, boolean smoothScroll) {
|
||||
if (rootPager == null) {
|
||||
return;
|
||||
}
|
||||
int targetIndex = indexForTab(tab);
|
||||
if (targetIndex < 0 || rootPager.getCurrentItem() == targetIndex) {
|
||||
return;
|
||||
}
|
||||
syncingRootPagerSelection = true;
|
||||
rootPager.setCurrentItem(targetIndex, smoothScroll);
|
||||
rootPager.post(() -> syncingRootPagerSelection = false);
|
||||
}
|
||||
|
||||
private void syncActivePageViews(String tab) {
|
||||
if (rootPagerAdapter == null) {
|
||||
screenRefresh = null;
|
||||
screenScroll = null;
|
||||
screenContent = null;
|
||||
return;
|
||||
}
|
||||
RootPageViewHolder holder = rootPagerAdapter.findHolder(tab);
|
||||
if (holder == null) {
|
||||
screenRefresh = null;
|
||||
screenScroll = null;
|
||||
screenContent = null;
|
||||
rootPager.post(() -> {
|
||||
RootPageViewHolder pendingHolder = rootPagerAdapter.findHolder(activeTab);
|
||||
if (pendingHolder != null) {
|
||||
screenRefresh = pendingHolder.refresh;
|
||||
screenScroll = pendingHolder.scroll;
|
||||
screenContent = pendingHolder.content;
|
||||
renderCurrentTab();
|
||||
updateConversationAutoRefresh();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
screenRefresh = holder.refresh;
|
||||
screenScroll = holder.scroll;
|
||||
screenContent = holder.content;
|
||||
}
|
||||
|
||||
private static int indexForTab(String tab) {
|
||||
switch (tab) {
|
||||
case "devices":
|
||||
return 1;
|
||||
case "me":
|
||||
return 2;
|
||||
case "conversations":
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static String tabForIndex(int index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
return "devices";
|
||||
case 2:
|
||||
return "me";
|
||||
case 0:
|
||||
default:
|
||||
return "conversations";
|
||||
}
|
||||
}
|
||||
|
||||
private final class RootPagerAdapter extends RecyclerView.Adapter<RootPageViewHolder> {
|
||||
private final String[] tabs = new String[] {"conversations", "devices", "me"};
|
||||
private final RootPageViewHolder[] holders = new RootPageViewHolder[tabs.length];
|
||||
|
||||
RootPagerAdapter() {
|
||||
LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
|
||||
for (int i = 0; i < tabs.length; i++) {
|
||||
RootPageViewHolder holder = RootPageViewHolder.create(inflater, rootPager, tabs[i]);
|
||||
holder.refresh.setOnRefreshListener(() -> {
|
||||
if (!holder.tab.equals(activeTab)) {
|
||||
setActiveTab(holder.tab, true);
|
||||
return;
|
||||
}
|
||||
refreshCurrentTab();
|
||||
});
|
||||
holders[i] = holder;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return tabs.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootPageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
RootPageViewHolder holder = holders[viewType];
|
||||
ViewParent currentParent = holder.itemView.getParent();
|
||||
if (currentParent instanceof ViewGroup) {
|
||||
((ViewGroup) currentParent).removeView(holder.itemView);
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RootPageViewHolder holder, int position) {
|
||||
holders[position] = holder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
RootPageViewHolder findHolder(String tab) {
|
||||
int index = indexForTab(tab);
|
||||
if (index < 0 || index >= holders.length) {
|
||||
return null;
|
||||
}
|
||||
return holders[index];
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RootPageViewHolder extends RecyclerView.ViewHolder {
|
||||
final String tab;
|
||||
final SwipeRefreshLayout refresh;
|
||||
final ScrollView scroll;
|
||||
final LinearLayout content;
|
||||
|
||||
static RootPageViewHolder create(LayoutInflater inflater, ViewGroup parent, String tab) {
|
||||
View view = inflater.inflate(R.layout.view_root_tab_page, parent, false);
|
||||
return new RootPageViewHolder(view, tab);
|
||||
}
|
||||
|
||||
RootPageViewHolder(View itemView, String tab) {
|
||||
super(itemView);
|
||||
this.tab = tab;
|
||||
this.refresh = itemView.findViewById(R.id.root_page_refresh);
|
||||
this.scroll = itemView.findViewById(R.id.root_page_scroll);
|
||||
this.content = itemView.findViewById(R.id.root_page_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingTop="14dp"
|
||||
android:paddingRight="32dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<Button
|
||||
@@ -142,7 +142,6 @@
|
||||
android:id="@+id/refresh_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_secondary_button"
|
||||
android:minWidth="0dp"
|
||||
android:paddingLeft="12dp"
|
||||
@@ -160,29 +159,10 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/screen_refresh"
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/root_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/screen_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/screen_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/boss_bg_app"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingRight="0dp"
|
||||
android:paddingBottom="88dp" />
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
24
android/app/src/main/res/layout/view_root_tab_page.xml
Normal file
24
android/app/src/main/res/layout/view_root_tab_page.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root_page_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/root_page_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/root_page_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/boss_bg_app"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingRight="0dp"
|
||||
android:paddingBottom="88dp" />
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
Reference in New Issue
Block a user