mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-26 20:06:52 +01:00
Add First Start screens with permissions screen
This commit is contained in:
parent
126102aa05
commit
5b515319bc
@ -150,6 +150,14 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.welcome.WelcomeActivity"
|
||||
android:label="Welcome"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.PermissionsActivity"
|
||||
android:label="Permissions"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
|
@ -21,46 +21,29 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Dialog;
|
||||
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.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.core.view.MenuProvider;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
@ -70,26 +53,22 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
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.activities.discovery.DiscoveryActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
@ -98,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
|
||||
@ -105,16 +85,11 @@ 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 boolean isLanguageInvalid = false;
|
||||
private boolean isThemeInvalid = false;
|
||||
private ViewPager2 viewPager;
|
||||
private FragmentStateAdapter pagerAdapter;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private static PhoneStateListener fakeStateListener;
|
||||
private AlertDialog clDialog;
|
||||
|
||||
//needed for KK compatibility
|
||||
@ -140,12 +115,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
handleRealtimeSample(device, intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
|
||||
break;
|
||||
case ACTION_REQUEST_PERMISSIONS:
|
||||
checkAndRequestPermissions();
|
||||
break;
|
||||
case ACTION_REQUEST_LOCATION_PERMISSIONS:
|
||||
checkAndRequestLocationPermissions();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (dev != null && !dev.isBusy()) {
|
||||
@ -310,79 +279,21 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE);
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
||||
filterLocal.addAction(ACTION_REQUEST_PERMISSIONS);
|
||||
filterLocal.addAction(ACTION_REQUEST_LOCATION_PERMISSIONS);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
/*
|
||||
* Ask for permission to intercept notifications on first run.
|
||||
*/
|
||||
pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
|
||||
|
||||
boolean displayPermissionDialog = !prefs.getBoolean("permission_dialog_displayed", false);
|
||||
prefs.getPreferences().edit().putBoolean("permission_dialog_displayed", true).apply();
|
||||
|
||||
|
||||
Set<String> 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");
|
||||
// Open the Welcome flow on first run, only check permissions on next runs
|
||||
boolean firstRun = prefs.getBoolean("first_run", true);
|
||||
if (firstRun) {
|
||||
launchWelcomeActivity();
|
||||
} else {
|
||||
pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
|
||||
if (pesterWithPermissions && !PermissionsUtils.checkAllPermissions(this)) {
|
||||
Intent permissionsIntent = new Intent(this, PermissionsActivity.class);
|
||||
startActivity(permissionsIntent);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 = GBChangeLog.createChangeLog(this);
|
||||
boolean showChangelog = prefs.getBoolean("show_changelog", true);
|
||||
if (showChangelog && cl.isFirstRun() && cl.hasChanges(cl.isFirstRunEver())) {
|
||||
@ -473,6 +384,10 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
|
||||
|
||||
private void launchWelcomeActivity() {
|
||||
startActivity(new Intent(this, WelcomeActivity.class));
|
||||
}
|
||||
|
||||
private void launchDiscoveryActivity() {
|
||||
startActivity(new Intent(this, DiscoveryActivityV2.class));
|
||||
}
|
||||
@ -489,145 +404,6 @@ 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!");
|
||||
GB.toast(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<String> getWantedPermissions() {
|
||||
List<String> 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<String> 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<String> 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;
|
||||
@ -635,137 +411,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
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
|
||||
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<String[]> 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();
|
||||
}
|
||||
}
|
||||
|
||||
private class MainFragmentsPagerAdapter extends FragmentStateAdapter {
|
||||
public MainFragmentsPagerAdapter(FragmentActivity fa) {
|
||||
super(fa);
|
||||
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeFragmentPermissions;
|
||||
|
||||
public class PermissionsActivity extends AbstractGBActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_permissions);
|
||||
|
||||
WelcomeFragmentPermissions permissionsFragment = new WelcomeFragmentPermissions();
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction transaction = fragmentManager.beginTransaction();
|
||||
transaction.replace(R.id.fragment_container, permissionsFragment).commit();
|
||||
}
|
||||
}
|
@ -65,6 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryPairingPreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||
@ -437,6 +438,14 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
|
||||
});
|
||||
}
|
||||
|
||||
pref = findPreference("pref_show_first_run_screen");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
Intent enableIntent = new Intent(requireContext(), WelcomeActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
pref = findPreference("pref_discovery_pairing");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
|
@ -0,0 +1,83 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
|
||||
public class WelcomeActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeActivity.class);
|
||||
|
||||
private ViewPager2 viewPager;
|
||||
private WelcomeFragmentsPagerAdapter pagerAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_welcome);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
// Configure ViewPager2 with fragment adapter and default fragment
|
||||
viewPager = findViewById(R.id.welcome_viewpager);
|
||||
pagerAdapter = new WelcomeFragmentsPagerAdapter(this);
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
|
||||
// Set up welcome page indicator
|
||||
WelcomePageIndicator pageIndicator = findViewById(R.id.welcome_page_indicator);
|
||||
pageIndicator.setViewPager(viewPager);
|
||||
}
|
||||
|
||||
private class WelcomeFragmentsPagerAdapter extends FragmentStateAdapter {
|
||||
public WelcomeFragmentsPagerAdapter(FragmentActivity fa) {
|
||||
super(fa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new WelcomeFragmentIntro();
|
||||
case 1:
|
||||
return new WelcomeFragmentOverview();
|
||||
case 2:
|
||||
return new WelcomeFragmentDocsSource();
|
||||
case 3:
|
||||
return new WelcomeFragmentPermissions();
|
||||
default:
|
||||
return new WelcomeFragmentGetStarted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomeFragmentDocsSource extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentDocsSource.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
return inflater.inflate(R.layout.fragment_welcome_docs_source, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.DataManagementActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class WelcomeFragmentGetStarted extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentGetStarted.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.fragment_welcome_get_started, container, false);
|
||||
|
||||
Button firstDevice = view.findViewById(R.id.welcome_button_add_device);
|
||||
firstDevice.setOnClickListener(firstDeviceButton -> startActivity(new Intent(requireActivity(), DiscoveryActivityV2.class)));
|
||||
Button restore = view.findViewById(R.id.welcome_button_restore);
|
||||
restore.setOnClickListener(restoreButton -> startActivity(new Intent(requireActivity(), DataManagementActivity.class)));
|
||||
Button toApp = view.findViewById(R.id.welcome_button_to_app);
|
||||
toApp.setOnClickListener(toAppButton -> {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
prefs.getPreferences().edit().putBoolean("first_run", false).apply();
|
||||
requireActivity().finish();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomeFragmentIntro extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentIntro.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
return inflater.inflate(R.layout.fragment_welcome_intro, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomeFragmentOverview extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentOverview.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
return inflater.inflate(R.layout.fragment_welcome_overview, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
|
||||
|
||||
public class WelcomeFragmentPermissions extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentPermissions.class);
|
||||
|
||||
private RecyclerView permissionsListView;
|
||||
private PermissionAdapter permissionAdapter;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.fragment_welcome_permissions, container, false);
|
||||
|
||||
Button requestAllButton = view.findViewById(R.id.button_request_all);
|
||||
requestAllButton.setOnClickListener(v -> PermissionsUtils.requestAllPermissions(requireActivity()));
|
||||
|
||||
// Initialize RecyclerView and data
|
||||
permissionsListView = view.findViewById(R.id.permissions_list);
|
||||
|
||||
// Set up RecyclerView
|
||||
permissionAdapter = new PermissionAdapter(PermissionsUtils.getRequiredPermissionsList(requireActivity()), requireContext());
|
||||
permissionsListView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
permissionsListView.setAdapter(permissionAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
permissionAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private class PermissionHolder extends RecyclerView.ViewHolder {
|
||||
TextView titleTextView;
|
||||
TextView summaryTextView;
|
||||
ImageView checkmarkImageView;
|
||||
Button requestButton;
|
||||
|
||||
public PermissionHolder(View itemView) {
|
||||
super(itemView);
|
||||
titleTextView = itemView.findViewById(R.id.permission_title);
|
||||
summaryTextView = itemView.findViewById(R.id.permission_summary);
|
||||
checkmarkImageView = itemView.findViewById(R.id.permission_check);
|
||||
requestButton = itemView.findViewById(R.id.permission_request);
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionAdapter extends RecyclerView.Adapter<PermissionHolder> {
|
||||
private List<PermissionsUtils.PermissionDetails> permissionList;
|
||||
private Context context;
|
||||
|
||||
public PermissionAdapter(List<PermissionsUtils.PermissionDetails> permissionList, Context context) {
|
||||
this.permissionList = permissionList;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PermissionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.fragment_welcome_permission_row, parent, false);
|
||||
return new PermissionHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PermissionHolder holder, int position) {
|
||||
PermissionsUtils.PermissionDetails permissionData = permissionList.get(position);
|
||||
holder.titleTextView.setText(permissionData.getTitle());
|
||||
holder.summaryTextView.setText(permissionData.getSummary());
|
||||
if (PermissionsUtils.checkPermission(requireContext(), permissionData.getPermission())) {
|
||||
holder.requestButton.setVisibility(View.INVISIBLE);
|
||||
holder.requestButton.setEnabled(false);
|
||||
holder.checkmarkImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.requestButton.setOnClickListener(view -> {
|
||||
PermissionsUtils.requestPermission(requireActivity(), permissionData.getPermission());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return permissionList.size();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomePageIndicator extends View {
|
||||
private ViewPager2 viewPager;
|
||||
private int pageCount;
|
||||
private int dotRadius = 15;
|
||||
private int color;
|
||||
|
||||
private Paint outlinePaint;
|
||||
private Paint filledPaint;
|
||||
private float currentX = 0.0f;
|
||||
|
||||
private ValueAnimator dotAnimator;
|
||||
|
||||
public WelcomePageIndicator(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
determineColor(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
determineColor(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void determineColor(Context context, @Nullable AttributeSet attrs) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WelcomePageIndicator);
|
||||
color = a.getColor(R.styleable.WelcomePageIndicator_page_indicator_color, Color.BLACK);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
outlinePaint = new Paint();
|
||||
outlinePaint.setColor(color);
|
||||
outlinePaint.setStyle(Paint.Style.STROKE);
|
||||
outlinePaint.setStrokeWidth(4);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
filledPaint = new Paint();
|
||||
filledPaint.setColor(color);
|
||||
filledPaint.setStyle(Paint.Style.FILL);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public void setViewPager(ViewPager2 viewPager) {
|
||||
this.viewPager = viewPager;
|
||||
this.pageCount = viewPager.getAdapter().getItemCount();
|
||||
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
animateIndicator(position);
|
||||
}
|
||||
});
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private int getHorizontalMargin() {
|
||||
int dotDiameter = dotRadius * 2;
|
||||
int dotSpaces = pageCount * 2 - 1;
|
||||
return (getWidth() - dotSpaces * dotDiameter) / 2 + dotRadius;
|
||||
}
|
||||
|
||||
private void animateIndicator(int position) {
|
||||
float horizontalMargin = getHorizontalMargin();
|
||||
if (horizontalMargin <= 0.0f) {
|
||||
// Not animating because the drawable is not ready yet
|
||||
return;
|
||||
}
|
||||
float targetX = horizontalMargin + 4 * dotRadius * position;
|
||||
if (dotAnimator != null && dotAnimator.isRunning()) {
|
||||
dotAnimator.cancel();
|
||||
}
|
||||
if (currentX == 0.0f) currentX = horizontalMargin;
|
||||
dotAnimator = ValueAnimator.ofFloat(currentX, targetX);
|
||||
dotAnimator.addUpdateListener(animation -> {
|
||||
currentX = (float) animation.getAnimatedValue();
|
||||
invalidate();
|
||||
});
|
||||
dotAnimator.setDuration(300);
|
||||
dotAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (viewPager == null || pageCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float horizontalMargin = getHorizontalMargin();
|
||||
if (currentX == 0.0f && horizontalMargin != 0.0f) currentX = horizontalMargin;
|
||||
float circleY = getHeight() / 2f;
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
float circleX = horizontalMargin + 4 * dotRadius * i;
|
||||
canvas.drawCircle(circleX, circleY, dotRadius, outlinePaint);
|
||||
}
|
||||
canvas.drawCircle(currentX, circleY, dotRadius, filledPaint);
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
/* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class PermissionsUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PermissionsUtils.class);
|
||||
|
||||
private static final String CUSTOM_PERM_NOTIFICATION_LISTENER = "custom_perm_notifications_listener";
|
||||
private static final String CUSTOM_PERM_NOTIFICATION_SERVICE = "custom_perm_notifications_service";
|
||||
private static final String CUSTOM_PERM_DISPLAY_OVER = "custom_perm_display_over";
|
||||
|
||||
public static ArrayList<PermissionDetails> getRequiredPermissionsList(Activity activity) {
|
||||
ArrayList<PermissionDetails> permissionsList = new ArrayList<>();
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_NOTIFICATION_LISTENER,
|
||||
activity.getString(R.string.menuitem_notifications),
|
||||
"Forwarding notifications to connected gadgets"));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_NOTIFICATION_SERVICE,
|
||||
"Manage Do Not Disturb",
|
||||
"Change DND notification policy"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_DISPLAY_OVER,
|
||||
"Display over other apps",
|
||||
"Used by Bangle.js to start apps and other functionality on your phone"));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
|
||||
"Background location",
|
||||
"Required for scanning for Bluetooth devices"));
|
||||
}
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
"Fine location",
|
||||
"Send location to gadgets which don't have GPS"));
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH,
|
||||
"Bluetooth",
|
||||
"Connect to Bluetooth devices"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_ADMIN,
|
||||
"Bluetooth admin",
|
||||
"Discover and pair Bluetooth devices"));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
"Bluetooth scan",
|
||||
"Scan for Bluetooth devices"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
"Bluetooth connect",
|
||||
"Connect to Bluetooth devices"));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
"Post notifications",
|
||||
"Post ongoing notification which keeps the service running"));
|
||||
}
|
||||
if (BuildConfig.INTERNET_ACCESS) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.INTERNET,
|
||||
"Internet access",
|
||||
"Synchronization with online resources"));
|
||||
}
|
||||
// permissionsList.add(new PermissionDetails( // NOTE: can't request this, it's only allowed for system apps
|
||||
// Manifest.permission.MEDIA_CONTENT_CONTROL,
|
||||
// "Media content control",
|
||||
// "Read and control media playback"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
"Contacts",
|
||||
"Send contacts to gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CALENDAR,
|
||||
"Calendar",
|
||||
"Send calendar to gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.RECEIVE_SMS,
|
||||
"Receive SMS",
|
||||
"Forward SMS messages to gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.SEND_SMS,
|
||||
"Send SMS",
|
||||
"Send SMS (canned response) from gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CALL_LOG,
|
||||
"Read call log",
|
||||
"Forward call log to gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
"Read phone state",
|
||||
"Read status of ongoing calls"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.CALL_PHONE,
|
||||
"Call phone",
|
||||
"Initiate phone calls from gadgets"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.PROCESS_OUTGOING_CALLS,
|
||||
"Process outgoing calls",
|
||||
"Read the number of an outgoing call to display it on a gadget"));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ANSWER_PHONE_CALLS,
|
||||
"Answer phone calls",
|
||||
"Answer phone calls from gadgets"));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
"External storage",
|
||||
"Using images, ringtones, app files and more"));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.QUERY_ALL_PACKAGES,
|
||||
"Query all packages",
|
||||
"Read names and icons of all installed apps"));
|
||||
}
|
||||
return permissionsList;
|
||||
}
|
||||
|
||||
public static boolean checkPermission(Context context, String permission) {
|
||||
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
||||
Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(context);
|
||||
return set.contains(context.getPackageName());
|
||||
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted();
|
||||
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return Settings.canDrawOverlays(context);
|
||||
} else {
|
||||
return ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkAllPermissions(Activity activity) {
|
||||
boolean result = true;
|
||||
for (PermissionDetails permission : getRequiredPermissionsList(activity)) {
|
||||
if (!checkPermission(activity, permission.getPermission())) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void requestAllPermissions(Activity activity) {
|
||||
List<PermissionDetails> wantedPermissions = getRequiredPermissionsList(activity);
|
||||
|
||||
if (!wantedPermissions.isEmpty()) {
|
||||
ArrayList<String> wantedPermissionsStrings = new ArrayList<>();
|
||||
for (PermissionDetails wantedPermission : wantedPermissions) {
|
||||
wantedPermissionsStrings.add(wantedPermission.getPermission());
|
||||
}
|
||||
if (!wantedPermissionsStrings.isEmpty()) {
|
||||
if (wantedPermissionsStrings.contains(CUSTOM_PERM_NOTIFICATION_LISTENER) && !checkPermission(activity, CUSTOM_PERM_NOTIFICATION_LISTENER))
|
||||
requestPermission(activity, CUSTOM_PERM_NOTIFICATION_LISTENER);
|
||||
if (wantedPermissionsStrings.contains(CUSTOM_PERM_NOTIFICATION_SERVICE) && !checkPermission(activity, CUSTOM_PERM_NOTIFICATION_SERVICE))
|
||||
requestPermission(activity, CUSTOM_PERM_NOTIFICATION_SERVICE);
|
||||
if (wantedPermissionsStrings.contains(CUSTOM_PERM_DISPLAY_OVER) && !checkPermission(activity, CUSTOM_PERM_DISPLAY_OVER))
|
||||
requestPermission(activity, CUSTOM_PERM_DISPLAY_OVER);
|
||||
ActivityCompat.requestPermissions(activity, wantedPermissionsStrings.toArray(new String[0]), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestPermission(Activity activity, String permission) {
|
||||
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
||||
try {
|
||||
activity.startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
GB.toast(activity, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE)) {
|
||||
try {
|
||||
activity.startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
GB.toast(activity, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("'Notification Policy' activity not found");
|
||||
}
|
||||
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER)) {
|
||||
activity.startActivity(new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{permission}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PermissionDetails {
|
||||
private String permission;
|
||||
private String title;
|
||||
private String summary;
|
||||
|
||||
public PermissionDetails(String permission, String title, String summary) {
|
||||
this.permission = permission;
|
||||
this.title = title;
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
10
app/src/main/res/layout/activity_permissions.xml
Normal file
10
app/src/main/res/layout/activity_permissions.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</RelativeLayout>
|
25
app/src/main/res/layout/activity_welcome.xml
Normal file
25
app/src/main/res/layout/activity_welcome.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.welcome.WelcomeActivity">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/welcome_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/welcome_page_indicator" />
|
||||
|
||||
<nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomePageIndicator
|
||||
android:id="@+id/welcome_page_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:page_indicator_color="?attr/colorPrimary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
43
app/src/main/res/layout/fragment_welcome_docs_source.xml
Normal file
43
app/src/main/res/layout/fragment_welcome_docs_source.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="Open Source"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Gadgetbridge is an open source app. It is developed by the community, for the community.\n\nAnyone is welcome to contribute via code, documentation, testing and donations.\n\nGadgetbridge contains no ads and no tracking. It works only locally on your Android device, so it is 100% privacy friendly.\n\nVisit our website for more information, documentation and links to our communication channels." />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:src="@drawable/ic_engineering"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
52
app/src/main/res/layout/fragment_welcome_get_started.xml
Normal file
52
app/src/main/res/layout/fragment_welcome_get_started.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/intro_gadgetbridge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="Get started"
|
||||
android:textSize="25sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="To get started, add your first device directly from this screen, restore a backup or start with a clean database." />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_add_device"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="Add first device" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_restore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Restore backup" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_to_app"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Go to the app" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
38
app/src/main/res/layout/fragment_welcome_intro.xml
Normal file
38
app/src/main/res/layout/fragment_welcome_intro.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="Welcome to\nGadgetbridge"
|
||||
android:textSize="30sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Break free from the proprietary apps and cloud services of gadget vendors." />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:background="@color/accent"
|
||||
android:src="@drawable/ic_launcher_foreground" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
64
app/src/main/res/layout/fragment_welcome_overview.xml
Normal file
64
app/src/main/res/layout/fragment_welcome_overview.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="Overview"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Gadgetbridge has two main views, each with their own purpose." />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:src="@drawable/ic_dashboard"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="The dashboard allows you to get a quick idea of how you're doing today. The calendar view shows the status of your goals over a whole month." />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_devices_other"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="The devices view shows all devices you have configured and their status, and gives access to device specific functions such as detailed charts, settings, apps and alarms." />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
47
app/src/main/res/layout/fragment_welcome_permission_row.xml
Normal file
47
app/src/main/res/layout/fragment_welcome_permission_row.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="50dp">
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/permission_request"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/permission_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:text="Permission name" />
|
||||
<TextView
|
||||
android:id="@+id/permission_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Permission summary" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/permission_request"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:text="Request" />
|
||||
<ImageView
|
||||
android:id="@+id/permission_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/cpv_preset_checked"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/holo_green_dark"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
37
app/src/main/res/layout/fragment_welcome_permissions.xml
Normal file
37
app/src/main/res/layout/fragment_welcome_permissions.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:textAlignment="center"
|
||||
android:text="Permissions"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Gadgetbridge needs a lot of permissions to perform all its functions. Review the permissions and their purposes below." />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_request_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Request all permissions"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/permissions_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp" />
|
||||
</LinearLayout>
|
6
app/src/main/res/values/wpi_attrs.xml
Normal file
6
app/src/main/res/values/wpi_attrs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<resources>
|
||||
<declare-styleable name="WelcomePageIndicator">
|
||||
<attr name="page_indicator_color" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -385,6 +385,10 @@
|
||||
android:summary="@string/pref_cache_weather_summary"
|
||||
android:title="@string/pref_cache_weather"
|
||||
app:iconSpaceReserved="false" />
|
||||
<Preference
|
||||
android:key="pref_show_first_run_screen"
|
||||
android:title="Show first run screen"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_screen_intent_api"
|
||||
|
Loading…
Reference in New Issue
Block a user