1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-19 00:19:25 +01:00

Use modern navigation component

This commit is contained in:
Arjan Schrijver 2024-02-12 11:12:02 +01:00
parent a550aca626
commit 70a8b80af0
7 changed files with 136 additions and 93 deletions

View File

@ -228,6 +228,8 @@ 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 "com.google.android.material:material:1.9.0"
implementation 'com.google.android.flexbox:flexbox:3.0.0'

View File

@ -19,10 +19,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
@ -31,15 +31,18 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.io.Serializable;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -50,8 +53,9 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ControlCenterv2 extends Fragment {
@ -63,6 +67,33 @@ public class ControlCenterv2 extends Fragment {
List<GBDevice> deviceList;
private HashMap<String,long[]> deviceActivityHashMap = new HashMap();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (Objects.requireNonNull(action)) {
case DeviceManager.ACTION_DEVICES_CHANGED:
case GBApplication.ACTION_NEW_DATA:
createRefreshTask("get activity data", requireContext()).execute();
mGBDeviceAdapter.rebuildFolders();
refreshPairedDevices();
break;
case DeviceService.ACTION_REALTIME_SAMPLES:
handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
break;
}
}
};
private void handleRealtimeSample(Serializable extra) {
if (extra instanceof ActivitySample) {
ActivitySample sample = (ActivitySample) extra;
if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) {
refreshPairedDevices();
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View currentView = inflater.inflate(R.layout.activity_controlcenterv2_content_main, container, false);
@ -130,6 +161,12 @@ public class ControlCenterv2 extends Fragment {
registerForContextMenu(deviceListView);
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(GBApplication.ACTION_NEW_DATA);
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filterLocal);
refreshPairedDevices();
if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@ -160,6 +197,7 @@ public class ControlCenterv2 extends Fragment {
@Override
public void onDestroy() {
unregisterForContextMenu(deviceListView);
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver);
super.onDestroy();
}
@ -181,17 +219,6 @@ public class ControlCenterv2 extends Fragment {
return new RefreshTask(task, context);
}
private void handleShortcut(Intent intent) {
if(ACTION_CONNECT.equals(intent.getAction())) {
String btDeviceAddress = intent.getStringExtra("device");
if(btDeviceAddress!=null){
GBDevice candidate = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, getActivity());
if (candidate != null && !candidate.isConnected()) {
GBApplication.deviceService(candidate).connect();
}
}
}
}
public class RefreshTask extends DBAccess {
public RefreshTask(String task, Context context) {
super(task, context);

View File

@ -51,8 +51,8 @@ import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardGoalsW
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardSleepWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStepsWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class DashboardFragment extends Fragment {
@ -103,11 +103,21 @@ public class DashboardFragment extends Fragment {
distanceWidget = null;
activeTimeWidget = null;
sleepWidget = null;
refresh();
// Only load widgets here if the Dashboard is the initial view.
// This prevents a hard crash when replacing the fragment in createWidget() via a FragmentManager.
Prefs prefs = GBApplication.getPrefs();
if (prefs.getBoolean("dashboard_as_default_view", true)) refresh();
return dashboardView;
}
@Override
public void onResume() {
super.onResume();
refresh();
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
@ -222,7 +232,7 @@ public class DashboardFragment extends Fragment {
FragmentContainerView fragment = new FragmentContainerView(requireActivity());
int fragmentId = View.generateViewId();
fragment.setId(fragmentId);
getChildFragmentManager()
getParentFragmentManager()
.beginTransaction()
.replace(fragmentId, widgetObj)
.commit();

View File

@ -17,6 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Dialog;
@ -33,12 +35,10 @@ import android.os.Bundle;
import android.provider.Settings;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
@ -46,6 +46,10 @@ import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.navigation.NavController;
import androidx.navigation.NavGraph;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -64,15 +68,16 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBChangeLog;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class MainActivity extends AbstractGBActivity implements BottomNavigationView.OnNavigationItemSelectedListener, GBActivity {
public class MainActivity extends AbstractGBActivity implements GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
public static final String ACTION_REQUEST_PERMISSIONS
= "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions";
@ -82,12 +87,6 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
private boolean isThemeInvalid = false;
private static PhoneStateListener fakeStateListener;
private BottomNavigationView bottomNavigationView;
private int activeFragment;
private DashboardFragment dashboardFragment = new DashboardFragment();
private ControlCenterv2 devicesFragment = new ControlCenterv2();
private MainMenuFragment mainMenuFragment = new MainMenuFragment();
//needed for KK compatibility
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
@ -107,25 +106,20 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
case GBApplication.ACTION_QUIT:
finish();
break;
case DeviceManager.ACTION_DEVICES_CHANGED:
case GBApplication.ACTION_NEW_DATA:
devicesFragment.createRefreshTask("get activity data", getApplication()).execute();
// mGBDeviceAdapter.rebuildFolders();
devicesFragment.refreshPairedDevices();
break;
case DeviceService.ACTION_REALTIME_SAMPLES:
handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
break;
case ACTION_REQUEST_PERMISSIONS:
checkAndRequestPermissions();
break;
case ACTION_REQUEST_LOCATION_PERMISSIONS:
checkAndRequestLocationPermissions();
break;
case DeviceService.ACTION_REALTIME_SAMPLES:
handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
break;
}
}
};
private boolean pesterWithPermissions = true;
private ActivitySample currentHRSample;
public ActivitySample getCurrentHRSample() {
@ -135,9 +129,6 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
private void setCurrentHRSample(ActivitySample sample) {
if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) {
currentHRSample = sample;
if (devicesFragment.isResumed()) {
devicesFragment.refreshPairedDevices();
}
}
}
@ -156,25 +147,24 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
Prefs prefs = GBApplication.getPrefs();
bottomNavigationView = findViewById(R.id.bottom_nav_bar);
bottomNavigationView.setOnNavigationItemSelectedListener(this);
if (prefs.getBoolean("dashboard_as_default_view", true)) {
bottomNavigationView.setSelectedItemId(R.id.bottom_nav_dashboard);
activeFragment = R.id.bottom_nav_dashboard;
} else {
bottomNavigationView.setSelectedItemId(R.id.bottom_nav_devices);
activeFragment = R.id.bottom_nav_devices;
NavHostFragment navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
NavController navController = navHostFragment.getNavController();
if (!prefs.getBoolean("dashboard_as_default_view", true)) {
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);
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE);
filterLocal.addAction(GBApplication.ACTION_QUIT);
filterLocal.addAction(GBApplication.ACTION_NEW_DATA);
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
filterLocal.addAction(ACTION_REQUEST_PERMISSIONS);
filterLocal.addAction(ACTION_REQUEST_LOCATION_PERMISSIONS);
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
/*
@ -256,27 +246,15 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("activeFragment", activeFragment); // Save variables into the Bundle
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
activeFragment = savedInstanceState.getInt("activeFragment"); // Retrieve variables from the Bundle
}
@Override
protected void onResume() {
super.onResume();
handleShortcut(getIntent());
if (isLanguageInvalid || isThemeInvalid) {
isLanguageInvalid = false;
isThemeInvalid = false;
recreate();
}
updateFragment();
}
@Override
@ -285,34 +263,15 @@ public class MainActivity extends AbstractGBActivity implements BottomNavigation
super.onDestroy();
}
@Override
public boolean
onNavigationItemSelected(@NonNull MenuItem item) {
activeFragment = item.getItemId();
updateFragment();
return true;
}
private void updateFragment() {
switch (activeFragment) {
case R.id.bottom_nav_dashboard:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, dashboardFragment)
.commit();
break;
case R.id.bottom_nav_devices:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, devicesFragment)
.commit();
break;
case R.id.bottom_nav_menu:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, mainMenuFragment)
.commit();
break;
private void handleShortcut(Intent intent) {
if(ACTION_CONNECT.equals(intent.getAction())) {
String btDeviceAddress = intent.getStringExtra("device");
if(btDeviceAddress!=null){
GBDevice candidate = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, this);
if (candidate != null && !candidate.isConnected()) {
GBApplication.deviceService(candidate).connect();
}
}
}
}

View File

@ -213,7 +213,6 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
private final List<GeneralizedActivity> generalizedActivities = new ArrayList<>();
private void addActivity(long timeFrom, long timeTo, int activityKind) {
LOG.info("Adding activity: timeFrom=" + timeFrom + ", timeTo=" + timeTo + ", activityKind=" + activityKind);
for (long i = timeFrom; i<=timeTo; i++) {
// If the current timestamp isn't saved yet, do so immediately
if (activityTimestamps.get(i) == null) {

View File

@ -9,6 +9,9 @@
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/main"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar"
@ -22,7 +25,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
app:startDestination="@id/bottom_nav_dashboard">
<fragment
android:id="@+id/bottom_nav_dashboard"
android:name="nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment"
android:label="fragment_dashboard"
tools:layout="@layout/fragment_dashboard" >
<action
android:id="@+id/dashboard_to_devices"
app:destination="@id/bottom_nav_devices" />
<action
android:id="@+id/dashboard_to_menu"
app:destination="@id/bottom_nav_menu" />
</fragment>
<fragment
android:id="@+id/bottom_nav_devices"
android:name="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2"
android:label="activity_controlcenterv2_content_main"
tools:layout="@layout/activity_controlcenterv2_content_main" >
<action
android:id="@+id/devices_to_menu"
app:destination="@id/bottom_nav_menu" />
<action
android:id="@+id/devices_to_dashboard"
app:destination="@id/bottom_nav_dashboard" />
</fragment>
<fragment
android:id="@+id/bottom_nav_menu"
android:name="nodomain.freeyourgadget.gadgetbridge.activities.MainMenuFragment"
android:label="fragment_main_menu"
tools:layout="@layout/fragment_main_menu" >
<action
android:id="@+id/menu_to_dashboard"
app:destination="@id/bottom_nav_dashboard" />
<action
android:id="@+id/menu_to_devices"
app:destination="@id/bottom_nav_devices" />
</fragment>
</navigation>