From 9c869761188bcbd76224720d9efc89de858d982c Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Thu, 11 May 2023 22:26:42 +0200 Subject: [PATCH] Introduce bottom navigation bar to replace drawer --- app/src/main/AndroidManifest.xml | 44 +- .../freeyourgadget/gadgetbridge/Widget.java | 4 +- .../activities/ControlCenterv2.java | 627 +----------------- .../activities/DashboardFragment.java | 19 + .../gadgetbridge/activities/MainActivity.java | 488 ++++++++++++++ .../activities/MainMenuFragment.java | 104 +++ .../adapter/GBDeviceAdapterv2.java | 6 +- .../devices/UnknownDeviceCoordinator.java | 4 +- .../lenovo/LenovoWatchPairingActivity.java | 4 +- .../devices/miband/MiBandPairingActivity.java | 4 +- .../devices/pebble/PebblePairingActivity.java | 4 +- .../devices/watch9/Watch9PairingActivity.java | 4 +- .../freeyourgadget/gadgetbridge/util/GB.java | 12 +- app/src/main/res/drawable/ic_dashboard.xml | 10 + .../activity_controlcenterv2_content_main.xml | 15 +- .../res/layout/activity_data_management.xml | 2 +- app/src/main/res/layout/activity_debug.xml | 2 +- app/src/main/res/layout/activity_main.xml | 31 + .../main/res/layout/fragment_dashboard.xml | 28 + ...rolcenterv2.xml => fragment_main_menu.xml} | 17 +- app/src/main/res/menu/bottom_nav_menu.xml | 17 + 21 files changed, 788 insertions(+), 658 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainMenuFragment.java create mode 100644 app/src/main/res/drawable/ic_dashboard.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_dashboard.xml rename app/src/main/res/layout/{activity_controlcenterv2.xml => fragment_main_menu.xml} (54%) create mode 100644 app/src/main/res/menu/bottom_nav_menu.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc014a9dc..26e01d812 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -111,7 +111,7 @@ android:theme="@style/GadgetbridgeTheme" tools:replace="android:label"> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> @@ -515,23 +515,23 @@ + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> @@ -566,7 +566,7 @@ android:label="@string/title_activity_appmanager" android:launchMode="singleTop" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|layoutDirection|uiMode" - android:parentActivityName=".activities.ControlCenterv2" /> + android:parentActivityName=".activities.MainActivity" /> @@ -588,15 +588,15 @@ + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> + android:parentActivityName=".activities.MainActivity" /> diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java index 42e64b636..5e861ad5d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Widget.java @@ -40,7 +40,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; import java.util.concurrent.TimeUnit; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -98,7 +98,7 @@ public class Widget extends AppWidgetProvider { views.setOnClickPendingIntent(R.id.todaywidget_header_container, refreshDataIntent); //open GB main window - Intent startMainIntent = new Intent(context, ControlCenterv2.class); + Intent startMainIntent = new Intent(context, MainActivity.class); PendingIntent startMainPIntent = PendingIntentUtils.getActivity(context, 0, startMainIntent, 0, false); views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent); 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 756539e56..963d9fe87 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -29,7 +29,6 @@ import android.app.NotificationManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -42,8 +41,11 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.TypedValue; import android.view.MenuItem; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; import android.view.View; -import android.widget.Toast; +import android.view.ViewGroup; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -61,13 +63,14 @@ import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.DialogFragment; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.navigation.NavigationView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,11 +80,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -93,149 +92,43 @@ 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.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.GBChangeLog; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -//TODO: extend AbstractGBActivity, but it requires actionbar that is not available -public class ControlCenterv2 extends AppCompatActivity - implements NavigationView.OnNavigationItemSelectedListener, GBActivity { - - private static final Logger LOG = LoggerFactory.getLogger(ControlCenterv2.class); - public static final int MENU_REFRESH_CODE = 1; - public static final String ACTION_REQUEST_PERMISSIONS - = "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions"; - public static final String ACTION_REQUEST_LOCATION_PERMISSIONS - = "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestlocationpermissions"; - private static PhoneStateListener fakeStateListener; - - //needed for KK compatibility - static { - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); - } +public class ControlCenterv2 extends Fragment { private DeviceManager deviceManager; private GBDeviceAdapterv2 mGBDeviceAdapter; private RecyclerView deviceListView; private FloatingActionButton fab; - private boolean isLanguageInvalid = false; - private boolean isThemeInvalid = false; List deviceList; private HashMap 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 GBApplication.ACTION_LANGUAGE_CHANGE: - setLanguage(GBApplication.getLanguage(), true); - break; - case GBApplication.ACTION_THEME_CHANGE: - isThemeInvalid = true; - break; - case GBApplication.ACTION_QUIT: - finish(); - break; - case DeviceManager.ACTION_DEVICES_CHANGED: - case GBApplication.ACTION_NEW_DATA: - createRefreshTask("get activity data", getApplication()).execute(); - mGBDeviceAdapter.rebuildFolders(); - 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; - - } - } - }; - private boolean pesterWithPermissions = true; - private ActivitySample currentHRSample; - - public ActivitySample getCurrentHRSample() { - return currentHRSample; - } - - private void setCurrentHRSample(ActivitySample sample) { - if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) { - currentHRSample = sample; - refreshPairedDevices(); - } - } - - private void handleRealtimeSample(Serializable extra) { - if (extra instanceof ActivitySample) { - ActivitySample sample = (ActivitySample) extra; - setCurrentHRSample(sample); - } - } - @Override - protected void onCreate(Bundle savedInstanceState) { - AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR); + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + View currentView = inflater.inflate(R.layout.activity_controlcenterv2_content_main, container, false); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_controlcenterv2); - MaterialToolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); + deviceManager = ((GBApplication) getActivity().getApplication()).getDeviceManager(); - if (GBApplication.areDynamicColorsEnabled()) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = getTheme(); - theme.resolveAttribute(R.attr.colorSurface, typedValue, true); - @ColorInt int toolbarBackground = typedValue.data; - toolbar.setBackgroundColor(toolbarBackground); - } else { - toolbar.setBackgroundColor(getResources().getColor(R.color.primarydark_light)); - toolbar.setTitleTextColor(getResources().getColor(android.R.color.white)); - } - - DrawerLayout drawer = findViewById(R.id.drawer_layout); - ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( - this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close); - drawer.setDrawerListener(toggle); - toggle.syncState(); - - /* This sucks but for the play store we're not allowed a donation link. Instead for - the Bangle.js Play Store app we put a message in the About dialog via @string/about_description */ - if (BuildConfig.FLAVOR == "banglejs") { - MenuItemImpl v = (MenuItemImpl) ((NavigationView) drawer.getChildAt(1)).getMenu().findItem(R.id.donation_link); - if (v != null) v.setVisible(false); - } - - NavigationView navigationView = findViewById(R.id.nav_view); - navigationView.setNavigationItemSelectedListener(this); - - //end of material design boilerplate - deviceManager = ((GBApplication) getApplication()).getDeviceManager(); - - deviceListView = findViewById(R.id.deviceListView); + deviceListView = currentView.findViewById(R.id.deviceListView); deviceListView.setHasFixedSize(true); - deviceListView.setLayoutManager(new LinearLayoutManager(this)); + deviceListView.setLayoutManager(new LinearLayoutManager(currentView.getContext())); deviceList = deviceManager.getDevices(); - mGBDeviceAdapter = new GBDeviceAdapterv2(this, deviceList, deviceActivityHashMap); + mGBDeviceAdapter = new GBDeviceAdapterv2(currentView.getContext(), deviceList, deviceActivityHashMap); mGBDeviceAdapter.setHasStableIds(true); - // get activity data asynchronously, this fills the deviceActivityHashMap - // and calls refreshPairedDevices() → notifyDataSetChanged - createRefreshTask("get activity data", getApplication()).execute(); - deviceListView.setAdapter(this.mGBDeviceAdapter); - fab = findViewById(R.id.fab); + // get activity data asynchronously, this fills the deviceActivityHashMap + // and calls refreshPairedDevices() → notifyDataSetChanged + createRefreshTask("get activity data", getActivity().getApplication()).execute(); + + fab = currentView.findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -282,211 +175,19 @@ public class ControlCenterv2 extends AppCompatActivity registerForContextMenu(deviceListView); - 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); - LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); - refreshPairedDevices(); - /* - * Ask for permission to intercept notifications on first run. - */ - Prefs prefs = GBApplication.getPrefs(); - pesterWithPermissions = prefs.getBoolean("permission_pestering", true); - - boolean displayPermissionDialog = !prefs.getBoolean("permission_dialog_displayed", false); - prefs.getPreferences().edit().putBoolean("permission_dialog_displayed", true).apply(); - - - Set set = NotificationManagerCompat.getEnabledListenerPackages(this); - if (pesterWithPermissions) { - if (!set.contains(this.getPackageName())) { // If notification listener access hasn't been granted - // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) - // When accepted, we open the Activity for Notification access - DialogFragment dialog = new NotifyListenerPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "NotifyListenerPermissionsDialogFragment"); - } - } - - /* We not put up dialogs explaining why we need permissions (Polite, but also Play Store policy). - - Rather than chaining the calls, we just open a bunch of dialogs. Last in this list = first - on the page, and as they are accepted the permissions are requested in turn. - - When accepted, we request it or open the Activity for permission to display over other apps. */ - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - /* In order to be able to set ringer mode to silent in GB's PhoneCallReceiver - the permission to access notifications is needed above Android M - ACCESS_NOTIFICATION_POLICY is also needed in the manifest */ - if (pesterWithPermissions) { - if (!((NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted()) { - // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) - // When accepted, we open the Activity for Notification access - DialogFragment dialog = new NotifyPolicyPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "NotifyPolicyPermissionsDialogFragment"); - } - } - - if (!Settings.canDrawOverlays(getApplicationContext())) { - // If diplay over other apps access hasn't been granted - // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) - // When accepted, we open the Activity for permission to display over other apps. - if (pesterWithPermissions) { - DialogFragment dialog = new DisplayOverOthersPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "DisplayOverOthersPermissionsDialogFragment"); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && - ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) { - if (pesterWithPermissions) { - DialogFragment dialog = new LocationPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "LocationPermissionsDialogFragment"); - } - } - - // Check all the other permissions that we need to for Android M + later - if (getWantedPermissions().isEmpty()) - displayPermissionDialog = false; - if (displayPermissionDialog && pesterWithPermissions) { - DialogFragment dialog = new PermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment"); - // when 'ok' clicked, checkAndRequestPermissions() is called - } else - checkAndRequestPermissions(); - } - - GBChangeLog cl = createChangeLog(); - final boolean showChangelog = prefs.getBoolean("show_changelog", true); - if (showChangelog && cl.isFirstRun() && cl.hasChanges(cl.isFirstRunEver())) { - try { - cl.getMaterialLogDialog().show(); - } catch (Exception ignored) { - GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR); - } - } - if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - launchDiscoveryActivity(); + startActivity(new Intent(getActivity(), DiscoveryActivity.class)); } else { GBApplication.deviceService().requestDeviceInfo(); } - } - @Override - protected void onResume() { - super.onResume(); - handleShortcut(getIntent()); - if (isLanguageInvalid || isThemeInvalid) { - isLanguageInvalid = false; - isThemeInvalid = false; - recreate(); - } - } - - @Override - protected void onDestroy() { - unregisterForContextMenu(deviceListView); - LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); - super.onDestroy(); - } - - @Override - public void onBackPressed() { - DrawerLayout drawer = findViewById(R.id.drawer_layout); - if (drawer.isDrawerOpen(GravityCompat.START)) { - drawer.closeDrawer(GravityCompat.START); - } else { - super.onBackPressed(); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == MENU_REFRESH_CODE) { - showFabIfNeccessary(); - } - } - - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - - DrawerLayout drawer = findViewById(R.id.drawer_layout); - drawer.closeDrawer(GravityCompat.START); - - switch (item.getItemId()) { - case R.id.action_settings: - Intent settingsIntent = new Intent(this, SettingsActivity.class); - startActivityForResult(settingsIntent, MENU_REFRESH_CODE); - return false; //we do not want the drawer menu item to get selected - case R.id.action_debug: - Intent debugIntent = new Intent(this, DebugActivity.class); - startActivity(debugIntent); - return false; - case R.id.action_data_management: - Intent dbIntent = new Intent(this, DataManagementActivity.class); - startActivity(dbIntent); - return false; - case R.id.action_notification_management: - Intent blIntent = new Intent(this, NotificationManagementActivity.class); - startActivity(blIntent); - return false; - case R.id.device_action_discover: - launchDiscoveryActivity(); - return false; - case R.id.action_quit: - GBApplication.quit(); - return false; - case R.id.donation_link: - Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://liberapay.com/Gadgetbridge")); //TODO: centralize if ever used somewhere else - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(i); - return false; - case R.id.external_changelog: - GBChangeLog cl = createChangeLog(); - try { - if (cl.hasChanges(false)) { - cl.getMaterialLogDialog().show(); - } else { - cl.getMaterialFullLogDialog().show(); - } - } catch (Exception ignored) { - GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR); - } - return false; - case R.id.about: - Intent aboutIntent = new Intent(this, AboutActivity.class); - startActivity(aboutIntent); - return false; - } - - return false; - } - - private GBChangeLog createChangeLog() { - String css = GBChangeLog.DEFAULT_CSS; - css += "body { " - + "color: " + AndroidUtils.getTextColorHex(getBaseContext()) + "; " - + "}"; - return new GBChangeLog(this, css); + return currentView; } private void launchDiscoveryActivity() { - startActivity(new Intent(this, DiscoveryActivityV2.class)); - } - - private void refreshPairedDevices() { - mGBDeviceAdapter.notifyDataSetChanged(); + startActivity(new Intent(getActivity(), DiscoveryActivity.class)); } private void showFabIfNeccessary() { @@ -501,150 +202,10 @@ public class ControlCenterv2 extends AppCompatActivity } } - private void checkAndRequestLocationPermissions() { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { - LOG.error("No permission to access background location!"); - toast(ControlCenterv2.this, getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR); - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0); - } - } - - @TargetApi(Build.VERSION_CODES.M) - private List getWantedPermissions() { - List wantedPermissions = new ArrayList<>(); - - if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.BLUETOOTH); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.BLUETOOTH_ADMIN); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_CONTACTS); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.CALL_PHONE); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_CALL_LOG); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_PHONE_STATE); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.RECEIVE_SMS); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_SMS); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.SEND_SMS); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.READ_CALENDAR); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); - - try { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED) - wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL); - } catch (Exception ignored) { - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (pesterWithPermissions) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS); - } - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); - } - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.QUERY_ALL_PACKAGES) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.QUERY_ALL_PACKAGES); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.BLUETOOTH_SCAN); - } - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.POST_NOTIFICATIONS); - } - } - - if (BuildConfig.INTERNET_ACCESS) { - if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) { - wantedPermissions.add(Manifest.permission.INTERNET); - } - } - - return wantedPermissions; - } - - @TargetApi(Build.VERSION_CODES.M) - private void checkAndRequestPermissions() { - List wantedPermissions = getWantedPermissions(); - - if (!wantedPermissions.isEmpty()) { - Prefs prefs = GBApplication.getPrefs(); - // If this is not the first run, we can rely on - // shouldShowRequestPermissionRationale(String permission) - // and ignore permissions that shouldn't or can't be requested again - if (prefs.getBoolean("permissions_asked", false)) { - // Don't request permissions that we shouldn't show a prompt for - // e.g. permissions that are "Never" granted by the user or never granted by the system - Set shouldNotAsk = new HashSet<>(); - for (String wantedPermission : wantedPermissions) { - if (!shouldShowRequestPermissionRationale(wantedPermission)) { - shouldNotAsk.add(wantedPermission); - } - } - wantedPermissions.removeAll(shouldNotAsk); - } else { - // Permissions have not been asked yet, but now will be - prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply(); - } - - if (!wantedPermissions.isEmpty()) { - GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); - } else { - requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0])); - } - } - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // The enclosed hack in it's current state cause crash on Banglejs builds tarkgetSDK=31 on a Android 13 device. - // HACK: On Lineage we have to do this so that the permission dialog pops up - if (fakeStateListener == null) { - fakeStateListener = new PhoneStateListener(); - TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE); - telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE); - } - } - } - - public void setLanguage(Locale language, boolean invalidateLanguage) { - if (invalidateLanguage) { - isLanguageInvalid = true; - } - AndroidUtils.setLanguage(this, language); + @Override + public void onDestroy() { + unregisterForContextMenu(deviceListView); + super.onDestroy(); } private long[] getSteps(GBDevice device, DBHandler db) { @@ -654,7 +215,12 @@ public class ControlCenterv2 extends AppCompatActivity return ds.getDailyTotalsForDevice(device, day, db); } - protected RefreshTask createRefreshTask(String task, Context context) { + public void refreshPairedDevices() { + mGBDeviceAdapter.notifyDataSetChanged(); + mGBDeviceAdapter.rebuildFolders(); + } + + public RefreshTask createRefreshTask(String task, Context context) { return new RefreshTask(task, context); } @@ -691,135 +257,4 @@ public class ControlCenterv2 extends AppCompatActivity } } - - /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity - public static class NotifyPolicyPermissionsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use the Builder class for convenient dialog construction - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - final Context context = getContext(); - builder.setMessage(context.getString(R.string.permission_notification_policy_access, - getContext().getString(R.string.app_name), - getContext().getString(R.string.ok))) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @RequiresApi(api = Build.VERSION_CODES.M) - public void onClick(DialogInterface dialog, int id) { - try { - startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)); - } catch (ActivityNotFoundException e) { - GB.toast(context, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR); - } - } - }); - return builder.create(); - } - } - - /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity - public static class NotifyListenerPermissionsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use the Builder class for convenient dialog construction - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - final Context context = getContext(); - builder.setMessage(context.getString(R.string.permission_notification_listener, - getContext().getString(R.string.app_name), - getContext().getString(R.string.ok))) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - try { - startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")); - } catch (ActivityNotFoundException e) { - GB.toast(context, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR); - } - } - }); - return builder.create(); - } - } - - /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity - public static class DisplayOverOthersPermissionsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use the Builder class for convenient dialog construction - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - Context context = getContext(); - builder.setMessage(context.getString(R.string.permission_display_over_other_apps, - getContext().getString(R.string.app_name), - getContext().getString(R.string.ok))) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @RequiresApi(api = Build.VERSION_CODES.M) - public void onClick(DialogInterface dialog, int id) { - Intent enableIntent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - startActivity(enableIntent); - } - }).setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) {} - }); - return builder.create(); - } - } - - - /// Called from onCreate - this puts up a dialog explaining we need backgound location permissions, and then requests permissions when 'ok' pressed - public static class LocationPermissionsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use the Builder class for convenient dialog construction - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - Context context = getContext(); - builder.setMessage(context.getString(R.string.permission_location, - getContext().getString(R.string.app_name), - getContext().getString(R.string.ok))) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Intent intent = new Intent(ACTION_REQUEST_LOCATION_PERMISSIONS); - LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); - } - }); - return builder.create(); - } - } - - // Register the permissions callback, which handles the user's response to the - // system permissions dialog. Save the return value, an instance of - // ActivityResultLauncher, as an instance variable. - // This is required here rather than where it is used because it'll cause a - // "LifecycleOwners must call register before they are STARTED" if not called from onCreate - public ActivityResultLauncher requestMultiplePermissionsLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> { - if (isGranted.containsValue(true)) { - // Permission is granted. Continue the action or workflow in your - // app. - } else { - // Explain to the user that the feature is unavailable because the - // feature requires a permission that the user has denied. At the - // same time, respect the user's decision. Don't link to system - // settings in an effort to convince the user to change their - // decision. - GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); - } - }); - - /// Called from onCreate - this puts up a dialog explaining we need permissions, and then requests permissions when 'ok' pressed - public static class PermissionsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use the Builder class for convenient dialog construction - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - Context context = getContext(); - builder.setMessage(context.getString(R.string.permission_request, - getContext().getString(R.string.app_name), - getContext().getString(R.string.ok))) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); - LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); - } - }); - return builder.create(); - } - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java new file mode 100644 index 000000000..b1135a94c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java @@ -0,0 +1,19 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; + +import nodomain.freeyourgadget.gadgetbridge.R; + +public class DashboardFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_dashboard, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainActivity.java new file mode 100644 index 000000000..cb7cce743 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainActivity.java @@ -0,0 +1,488 @@ +/* Copyright (C) 2016-2023 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, Johannes Tysiak, Taavi Eomäe, vanous, Arjan Schrijver + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.NotificationManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +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; +import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import com.google.android.material.bottomnavigation.BottomNavigationView; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +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.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class MainActivity extends AbstractGBActivity implements BottomNavigationView.OnNavigationItemSelectedListener, GBActivity { + public static final String ACTION_REQUEST_PERMISSIONS + = "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions"; + private boolean isLanguageInvalid = false; + private static PhoneStateListener fakeStateListener; + + BottomNavigationView bottomNavigationView; + DashboardFragment dashboardFragment = new DashboardFragment(); + ControlCenterv2 devicesFragment = new ControlCenterv2(); + MainMenuFragment mainMenuFragment = new MainMenuFragment(); + + //needed for KK compatibility + static { + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + switch (Objects.requireNonNull(action)) { + case GBApplication.ACTION_LANGUAGE_CHANGE: + setLanguage(GBApplication.getLanguage(), true); + break; + case GBApplication.ACTION_QUIT: + finish(); + break; + case DeviceManager.ACTION_DEVICES_CHANGED: + case GBApplication.ACTION_NEW_DATA: + if (devicesFragment.isResumed()) { + 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(false); + break; + } + } + }; + private boolean pesterWithPermissions = true; + private ActivitySample currentHRSample; + + public ActivitySample getCurrentHRSample() { + return currentHRSample; + } + + private void setCurrentHRSample(ActivitySample sample) { + if (HeartRateUtils.getInstance().isValidHeartRateValue(sample.getHeartRate())) { + currentHRSample = sample; + if (devicesFragment.isResumed()) { + devicesFragment.refreshPairedDevices(); + } + } + } + + private void handleRealtimeSample(Serializable extra) { + if (extra instanceof ActivitySample) { + ActivitySample sample = (ActivitySample) extra; + setCurrentHRSample(sample); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + bottomNavigationView = findViewById(R.id.bottom_nav_bar); + bottomNavigationView.setOnNavigationItemSelectedListener(this); + // TODO: read last view from savedInstanceState + bottomNavigationView.setSelectedItemId(R.id.bottom_nav_devices); + + IntentFilter filterLocal = new IntentFilter(); + filterLocal.addAction(GBApplication.ACTION_LANGUAGE_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); + LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); + + /* + * Ask for permission to intercept notifications on first run. + */ + Prefs prefs = GBApplication.getPrefs(); + pesterWithPermissions = prefs.getBoolean("permission_pestering", true); + + Set set = NotificationManagerCompat.getEnabledListenerPackages(this); + if (pesterWithPermissions) { + if (!set.contains(this.getPackageName())) { // If notification listener access hasn't been granted + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for Notification access + DialogFragment dialog = new MainActivity.NotifyListenerPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "NotifyListenerPermissionsDialogFragment"); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + /* In order to be able to set ringer mode to silent in GB's PhoneCallReceiver + the permission to access notifications is needed above Android M + ACCESS_NOTIFICATION_POLICY is also needed in the manifest */ + if (pesterWithPermissions) { + if (!((NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted()) { + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for Notification access + DialogFragment dialog = new MainActivity.NotifyPolicyPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "NotifyPolicyPermissionsDialogFragment"); + } + } + + if (!android.provider.Settings.canDrawOverlays(getApplicationContext())) { + // If diplay over other apps access hasn't been granted + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for permission to display over other apps. + if (pesterWithPermissions) { + DialogFragment dialog = new MainActivity.DisplayOverOthersPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "DisplayOverOthersPermissionsDialogFragment"); + } + } + + // Check all the other permissions that we need to for Android M + later + checkAndRequestPermissions(true); + } + } + + + @Override + protected void onResume() { + super.onResume(); + if (isLanguageInvalid) { + isLanguageInvalid = false; + recreate(); + } + } + + @Override + protected void onDestroy() { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); + super.onDestroy(); + } + + @Override + public boolean + onNavigationItemSelected(@NonNull MenuItem item) + { + // TODO: save current view so we can restore it in onCreate() + switch (item.getItemId()) { + case R.id.bottom_nav_dashboard: + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, dashboardFragment) + .commit(); + return true; + + case R.id.bottom_nav_devices: + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, devicesFragment) + .commit(); + return true; + + case R.id.bottom_nav_menu: + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, mainMenuFragment) + .commit(); + return true; + } + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + private void checkAndRequestPermissions(boolean showDialogFirst) { + List wantedPermissions = new ArrayList<>(); + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.BLUETOOTH); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.BLUETOOTH_ADMIN); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_CONTACTS); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.CALL_PHONE); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_CALL_LOG); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_PHONE_STATE); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.RECEIVE_SMS); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_SMS); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.SEND_SMS); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.READ_CALENDAR); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); + + try { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED) + wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL); + } catch (Exception ignored) { + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (pesterWithPermissions) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS); + } + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.QUERY_ALL_PACKAGES) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.QUERY_ALL_PACKAGES); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.BLUETOOTH_SCAN); + } + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.BLUETOOTH_CONNECT); + } + } + + if (BuildConfig.INTERNET_ACCESS) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.INTERNET); + } + } + + if (!wantedPermissions.isEmpty()) { + Prefs prefs = GBApplication.getPrefs(); + // If this is not the first run, we can rely on + // shouldShowRequestPermissionRationale(String permission) + // and ignore permissions that shouldn't or can't be requested again + if (prefs.getBoolean("permissions_asked", false)) { + // Don't request permissions that we shouldn't show a prompt for + // e.g. permissions that are "Never" granted by the user or never granted by the system + Set shouldNotAsk = new HashSet<>(); + for (String wantedPermission : wantedPermissions) { + if (!shouldShowRequestPermissionRationale(wantedPermission)) { + shouldNotAsk.add(wantedPermission); + } + } + wantedPermissions.removeAll(shouldNotAsk); + } else if (!showDialogFirst) { + // Permissions have not been asked yet, but now will be + prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply(); + } + + if (!wantedPermissions.isEmpty()) { + if (showDialogFirst) { + // Show a dialog - this will then call checkAndRequestPermissions(false) + DialogFragment dialog = new LocationPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "LocationPermissionsDialogFragment"); + //requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0])); + } else { + GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); + } else { + requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0])); + //ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); //Actually this still works if I test it, not sure if the new way is more reliable or not... + } + } + } + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // The enclosed hack in it's current state cause crash on Banglejs builds tarkgetSDK=31 on a Android 13 device. + // HACK: On Lineage we have to do this so that the permission dialog pops up + if (fakeStateListener == null) { + fakeStateListener = new PhoneStateListener(); + TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE); + telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE); + } + } + } + + public void setLanguage(Locale language, boolean invalidateLanguage) { + if (invalidateLanguage) { + isLanguageInvalid = true; + } + AndroidUtils.setLanguage(this, language); + } + + + /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity + public static class NotifyPolicyPermissionsDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_notification_policy_access, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @RequiresApi(api = Build.VERSION_CODES.M) + public void onClick(DialogInterface dialog, int id) { + try { + startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)); + } catch (ActivityNotFoundException e) { + GB.toast(context, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR); + } + } + }); + return builder.create(); + } + } + + /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity + public static class NotifyListenerPermissionsDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_notification_listener, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + try { + startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")); + } catch (ActivityNotFoundException e) { + GB.toast(context, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR); + } + } + }); + return builder.create(); + } + } + + /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity + public static class DisplayOverOthersPermissionsDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_display_over_other_apps, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @RequiresApi(api = Build.VERSION_CODES.M) + public void onClick(DialogInterface dialog, int id) { + Intent enableIntent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + startActivity(enableIntent); + } + }).setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) {} + }); + return builder.create(); + } + } + + + /// Called from checkAndRequestPermissions - this puts up a dialog explaining we need permissions, and then calls checkAndRequestPermissions (via an intent) when 'ok' pressed + public static class LocationPermissionsDialogFragment extends DialogFragment { + ControlCenterv2 controlCenter; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_location, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + }); + return builder.create(); + } + } + + // Register the permissions callback, which handles the user's response to the + // system permissions dialog. Save the return value, an instance of + // ActivityResultLauncher, as an instance variable. + public ActivityResultLauncher requestMultiplePermissionsLauncher = + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> { + if (isGranted.containsValue(true)) { + // Permission is granted. Continue the action or workflow in your + // app. + } else { + // Explain to the user that the feature is unavailable because the + // feature requires a permission that the user has denied. At the + // same time, respect the user's decision. Don't link to system + // settings in an effort to convince the user to change their + // decision. + GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); + } + }); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainMenuFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainMenuFragment.java new file mode 100644 index 000000000..7279183df --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/MainMenuFragment.java @@ -0,0 +1,104 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.view.menu.MenuItemImpl; +import androidx.fragment.app.Fragment; + +import com.google.android.material.navigation.NavigationView; + +import de.cketti.library.changelog.ChangeLog; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class MainMenuFragment extends Fragment implements NavigationView.OnNavigationItemSelectedListener { + public static final int MENU_REFRESH_CODE = 1; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View currentView = inflater.inflate(R.layout.fragment_main_menu, container, false); + + NavigationView navigationView = currentView.findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + /* This sucks but for the play store we're not allowed a donation link. Instead for + the Bangle.js Play Store app we put a message in the About dialog via @string/about_description */ + if (BuildConfig.FLAVOR == "banglejs") { + MenuItemImpl v = (MenuItemImpl) navigationView.getMenu().findItem(R.id.donation_link); + if (v != null) v.setVisible(false); + } + + return currentView; + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class); + startActivityForResult(settingsIntent, MENU_REFRESH_CODE); + return false; //we do not want the drawer menu item to get selected + case R.id.action_debug: + Intent debugIntent = new Intent(getActivity(), DebugActivity.class); + startActivity(debugIntent); + return false; + case R.id.action_data_management: + Intent dbIntent = new Intent(getActivity(), DataManagementActivity.class); + startActivity(dbIntent); + return false; + case R.id.action_notification_management: + Intent blIntent = new Intent(getActivity(), NotificationManagementActivity.class); + startActivity(blIntent); + return false; + case R.id.device_action_discover: + launchDiscoveryActivity(); + return false; + case R.id.action_quit: + GBApplication.quit(); + return false; + case R.id.donation_link: + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://liberapay.com/Gadgetbridge")); //TODO: centralize if ever used somewhere else + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + return false; + case R.id.external_changelog: + ChangeLog cl = createChangeLog(); + try { + cl.getLogDialog().show(); + } catch (Exception ignored) { + GB.toast(getActivity().getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR); + } + return false; + case R.id.about: + Intent aboutIntent = new Intent(getActivity(), AboutActivity.class); + startActivity(aboutIntent); + return false; + } + + return false; + } + + private ChangeLog createChangeLog() { + String css = ChangeLog.DEFAULT_CSS; + css += "body { " + + "color: " + AndroidUtils.getTextColorHex(getActivity().getBaseContext()) + "; " + + "background-color: " + AndroidUtils.getBackgroundColorHex(getActivity().getBaseContext()) + ";" + + "}"; + return new ChangeLog(getContext(), css); + } + + private void launchDiscoveryActivity() { + startActivity(new Intent(getActivity(), DiscoveryActivity.class)); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java index 455eaa649..57a2fb126 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java @@ -102,8 +102,8 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.OpenFwAppInstallerActivity; import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity; @@ -396,8 +396,8 @@ public class GBDeviceAdapterv2 extends ListAdapter getPairingActivity() { - return ControlCenterv2.class; + return MainActivity.class; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java index c845d92d9..0cae7c948 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -101,7 +101,7 @@ public class LenovoWatchPairingActivity extends AbstractGBActivity implements Bo @Override public void onBondingComplete(boolean success) { - startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + startActivity(new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); finish(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java index 644e26403..b1240d7de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java @@ -41,7 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AboutUserPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -173,7 +173,7 @@ public class MiBandPairingActivity extends AbstractGBActivity implements Bonding Prefs prefs = GBApplication.getPrefs(); prefs.getPreferences().edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, macAddress).apply(); } - Intent intent = new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Intent intent = new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } finish(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java index b3b18492b..c06944d3d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java @@ -41,7 +41,7 @@ import de.greenrobot.dao.query.Query; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; @@ -170,7 +170,7 @@ public class PebblePairingActivity extends AbstractGBActivity implements Bonding public void onBondingComplete(boolean success) { unregisterBroadcastReceivers(); if (success) { - startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + startActivity(new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); } else { startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java index afa7e7dc4..aaa16f4dd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9PairingActivity.java @@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -93,7 +93,7 @@ public class Watch9PairingActivity extends AbstractGBActivity implements Bonding @Override public void onBondingComplete(boolean success) { - startActivity(new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + startActivity(new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); finish(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index a151a64ec..60587e237 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -55,7 +55,7 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBEnvironment; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.MainActivity; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -157,7 +157,7 @@ public class GB { } private static PendingIntent getContentIntent(Context context) { - Intent notificationIntent = new Intent(context, ControlCenterv2.class); + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, @@ -465,7 +465,7 @@ public class GB { private static Notification createTransferNotification(String title, String text, boolean ongoing, int percentage, Context context) { - Intent notificationIntent = new Intent(context, ControlCenterv2.class); + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, @@ -501,7 +501,7 @@ public class GB { } public static void createGpsNotification(Context context, int numDevices) { - Intent notificationIntent = new Intent(context, ControlCenterv2.class); + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, notificationIntent, 0, false); @@ -523,7 +523,7 @@ public class GB { private static Notification createInstallNotification(String text, boolean ongoing, int percentage, Context context) { - Intent notificationIntent = new Intent(context, ControlCenterv2.class); + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, @@ -553,7 +553,7 @@ public class GB { } private static Notification createBatteryNotification(String text, String bigText, Context context) { - Intent notificationIntent = new Intent(context, ControlCenterv2.class); + Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, diff --git a/app/src/main/res/drawable/ic_dashboard.xml b/app/src/main/res/drawable/ic_dashboard.xml new file mode 100644 index 000000000..be3329b05 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_controlcenterv2_content_main.xml b/app/src/main/res/layout/activity_controlcenterv2_content_main.xml index 6dcd9ba27..5ef472b06 100644 --- a/app/src/main/res/layout/activity_controlcenterv2_content_main.xml +++ b/app/src/main/res/layout/activity_controlcenterv2_content_main.xml @@ -5,10 +5,7 @@ android:id="@+id/content_main" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior" - tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2" - tools:showIn="@layout/activity_controlcenterv2_app_bar_main"> - + tools:context=".activities.ControlCenterv2"> + + diff --git a/app/src/main/res/layout/activity_data_management.xml b/app/src/main/res/layout/activity_data_management.xml index d04c07c24..ff0c5fe22 100644 --- a/app/src/main/res/layout/activity_data_management.xml +++ b/app/src/main/res/layout/activity_data_management.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.MainActivity"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.MainActivity"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 000000000..caf1397de --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_controlcenterv2.xml b/app/src/main/res/layout/fragment_main_menu.xml similarity index 54% rename from app/src/main/res/layout/activity_controlcenterv2.xml rename to app/src/main/res/layout/fragment_main_menu.xml index 3246e894a..f163a0fd3 100644 --- a/app/src/main/res/layout/activity_controlcenterv2.xml +++ b/app/src/main/res/layout/fragment_main_menu.xml @@ -1,25 +1,16 @@ - - - + tools:context=".activities.MainMenuFragment"> - - + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 000000000..7674e9e9e --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file