From c67225b407f30c4e5eeac8cf55a9a55dcbdaa3d8 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Wed, 28 Feb 2024 20:31:10 +0100 Subject: [PATCH] Re-jig the welcome modal shown to 1st time users This allows some configuration directly in the modal including starting the authorization process when the modal is closed. Fixes https://github.com/MarcusWolschon/osmeditor4android/issues/1506 Fixes https://github.com/MarcusWolschon/osmeditor4android/issues/2430 --- .../java/de/blau/android/TestUtils.java | 6 +- .../java/de/blau/android/dialogs/Newbie.java | 171 ++++++++++++++--- .../de/blau/android/prefs/Preferences.java | 29 ++- src/main/java/de/blau/android/util/Util.java | 2 +- src/main/res/layout/welcome_tabs.xml | 175 ++++++++++++++++++ src/main/res/values/strings.xml | 36 +++- 6 files changed, 389 insertions(+), 30 deletions(-) create mode 100644 src/main/res/layout/welcome_tabs.xml diff --git a/src/androidTest/java/de/blau/android/TestUtils.java b/src/androidTest/java/de/blau/android/TestUtils.java index a306dbb79a..0f0eb1019e 100644 --- a/src/androidTest/java/de/blau/android/TestUtils.java +++ b/src/androidTest/java/de/blau/android/TestUtils.java @@ -98,7 +98,11 @@ public static void grantPermissons(@NonNull UiDevice device) { * @param ctx Android context */ public static void dismissStartUpDialogs(@NonNull UiDevice device, @NonNull Context ctx) { - clickText(device, true, ctx.getResources().getString(R.string.okay), false, false); + if (findText(device, false, ctx.getResources().getString(R.string.welcome_title))) { + clickText(device, true, ctx.getResources().getString(R.string.next), true, false); + clickResource(device, false, device.getCurrentPackageName() + ":id/authorize", false); + clickText(device, true, ctx.getResources().getString(R.string.welcome_start), true, false); + } } /** diff --git a/src/main/java/de/blau/android/dialogs/Newbie.java b/src/main/java/de/blau/android/dialogs/Newbie.java index 825cb46385..f6645d2d04 100644 --- a/src/main/java/de/blau/android/dialogs/Newbie.java +++ b/src/main/java/de/blau/android/dialogs/Newbie.java @@ -1,31 +1,59 @@ package de.blau.android.dialogs; +import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AppCompatDialog; +import androidx.appcompat.widget.SwitchCompat; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; +import androidx.preference.PreferenceManager; +import de.blau.android.App; +import de.blau.android.Authorize; import de.blau.android.HelpViewer; import de.blau.android.Main; import de.blau.android.R; +import de.blau.android.osm.ViewBox; +import de.blau.android.prefs.AdvancedPrefDatabase; +import de.blau.android.prefs.Preferences; +import de.blau.android.resources.DataStyle; +import de.blau.android.resources.TileLayerSource; +import de.blau.android.resources.TileLayerSource.Category; +import de.blau.android.resources.TileLayerSource.TileType; import de.blau.android.util.ImmersiveDialogFragment; +import de.blau.android.util.OnPageSelectedListener; +import de.blau.android.util.ThemeUtils; import de.blau.android.util.Util; +import de.blau.android.views.ExtendedViewPager; +import de.blau.android.views.layers.MapTilesLayer; /** * Display a dialog giving new users minimal instructions * */ public class Newbie extends ImmersiveDialogFragment { - private static final String DEBUG_TAG = Newbie.class.getSimpleName(); private static final String TAG = "fragment_newbie"; - private Main main; + private static final String PAGER_POS_KEY = "pagerPos"; + private static final String AUTHORIZE_KEY = "authorize"; + private static final String PEN_SETUP_KEY = "penSetup"; + private static final String AUTO_DOWNLOAD_KEY = "autoDownload"; + private static final String USE_IMAGERY_KEY = "useImagery"; + + private static final int SETTINGS_PAGE_INDEX = 1; + private static final int WELCOME_PAGE_INDEX = 0; /** * Display a dialog giving new users minimal instructions @@ -71,37 +99,136 @@ public void onAttach(Context context) { if (!(context instanceof Main)) { throw new ClassCastException(context.toString() + " can only be called from Main"); } - main = (Main) context; } @NonNull @Override public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { - Builder builder = new AlertDialog.Builder(getActivity()); + final FragmentActivity activity = getActivity(); + if (!(activity instanceof Main)) { + throw new ClassCastException(activity.toString() + " can only be called from Main"); + } + Builder builder = new AlertDialog.Builder(activity); builder.setIcon(null); builder.setTitle(R.string.welcome_title); + final LayoutInflater inflater = ThemeUtils.getLayoutInflater(activity); + final View layout = inflater.inflate(R.layout.welcome_tabs, null); + final SwitchCompat displayImagery = layout.findViewById(R.id.use_imagery); + final SwitchCompat autoDownload = layout.findViewById(R.id.auto_download); + final SwitchCompat penSetup = layout.findViewById(R.id.pen_setup); + final SwitchCompat authorize = layout.findViewById(R.id.authorize); + final ExtendedViewPager pager = (ExtendedViewPager) layout.findViewById(R.id.pager); + pager.setAdapter(new ViewPagerAdapter(activity, layout, new int[] { R.id.welcome_page, R.id.settings_page }, + new int[] { R.string.confirm_upload_edits_page, R.string.menu_tags })); + // set saved state before the on page change listener is set + if (savedInstanceState != null) { + displayImagery.setChecked(savedInstanceState.getBoolean(USE_IMAGERY_KEY)); + autoDownload.setChecked(savedInstanceState.getBoolean(AUTO_DOWNLOAD_KEY)); + penSetup.setChecked(savedInstanceState.getBoolean(PEN_SETUP_KEY)); + authorize.setChecked(savedInstanceState.getBoolean(AUTHORIZE_KEY, true)); + pager.setCurrentItem(savedInstanceState.getInt(PAGER_POS_KEY, 0)); + } + pager.addOnPageChangeListener((OnPageSelectedListener) position -> { + AlertDialog dialog = ((AlertDialog) getDialog()); + if (dialog == null) { + Log.e(DEBUG_TAG, "Dialog null"); + return; + } + Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); + positive.clearFocus(); + if (position == WELCOME_PAGE_INDEX) { + positive.setText(R.string.next); + positive.setOnClickListener((View v) -> pager.setCurrentItem(SETTINGS_PAGE_INDEX)); + negative.setText(R.string.skip); + negative.setOnClickListener((View v) -> getDialog().dismiss()); + } + if (position == SETTINGS_PAGE_INDEX) { + positive.setText(R.string.welcome_start); + positive.setOnClickListener((View v) -> { + ((Main) activity).gotoCurrentLocation(); + ((Main) activity).setFollowGPS(true); + + Preferences prefs = App.getPreferences(activity); + if (displayImagery.isChecked()) { + setBestBackground(activity); + } + + prefs.setPanAndZoomAutoDownload(autoDownload.isChecked()); + + boolean penConfig = penSetup.isChecked(); + prefs.setLargeDragArea(!penConfig); + prefs.setWayNodeDragging(penConfig); + prefs.setDataStyle(penConfig ? Preferences.DEFAULT_PEN_MAP_STYLE : Preferences.DEFAULT_MAP_STYLE); + + ((Main) activity).getMap().setPrefs(activity, prefs); + + Newbie.dismissDialog(activity); + + if (authorize.isChecked()) { + Authorize.startForResult(activity, null); + } + }); + negative.setText(R.string.back); + negative.setOnClickListener((View v) -> pager.setCurrentItem(0)); + } + }); + String message = getString(R.string.welcome_message); - if (main.isFullScreen()) { + if (((Main) activity).isFullScreen()) { message = message + getString(R.string.welcome_message_fullscreen); } - builder.setMessage(Util.fromHtml(message)); - builder.setPositiveButton(R.string.okay, (dialog, which) -> { - FragmentActivity activity = getActivity(); - if (activity instanceof Main) { - main.gotoCurrentLocation(); - main.setFollowGPS(true); - } else { - Log.e(DEBUG_TAG, "getActivity returned null in onClick"); - } + ((TextView) layout.findViewById(R.id.welcome_message)).setText(Util.fromHtml(message)); + builder.setView(layout); + + builder.setNegativeButton(R.string.skip, null); + builder.setPositiveButton(R.string.next, null); + builder.setNeutralButton(R.string.read_introduction, null); + AlertDialog dialog = builder.create(); + dialog.setOnShowListener((DialogInterface d) -> { + Button neutral = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); + neutral.setOnClickListener((View v) -> { + Context ctx = getActivity(); + if (ctx instanceof FragmentActivity) { + HelpViewer.start((FragmentActivity) ctx, R.string.help_introduction); + return; + } + Log.e(DEBUG_TAG, "Not a fragment activity"); + }); + Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + positive.setOnClickListener((View v) -> pager.setCurrentItem(SETTINGS_PAGE_INDEX)); }); - builder.setNeutralButton(R.string.read_introduction, (dialog, which) -> { - FragmentActivity activity = getActivity(); - if (activity != null) { - HelpViewer.start(activity, R.string.help_introduction); - } else { - Log.e(DEBUG_TAG, "getActivity returned null in onClick"); + return dialog; + } + + /** + * Set the best background for the current ViewBox + * + * @param activity the current activity + */ + private void setBestBackground(@NonNull final FragmentActivity activity) { + final String[] ids = TileLayerSource.getIds(App.getLogic().getMap().getViewBox(), true, Category.photo, null); + if (ids.length > 0) { + TileLayerSource tileSource = TileLayerSource.get(activity, ids[0], false); + MapTilesLayer tileLayer = ((Main) activity).getMap().getBackgroundLayer(); + tileLayer.setRendererInfo(tileSource); + try (AdvancedPrefDatabase db = new AdvancedPrefDatabase(activity)) { + db.setLayerContentId(tileLayer.getIndex(), tileSource.getId()); } - }); - return builder.create(); + } else { + Log.w(DEBUG_TAG, "No applicable imagery found!"); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Log.d(DEBUG_TAG, "onSaveInstanceState"); + Dialog dialog = getDialog(); + outState.putBoolean(USE_IMAGERY_KEY, ((SwitchCompat) dialog.findViewById(R.id.use_imagery)).isChecked()); + outState.putBoolean(AUTO_DOWNLOAD_KEY, ((SwitchCompat) dialog.findViewById(R.id.auto_download)).isChecked()); + outState.putBoolean(PEN_SETUP_KEY, ((SwitchCompat) dialog.findViewById(R.id.pen_setup)).isChecked()); + outState.putBoolean(AUTHORIZE_KEY, ((SwitchCompat) dialog.findViewById(R.id.authorize)).isChecked()); + outState.putInt(PAGER_POS_KEY, ((ExtendedViewPager) dialog.findViewById(R.id.pager)).getCurrentItem()); } } diff --git a/src/main/java/de/blau/android/prefs/Preferences.java b/src/main/java/de/blau/android/prefs/Preferences.java index 830f76c413..30d1ddc2d9 100755 --- a/src/main/java/de/blau/android/prefs/Preferences.java +++ b/src/main/java/de/blau/android/prefs/Preferences.java @@ -45,7 +45,7 @@ public class Preferences { private final boolean isAntiAliasingEnabled; private final boolean isKeepScreenOnEnabled; private final boolean useBackForUndo; - private final boolean largeDragArea; + private boolean largeDragArea; private final boolean tagFormEnabled; private String scaleLayer; private String mapProfile; @@ -122,7 +122,7 @@ public class Preferences { private final int maxOffsetDistance; private final Set enabledValidations; private final int autoNameCap; - private final boolean wayNodeDragging; + private boolean wayNodeDragging; private final boolean splitWindowForPropertyEditor; private final boolean useImperialUnits; private final boolean supportPresetLabels; @@ -140,7 +140,8 @@ public class Preferences { private final double maxCircleSegment; private final double minCircleSegment; - private static final String DEFAULT_MAP_PROFILE = "Color Round Nodes"; + public static final String DEFAULT_MAP_STYLE = "Color Round Nodes"; + public static final String DEFAULT_PEN_MAP_STYLE = "Pen Round Nodes"; private final SharedPreferences prefs; @@ -434,6 +435,16 @@ public boolean largeDragArea() { return largeDragArea; } + /** + * Enable or disable the large drag area + * + * @param enabled if true enable the large drag area + */ + public void setLargeDragArea(boolean enabled) { + largeDragArea = enabled; + prefs.edit().putBoolean(r.getString(R.string.config_largeDragArea_key), enabled).commit(); + } + /** * Get kind of scale that should be displayed * @@ -463,7 +474,7 @@ public String getDataStyle() { // check if we actually still have the profile if (DataStyle.getStyle(mapProfile) == null) { Log.w(DEBUG_TAG, "Style " + mapProfile + " missing, replacing by default"); - setDataStyle(DataStyle.getStyle(DEFAULT_MAP_PROFILE) == null ? DataStyle.getBuiltinStyleName() : DEFAULT_MAP_PROFILE); + setDataStyle(DataStyle.getStyle(DEFAULT_MAP_STYLE) == null ? DataStyle.getBuiltinStyleName() : DEFAULT_MAP_STYLE); } return mapProfile; } @@ -1674,6 +1685,16 @@ public boolean isWayNodeDraggingEnabled() { return wayNodeDragging; } + /** + * Enable or disable the way node dragging + * + * @param enabled if true enable way node dragging + */ + public void setWayNodeDragging(boolean enabled) { + wayNodeDragging = enabled; + prefs.edit().putBoolean(r.getString(R.string.config_wayNodeDragging_key), enabled).commit(); + } + /** * Check if we should try to use split window functionality for the PropertyEditor * diff --git a/src/main/java/de/blau/android/util/Util.java b/src/main/java/de/blau/android/util/Util.java index 03ed792460..e033d0d9d2 100644 --- a/src/main/java/de/blau/android/util/Util.java +++ b/src/main/java/de/blau/android/util/Util.java @@ -446,7 +446,7 @@ public void handleTag(boolean opening, String tag, Editable output, XMLReader xm output.append("\n"); } if ("li".equals(tag) && opening) { - output.append("\n\t•"); + output.append("\n\t"); } } } diff --git a/src/main/res/layout/welcome_tabs.xml b/src/main/res/layout/welcome_tabs.xml new file mode 100644 index 0000000000..1ab36c5337 --- /dev/null +++ b/src/main/res/layout/welcome_tabs.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 881298370c..51c04515cc 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -96,11 +96,43 @@ Duplicate keys You have added the following key more than once, this needs to be resolved before you can proceed: Select file picker - + Welcome - Thank you for installing Vespucci.\n<br><br>To get started you need to know three things:\n<ul><li>tapping the lock icon in the upper left corner will switch between locked (no editing) and unlocked mode,</li><li>to edit OpenStreetMap you need to download the data for the area where you want to change something first (zoom in before attempting that),</li><li>to add a new object: tap the large green button and select what you want to add, then tap the position you want to locate it at.</li></ul>\nYou are all set to go now. Start mapping and have fun! + Thank you for installing Vespucci.\n<br><br>Vespucci is a powerful tool for editing OpenStreetMap. + It supports nearly everything on mobile that you can accomplish with a desktop editor, this however comes with a certain level of complexity. + The help pages on the device or online will answer most questions, if you need more support we recommend asking on Openstreetmaps community forum. + <br><br>To get started you need to know three things: + <ul><li>tapping the lock icon in the upper left corner will switch between locked (no editing) and unlocked mode. + </li><li>to add a new object tap the large green button and select what you want to add from the menu, then tap the position you want to locate it at. + </li><li>to open the nearby point of interest display drag the handle above the bottom menu bar up. + </li></ul> + On the next page you can set some initial preferences that will make your journey with Vespucci more pleasant. + \n<br><br>Your device has \"soft keys\" that are hidden from view. Swipe from the bottom to show them again. + To improve your initial experience we can configure some options right now, + you can skip this step and set individual preferences later if you want to. + + Display aerial imagery\nfor your current location + Aerial imagery sources are provided by third parties and it is unavoidable + that they can access your current network address and the location you are viewing. If you have privacy concerns don\'t + select this and we will display the standard OpenStreetMap map as the background. + + Automatically download data + "Automatically downloading OpenStreetMap data can cause significant mobile traffic. + If you are using a metered connection you might prefer to not enable this and manually download data for the area you want to work on. + + Configure for pen or mouse usage + If you are using a pen, a mouse or other pointing device, + selecting this option will make editing more efficient. + + Authorize now + You need to authorize Vespucci to upload to your OpenStreetMap account. + If you have your login and password handy you can start that automatically when you have completed this form, + otherwise the process will start automatically the first time you upload edits. + Read the introduction + Start! + This is a new Vespucci version New features and behaviour are detailed in the release notes. Read about the new version