From 78ff154edc600a5f07a68d6165d74a73c12688bd Mon Sep 17 00:00:00 2001 From: OliE Date: Mon, 9 Dec 2019 18:26:00 +0100 Subject: [PATCH] App intro (#526) added App intro slides --- android_app/app/build.gradle | 10 +- android_app/app/src/main/AndroidManifest.xml | 2 + .../health/openscale/gui/MainActivity.java | 13 +- .../gui/activities/AppIntroActivity.java | 87 ++++ .../activities/BluetoothSettingsActivity.java | 396 ++++++++++++++++++ .../gui/activities/SettingsActivity.java | 4 - .../gui/preferences/BluetoothPreferences.java | 242 +---------- .../gui/slides/BluetoothIntroSlide.java | 106 +++++ .../gui/slides/MetricsIntroSlide.java | 68 +++ .../gui/slides/OpenSourceIntroSlide.java | 68 +++ .../gui/slides/PrivacyIntroSlide.java | 68 +++ .../gui/slides/SupportIntroSlide.java | 68 +++ .../openscale/gui/slides/UserIntroSlide.java | 165 ++++++++ .../gui/slides/WelcomeIntroSlide.java | 56 +++ .../openscale/gui/utils/PermissionHelper.java | 4 +- .../main/res/drawable-hdpi/ic_slide_group.png | Bin 0 -> 862 bytes .../res/drawable-hdpi/ic_slide_opensource.png | Bin 0 -> 1033 bytes .../res/drawable-hdpi/ic_slide_privacy.png | Bin 0 -> 1072 bytes .../res/drawable-hdpi/ic_slide_support.png | Bin 0 -> 756 bytes .../main/res/drawable-ldpi/ic_slide_group.png | Bin 0 -> 426 bytes .../res/drawable-ldpi/ic_slide_opensource.png | Bin 0 -> 475 bytes .../res/drawable-ldpi/ic_slide_privacy.png | Bin 0 -> 482 bytes .../res/drawable-ldpi/ic_slide_support.png | Bin 0 -> 338 bytes .../main/res/drawable-mdpi/ic_slide_group.png | Bin 0 -> 561 bytes .../res/drawable-mdpi/ic_slide_opensource.png | Bin 0 -> 641 bytes .../res/drawable-mdpi/ic_slide_privacy.png | Bin 0 -> 667 bytes .../res/drawable-mdpi/ic_slide_support.png | Bin 0 -> 503 bytes .../res/drawable-xhdpi/ic_slide_group.png | Bin 0 -> 1221 bytes .../drawable-xhdpi/ic_slide_opensource.png | Bin 0 -> 1356 bytes .../res/drawable-xhdpi/ic_slide_privacy.png | Bin 0 -> 1573 bytes .../res/drawable-xhdpi/ic_slide_support.png | Bin 0 -> 1071 bytes .../res/drawable-xxhdpi/ic_slide_group.png | Bin 0 -> 1923 bytes .../drawable-xxhdpi/ic_slide_opensource.png | Bin 0 -> 2080 bytes .../res/drawable-xxhdpi/ic_slide_privacy.png | Bin 0 -> 2409 bytes .../res/drawable-xxhdpi/ic_slide_support.png | Bin 0 -> 1577 bytes .../res/drawable-xxxhdpi/ic_slide_group.png | Bin 0 -> 2579 bytes .../drawable-xxxhdpi/ic_slide_opensource.png | Bin 0 -> 2870 bytes .../res/drawable-xxxhdpi/ic_slide_privacy.png | Bin 0 -> 3252 bytes .../res/drawable-xxxhdpi/ic_slide_support.png | Bin 0 -> 2084 bytes .../res/layout/activity_bluetoothsettings.xml | 41 ++ .../src/main/res/layout/slide_bluetooth.xml | 98 +++++ .../app/src/main/res/layout/slide_metrics.xml | 61 +++ .../src/main/res/layout/slide_opensource.xml | 61 +++ .../app/src/main/res/layout/slide_privacy.xml | 61 +++ .../app/src/main/res/layout/slide_support.xml | 61 +++ .../app/src/main/res/layout/slide_user.xml | 87 ++++ .../app/src/main/res/layout/slide_welcome.xml | 39 ++ .../app/src/main/res/values/strings.xml | 22 + 48 files changed, 1640 insertions(+), 248 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java create mode 100644 android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java create mode 100644 android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-hdpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-hdpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-ldpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-ldpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/drawable-mdpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-mdpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/drawable-xhdpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-xhdpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-xhdpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/drawable-xxhdpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-xxhdpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-xxhdpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_group.png create mode 100644 android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png create mode 100644 android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_privacy.png create mode 100644 android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_support.png create mode 100644 android_app/app/src/main/res/layout/activity_bluetoothsettings.xml create mode 100644 android_app/app/src/main/res/layout/slide_bluetooth.xml create mode 100644 android_app/app/src/main/res/layout/slide_metrics.xml create mode 100644 android_app/app/src/main/res/layout/slide_opensource.xml create mode 100644 android_app/app/src/main/res/layout/slide_privacy.xml create mode 100644 android_app/app/src/main/res/layout/slide_support.xml create mode 100644 android_app/app/src/main/res/layout/slide_user.xml create mode 100644 android_app/app/src/main/res/layout/slide_welcome.xml diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle index ed852f07..d0ea3d7f 100644 --- a/android_app/app/build.gradle +++ b/android_app/app/build.gradle @@ -60,7 +60,7 @@ android { } dependencies { - implementation 'com.google.android.material:material:1.1.0-beta01' + implementation 'com.google.android.material:material:1.2.0-alpha01' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.0.0' @@ -72,10 +72,12 @@ dependencies { implementation 'com.github.weliem:blessed-android:0.10' // CustomActivityOnCrash implementation 'cat.ereza:customactivityoncrash:2.2.0' + // AppIntro + implementation 'com.github.AppIntro:AppIntro:5.1.0' // Room - implementation 'androidx.room:room-runtime:2.2.0' - annotationProcessor 'androidx.room:room-compiler:2.2.0' - androidTestImplementation 'androidx.room:room-testing:2.2.0' + implementation 'androidx.room:room-runtime:2.2.1' + annotationProcessor 'androidx.room:room-compiler:2.2.1' + androidTestImplementation 'androidx.room:room-testing:2.2.1' // Timber implementation 'com.jakewharton.timber:timber:4.7.1' // Local unit tests diff --git a/android_app/app/src/main/AndroidManifest.xml b/android_app/app/src/main/AndroidManifest.xml index f1a01bce..a775fedc 100644 --- a/android_app/app/src/main/AndroidManifest.xml +++ b/android_app/app/src/main/AndroidManifest.xml @@ -38,6 +38,8 @@ + + diff --git a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java index a8a0d60f..3fa336d0 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java @@ -55,15 +55,15 @@ import com.health.openscale.core.OpenScale; import com.health.openscale.core.bluetooth.BluetoothCommunication; import com.health.openscale.core.datatypes.ScaleMeasurement; import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.gui.activities.AppIntroActivity; import com.health.openscale.gui.activities.BaseAppCompatActivity; +import com.health.openscale.gui.activities.BluetoothSettingsActivity; import com.health.openscale.gui.activities.DataEntryActivity; import com.health.openscale.gui.activities.SettingsActivity; -import com.health.openscale.gui.activities.UserSettingsActivity; import com.health.openscale.gui.fragments.GraphFragment; import com.health.openscale.gui.fragments.OverviewFragment; import com.health.openscale.gui.fragments.StatisticsFragment; import com.health.openscale.gui.fragments.TableFragment; -import com.health.openscale.gui.preferences.BluetoothPreferences; import java.io.File; import java.util.List; @@ -150,9 +150,8 @@ public class MainActivity extends BaseAppCompatActivity } if (prefs.getBoolean("firstStart", true)) { - Intent intent = new Intent(this, UserSettingsActivity.class); - intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST); - startActivity(intent); + Intent appIntroIntent = new Intent(this, AppIntroActivity.class); + startActivity(appIntroIntent); prefs.edit().putBoolean("firstStart", false).apply(); } @@ -479,9 +478,9 @@ public class MainActivity extends BaseAppCompatActivity } String deviceName = prefs.getString( - BluetoothPreferences.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""); + BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""); String hwAddress = prefs.getString( - BluetoothPreferences.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""); + BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""); if (!BluetoothAdapter.checkBluetoothAddress(hwAddress)) { setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost); diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java new file mode 100644 index 00000000..b7efb31b --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java @@ -0,0 +1,87 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.activities; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.github.paolorotolo.appintro.AppIntro; +import com.health.openscale.R; +import com.health.openscale.core.OpenScale; +import com.health.openscale.gui.slides.BluetoothIntroSlide; +import com.health.openscale.gui.slides.MetricsIntroSlide; +import com.health.openscale.gui.slides.OpenSourceIntroSlide; +import com.health.openscale.gui.slides.PrivacyIntroSlide; +import com.health.openscale.gui.slides.SupportIntroSlide; +import com.health.openscale.gui.slides.UserIntroSlide; +import com.health.openscale.gui.slides.WelcomeIntroSlide; + +public class AppIntroActivity extends AppIntro { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setBarColor(getResources().getColor(R.color.blue_normal)); + setWizardMode(true); + setBackButtonVisibilityWithDone(true); + + showSkipButton(true); + + addSlide(WelcomeIntroSlide.newInstance(R.layout.slide_welcome)); + addSlide(PrivacyIntroSlide.newInstance(R.layout.slide_privacy)); + addSlide(UserIntroSlide.newInstance(R.layout.slide_user)); + addSlide(OpenSourceIntroSlide.newInstance(R.layout.slide_opensource)); + addSlide(BluetoothIntroSlide.newInstance(R.layout.slide_bluetooth)); + addSlide(MetricsIntroSlide.newInstance(R.layout.slide_metrics)); + addSlide(SupportIntroSlide.newInstance(R.layout.slide_support)); + } + + @Override + public void onSkipPressed(Fragment currentFragment) { + super.onSkipPressed(currentFragment); + finish(); + checkUserCreation(); + } + + @Override + public void onDonePressed(Fragment currentFragment) { + super.onDonePressed(currentFragment); + finish(); + checkUserCreation(); + } + + @Override + public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) { + super.onSlideChanged(oldFragment, newFragment); + + if (newFragment instanceof WelcomeIntroSlide) { + showSkipButton(true); + } else { + showSkipButton(false); + } + } + + private void checkUserCreation() { + if (OpenScale.getInstance().getSelectedScaleUserId() == -1) { + Intent intent = new Intent(this, UserSettingsActivity.class); + intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST); + startActivity(intent); + } + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java new file mode 100644 index 00000000..d69cbd9c --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java @@ -0,0 +1,396 @@ +/* Copyright (C) 2019 olie.xdev +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see +*/ +package com.health.openscale.gui.activities; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.ScanResult; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RadioGroup; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.widget.Toolbar; +import androidx.core.graphics.drawable.DrawableCompat; + +import com.health.openscale.R; +import com.health.openscale.core.OpenScale; +import com.health.openscale.core.bluetooth.BluetoothCommunication; +import com.health.openscale.core.bluetooth.BluetoothFactory; +import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.core.utils.Converters; +import com.health.openscale.gui.preferences.BluetoothPreferences; +import com.health.openscale.gui.utils.ColorUtil; +import com.health.openscale.gui.utils.PermissionHelper; +import com.welie.blessed.BluetoothCentral; +import com.welie.blessed.BluetoothCentralCallback; +import com.welie.blessed.BluetoothPeripheral; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import timber.log.Timber; + +public class BluetoothSettingsActivity extends BaseAppCompatActivity { + private Context context; + + public static final int GET_SCALE_REQUEST = 150; + + public static final String PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME = "btDeviceName"; + public static final String PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS = "btHwAddress"; + + private Map foundDevices = new HashMap<>(); + + private LinearLayout deviceListView; + private TextView txtSearching; + private ProgressBar progressBar; + private Handler progressHandler; + private BluetoothCentral central; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_bluetoothsettings); + context = this; + + deviceListView = findViewById(R.id.deviceListView); + txtSearching = findViewById(R.id.txtSearching); + progressBar = findViewById(R.id.progressBar); + Toolbar toolbar = findViewById(R.id.bluetoothSettingToolbar); + setSupportActionBar(toolbar); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(R.string.label_bluetooth_title); + + if (PermissionHelper.requestBluetoothPermission(this)) { + if (PermissionHelper.requestLocationServicePermission(this)) { + startBluetoothDiscovery(); + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // Override the default behaviour in order to return to the correct fragment + // (e.g. the table view) and not always go to the overview. + case android.R.id.home: + stopBluetoothDiscovery(); + onBackPressed(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private static final String formatDeviceName(String name, String address) { + if (name.isEmpty() || address.isEmpty()) { + return "-"; + } + return String.format("%s [%s]", name, address); + } + + private static final String formatDeviceName(BluetoothDevice device) { + return formatDeviceName(device.getName(), device.getAddress()); + } + + private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() { + @Override + public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) { + onDeviceFound(scanResult); + } + }; + + private void startBluetoothDiscovery() { + deviceListView.removeAllViews(); + foundDevices.clear(); + + central = new BluetoothCentral(getApplicationContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper())); + central.scanForPeripherals(); + + txtSearching.setVisibility(View.VISIBLE); + txtSearching.setText(R.string.label_bluetooth_searching); + progressBar.setVisibility(View.VISIBLE); + + progressHandler = new Handler(); + + // Don't let the BLE discovery run forever + progressHandler.postDelayed(new Runnable() { + @Override + public void run() { + stopBluetoothDiscovery(); + + txtSearching.setText(R.string.label_bluetooth_searching_finished); + progressBar.setVisibility(View.GONE); + + BluetoothDeviceView notSupported = new BluetoothDeviceView(context); + notSupported.setDeviceName(getString(R.string.label_scale_not_supported)); + notSupported.setSummaryText(getString(R.string.label_click_to_help_add_support)); + notSupported.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent notSupportedIntent = new Intent(Intent.ACTION_VIEW); + notSupportedIntent.setData( + Uri.parse("https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale")); + + startActivity(notSupportedIntent); + } + }); + deviceListView.addView(notSupported); + } + }, 20 * 1000); + } + + private void stopBluetoothDiscovery() { + if (progressHandler != null) { + progressHandler.removeCallbacksAndMessages(null); + progressHandler = null; + } + + if (central != null) { + central.stopScan(); + } + } + + private void onDeviceFound(final ScanResult bleScanResult) { + BluetoothDevice device = bleScanResult.getDevice(); + + if (device.getName() == null || foundDevices.containsKey(device.getAddress())) { + return; + } + + BluetoothDeviceView deviceView = new BluetoothDeviceView(this); + deviceView.setDeviceName(formatDeviceName(bleScanResult.getDevice())); + + BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(this, device.getName()); + if (btDevice != null) { + Timber.d("Found supported device %s (driver: %s)", + formatDeviceName(device), btDevice.driverName()); + deviceView.setOnClickListener(new onClickListenerDeviceSelect()); + deviceView.setDeviceAddress(device.getAddress()); + deviceView.setIcon(R.drawable.ic_bluetooth_device_supported); + deviceView.setSummaryText(btDevice.driverName()); + } + else { + Timber.d("Found unsupported device %s", + formatDeviceName(device)); + deviceView.setIcon(R.drawable.ic_bluetooth_device_not_supported); + deviceView.setSummaryText(getString(R.string.label_bt_device_no_support)); + deviceView.setEnabled(false); + + if (OpenScale.DEBUG_MODE) { + deviceView.setEnabled(true); + deviceView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getDebugInfo(device); + } + }); + } + } + + foundDevices.put(device.getAddress(), btDevice != null ? device : null); + deviceListView.addView(deviceView); + } + + private class onClickListenerDeviceSelect implements View.OnClickListener { + @Override + public void onClick(View view) { + BluetoothDeviceView deviceView = (BluetoothDeviceView)view; + + BluetoothDevice device = foundDevices.get(deviceView.getDeviceAddress()); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + prefs.edit() + .putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getAddress()) + .putString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, device.getName()) + .apply(); + + finishActivity(GET_SCALE_REQUEST); + } + } + + private void getDebugInfo(final BluetoothDevice device) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Fetching info") + .setMessage("Please wait while we fetch extended info from your scale...") + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + OpenScale.getInstance().disconnectFromBluetoothDevice(); + dialog.dismiss(); + } + }); + + final AlertDialog dialog = builder.create(); + + Handler btHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (BluetoothCommunication.BT_STATUS.values()[msg.what]) { + case CONNECTION_LOST: + OpenScale.getInstance().disconnectFromBluetoothDevice(); + dialog.dismiss(); + break; + } + } + }; + + dialog.show(); + + String macAddress = device.getAddress(); + stopBluetoothDiscovery(); + OpenScale.getInstance().connectToBluetoothDeviceDebugMode(macAddress, btHandler); + } + + private class BluetoothDeviceView extends LinearLayout { + + private TextView deviceName; + private ImageView deviceIcon; + private String deviceAddress; + + public BluetoothDeviceView(Context context) { + super(context); + + deviceName = new TextView(context); + deviceName.setLines(2); + deviceIcon = new ImageView(context); + + addView(deviceIcon); + addView(deviceName); + } + + public void setDeviceAddress(String address) { + deviceAddress = address; + } + + public String getDeviceAddress() { + return deviceAddress; + } + + public void setDeviceName(String name) { + deviceName.setText(name); + } + + public void setSummaryText(String text) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(new String()); + + stringBuilder.append(deviceName.getText()); + stringBuilder.append("\n"); + + int deviceNameLength = deviceName.getText().length(); + + stringBuilder.append(text); + stringBuilder.setSpan(new ForegroundColorSpan(Color.GRAY), deviceNameLength, deviceNameLength + text.length()+1, + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + stringBuilder.setSpan(new RelativeSizeSpan(0.8f), deviceNameLength, deviceNameLength + text.length()+1, + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + deviceName.setText(stringBuilder); + } + + public void setIcon(int resId) { + deviceIcon.setImageResource(resId); + + int tintColor = ColorUtil.getTextColor(getApplicationContext()); + deviceIcon.setColorFilter(tintColor, PorterDuff.Mode.SRC_IN); + } + + @Override + public void setOnClickListener(OnClickListener listener) { + super.setOnClickListener(listener); + deviceName.setOnClickListener(listener); + deviceIcon.setOnClickListener(listener); + } + + @Override + public void setEnabled(boolean status) { + super.setEnabled(status); + deviceName.setEnabled(status); + deviceIcon.setEnabled(status); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PermissionHelper.ENABLE_BLUETOOTH_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + if (PermissionHelper.requestBluetoothPermission(this)) { + startBluetoothDiscovery(); + } + } + else { + onBackPressed(); + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + switch (requestCode) { + case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (PermissionHelper.requestLocationServicePermission(this)) { + startBluetoothDiscovery(); + } + } else { + Toast.makeText(this, R.string.permission_not_granted, Toast.LENGTH_SHORT).show(); + onBackPressed(); + } + break; + } + } + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java index 39340003..78681de0 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java @@ -105,10 +105,6 @@ public class SettingsActivity extends PreferenceActivity public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { // TODO HACK to call RequestPermissionResult(...) in PreferenceFragment otherwise API level > 23 is required switch(requestCode) { - case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: - BluetoothPreferences bluetoothPreferences = (BluetoothPreferences)currentFragment; - bluetoothPreferences.onMyOwnRequestPermissionsResult(requestCode, permissions, grantResults); - break; case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_READ_STORAGE: case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_WRITE_STORAGE: BackupPreferences backupPreferences = (BackupPreferences)currentFragment; diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java index e3382cc1..bfc8bb1e 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java @@ -40,6 +40,8 @@ import com.health.openscale.R; import com.health.openscale.core.OpenScale; import com.health.openscale.core.bluetooth.BluetoothCommunication; import com.health.openscale.core.bluetooth.BluetoothFactory; +import com.health.openscale.gui.activities.BluetoothSettingsActivity; +import com.health.openscale.gui.activities.UserSettingsActivity; import com.health.openscale.gui.utils.ColorUtil; import com.health.openscale.gui.utils.PermissionHelper; import com.welie.blessed.BluetoothCentral; @@ -51,18 +53,13 @@ import java.util.Map; import timber.log.Timber; +import static android.app.Activity.RESULT_OK; + public class BluetoothPreferences extends PreferenceFragment { private static final String PREFERENCE_KEY_BLUETOOTH_SCANNER = "btScanner"; - public static final String PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME = "btDeviceName"; - public static final String PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS = "btHwAddress"; - private PreferenceScreen btScanner; - private Map foundDevices = new HashMap<>(); - - private Handler progressHandler; - private BluetoothCentral central; private static final String formatDeviceName(String name, String address) { if (name.isEmpty() || address.isEmpty()) { @@ -71,141 +68,11 @@ public class BluetoothPreferences extends PreferenceFragment { return String.format("%s [%s]", name, address); } - private static final String formatDeviceName(BluetoothDevice device) { - return formatDeviceName(device.getName(), device.getAddress()); - } - private String getCurrentDeviceName() { SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); return formatDeviceName( - prefs.getString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""), - prefs.getString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "")); - } - - private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() { - @Override - public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) { - onDeviceFound(scanResult); - } - }; - - private void startBluetoothDiscovery() { - foundDevices.clear(); - btScanner.removeAll(); - - central = new BluetoothCentral(getActivity().getApplicationContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper())); - central.scanForPeripherals(); - - final Preference scanning = new Preference(getActivity()); - scanning.setEnabled(false); - btScanner.addPreference(scanning); - btScanner.getPreference(0).setTitle(R.string.label_bluetooth_searching); - - progressHandler = new Handler(); - - // Draw a simple progressbar during the discovery/scanning - final int progressUpdatePeriod = 150; - final String[] blocks = {"▏","▎","▍","▌","▋","▊","▉","█"}; - scanning.setSummary(blocks[0]); - progressHandler.postDelayed(new Runnable() { - int index = 1; - @Override - public void run() { - String summary = scanning.getSummary() - .subSequence(0, scanning.getSummary().length() - 1).toString(); - if (index == blocks.length) { - summary += blocks[blocks.length - 1]; - index = 0; - } - summary += blocks[index++]; - scanning.setSummary(summary); - progressHandler.postDelayed(this, progressUpdatePeriod); - } - }, progressUpdatePeriod); - - // Don't let the BLE discovery run forever - progressHandler.postDelayed(new Runnable() { - @Override - public void run() { - stopBluetoothDiscovery(); - - Preference scanning = btScanner.getPreference(0); - scanning.setTitle(R.string.label_bluetooth_searching_finished); - scanning.setSummary(""); - - Intent notSupportedIntent = new Intent(Intent.ACTION_VIEW); - notSupportedIntent.setData( - Uri.parse("https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale")); - - Preference notSupported = new Preference(getActivity()); - notSupported.setTitle(R.string.label_scale_not_supported); - notSupported.setSummary(R.string.label_click_to_help_add_support); - notSupported.setIntent(notSupportedIntent); - btScanner.addPreference(notSupported); - } - }, 20 * 1000); - - // Abort discovery and scan if back is pressed or a scale is selected - btScanner.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - stopBluetoothDiscovery(); - } - }); - } - - private void stopBluetoothDiscovery() { - if (progressHandler != null) { - progressHandler.removeCallbacksAndMessages(null); - progressHandler = null; - } - - central.stopScan(); - } - - private void onDeviceFound(final ScanResult bleScanResult) { - BluetoothDevice device = bleScanResult.getDevice(); - - if (device.getName() == null || foundDevices.containsKey(device.getAddress())) { - return; - } - - Preference prefBtDevice = new Preference(getActivity()); - prefBtDevice.setTitle(formatDeviceName(bleScanResult.getDevice())); - - BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(getActivity(), device.getName()); - if (btDevice != null) { - Timber.d("Found supported device %s (driver: %s)", - formatDeviceName(device), btDevice.driverName()); - prefBtDevice.setOnPreferenceClickListener(new onClickListenerDeviceSelect()); - prefBtDevice.setKey(device.getAddress()); - prefBtDevice.setIcon(R.drawable.ic_bluetooth_device_supported); - prefBtDevice.setSummary(btDevice.driverName()); - - int tintColor = ColorUtil.getTextColor(getActivity().getApplicationContext()); - prefBtDevice.getIcon().setColorFilter(tintColor, PorterDuff.Mode.SRC_IN); - } - else { - Timber.d("Found unsupported device %s", - formatDeviceName(device)); - prefBtDevice.setIcon(R.drawable.ic_bluetooth_device_not_supported); - prefBtDevice.setSummary(R.string.label_bt_device_no_support); - prefBtDevice.setEnabled(false); - - if (OpenScale.DEBUG_MODE) { - prefBtDevice.setEnabled(true); - prefBtDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getDebugInfo(device); - return false; - } - }); - } - } - - foundDevices.put(device.getAddress(), btDevice != null ? device : null); - btScanner.addPreference(prefBtDevice); + prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""), + prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "")); } private void updateBtScannerSummary() { @@ -222,15 +89,11 @@ public class BluetoothPreferences extends PreferenceFragment { btScanner = (PreferenceScreen) findPreference(PREFERENCE_KEY_BLUETOOTH_SCANNER); - final Fragment fragment = this; btScanner.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - if (PermissionHelper.requestBluetoothPermission(getActivity(), fragment)) { - if (PermissionHelper.requestLocationServicePermission(getActivity())) { - startBluetoothDiscovery(); - } - } + Intent intent = new Intent(preference.getContext(), BluetoothSettingsActivity.class); + startActivityForResult(intent, BluetoothSettingsActivity.GET_SCALE_REQUEST); return true; } }); @@ -239,98 +102,15 @@ public class BluetoothPreferences extends PreferenceFragment { // Dummy preference to make screen open btScanner.addPreference(new Preference(getActivity())); - - } - - @Override - public void onStart() { - super.onStart(); - - // Restart discovery after e.g. orientation change - if (btScanner.getDialog() != null && btScanner.getDialog().isShowing()) { - startBluetoothDiscovery(); - } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PermissionHelper.ENABLE_BLUETOOTH_REQUEST) { - if (resultCode == Activity.RESULT_OK) { - if (PermissionHelper.requestBluetoothPermission(getActivity(), this)) { - startBluetoothDiscovery(); - } - } - else { - btScanner.getDialog().dismiss(); - } + if (resultCode != RESULT_OK) { + return; } - } - - private void getDebugInfo(final BluetoothDevice device) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle("Fetching info") - .setMessage("Please wait while we fetch extended info from your scale...") - .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - OpenScale.getInstance().disconnectFromBluetoothDevice(); - dialog.dismiss(); - } - }); - - final AlertDialog dialog = builder.create(); - - Handler btHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (BluetoothCommunication.BT_STATUS.values()[msg.what]) { - case CONNECTION_LOST: - OpenScale.getInstance().disconnectFromBluetoothDevice(); - dialog.dismiss(); - break; - } - } - }; - - dialog.show(); - - String macAddress = device.getAddress(); - stopBluetoothDiscovery(); - OpenScale.getInstance().connectToBluetoothDeviceDebugMode( - macAddress, btHandler); - } - - private class onClickListenerDeviceSelect implements Preference.OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(final Preference preference) { - BluetoothDevice device = foundDevices.get(preference.getKey()); - - preference.getSharedPreferences().edit() - .putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getAddress()) - .putString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, device.getName()) - .apply(); - + if (requestCode == BluetoothSettingsActivity.GET_SCALE_REQUEST) { updateBtScannerSummary(); - - btScanner.getDialog().dismiss(); - - return true; - } - } - - public void onMyOwnRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - switch (requestCode) { - case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (PermissionHelper.requestLocationServicePermission(getActivity())) { - startBluetoothDiscovery(); - } - } else { - Toast.makeText(getActivity(), R.string.permission_not_granted, Toast.LENGTH_SHORT).show(); - btScanner.getDialog().dismiss(); - } - break; - } } } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java new file mode 100644 index 00000000..fd730826 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java @@ -0,0 +1,106 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; +import com.health.openscale.gui.activities.BluetoothSettingsActivity; + +import static android.app.Activity.RESULT_OK; + +public class BluetoothIntroSlide extends Fragment { + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + private Button btnSearchScale; + private TextView txtFoundDevice; + + public static BluetoothIntroSlide newInstance(int layoutResId) { + BluetoothIntroSlide sampleSlide = new BluetoothIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + txtFoundDevice = view.findViewById(R.id.txtFoundDevice); + txtFoundDevice.setText(getCurrentDeviceName()); + + btnSearchScale = view.findViewById(R.id.btnSearchScale); + btnSearchScale.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getContext(), BluetoothSettingsActivity.class); + startActivityForResult(intent, BluetoothSettingsActivity.GET_SCALE_REQUEST); + } + }); + + return view; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != RESULT_OK) { + return; + } + if (requestCode == BluetoothSettingsActivity.GET_SCALE_REQUEST) { + txtFoundDevice.setText(getCurrentDeviceName()); + } + } + + private final String formatDeviceName(String name, String address) { + if (name.isEmpty() || address.isEmpty()) { + return "[" + getContext().getString(R.string.label_empty) + "]"; + } + return String.format("%s [%s]", name, address); + } + + private String getCurrentDeviceName() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + return formatDeviceName( + prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""), + prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "")); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java new file mode 100644 index 00000000..439f732f --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; + +public class MetricsIntroSlide extends Fragment { + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + private TextView slideMainText; + + public static MetricsIntroSlide newInstance(int layoutResId) { + MetricsIntroSlide sampleSlide = new MetricsIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + slideMainText = view.findViewById(R.id.slideMainText); + slideMainText.setLinksClickable(true); + slideMainText.setMovementMethod(LinkMovementMethod.getInstance()); + + return view; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java new file mode 100644 index 00000000..dff77219 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; + +public class OpenSourceIntroSlide extends Fragment { + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + private TextView slideMainText; + + public static OpenSourceIntroSlide newInstance(int layoutResId) { + OpenSourceIntroSlide sampleSlide = new OpenSourceIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + slideMainText = view.findViewById(R.id.slideMainText); + slideMainText.setLinksClickable(true); + slideMainText.setMovementMethod(LinkMovementMethod.getInstance()); + + return view; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java new file mode 100644 index 00000000..c3d32ad7 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; + +public class PrivacyIntroSlide extends Fragment { + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + private TextView slideMainText; + + public static PrivacyIntroSlide newInstance(int layoutResId) { + PrivacyIntroSlide sampleSlide = new PrivacyIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + slideMainText = view.findViewById(R.id.slideMainText); + slideMainText.setLinksClickable(true); + slideMainText.setMovementMethod(LinkMovementMethod.getInstance()); + + return view; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java new file mode 100644 index 00000000..531b7b8c --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; + +public class SupportIntroSlide extends Fragment { + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + private TextView slideMainText; + + public static SupportIntroSlide newInstance(int layoutResId) { + SupportIntroSlide sampleSlide = new SupportIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + slideMainText = view.findViewById(R.id.slideMainText); + slideMainText.setLinksClickable(true); + slideMainText.setMovementMethod(LinkMovementMethod.getInstance()); + + return view; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java new file mode 100644 index 00000000..4cef57ec --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java @@ -0,0 +1,165 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.health.openscale.R; +import com.health.openscale.core.OpenScale; +import com.health.openscale.core.datatypes.ScaleUser; +import com.health.openscale.gui.activities.UserSettingsActivity; + +import java.util.List; + +public class UserIntroSlide extends Fragment{ + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + private Button btnAddUser; + private TableLayout tblUsers; + + public static UserIntroSlide newInstance(int layoutResId) { + UserIntroSlide sampleSlide = new UserIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(layoutResId, container, false); + + btnAddUser = view.findViewById(R.id.btnAddUser); + tblUsers = view.findViewById(R.id.tblUsers); + + btnAddUser.setOnClickListener(new onBtnAddUserClickListener()); + + updateTableUsers(); + + return view; + } + + private class onBtnAddUserClickListener implements View.OnClickListener { + + @Override + public void onClick(View view) { + Intent intent = new Intent(getContext(), UserSettingsActivity.class); + intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST); + startActivityForResult(intent, 100); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + updateTableUsers(); + } + + private void updateTableUsers() { + tblUsers.removeAllViews(); + tblUsers.setStretchAllColumns(true); + + List scaleUserList = OpenScale.getInstance().getScaleUserList(); + + TableRow header = new TableRow(getContext()); + + TextView headerUsername = new TextView(getContext()); + headerUsername.setText(R.string.label_user_name); + headerUsername.setGravity(Gravity.CENTER_HORIZONTAL); + headerUsername.setTypeface(null, Typeface.BOLD); + header.addView(headerUsername); + + TextView headAge = new TextView(getContext()); + headAge.setText(R.string.label_age); + headAge.setGravity(Gravity.CENTER_HORIZONTAL); + headAge.setTypeface(null, Typeface.BOLD); + header.addView(headAge); + + TextView headerGender = new TextView(getContext()); + headerGender.setText(R.string.label_gender); + headerGender.setGravity(Gravity.CENTER_HORIZONTAL); + headerGender.setTypeface(null, Typeface.BOLD); + header.addView(headerGender); + + tblUsers.addView(header); + + if (!scaleUserList.isEmpty()) { + TableRow row = new TableRow(getContext()); + + for (ScaleUser scaleUser : scaleUserList) { + row = new TableRow(getContext()); + + TextView txtUsername = new TextView(getContext()); + txtUsername.setText(scaleUser.getUserName()); + txtUsername.setGravity(Gravity.CENTER_HORIZONTAL); + row.addView(txtUsername); + + TextView txtAge = new TextView(getContext()); + txtAge.setText(Integer.toString(scaleUser.getAge())); + txtAge.setGravity(Gravity.CENTER_HORIZONTAL); + row.addView(txtAge); + + TextView txtGender = new TextView(getContext()); + txtGender.setText((scaleUser.getGender().isMale()) ? getString(R.string.label_male) : getString(R.string.label_female)); + txtGender.setGravity(Gravity.CENTER_HORIZONTAL); + row.addView(txtGender); + + row.setGravity(Gravity.CENTER_HORIZONTAL); + + tblUsers.addView(row); + } + } else { + TableRow row = new TableRow(getContext()); + + TextView txtEmpty = new TextView(getContext()); + txtEmpty.setText("[" + getContext().getString(R.string.label_empty) + "]"); + txtEmpty.setGravity(Gravity.CENTER_HORIZONTAL); + row.addView(txtEmpty); + + row.setGravity(Gravity.CENTER_HORIZONTAL); + + tblUsers.addView(row); + } + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java new file mode 100644 index 00000000..f73e43c2 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2019 olie.xdev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +package com.health.openscale.gui.slides; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +public class WelcomeIntroSlide extends Fragment { + + private static final String ARG_LAYOUT_RES_ID = "layoutResId"; + private int layoutResId; + + public static WelcomeIntroSlide newInstance(int layoutResId) { + WelcomeIntroSlide sampleSlide = new WelcomeIntroSlide(); + + Bundle args = new Bundle(); + args.putInt(ARG_LAYOUT_RES_ID, layoutResId); + sampleSlide.setArguments(args); + + return sampleSlide; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) { + layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(layoutResId, container, false); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java b/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java index c40ae959..8d01ba21 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java @@ -43,7 +43,7 @@ public class PermissionHelper { public final static int ENABLE_BLUETOOTH_REQUEST = 5; - public static boolean requestBluetoothPermission(final Activity activity, Fragment fragment) { + public static boolean requestBluetoothPermission(final Activity activity) { final BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter btAdapter = bluetoothManager.getAdapter(); @@ -52,7 +52,7 @@ public class PermissionHelper { if (btAdapter != null) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - fragment.startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST); + activity.startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST); } return false; } diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png new file mode 100644 index 0000000000000000000000000000000000000000..52dd6daf3269228feebbc34deb0d31c10db52d1c GIT binary patch literal 862 zcmV-k1EKthP)4Evc4O91$yq_|co{qKF&@FV%=|f`NXz0h z#%9Ln;vsCuXV`!%1`Aq%FEInJV;7zjCfAxcM=`wtL6@gkI1lgP34D$&Tr2$S=Kg^3 z_z`OdEi?^B@N62d!U5&neGCra!8Cpb-(yOKQxVnZp{IkUSP zzhiP5@4?a>-gunCEri|gtFhob1MB8-{3P7C2v_14Jes-Ji<@fj_u_uy8}?yp4rw*M z$&6dDNz_C=2cD0g@tT2+vlEZy@F(L>u@k5yVwZtkbq#v(K;~Wr52K2I@Q$dDMIzm+ z@oAc`rg66BR^SA#%iJ4>t@t9#eL5}Bjg5E%e~HZXdq|t;x^CQytMEL&6}8nXJm)!K zA@_+)e&}rWXNX$qO9|MRI8~{sD(J*pxJMizt`~*49V^q@+=zEYO)O4tITW;Ei#VQi ziet$^tQQ%*P?W?8Y{u>Iej?VA_;o2u+D;8lBj77 zi0Q}0!dklOa2ATaPZb?CNG&)lzcb2W4UUPeBM%)(OI<*qe4`v^Cl3P)z*?Ox$Y7=^-gViDU&A|VgkvJ!=kpr}yC`EBw`a<9|{1THZ20%6kZ{mDJbrj1Axz3VOKpWS>bMf$5A;Ds)hPCl!Jf9uc z5>>f4jynoDb0&WVHpIi40>d+~KlI<%9Im6WQB@|!74WVN{r6ydJltPkcwgxLiOC)G zrb@k08}}CQJ`lRwS~cQ51%?Y^d`H@;zOWbX#{Ja=yt`wZ(o${vUj>F!V*If{cSnp< z3v~0^V_MSoQ}(U*#aPnQ%5Ud%!FVZAt}EcZG{&9qwDgC6QeX&=s1o1?sY}S{R)u^U4;OOg zcH}8i^PRKnv;yNUtg0=#v?jZ|Hox)&%VtbhHG~UcgsPS;FC>{=TeyDAnOmE`q>$^e z(BG$4oS%=$9m*>9=*q@FASH? z^=+w5X~Jk#h2O11Ql78+bRYEL)TWTX+7}643!RM(vJp}(%;&QO4LUuLD{sy-6Kk^Z zssrSr4ji}D793xjpQvY^D(Ntg5?b!!wqst*FIKgxP3QWIZYiU;RVUQ>QWWPy;zOm* zWGqqj%g6D$>h0DS*uNFJo5D5+!XfF!28HwRp%Z)NrJald_cYjT);WTUFia{5->S|~ zC7r=!)j@Ep^b*5P)|on5QcE$fKgAm6g-uge(1`y4T3Ip~Q|9gq00000NkvXXu0mjf D<5u&e literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png new file mode 100644 index 0000000000000000000000000000000000000000..1792caef6db1a201788104afa9ad5324646374d6 GIT binary patch literal 1072 zcmV-01kd}4P)2c?-)(qS2r-Q7TbOD7z7*RA?6o(xQuW zfe3mr!?!L_20?UrXvm~qBz!Q)f{H93tTB@$vdIw}(x|i=A+j_kJw2@TalSduIh}Fz z{9&=b^X;|Q{(oz)wf5eJ;b8<8 zPQw^HhM%%;KRPkS@qcWDZY!o%2H(%+7kaeLZq8n<9AzQpe( zgGcbS@XqwqZNp92Skl{w8?#U9KA&Tcl=ObfIo4ufnLwL?*n(NuiJok?;S&)fFd6sY zKAev~u`2r>z&Px}TPZ(g-&wdW?N7l}T!^nQr=Ewlrj#)nSK{gHE539G9>@5S?szQE zzQVIx@l@Iv-Nbx#73K?9_p7?P7cXIMBO{5qB5rz1=7wki*2G9&T1v0LyoTU zWwfQvka4VpKEotjKS=M~4={%qUvZzDo| zs>g}_*-orue7rd0^wbcVXyB2gjE-!7&i3@2vo4|CB74S(gA-g;$9PeR%Y=4jdqJHs z*JQh0oWnctLnA_)#1dVNk8nU78=585Q`1+ArT7CM;@X^lKzXPQ>JM-b_u_q#-pg>C z$e=O`FWijF^ZAShgzgaDeir}a9DnDW4Y67T^d%w|_T*PxRHL^-^(0s;D{6G7idfk# zu1hm=j#Zk#x%dm83lCl)yw>aJ*qDs-YGiVaPPej_F@ue^1?*&P+ z&*0ldBa9G7mHGMm#k9R_C`Q^;t8OuHwkUf2;s!G6e{3woy9OrC6KQju#+&n?7_E8< zd@c=~E$(O)e6PBFlrbUOMPgrTOIexYK5gP->S7Gmr=gEU(Q6Y2v*HfF8Mmf%iC`@! zzyy)86};`}yV;P@F0R8z@Jo^*isv(;q8HiKCoYA>cC#eM92UzpaKui>WF6>NMuKwwuDf|bOK?w2zJmtgy0000YaLz;VY zU%7W+f7opHUh7}`Kj-YT*E)y)Q6FBwcAUl(rtvTCV**>yS%Ke)t>Jq=xYH5qG`7VW zRbdl;LUEpU18e5suffeK__y%hd}2oMGzh<8Kh~lb%keJ0#>4P=f)8{2!I&%J0lvaJ z!R?J$`|*4DJjJKw#O%$fKaB1Q!h0~83%WU4b}^ITQx4rYl3Q=@Y=<{66UNaBf$~^{ zZ-VnES`jD1Rt-jj^EU?ahKpgCs4`Gq)D`JUFV`RjI&dbq7qZFMV`2uYY7`_mccRt9 zUoGvs0r)Zo<8=hf&=IX(Wc%@p0@lamxdsAekE~u~Yp*L{Wel!05U^2PkA7tVug2id zHi>~hqJOP`$1!-RO=6%c`o{&O6PeO!+Ad^k|60J0F<4*5xfR$D{STBfZxKJM?NDnL zI&nKRY=I?GvK(xKpdl$4XM4#=Oy0%IEfn(#eu=d|$r~0+x9z(Y3L2NT%YWYTx1@}} zuLWZENjdb*a!$iijk9Y3#SBWd)9`%Ghoow7`vQpBA!YZmY6u0X@cIY4nG%fx^Pyy z)1NmYB#-x%s)C|Y-L#?#&N6%-oN1*L)l|GKrI;!7SMco*pDZQJ^+DMb^hsZ}e_`Vs zJ{!YU4Bx(m5|g0^m!xCiqnvF>I_Zu8pO&IS>8+Xl6Jbmp mXG}A(6ZfKh&`u%G2c7{%lFfVP<2>U400007- literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png new file mode 100644 index 0000000000000000000000000000000000000000..db69c82b25cd2495ea0935326a2a7fd8374330f5 GIT binary patch literal 426 zcmV;b0agBqP)LNvOk`^?%6Pyv>B78+P!dtw=8s;#AQ=w}e2N()=DxuK|UZH_2SS|!h4IIZGT*nXG!0(K; zQm8$KMYQlO3}7iySin0R(HUjDmwwDk#h}{8MXdTi>4YeMGYN7TYI(?D85oOjpxT$;C#JBo-P(!5f7nl`1 U?w=FO@c;k-07*qoM6N<$fVK~y-6ebi4&RZ$cL@SpFMSy7G32SU`OKVXB9ut7vnpjbE;k|636 zM1}NF5n+R~FC35exnK{@T5IpS&%WoJF7-Hw zVO&BVw(u42@fN?zghsL6hY76VF`AgiN9@6q&>o<>Llcf+7N2mnlL^=GIl4PqiU+WO zDeT{+NbY|foo@3#$GhFytwFc6gJ%_S8ozO>L&DyOI-JRKAK_JvsU@}Fj+jDkjc*bU z8yLcLAvW;_|Ii9DR&rEZ;TO`e8>jCK}T`!8ej}44S zPsd-|4`6R_eevP%xK|dbFfI*xS-M9hW<%S=%}zxsw4`@uv_>11{#>gR{sRLxRCZJ- R?%)6b002ovPDHLkV1n_}()IuV literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png new file mode 100644 index 0000000000000000000000000000000000000000..ff9de10143d95f7b99370af6f45163d4a99b829f GIT binary patch literal 482 zcmV<80UiE{P)y#MNms96hugY zYh%rVYb}LS6cj=Q4PseDNk3jm!o`L_{0AC}-a?9o=e+VL_g?S|hkKv%{ho8r^W|L1 zu?=VOHsVa=O2s)ka0J6Rhk4uyZX8GP46|rpZF8exOrnV|c!_VgivG~dV+wmQgoD_D z8<=f18jI&DF5oa$a1S%VoeJF^EMPj`;jXOFSO|{A70b{OnuV-RTY>OhcK1T0Waj7xET zB&|f{L_g4lpQvSgOlIlPXT%cl7#ZFVDSH~ed(7>t6;xESN_48Doq!TrSvyuic!YVZJmZ~(XQ3McVB z;va6|erTGlCPmne2|S6>r|>3X9KCoNnyS@V$7w9#rsP{ardbMZOX$RP$UtP)EV(O%0O~fD}24djN zu#1J!D=>JNG@&Bhgw623O`4|9jgy?^+bOO*sa}k%9zA#1XoVc( z4Lg`b1{Ks$4%Z@^j-E2AIBGyv@e#wVD&Q+zXj9L`Y!R-p-{r~c;Z8chmALX;m&RV2 z$2|WK~zYIwbsjTO;HpE@ZY_?B#25xsd}|qk;bqZ?}33Bi-;JIh#>v| z8pcL~FcHPTBVxn|V-g0qjhg5U1COB)(SdhdgS{K)o}OOkXnm7&_WIV^-#+_Wd*|?< zQ7VwT3ae2^70;(ipc5A`iU%>a3A3>uZwf4O96B(B{tRwS+DIV8%pbIq}|xm1v3M<*4Nls0lt1Jng~UF|rb$XRsb`u>`yD8Lc@4T4QdPu%+)X z9}jUDw&S8d9o3IJar{h^&&RBoQ^H-WO>pP24NhWXs&FVi58-qZfpa(*-_w3=#;Gz! zgzXu_Vth%gq8)=+n8;a%E0`mk5%{LDWjCN!!A-o5tprR6fq4?b4;9GZO!BJMpTZ zKs^fdq=|}fMI1=PI`F=r0O17h#JWVRt&C$CqG?a^AgktRR$`xUu!>?Z);1u^gDJ&C zecr3lmptgr3btg#M)6Cr9|9Q0J^UzGH#z(UMrvFSL77r&00000NkvXXu0mjfL_YoB literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png new file mode 100644 index 0000000000000000000000000000000000000000..aee10d5fd3f67585b47602915a3fc75d5f5318a4 GIT binary patch literal 641 zcmV-{0)G98P)J zLSYuNclirg2!&E8B#Rj#GvhKF#%1Mo%`L{_{9c}!p6B)ATc_uFzrXMI_q^wv-}#+7 zf5sTJVmc-U_X54RjJGx9$i;kI#8>pkcpani`wK2$-rpo{#_34VheMcyQbtq4TpY%8 z{J<$`wpB3`5Ahkh(OAPDYQmoI^hAzajK@>F^p{f(%*U(9(K0wV6u0mWv;HA@1+(!U zH!&m|*dVR>ivLJn!RpAhzCx6wvb$D;xjDw4a9!H`5-f`^wpKB$M3>*?`2fqS=$C}9 z4Ngk6J+wf)CB*lzH^%+oHWhdpP{wh%gR=#~3HXFd7?$CO1@}=-#RSfyqdt;6FAy!2 z)^V3~qAU0+os%Z93_0k-qc`pw zF{v!Gv%DHy5!OY{Up?)vw3ZDy;u#^{n&GwuHzP;iAnjdemRuBu138AB5u29brb>6f zjvW2L&@UbwIDzk2HHg1~j+{iD5zAstnpjZ5glj<9XVChW&XX;*jf z5HF&bZzB`*Vm6*)0CyvJ9S&k&R1J-|jB^-@Bpa|Ci}41lM(*Z0nlKG5IE_Ah#rX(Y zMk6l7T5s64gl|*C8ijI#&Io)G>oK8{abQB&oQ&Od=1FiHtrgY_SQ=8Ou-+QJza=PE zsV<9!xQ-c8-`-)n)Xb`=R7oXV!wqQ&W}yRD^El>7(11laf#HzjSc3Zhk)Ui-lhM^k zM>VE@o&>M(2xlX3O}szA;8@Oio4ixHr8nby8&>CW%qAGdc>IWG7H_4>Ur0w&lqM;Q z0)B??&q@h~q=pV+?g;0pA?5k5i|?U~O+yqiC#%o)pbsl>PrCM5ER5>EhrE=CF^d+7 zyBa0>7=^8PjoR47?#$XQyq8$CJ>GYXSbvEr647lbFJ#%YAWg=Vc;1ql*@KyA&e#u2 z4Sz3DM~`%~S7Hy=%Jd_yzIHhTk3#F|Pms002ovPDHLkV1j#5 BFLwX{ literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png new file mode 100644 index 0000000000000000000000000000000000000000..e13a6efb7cf4f476285046787385dabf2dc304bd GIT binary patch literal 503 zcmV tW0!O{|BCf0Y^7ow?np-(B=r(SQCLD6VL~ENS|VM{(k!F2K(jP4r83Ra!`h$od73%rJD+E? zhxvmXX6<$Puf6wLd+$%W(TyWQOBeWT!_~L|!;)+>KF8bmvI}B11sCE2RMNlS@Dg@UkQDF*f;lxG9VpKr4R6>L&PF zgRM%o^wyF=&%|IXDQTa_60wDbmoYAzKwJ7=ID4lj|19I&59sD%i();avHnp$hd=Rldnlt^1B>< zf>-bto<^T)C%wfHU%|3Oa4e>ZHd#r@Wf+}gcQzq`yOXbxIoVT5_62TDT&~B`BwvAE zc_(wy!0ZkJ^ujx7{K_QjAF)cu|5U3WaE9IpQ!E^ z>ll443ObLT;*nCpGuroYE%e6A$!4+t?(=nGi`2raV&(c}f`#}K=XPRnLkf6L2V?!j zi!19IlhMBuy*u!I(%qaVki-3H{JKuKj208VC@(xi?C-6j3uNep+mgXLaaQDUF*c-t zn?zw%#O$mTb+bsU+}~+zdoGFfxTu3fH;4(HrhSE4FhLxyyTx_p5Jurw?FYv86m(Bc zwh@DJHn$|AJ24{#>VqkG9pB;)@%q{#w#!Xv5_*X!A^ zAXgNN#RRq0Qxu#ljXY?kii7Hj62yw)aj`w77d2=kfonv^Slb|}M)0wyuZarYuZkux zP%P`RdWucJQt`>A&@H!U0&QYOx^nh2WcRHibx=5glhg0khUqkppVIH3npz1(6Ug@C z-iGNkj_j&js0OmH5)(0|hTci(w^EW+BgkSrpmsMpok-v%^}op!x#u5kY!PquLYOXQ zrq(Jo3u98OLT5qQFLVDJbSH4E(Vf7tMn_+#YvC2~&h1K6e>kX#qWyiJ_;1pqaggm7 jZ}bJ?Lqj*Z(UtfY0h1bX8zk^N00000NkvXXu0mjfn`>Aw literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png new file mode 100644 index 0000000000000000000000000000000000000000..c86cc074d83cfd8e1101e3c8a3337c808c096f5f GIT binary patch literal 1356 zcmV-S1+)5zP)<2=D=^xd_6N`nbb77Z>x<%a0;`G{ z_$=Vl9M1OxF9OSf3nDaXFrV5c;6M(oR^ZY?2fqe03Dwa6umzZ(LU+EWF&NPP6SyaZ z?tjPtuLQ)$fUQ7XAsl#^0c`WM)z?d;SZGRtcLIVx0oSJtHVTV@p94PM_F{2D2J=Q# z=o8@lLPZ*Z=}ew$Z@P+X3?JXd6Zx3K5lE%`__ zz)2ys17*dFF)zx1FC?DhKy?(MC189}kpql_I^Z|5alM|)umO7+yW?y`7ZZ13e@Y9& zj6TF(asF@EMK-=2!NGd6Z-1N|Pw0p`V4>FvvFTlniERS2$Xup_o5()ht`ttHF*(s8 z>?Lpl_z|-_SEbP3EqWVu;HV7TolyK{$&@{O|Qv&o#N1REh=#lG14m`E( z$!Qf1efYk#56~{xE*q2Dw&%18N3C4|(wmf%rkuSQ9e7R>lt|(67KgFhSia+-bOLCK|VYp@k{_W0kL?odCB7wwK2w43u;;^d5%#TOyFP~g(_$ZWfYyM?_kG)am^~{N zW&$UK+y^WRv8%v-L-jGV>&1SM@Fn}kK=8*g|2;cl5%#(}f_cNbe65!8E(h%{;prJs z29w`tk#z!dDwhazF?FX-qR)VN-uQOu-|V2fS$NyMzInjs0bj>(&sHa4CX@dc9>!#n zz0!A$gXXou>-DsT13o%Q@Y&=-8RpbMQ(eqIy{4Va}e=rl=Q zuXTjq_F_)6i;iWOxiF4dNs>5S=yAs}r_M_Y7M%lT0~e>@&%iyYv+yrU*dmKw3Eg1; O0000kOK~!jg&Dnd5UPToE@ZT;}+SS$y*h00^A{ayyEJVBUMTsa;1ICDv z1Qj1VVvI__w`dR(t3ewdF_@SrL5#6c(TIr#LQ=FzrA0xJWs4Rx4W)`$Kr6Jn{ruyc z$-Uq0-F{!Yx3njj%=gWlIgdMY9&?6+14rULn8KFV=IJ$58#TUf!DSTgr_lg~;D+yb%0S^k>c@SHK;(G=u^-X&lU}<}B7ru%&VXU!s zTztvhEo*n-`&ijnGln-~4em}JGx7h%AmQPbME)1n;XC*k&ckzC@Q=^=dmGGthx5f3 zzN0bsXM7aLwa{CHx8UQ+dtJ)XOWB52;L;i9M{ou{E4F!B|5v^VXSS?cg`er)o#~`; z6;9WFq@^qetiXskE~{}XcG!Ds@4=N=JOusY#Mi&4h0c!Tu{wDcg=VF|yIL4Mh1-Oo zo;(cE8Q|okaa+=C@_FYV1%`==OR|3e-x6DXC!U;z;05j%#=8b{#nBwc2ZaZG-XH?| z6V^8g?{l!=N#Tmu3s1Ftm_-LGFPV?m3j@RfbwT`LTE30~d>4MpJAL;Z4@Y%Wf=AoI$WO`>olRD&9Yvh;0fKq!Y3) zKK?X=ccB!ec8J1Pf$cfQ;0Qccqw}}yUy%Jjn#gSmY|4H-`@2NBa17@RU|#OWlPzD6`JqHk8Un0#5po7d$3qg8JRznTr<#j2yU0ng0waq+c! z__qW5LT8HvuL<*nx9Q;_aaElatQ5&usZEx%e-i$nGS0PTHtr@Y5*Pg`{cj8lL~*w# z?$@2%o8fih+Sn7uSOlFb9vw=F{W0-`G_QrmT-EtLLe!AzDfjXCebU*9v$_(#z$-`F4C-7kx)odNf{+d5N!uAG%Ipx&`x_)7Iy8ih4u^H*$Ar?r$N z4&Y;Ks7XJL1MxhHAuPu|Li5TvhmF`DlICC$OzjtFi4OgvV1$mU`}V^z>R1ncEbwWC9hGWxaGY|8jhu6JDp4zOM|$pny9}@G3a|zYr*8U7{)h= zhzr6#KqDTLhX<>|{OhLak=U5|!mdpwW)d$d?|^+VZ!#%=zzkT2&%sBEqsVSt(`1>R zvsLl!9>jIZztStFX_#!6O^S1v@9{%&Kd!iUHX~Ung#K!GXwKr^-pOi?TX0w|{%}m| pIMdk=ONEQjG(Ku!h1AsF#MP|&FGK`{$aB*yED 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 KMNUMnLSTZjmIy)s literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png new file mode 100644 index 0000000000000000000000000000000000000000..47f4edca5141a0ec84a0c643bdf2b69d122cc4a6 GIT binary patch literal 2409 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 literal 0 HcmV?d00001 diff --git a/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png new file mode 100644 index 0000000000000000000000000000000000000000..9f7feaa30fb60e365b1210b5ce266d6d14b3e5f7 GIT binary patch literal 2870 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 Uh1VCEeE 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 O0000 + + + + + + + + + + diff --git a/android_app/app/src/main/res/layout/slide_bluetooth.xml b/android_app/app/src/main/res/layout/slide_bluetooth.xml new file mode 100644 index 00000000..3250aaae --- /dev/null +++ b/android_app/app/src/main/res/layout/slide_bluetooth.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + +