From 6f103b28976549ef98d35c0238b2b6b62c47f96b Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Mon, 8 Apr 2024 22:23:34 +0200 Subject: [PATCH] Dashboard: Implement horizontal swiping and fix fragment related crashes --- app/build.gradle | 3 +- .../activities/ControlCenterv2.java | 120 +++++++++++++++--- .../activities/DashboardFragment.java | 39 +----- .../main/res/layout/activity_main_app_bar.xml | 15 ++- .../main/res/layout/fragment_dashboard.xml | 93 ++++++-------- app/src/main/res/layout/fragment_devices.xml | 3 +- app/src/main/res/navigation/main.xml | 26 ---- 7 files changed, 157 insertions(+), 142 deletions(-) delete mode 100644 app/src/main/res/navigation/main.xml diff --git a/app/build.gradle b/app/build.gradle index 00bbd9ebe..96f898654 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -228,8 +228,7 @@ dependencies { implementation "androidx.palette:palette:1.0.0" implementation "androidx.activity:activity:1.7.2" implementation "androidx.fragment:fragment:1.6.2" - implementation "androidx.navigation:navigation-fragment:2.6.0" - implementation "androidx.navigation:navigation-ui:2.6.0" + implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "com.google.android.material:material:1.9.0" implementation 'com.google.android.flexbox:flexbox:3.0.0' diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 5946cfbc9..d24d0aac6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -41,6 +41,7 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.TypedValue; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.widget.Toast; @@ -58,11 +59,12 @@ import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.navigation.NavController; -import androidx.navigation.NavGraph; -import androidx.navigation.fragment.NavHostFragment; -import androidx.navigation.ui.NavigationUI; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.bottomnavigation.BottomNavigationView; @@ -85,8 +87,10 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -104,6 +108,9 @@ public class ControlCenterv2 extends AppCompatActivity = "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestlocationpermissions"; private boolean isLanguageInvalid = false; private boolean isThemeInvalid = false; + private ViewPager2 viewPager; + private FragmentStateAdapter pagerAdapter; + private SwipeRefreshLayout swipeLayout; private static PhoneStateListener fakeStateListener; //needed for KK compatibility @@ -134,6 +141,12 @@ public class ControlCenterv2 extends AppCompatActivity case ACTION_REQUEST_LOCATION_PERMISSIONS: checkAndRequestLocationPermissions(); break; + case GBDevice.ACTION_DEVICE_CHANGED: + GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); + if (dev != null && !dev.isBusy()) { + swipeLayout.setRefreshing(false); + } + break; } } }; @@ -165,6 +178,7 @@ public class ControlCenterv2 extends AppCompatActivity Prefs prefs = GBApplication.getPrefs(); + // Determine availability of device with activity tracking functionality boolean activityTrackerAvailable = false; List devices = GBApplication.app().getDeviceManager().getDevices(); for (GBDevice dev : devices) { @@ -174,21 +188,26 @@ public class ControlCenterv2 extends AppCompatActivity } } - NavHostFragment navHostFragment = (NavHostFragment) - getSupportFragmentManager().findFragmentById(R.id.fragment_container); - NavController navController = navHostFragment.getNavController(); - if (!prefs.getBoolean("dashboard_as_default_view", true) || !activityTrackerAvailable) { - NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.main); - navGraph.setStartDestination(R.id.bottom_nav_devices); - navController.setGraph(navGraph); - } - BottomNavigationView navigationView = findViewById(R.id.bottom_nav_bar); - NavigationUI.setupWithNavController(navigationView, navController); - navigationView.setVisibility(activityTrackerAvailable ? View.VISIBLE : View.GONE); - + // Initialize drawer NavigationView drawerNavigationView = findViewById(R.id.nav_view); drawerNavigationView.setNavigationItemSelectedListener(this); + // Initialize bottom navigation + BottomNavigationView navigationView = findViewById(R.id.bottom_nav_bar); + navigationView.setVisibility(activityTrackerAvailable ? View.VISIBLE : View.GONE); + navigationView.setOnItemSelectedListener(menuItem -> { + switch (menuItem.getItemId()) { + case R.id.bottom_nav_dashboard: + viewPager.setCurrentItem(0, true); + break; + case R.id.bottom_nav_devices: + viewPager.setCurrentItem(1, true); + break; + } + return true; + }); + + // Initialize actionbar MaterialToolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); @@ -196,7 +215,6 @@ public class ControlCenterv2 extends AppCompatActivity this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState(); - if (GBApplication.areDynamicColorsEnabled()) { TypedValue typedValue = new TypedValue(); Resources.Theme theme = getTheme(); @@ -208,6 +226,53 @@ public class ControlCenterv2 extends AppCompatActivity toolbar.setTitleTextColor(getResources().getColor(android.R.color.white)); } + // Configure ViewPager2 with fragment adapter and default fragment + viewPager = findViewById(R.id.dashboard_viewpager); + pagerAdapter = new MainFragmentsPagerAdapter(this); + viewPager.setAdapter(pagerAdapter); + if (!prefs.getBoolean("dashboard_as_default_view", true) || !activityTrackerAvailable) { + viewPager.setCurrentItem(1, false); + navigationView.getMenu().getItem(1).setChecked(true); + } + + // Sync ViewPager changes with BottomNavigationView + viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + navigationView.getMenu().getItem(position).setChecked(true); + } + }); + + // Make sure the SwipeRefreshLayout doesn't interfere with the ViewPager2 + viewPager.getChildAt(viewPager.getCurrentItem()).setOnTouchListener((v, event) -> { + if (event.getAction() == MotionEvent.ACTION_MOVE) { + swipeLayout.setEnabled(false); + } else { + swipeLayout.setEnabled(true); + } + return false; + }); + + // Set pull-down-to-refresh action + swipeLayout = findViewById(R.id.dashboard_swipe_layout); + swipeLayout.setOnRefreshListener(() -> { + // Signal DeviceCommunicationService to fetch activity for all connected devices + Intent intent = new Intent(getApplicationContext(), DeviceCommunicationService.class); + intent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA) + .putExtra(DeviceService.EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); + startService(intent); + // Hide 'refreshing' animation immediately if no health devices are connected + List devices1 = GBApplication.app().getDeviceManager().getDevices(); + for (GBDevice dev : devices1) { + if (dev.getDeviceCoordinator().supportsActivityTracking() && dev.isInitialized()) { + return; + } + } + swipeLayout.setRefreshing(false); + GB.toast(getString(R.string.info_no_devices_connected), Toast.LENGTH_LONG, GB.WARN); + }); + + // Set up local intent listener IntentFilter filterLocal = new IntentFilter(); filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE); filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE); @@ -215,6 +280,7 @@ public class ControlCenterv2 extends AppCompatActivity filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES); filterLocal.addAction(ACTION_REQUEST_PERMISSIONS); filterLocal.addAction(ACTION_REQUEST_LOCATION_PERMISSIONS); + filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); /* @@ -670,4 +736,24 @@ public class ControlCenterv2 extends AppCompatActivity return builder.create(); } } + + private class MainFragmentsPagerAdapter extends FragmentStateAdapter { + public MainFragmentsPagerAdapter(FragmentActivity fa) { + super(fa); + } + + @Override + public Fragment createFragment(int position) { + if (position == 0) { + return new DashboardFragment(); + } else { + return new DevicesFragment(); + } + } + + @Override + public int getItemCount() { + return 2; + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java index bbfdfc69d..7329f3eab 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java @@ -29,7 +29,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,7 +36,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentContainerView; import androidx.gridlayout.widget.GridLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.card.MaterialCardView; @@ -65,12 +63,8 @@ import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardSleepW import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStepsWidget; import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; -import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class DashboardFragment extends Fragment { @@ -81,7 +75,6 @@ public class DashboardFragment extends Fragment { private TextView arrowLeft; private TextView arrowRight; private GridLayout gridLayout; - private SwipeRefreshLayout swipeLayout; private DashboardTodayWidget todayWidget; private DashboardGoalsWidget goalsWidget; private DashboardStepsWidget stepsWidget; @@ -119,26 +112,6 @@ public class DashboardFragment extends Fragment { setHasOptionsMenu(true); textViewDate = dashboardView.findViewById(R.id.dashboard_date); gridLayout = dashboardView.findViewById(R.id.dashboard_gridlayout); - swipeLayout = dashboardView.findViewById(R.id.dashboard_swipe_layout); - swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - // Signal DeviceCommunicationService to fetch activity for all connected devices - Intent intent = new Intent(requireContext(), DeviceCommunicationService.class); - intent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA) - .putExtra(DeviceService.EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); - requireContext().startService(intent); - // Hide 'refreshing' animation immediately if no health devices are connected - List devices = GBApplication.app().getDeviceManager().getDevices(); - for (GBDevice dev : devices) { - if (dev.getDeviceCoordinator().supportsActivityTracking() && dev.isInitialized()) { - return; - } - } - swipeLayout.setRefreshing(false); - GB.toast(getString(R.string.info_no_devices_connected), Toast.LENGTH_LONG, GB.WARN); - } - }); // Increase column count on landscape, tablets and open foldables DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); @@ -164,14 +137,6 @@ public class DashboardFragment extends Fragment { dashboardData = (DashboardData) savedInstanceState.getSerializable("dashboard_data"); } - // Make sure the widget fragments are (re)instantiated when drawing the dashboard - todayWidget = null; - goalsWidget = null; - stepsWidget = null; - distanceWidget = null; - activeTimeWidget = null; - sleepWidget = null; - IntentFilter filterLocal = new IntentFilter(); filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED); filterLocal.addAction(ACTION_CONFIG_CHANGE); @@ -183,7 +148,6 @@ public class DashboardFragment extends Fragment { @Override public void onResume() { super.onResume(); - draw(); if (isConfigChanged) { isConfigChanged = false; fullRefresh(); @@ -246,7 +210,6 @@ public class DashboardFragment extends Fragment { } private void refresh() { - swipeLayout.setRefreshing(false); day.set(Calendar.HOUR_OF_DAY, 23); day.set(Calendar.MINUTE, 59); day.set(Calendar.SECOND, 59); @@ -336,7 +299,7 @@ public class DashboardFragment extends Fragment { FragmentContainerView fragment = new FragmentContainerView(requireActivity()); int fragmentId = View.generateViewId(); fragment.setId(fragmentId); - getParentFragmentManager() + getChildFragmentManager() .beginTransaction() .replace(fragmentId, widgetObj) .commit(); diff --git a/app/src/main/res/layout/activity_main_app_bar.xml b/app/src/main/res/layout/activity_main_app_bar.xml index 3dbd9a247..d03027daf 100644 --- a/app/src/main/res/layout/activity_main_app_bar.xml +++ b/app/src/main/res/layout/activity_main_app_bar.xml @@ -22,15 +22,18 @@ - + app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar"> + + + - - +