Compare commits

...
Sign in to create a new pull request.

10 commits

Author SHA1 Message Date
Mikhail Mitrofanov
bac234b277 Update android/app/src/main/java/app/organicmaps/bookmarks/data/CategoryDataSource.java
Co-authored-by: Alexander Borsuk <170263+biodranik@users.noreply.github.com>
Signed-off-by: Mikhail Mitrofanov <mk.mitrofanov@outlook.com>
2024-09-03 19:08:47 +02:00
Mikhail Mitrofanov
f05ed2564d After deleting the last track in a category, the category itself is now deleted
The issue was caused by calling RecyclerView.AdapterDataObserver.onChanged() at an unexpected time.

This PR resolves two problems:

Excessive updates to the category object, which occurred with any interaction with the list (e.g., simply selecting the "Share" option).
It enables updating the category as expected when something actually changes within it.
I should note that the invalidate() method is implemented specifically in CategoryDataSource rather than at the DataSource interface level, to avoid complicating other DataSource implementations that don't require this logic.

This PR is an alternative to: https://github.com/organicmaps/organicmaps/pull/9189.

Signed-off-by: Mikhail Mitrofanov <mk.mitrofanov@outlook.com>
2024-09-03 19:08:47 +02:00
Alexander Borsuk
624ca8c1f1 [strings] Added missing difficult trails translations
Signed-off-by: Alexander Borsuk <me@alex.bio>
2024-09-03 19:08:47 +02:00
174cc4a9bc [Android] hide UI in big direction mode
Signed-off-by: Harry Bond <me@hbond.xyz>
2024-09-03 19:08:47 +02:00
36527bd34a [android] Add "Open in another app" button
Signed-off-by: Roman Tsisyk <roman@tsisyk.com>
2024-09-03 19:08:47 +02:00
Isira Seneviratne
16a0a6c22a [android] Simplify TTS code using AudioManagerCompat
Signed-off-by: Isira Seneviratne <isirasen96@gmail.com>
2024-09-03 19:08:47 +02:00
parneet-guraya
adfd2fb96d Use new result apis
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-09-03 19:08:47 +02:00
Viktor Govako
ff436c1ec0 [planet] New data from 240824.
Signed-off-by: Viktor Govako <viktor.govako@gmail.com>
2024-09-03 19:08:47 +02:00
Alexander Borsuk
7868516222 [android] Updated Android to 15 (API 35)
Signed-off-by: Alexander Borsuk <me@alex.bio>
2024-09-03 19:08:46 +02:00
zyphlar
31359b9110 Add recalculating TTS announcement
Signed-off-by: zyphlar <zyphlar@users.noreply.github.com>
2024-09-03 19:08:46 +02:00
44 changed files with 2776 additions and 2536 deletions

View file

@ -384,6 +384,7 @@ dependencies {
implementation 'androidx.car.app:app-projected:1.7.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.fragment:fragment:1.8.2'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.work:work-runtime:2.9.1'

View file

@ -56,6 +56,8 @@
#include "base/math.hpp"
#include "base/sunrise_sunset.hpp"
#include "ge0/url_generator.hpp"
#include "3party/open-location-code/openlocationcode.h"
#include <memory>
@ -954,7 +956,16 @@ Java_app_organicmaps_Framework_nativeGetGe0Url(JNIEnv * env, jclass, jdouble lat
{
::Framework * fr = frm();
double const scale = (zoomLevel > 0 ? zoomLevel : fr->GetDrawScale());
string const url = fr->CodeGe0url(lat, lon, scale, jni::ToNativeString(env, name));
string const url = ge0::GenerateShortShowMapUrl(lat, lon, scale, jni::ToNativeString(env, name));
return jni::ToJavaString(env, url);
}
JNIEXPORT jstring JNICALL
Java_app_organicmaps_Framework_nativeGetGeoUri(JNIEnv * env, jclass, jdouble lat, jdouble lon, jdouble zoomLevel, jstring name)
{
::Framework * fr = frm();
double const scale = (zoomLevel > 0 ? zoomLevel : fr->GetDrawScale());
string const url = ge0::GenerateGeoUri(lat, lon, scale, jni::ToNativeString(env, name));
return jni::ToJavaString(env, url);
}

View file

@ -194,6 +194,7 @@ public class Framework
public static native String nativeFormatSpeed(double speed);
public static native String nativeGetGe0Url(double lat, double lon, double zoomLevel, String name);
public static native String nativeGetGeoUri(double lat, double lon, double zoomLevel, String name);
public static native String nativeGetAddress(double lat, double lon);

View file

@ -156,7 +156,11 @@ public class MwmActivity extends BaseMwmFragmentActivity
EditorHostFragment.class.getName(),
ReportFragment.class.getName() };
public static final int REQ_CODE_DRIVING_OPTIONS = 6;
public final ActivityResultLauncher<Intent> startDrivingOptionsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult ->
{
if( activityResult.getResultCode() == Activity.RESULT_OK)
rebuildLastRoute();
});
private static final String MAIN_MENU_ID = "MAIN_MENU_BOTTOM_SHEET";
private static final String LAYERS_MENU_ID = "LAYERS_MENU_BOTTOM_SHEET";
@ -624,7 +628,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (!mIsTabletLayout)
{
mRoutingPlanInplaceController = new RoutingPlanInplaceController(this, this, this);
mRoutingPlanInplaceController = new RoutingPlanInplaceController(this, startDrivingOptionsForResult, this, this);
removeCurrentFragment(false);
}
@ -701,6 +705,16 @@ public class MwmActivity extends BaseMwmFragmentActivity
}
}
/** Hides/shows UI while keeping state
* @param isUiHidden True to hide the UI
**/
public void hideOrShowUIWithoutClosingPlacePage(boolean isUiHidden)
{
// Used instead of closeBottomSheet to preserve state and hide instantly
UiUtils.showIf(!isUiHidden, findViewById(R.id.place_page_container_fragment));
mMapButtonsViewModel.setButtonsHidden(isUiHidden);
}
private void showSearchToolbar()
{
mSearchController.show();
@ -1030,18 +1044,6 @@ public class MwmActivity extends BaseMwmFragmentActivity
mPowerSaveDisclaimerState = PowerSaveDisclaimerState.SHOWN;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK)
return;
if (requestCode == REQ_CODE_DRIVING_OPTIONS)
rebuildLastRoute();
}
private void rebuildLastRoute()
{
RoutingController.get().attach(this);
@ -1695,7 +1697,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
.setTitle(R.string.unable_to_calc_alert_title)
.setMessage(R.string.unable_to_calc_alert_subtitle)
.setPositiveButton(R.string.settings, (dialog, which) -> DrivingOptionsActivity.start(this))
.setPositiveButton(R.string.settings, (dialog, which) -> DrivingOptionsActivity.start(this, startDrivingOptionsForResult))
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener(dialog -> mAlertDialog = null)
.show();

View file

@ -13,6 +13,7 @@ import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@ -55,9 +56,6 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
{
private static final String TAG = BookmarkCategoriesFragment.class.getSimpleName();
static final int REQ_CODE_DELETE_CATEGORY = 102;
static final int REQ_CODE_IMPORT_DIRECTORY = 103;
private static final int MAX_CATEGORY_NAME_LENGTH = 60;
public static final String BOOKMARKS_CATEGORIES_MENU_ID = "BOOKMARKS_CATEGORIES_BOTTOM_SHEET";
@ -73,6 +71,22 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
@NonNull
private DataChangedListener mCategoriesAdapterObserver;
private final ActivityResultLauncher<Intent> startBookmarkListForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
if( activityResult.getResultCode() == Activity.RESULT_OK)
onDeleteActionSelected(getSelectedCategory());
});
private final ActivityResultLauncher<Intent> startImportDirectoryForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult ->
{
if( activityResult.getResultCode() == Activity.RESULT_OK)
onImportDirectoryResult(activityResult.getData());
});
private final ActivityResultLauncher<Intent> startBookmarkSettingsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
// not handled at the moment
});
@Override
@LayoutRes
protected int getLayoutRes()
@ -254,14 +268,14 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
intent.putExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, true);
startActivityForResult(intent, REQ_CODE_IMPORT_DIRECTORY);
startImportDirectoryForResult.launch(intent);
}
@Override
public void onItemClick(@NonNull View v, @NonNull BookmarkCategory category)
{
mSelectedCategory = category;
BookmarkListActivity.startForResult(this, category);
BookmarkListActivity.startForResult(this, startBookmarkListForResult, category);
}
@Override
@ -289,54 +303,42 @@ public class BookmarkCategoriesFragment extends BaseMwmRecyclerFragment<Bookmark
private void onSettingsActionSelected(@NonNull BookmarkCategory category)
{
BookmarkCategorySettingsActivity.startForResult(this, category);
BookmarkCategorySettingsActivity.startForResult(this, startBookmarkSettingsForResult, category);
}
@Override
public final void onActivityResult(int requestCode, int resultCode, Intent data)
private void onImportDirectoryResult(Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK)
return;
switch (requestCode)
{
case REQ_CODE_DELETE_CATEGORY -> onDeleteActionSelected(getSelectedCategory());
case REQ_CODE_IMPORT_DIRECTORY ->
{
if (data == null)
throw new AssertionError("Data is null");
if (data == null)
throw new AssertionError("Data is null");
final Context context = requireActivity();
final Uri rootUri = data.getData();
final ProgressDialog dialog = new ProgressDialog(context, R.style.MwmTheme_ProgressDialog);
dialog.setMessage(getString(R.string.wait_several_minutes));
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
Logger.d(TAG, "Importing bookmarks from " + rootUri);
MwmApplication app = MwmApplication.from(context);
final File tempDir = new File(StorageUtils.getTempPath(app));
final ContentResolver resolver = context.getContentResolver();
ThreadPool.getStorage().execute(() -> {
AtomicInteger found = new AtomicInteger(0);
StorageUtils.listContentProviderFilesRecursively(
final Context context = requireActivity();
final Uri rootUri = data.getData();
final ProgressDialog dialog = new ProgressDialog(context, R.style.MwmTheme_ProgressDialog);
dialog.setMessage(getString(R.string.wait_several_minutes));
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
Logger.d(TAG, "Importing bookmarks from " + rootUri);
MwmApplication app = MwmApplication.from(context);
final File tempDir = new File(StorageUtils.getTempPath(app));
final ContentResolver resolver = context.getContentResolver();
ThreadPool.getStorage().execute(() -> {
AtomicInteger found = new AtomicInteger(0);
StorageUtils.listContentProviderFilesRecursively(
resolver, rootUri, uri -> {
if (BookmarkManager.INSTANCE.importBookmarksFile(resolver, uri, tempDir))
found.incrementAndGet();
});
UiThread.run(() -> {
if (dialog.isShowing())
dialog.dismiss();
int found_val = found.get();
String message = context.getResources().getQuantityString(
UiThread.run(() -> {
if (dialog.isShowing())
dialog.dismiss();
int found_val = found.get();
String message = context.getResources().getQuantityString(
R.plurals.bookmarks_detect_message, found_val, found_val);
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
});
});
}
default -> throw new AssertionError("Invalid requestCode: " + requestCode);
}
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
});
});
}
@Override

View file

@ -2,6 +2,7 @@ package app.organicmaps.bookmarks;
import android.content.Intent;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@ -11,7 +12,6 @@ import app.organicmaps.bookmarks.data.BookmarkCategory;
public class BookmarkCategorySettingsActivity extends BaseMwmFragmentActivity
{
public static final int REQUEST_CODE = 107;
public static final String EXTRA_BOOKMARK_CATEGORY = "bookmark_category";
@Override
@ -32,11 +32,11 @@ public class BookmarkCategorySettingsActivity extends BaseMwmFragmentActivity
return BookmarkCategorySettingsFragment.class;
}
public static void startForResult(@NonNull Fragment fragment,
public static void startForResult(@NonNull Fragment fragment, ActivityResultLauncher<Intent> startBookmarkSettingsForResult,
@NonNull BookmarkCategory category)
{
android.content.Intent intent = new Intent(fragment.requireActivity(), BookmarkCategorySettingsActivity.class)
.putExtra(EXTRA_BOOKMARK_CATEGORY, category);
fragment.startActivityForResult(intent, REQUEST_CODE);
startBookmarkSettingsForResult.launch(intent);
}
}

View file

@ -3,6 +3,7 @@ package app.organicmaps.bookmarks;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
@ -57,11 +58,11 @@ public class BookmarkListActivity extends BaseToolbarActivity
return R.layout.bookmarks_activity;
}
static void startForResult(@NonNull Fragment fragment, @NonNull BookmarkCategory category)
static void startForResult(@NonNull Fragment fragment, ActivityResultLauncher<Intent> startBookmarkListForResult, @NonNull BookmarkCategory category)
{
Bundle args = new Bundle();
Intent intent = new Intent(fragment.requireActivity(), BookmarkListActivity.class);
intent.putExtra(BookmarksListFragment.EXTRA_CATEGORY, category);
fragment.startActivityForResult(intent, BookmarkCategoriesFragment.REQ_CODE_DELETE_CATEGORY);
startBookmarkListForResult.launch(intent);
}
}

View file

@ -12,6 +12,7 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -66,6 +67,15 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
private static final String OPTIONS_MENU_ID = "OPTIONS_MENU_BOTTOM_SHEET";
private ActivityResultLauncher<SharingUtils.SharingIntent> shareLauncher;
private final ActivityResultLauncher<Intent> startBookmarkListForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
System.out.println("resultCode: " + activityResult.getResultCode());
handleActivityResult();
});
private final ActivityResultLauncher<Intent> startBookmarkSettingsForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
System.out.println("resultCode: " + activityResult.getResultCode());
handleActivityResult();
});
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
@ -132,7 +142,9 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
BookmarkCollectionAdapter adapter = new BookmarkCollectionAdapter(getCategoryOrThrow(),
mCategoryItems);
adapter.setOnClickListener((v, item) -> BookmarkListActivity.startForResult(this, item));
adapter.setOnClickListener((v, item) -> {
BookmarkListActivity.startForResult(this, startBookmarkListForResult, item);
});
return adapter;
}
@ -227,7 +239,6 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
private void configureBookmarksListAdapter()
{
BookmarkListAdapter adapter = getBookmarkListAdapter();
adapter.registerAdapterDataObserver(mCategoryDataSource);
adapter.setOnClickListener((v, position) -> onItemClick(position));
adapter.setOnLongClickListener((v, position) -> onItemMore(position));
adapter.setMoreListener((v, position) -> onItemMore(position));
@ -603,6 +614,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
private void onDeleteTrackSelected(long trackId)
{
BookmarkManager.INSTANCE.deleteTrack(trackId);
mCategoryDataSource.invalidate();
getBookmarkListAdapter().onDelete(mSelectedPosition);
getBookmarkListAdapter().notifyDataSetChanged();
}
@ -676,6 +688,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
BookmarkInfo info = (BookmarkInfo) getBookmarkListAdapter().getItem(mSelectedPosition);
adapter.onDelete(mSelectedPosition);
BookmarkManager.INSTANCE.deleteBookmark(info.getBookmarkId());
mCategoryDataSource.invalidate();
adapter.notifyDataSetChanged();
if (mSearchMode)
mNeedUpdateSorting = true;
@ -697,7 +710,7 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
private void onSettingsOptionSelected()
{
BookmarkCategorySettingsActivity.startForResult(this, mCategoryDataSource.getData());
BookmarkCategorySettingsActivity.startForResult(this, startBookmarkSettingsForResult, mCategoryDataSource.getData());
}
private void onDeleteOptionSelected()
@ -745,11 +758,8 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
BookmarksSharingHelper.INSTANCE.onPreparedFileForSharing(requireActivity(), shareLauncher, result);
}
@Override
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
public void onActivityResult(int requestCode, int resultCode, Intent data)
private void handleActivityResult()
{
super.onActivityResult(requestCode, resultCode, data);
getAdapter().notifyDataSetChanged();
ActionBar actionBar = ((AppCompatActivity) requireActivity()).getSupportActionBar();
actionBar.setTitle(mCategoryDataSource.getData().getName());

View file

@ -1,14 +1,12 @@
package app.organicmaps.bookmarks.data;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.content.DataSource;
import java.util.List;
public class CategoryDataSource extends RecyclerView.AdapterDataObserver implements
DataSource<BookmarkCategory>
public class CategoryDataSource implements DataSource<BookmarkCategory>
{
@NonNull
private BookmarkCategory mCategory;
@ -25,10 +23,8 @@ public class CategoryDataSource extends RecyclerView.AdapterDataObserver impleme
return mCategory;
}
@Override
public void onChanged()
public void invalidate()
{
super.onChanged();
List<BookmarkCategory> categories = BookmarkManager.INSTANCE.getCategories();
int index = categories.indexOf(mCategory);
if (index >= 0)

View file

@ -5,6 +5,8 @@ import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@ -38,6 +40,8 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment<DownloaderAdapte
private int mSubscriberSlot;
final ActivityResultLauncher<Intent> startVoiceRecognitionForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> mToolbarController.onVoiceRecognitionResult(activityResult));
private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
@ -206,13 +210,6 @@ public class DownloaderFragment extends BaseMwmRecyclerFragment<DownloaderAdapte
return mAdapter;
}
@Override
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
mToolbarController.onActivityResult(requestCode, resultCode, data);
}
@NonNull
@Override

View file

@ -58,10 +58,9 @@ class DownloaderToolbarController extends SearchToolbarController
}
@Override
@SuppressWarnings("deprecated") // https://github.com/organicmaps/organicmaps/issues/3630
protected void startVoiceRecognition(Intent intent, int code)
protected void startVoiceRecognition(Intent intent)
{
mFragment.startActivityForResult(intent, code);
mFragment.startVoiceRecognitionForResult.launch(intent);
}
@Override

View file

@ -1,12 +1,14 @@
package app.organicmaps.routing;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
@ -77,6 +79,7 @@ public class RoutingPlanController extends ToolbarController
}
RoutingPlanController(View root, Activity activity,
ActivityResultLauncher<Intent> startDrivingOptionsForResult,
@NonNull RoutingPlanInplaceController.RoutingPlanListener routingPlanListener,
@Nullable RoutingBottomMenuListener listener)
{
@ -102,7 +105,7 @@ public class RoutingPlanController extends ToolbarController
View btn = mDrivingOptionsBtnContainer.findViewById(R.id.driving_options_btn);
mDrivingOptionsImage = mFrame.findViewById(R.id.driving_options_btn_img);
btn.setOnClickListener(v -> DrivingOptionsActivity.start(requireActivity()));
btn.setOnClickListener(v -> DrivingOptionsActivity.start(requireActivity(), startDrivingOptionsForResult));
mDriverOptionsLayoutListener = new SelfTerminatedDrivingOptionsLayoutListener();
mAnimToggle = MwmApplication.from(activity.getApplicationContext())
.getResources().getInteger(R.integer.anim_default);

View file

@ -7,9 +7,9 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmFragment;
@ -21,15 +21,9 @@ public class RoutingPlanFragment extends BaseMwmFragment
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
final FragmentActivity activity = requireActivity();
final MwmActivity activity = (MwmActivity) requireActivity();
View res = inflater.inflate(R.layout.fragment_routing, container, false);
RoutingBottomMenuListener listener = null;
if (activity instanceof RoutingBottomMenuListener)
listener = (RoutingBottomMenuListener) activity;
RoutingPlanInplaceController.RoutingPlanListener planListener =
(RoutingPlanInplaceController.RoutingPlanListener) activity;
mPlanController = new RoutingPlanController(res, activity, planListener, listener);
mPlanController = new RoutingPlanController(res, activity, activity.startDrivingOptionsForResult, activity, activity);
return res;
}

View file

@ -2,8 +2,10 @@ package app.organicmaps.routing;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -20,10 +22,11 @@ public class RoutingPlanInplaceController extends RoutingPlanController
private Animator mAnimator;
public RoutingPlanInplaceController(@NonNull MwmActivity activity,
ActivityResultLauncher<Intent> startDrivingOptionsForResult,
@NonNull RoutingPlanListener routingPlanListener,
@NonNull RoutingBottomMenuListener listener)
{
super(activity.findViewById(R.id.routing_plan_frame), activity, routingPlanListener, listener);
super(activity.findViewById(R.id.routing_plan_frame), activity, startDrivingOptionsForResult, routingPlanListener, listener);
mRoutingPlanListener = routingPlanListener;
}

View file

@ -10,6 +10,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -112,10 +114,9 @@ public class SearchFragment extends BaseMwmFragment
}
@Override
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
protected void startVoiceRecognition(Intent intent, int code)
protected void startVoiceRecognition(Intent intent)
{
startActivityForResult(intent, code);
startVoiceRecognitionForResult.launch(intent);
}
@Override
@ -167,6 +168,11 @@ public class SearchFragment extends BaseMwmFragment
private String mInitialLocale;
private boolean mInitialSearchOnMap = false;
private final ActivityResultLauncher<Intent> startVoiceRecognitionForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult ->
{
mToolbarController.onVoiceRecognitionResult(activityResult);
});
private final LocationListener mLocationListener = new LocationListener()
{
@Override
@ -521,14 +527,6 @@ public class SearchFragment extends BaseMwmFragment
mToolbarController.showProgress(true);
}
@Override
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
mToolbarController.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onBackPressed()
{

View file

@ -3,10 +3,9 @@ package app.organicmaps.settings;
import android.app.Activity;
import android.content.Intent;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import app.organicmaps.MwmActivity;
import app.organicmaps.base.BaseMwmFragmentActivity;
public class DrivingOptionsActivity extends BaseMwmFragmentActivity
@ -17,9 +16,9 @@ public class DrivingOptionsActivity extends BaseMwmFragmentActivity
return DrivingOptionsFragment.class;
}
public static void start(@NonNull Activity activity)
public static void start(@NonNull Activity activity, ActivityResultLauncher<Intent> startDrivingOptionsForResult)
{
Intent intent = new Intent(activity, DrivingOptionsActivity.class);
activity.startActivityForResult(intent, MwmActivity.REQ_CODE_DRIVING_OPTIONS);
startDrivingOptionsForResult.launch(intent);
}
}

View file

@ -1,5 +1,6 @@
package app.organicmaps.settings;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.Configuration;
@ -11,6 +12,8 @@ import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@ -35,7 +38,6 @@ import java.util.Map;
public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment
{
private static final int REQUEST_INSTALL_DATA = 1;
@NonNull
@SuppressWarnings("NotNullFieldNotInitialized")
@ -67,6 +69,14 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment
private LanguageData mCurrentLanguage;
private String mSelectedLanguage;
private final ActivityResultLauncher<Intent> startInstallDataIntentForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
if(activityResult.getResultCode() == Activity.RESULT_OK)
{
onInstallDataResult();
}
});
private final Preference.OnPreferenceChangeListener mEnabledListener = (preference, newValue) -> {
final boolean set = (Boolean) newValue;
if (!set)
@ -107,8 +117,7 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment
if (lang.downloaded)
setLanguage(lang);
else
UiUtils.startActivityForResult(VoiceInstructionsSettingsFragment.this,
new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA), REQUEST_INSTALL_DATA);
UiUtils.startActivityForResult(startInstallDataIntentForResult, new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA));
return false;
};
@ -181,21 +190,13 @@ public class VoiceInstructionsSettingsFragment extends BaseXmlSettingsFragment
updateTts();
}
@Override
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
public void onActivityResult(int requestCode, int resultCode, Intent data)
private void onInstallDataResult()
{
// Do not check resultCode here as it is always RESULT_CANCELED
super.onActivityResult(requestCode, resultCode, data);
updateTts();
if (requestCode == REQUEST_INSTALL_DATA)
{
updateTts();
LanguageData lang = mLanguages.get(mSelectedLanguage);
if (lang != null && lang.downloaded)
setLanguage(lang);
}
LanguageData lang = mLanguages.get(mSelectedLanguage);
if (lang != null && lang.downloaded)
setLanguage(lang);
}
private void enableListeners(boolean enable)

View file

@ -1,64 +0,0 @@
package app.organicmaps.sound;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.Nullable;
import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
public class AudioFocusManager
{
@Nullable
private AudioManager mAudioManager = null;
@Nullable
private AudioManager.OnAudioFocusChangeListener mOnFocusChange = null;
@Nullable
private AudioFocusRequest mAudioFocusRequest = null;
public AudioFocusManager(@Nullable Context context)
{
if (context != null)
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT < 26)
mOnFocusChange = focusGain -> {};
else
mAudioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK).setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
).build();
}
public boolean requestAudioFocus()
{
boolean isMusicActive = false;
if (mAudioManager != null)
{
isMusicActive = mAudioManager.isMusicActive();
if (Build.VERSION.SDK_INT < 26)
mAudioManager.requestAudioFocus(mOnFocusChange, AudioManager.STREAM_VOICE_CALL, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
else
mAudioManager.requestAudioFocus(mAudioFocusRequest);
}
return isMusicActive;
}
public void releaseAudioFocus()
{
if (mAudioManager != null)
{
if (Build.VERSION.SDK_INT < 26)
mAudioManager.abandonAudioFocus(mOnFocusChange);
else
mAudioManager.abandonAudioFocusRequest(mAudioFocusRequest);
}
}
}

View file

@ -2,6 +2,7 @@ package app.organicmaps.sound;
import android.content.Context;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -11,6 +12,11 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.media.AudioAttributesCompat;
import androidx.media.AudioFocusRequestCompat;
import androidx.media.AudioManagerCompat;
import app.organicmaps.MwmApplication;
import app.organicmaps.R;
import app.organicmaps.util.Config;
@ -47,7 +53,18 @@ public enum TtsPlayer
private TextToSpeech mTts;
private boolean mInitializing;
private AudioFocusManager mAudioFocusManager;
private final AudioFocusRequestCompat mAudioFocusRequest =
new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
new AudioAttributesCompat.Builder()
.setUsage(AudioAttributesCompat.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
.build()
)
.setOnAudioFocusChangeListener(focusChange -> {})
.build();
private AudioManager mAudioManager;
private final Bundle mParams = new Bundle();
@ -162,27 +179,27 @@ public enum TtsPlayer
mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
mAudioFocusManager.requestAudioFocus();
AudioManagerCompat.requestAudioFocus(mAudioManager, mAudioFocusRequest);
}
@Override
public void onDone(String utteranceId) {
mAudioFocusManager.releaseAudioFocus();
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, mAudioFocusRequest);
}
@Override
@SuppressWarnings("deprecated") // abstract method must be implemented
public void onError(String utteranceId)
{
mAudioFocusManager.releaseAudioFocus();
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, mAudioFocusRequest);
}
@Override
public void onError(String utteranceId, int errorCode) {
mAudioFocusManager.releaseAudioFocus();
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, mAudioFocusRequest);
}
});
mAudioFocusManager = new AudioFocusManager(context);
mAudioManager = ContextCompat.getSystemService(context, AudioManager.class);
mParams.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, Config.TTS.getVolume());
mInitializing = false;
}));
@ -198,11 +215,10 @@ public enum TtsPlayer
if (Config.TTS.isEnabled())
try
{
boolean isMusicActive = mAudioFocusManager.requestAudioFocus();
if (isMusicActive)
delayHandler.postDelayed(() -> mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, mParams, textToSpeak), TTS_SPEAK_DELAY_MILLIS);
else
mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, mParams, textToSpeak);
final boolean isMusicActive = mAudioManager.isMusicActive();
AudioManagerCompat.requestAudioFocus(mAudioManager, mAudioFocusRequest);
final long delay = isMusicActive ? TTS_SPEAK_DELAY_MILLIS : 0;
delayHandler.postDelayed(() -> mTts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, mParams, textToSpeak), delay);
}
catch (IllegalArgumentException e)
{
@ -222,7 +238,7 @@ public enum TtsPlayer
if (isReady())
try
{
mAudioFocusManager.releaseAudioFocus();
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, mAudioFocusRequest);
mTts.stop();
}
catch (IllegalArgumentException e)

View file

@ -18,6 +18,8 @@ import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.AnyRes;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
@ -32,7 +34,6 @@ import androidx.core.graphics.Insets;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputLayout;
@ -384,10 +385,9 @@ public final class UiUtils
}
}
@SuppressWarnings("deprecation") // https://github.com/organicmaps/organicmaps/issues/3630
public static void startActivityForResult(@NonNull Fragment fragment, @NonNull Intent intent, int requestCode)
public static void startActivityForResult(ActivityResultLauncher<Intent> startForResult, @NonNull Intent intent)
{
fragment.startActivityForResult(intent, requestCode);
startForResult.launch(intent);
}
// utility class

View file

@ -10,6 +10,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import androidx.activity.result.ActivityResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@ -22,7 +23,6 @@ import com.google.android.material.textfield.TextInputEditText;
public class SearchToolbarController extends ToolbarController implements View.OnClickListener
{
private static final int REQUEST_VOICE_RECOGNITION = 0xCA11;
@Nullable
private final View mToolbarContainer;
@NonNull
@ -107,7 +107,7 @@ public class SearchToolbarController extends ToolbarController implements View.O
clear();
}
protected void startVoiceRecognition(Intent intent, int code)
protected void startVoiceRecognition(Intent intent)
{
throw new RuntimeException("To be used startVoiceRecognition() must be implemented by descendant class");
}
@ -130,7 +130,7 @@ public class SearchToolbarController extends ToolbarController implements View.O
try
{
startVoiceRecognition(InputUtils.createIntentForVoiceRecognition(
requireActivity().getString(getVoiceInputPrompt())), REQUEST_VOICE_RECOGNITION);
requireActivity().getString(getVoiceInputPrompt())));
}
catch (ActivityNotFoundException e)
{
@ -210,14 +210,18 @@ public class SearchToolbarController extends ToolbarController implements View.O
UiUtils.showIf(show, mSearchContainer);
}
public void onActivityResult(int requestCode, int resultCode, Intent data)
public void onVoiceRecognitionResult(ActivityResult activityResult)
{
if (requestCode == REQUEST_VOICE_RECOGNITION && resultCode == Activity.RESULT_OK)
{
String result = InputUtils.getBestRecognitionResult(data);
if (!TextUtils.isEmpty(result))
setQuery(result);
}
if(activityResult.getResultCode() == Activity.RESULT_OK)
{
if (activityResult.getData() == null)
{
return;
}
String recognitionResult = InputUtils.getBestRecognitionResult(activityResult.getData());
if (!TextUtils.isEmpty(recognitionResult))
setQuery(recognitionResult);
}
}
public void setHint(@StringRes int hint)

View file

@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmDialogFragment;
import app.organicmaps.bookmarks.data.DistanceAndAzimut;
@ -46,6 +47,7 @@ public class DirectionFragment extends BaseMwmDialogFragment
{
final View root = inflater.inflate(R.layout.fragment_direction, container, false);
root.setOnTouchListener((v, event) -> {
root.performClick();
dismiss();
return false;
});
@ -99,6 +101,7 @@ public class DirectionFragment extends BaseMwmDialogFragment
super.onResume();
LocationHelper.from(requireContext()).addListener(this);
SensorHelper.from(requireContext()).addListener(this);
((MwmActivity) requireActivity()).hideOrShowUIWithoutClosingPlacePage(true);
refreshViews();
}
@ -110,6 +113,13 @@ public class DirectionFragment extends BaseMwmDialogFragment
SensorHelper.from(requireContext()).removeListener(this);
}
@Override
public void onStop()
{
super.onStop();
((MwmActivity) requireActivity()).hideOrShowUIWithoutClosingPlacePage(false);
}
@Override
public void onLocationUpdated(@NonNull Location location)
{

View file

@ -2,6 +2,7 @@ package app.organicmaps.widget.placepage;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -238,6 +239,10 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
LinearLayout latlon = mFrame.findViewById(R.id.ll__place_latlon);
latlon.setOnClickListener(this);
LinearLayout openIn = mFrame.findViewById(R.id.ll__place_open_in);
openIn.setOnClickListener(this);
openIn.setOnLongClickListener(this);
openIn.setVisibility(VISIBLE);
mTvLatlon = mFrame.findViewById(R.id.tv__place_latlon);
mWifi = mFrame.findViewById(R.id.ll__place_wifi);
mTvWiFi = mFrame.findViewById(R.id.tv__place_wifi);
@ -579,6 +584,12 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
.apply();
refreshLatLon();
}
else if (id == R.id.ll__place_open_in)
{
final String uri = Framework.nativeGetGeoUri(mMapObject.getLat(), mMapObject.getLon(),
mMapObject.getScale(), mMapObject.getName());
Utils.openUri(requireContext(), Uri.parse(uri));
}
else if (id == R.id.direction_frame)
showBigDirection();
}
@ -614,6 +625,12 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
items.add(formatted);
}
}
else if (id == R.id.ll__place_open_in)
{
final String uri = Framework.nativeGetGeoUri(mMapObject.getLat(), mMapObject.getLon(),
mMapObject.getScale(), mMapObject.getName());
PlacePageUtils.copyToClipboard(requireContext(), mFrame, uri);
}
else if (id == R.id.ll__place_operator)
items.add(mTvOperator.getText().toString());
else if (id == R.id.ll__place_network)

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,480L840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM388,628L332,572L704,200L560,200L560,120L840,120L840,400L760,400L760,256L388,628Z"/>
</vector>

View file

@ -73,6 +73,8 @@
<!-- ToDo: Address is missing compared with iOS. It's shown in title but anyway .. -->
<include layout="@layout/place_page_latlon"/>
<include layout="@layout/place_page_open_in"/>
</LinearLayout>
<include

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll__place_open_in"
style="@style/PlacePageItemFrame"
android:tag="open_in"
tools:background="#20FF0000"
tools:visibility="visible">
<ImageView
android:id="@+id/iv__place_open_in"
style="@style/PlacePageMetadataIcon"
app:srcCompat="@drawable/ic_open_in"
app:tint="?colorAccent"/>
<TextView
android:id="@+id/tv__place_open_in"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/open_in_app"
android:textAppearance="@style/MwmTextAppearance.PlacePage.Accent"/>
</LinearLayout>

View file

@ -134,6 +134,7 @@
<item name="android:colorPrimaryDark">@android:color/black</item>
<item name="android:windowBackground">@color/bg_dialog_translucent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="MwmMain.DialogFragment.TimePicker" parent="Theme.MaterialComponents.Light.Dialog.Alert">

View file

@ -1,6 +1,6 @@
propMinSdkVersion=21
propTargetSdkVersion=34
propCompileSdkVersion=34
propTargetSdkVersion=35
propCompileSdkVersion=35
org.gradle.caching=true
org.gradle.jvmargs=-Xmx1024m -Xms256m

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -596,6 +596,50 @@
en = NULL
nl = Neem
[route_recalculating]
en = Recalculating the route
af = Herbereken die roete
ar = إعادة حساب المسار
az = Marşrutun yenidən hesablanması
be = Пераразлік маршруту
bg = Преизчисляване на маршрута
ca = Recalcular el recorregut
cs = Přepočítání trasy
da = Genberegning af ruten
de = Neuberechnung der Route
el = Επαναϋπολογισμός της διαδρομής
es = Recalcular la ruta
et = Marsruudi ümberarvutamine
eu = Ibilbidea berriro kalkulatzea
fa = محاسبه مجدد مسیر
fi = Reitin uudelleenlaskenta
fr = Recalcul de l'itinéraire
he = חישוב מחדש של המסלול
hi = मार्ग की पुनर्गणना
hu = Az útvonal újraszámítása
id = Menghitung ulang rute
it = Ricalcolo del percorso
ja = ルートの再計算
ko = 경로 다시 계산하기
lt = Maršruto perskaičiavimas
mr = मार्गाची पुनर्गणना करत आहे
nb = Beregner ruten på nytt
nl = De route herberekenen
pl = Ponowne obliczanie trasy
pt = Recalcular o itinerário
pt-BR = Recalcular a rota
ro = Recalcularea traseului
ru = Перерасчет маршрута
sk = Prepočítanie trasy
sv = Omberäkning av rutten
sw = Kuhesabu upya njia
th = การคำนวณเส้นทางใหม่
tr = Rotanın yeniden hesaplanması
uk = Перерахунок маршруту
vi = Tính toán lại lộ trình
zh-Hans = 重新计算路线
zh-Hant = 重新計算路
[destination]
en = Youll arrive.
ar = سوف تصل.

View file

@ -30853,7 +30853,7 @@
[open_in_app]
comment = Title for the "Open In Another App" button on the PlacePage.
tags = ios
tags = android,ios
en = Open In Another App
af = Maak oop in 'n ander toepassing
ar = فتح في تطبيق آخر

View file

@ -12855,6 +12855,9 @@
af = Pad
ar = مسار
az = Yol
be = Сцежка
bg = Път
ca = Camí
cs = Cesta
da = Sti
de = Pfad
@ -12872,16 +12875,17 @@
it = Sentiero
ja = 歩道
ko = 길
lt = Kelias
mr = पथ
nb = Sti
nl = Pad
pl = Ścieżka
pt = Caminho
pt-BR = Caminho
ro = Cale
ru = Тропа
sk = Cesta
sv = Gångväg
sw = Njia
th = เส้นทาง
tr = Yol
uk = Стежка
@ -12893,19 +12897,93 @@
comment = Hiking trail tagged as sac_scale=demanding_mountain_hiking (3 of 6) or trail_visibility=bad.
ref = type.highway.path
en = Difficult or Indistinct Trail
af = Moeilike of swak sigbare roete
ar = درب صعب أو ضعيف الرؤية
az = Çətin və ya zəif görünən cığır
be = Складаная ці дрэнна бачная сцежка
bg = Трудна или слабо видима пътека
ca = Ruta difícil o poc visible
cs = Obtížná nebo špatně viditelná stezka
da = Vanskelig eller dårligt synlig sti
de = Schwieriger oder kaum erkennbarer Pfad
he = רמה 3/6) שביל קשה או לא ברור)
el = Δύσκολη ή ελάχιστα ορατή διαδρομή
es = Sendero difícil o poco visible
et = Raske või halvasti nähtav rada
eu = Ibilbide zaila edo gaizki ikusten da
fa = مسیر دشوار یا ضعیف قابل مشاهده است
fi = Vaikea tai huonosti näkyvä reitti
fr = Sentier difficile ou peu visibl
he = שביל קשה או לא נראה לעין
hi = कठिन या ख़राब दिखाई देने वाला मार्ग
hu = Nehéz vagy rosszul látható nyomvonal
id = Jejak yang sulit atau kurang terlihר)
it = Sentiero difficile o poco tracciato
ja = 困難な、または見通しの悪いトレイル
ko = 어렵거나 잘 보이지 않는 트레일
lt = Sudėtingas arba blogai matomas takas
mr = अवघड किंवा खराब दृश्यमान पायवाट
nb = Vanskelig eller dårlig synlig sti
nl = Moeilijk of slecht zichtbaar pad
pl = Trudny lub słabo widoczny szlak
pt = Trilho difícil ou pouco visível
pt-BR = Trilha difícil ou pouco visível
ro = Traseu dificil sau puțin vizibil
ru = Сложная или плохо видимая тропа
sk = Náročná alebo zle viditeľná trasa
sv = Svår eller dåligt synlig led
sw = Njia ngumu au isiyoonekana vizuri
th = เส้นทางที่ยากลำบากหรือมองเห็นได้ไม่ดี
tr = Zor veya kötü görünür patika
uk = Складна або погано видима стежка
vi = Đường đi khó hoặc khó nhìn thấy
zh-Hans = 难走或难见的小路
zh-Hant = 困難或不明顯的小路
[type.highway.path.expert]
comment = Hiking trail tagged as sac_scale=alpine_hiking (4+ of 6) or trail_visibility=horrible or more extreme.
ref = type.highway.path
en = Expert or Indiscernible Trail
af = Baie moeilike of ononderskeibare roete
ar = أثر صعب للغاية أو لا يمكن تمييزه
az = Çox çətin və ya fərqlənməyən cığır
be = Вельмі складаная ці неадметная сцежка
bg = Много трудна или неразличима пътека
ca = Sender molt difícil o indistingible
cs = Velmi obtížná nebo nezřetelná stopa
da = Meget svær eller umulig at skelne fra hinanden
de = Alpinwanderweg oder wegloser Pfad
he = רמה 4+/6) מסלול למומחים או ללא שבילים מסודרים)
el = Πολύ δύσκολο ή δυσδιάκριτο μονοπάτι
es = Sendero muy difícil o indistinguible
et = Väga raske või eristamatu rada
eu = Oso bide zaila edo bereiztezina
fa = مسیر بسیار دشوار یا غیر قابل تشخیص
fi = Erittäin vaikea tai erottamaton jälki
fr = Sentier très difficile ou indiscernabl
he = מסלול קשה מאוד או בלתי ניתן להבחנה
hi = बहुत कठिन या अप्रभेद्य पथ
hu = Nagyon nehéz vagy megkülönböztethetetlen nyomvonal
id = Jejak yang sangat sulit atau tidak dapat dibedakם)
it = Sentiero estremamente difficile o molto poco tracciato
ja = 非常に難しい、または区別がつかないトレイル
ko = 매우 어렵거나 구별하기 어려운 흔적
lt = Labai sudėtingas arba neišsiskiriantis takas
mr = अतिशय अवघड किंवा वेगळे न करता येणारी पायवाट
nb = Svært vanskelig eller umulig å skille ut stien
nl = Zeer moeilijk of niet te onderscheiden spoor
pl = Bardzo trudny lub nierozróżnialny szlak
pt = Trilho muito difícil ou indistinguível
pt-BR = Trilha muito difícil ou indistinguível
ro = Traseu foarte dificil sau imposibil de distins
ru = Очень сложная или неразличимая тропа
sk = Veľmi náročná alebo nezreteľná stopa
sv = Mycket svår eller omöjlig att urskilja spår
sw = Njia ngumu sana au isiyoweza kutofautishwa
th = เส้นทางที่ยากมากหรือแยกไม่ออก
tr = Çok zor veya ayırt edilemeyen iz
uk = Дуже складна або невиразна стежка
vi = Đường đi rất khó khăn hoặc không thể phân biệt được
zh-Hans = 非常困难或难以分辨的路径
zh-Hant = 非常困難或難以區分的路線
[type.highway.path.bicycle]
ref = type.highway.path

View file

@ -396,7 +396,7 @@ This is important, otherwise the following menus won't be visible.
Install Android SDK and NDK:
- Open "SDK Manager" (under "More Actions" in a welcome screen or a three-dot menu in a list of recent projects screen or "Tools" top menu item in an open project).
- Select "Android 14.0 ("Upside Down Cake") / API Level 34" SDK.
- Select "Android 15.0 ("Vanilla Ice Cream") / API Level 35" SDK.
- Switch to "SDK Tools" tab.
- Check "Show Package Details" checkbox.
- Select "NDK (Side by side)" version **27.0.12077973**.

View file

@ -1,6 +1,7 @@
#include "testing/testing.hpp"
#include "ge0/url_generator.hpp"
#include "ge0/geo_url_parser.hpp"
#include <string>
@ -9,6 +10,7 @@ using namespace std;
namespace
{
int const kTestCoordBytes = 9;
double const kEps = 1e-10;
} // namespace
namespace ge0
@ -339,4 +341,22 @@ UNIT_TEST(GenerateShortShowMapUrl_UnicodeMixedWithOtherChars)
string res = GenerateShortShowMapUrl(0, 0, 19, "Back_in \xe2\x98\x84!\xd1\x8e\xd0\xbc");
TEST_EQUAL("om://8wAAAAAAAA/Back%20in_\xe2\x98\x84%21\xd1\x8e\xd0\xbc", res, ());
}
UNIT_TEST(GenerateGeoUri_SmokeTest)
{
string res = GenerateGeoUri(33.8904075, 35.5066454, 16.5, "Falafel M. Sahyoun");
TEST_EQUAL("geo:33.8904075,35.5066454?z=16.5(Falafel%20M.%20Sahyoun)", res, ());
// geo:33.8904075,35.5066454?z=16.5(Falafel%20M.%20Sahyoun)
// geo:33.890408,35.506645?z=16.5(Falafel%20M.%20Sahyoun)
geo::GeoURLInfo info;
geo::GeoParser parser;
TEST(parser.Parse(res, info), ());
TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.8904075, kEps, ());
TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.5066454, kEps, ());
TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.5, kEps, ());
TEST_EQUAL(info.m_label, "Falafel M. Sahyoun", ());
}
} // namespace ge0

View file

@ -1,8 +1,11 @@
#include "ge0/url_generator.hpp"
#include "coding/url.hpp"
#include "base/assert.hpp"
#include <cmath>
#include <iomanip>
#include <sstream>
namespace
{
@ -83,8 +86,8 @@ namespace ge0
{
std::string GenerateShortShowMapUrl(double lat, double lon, double zoom, std::string const & name)
{
size_t constexpr schemaLength = 5; // strlen("om://")
std::string urlSample = "om://ZCoordba64";
size_t constexpr schemaLength = 18; // strlen("https://omaps.app/")
std::string urlSample = "https://omaps.app/ZCoordba64";
int const zoomI = (zoom <= 4 ? 0 : (zoom >= 19.75 ? 63 : static_cast<int>((zoom - 4) * 4)));
urlSample[schemaLength] = Base64Char(zoomI);
@ -100,6 +103,17 @@ std::string GenerateShortShowMapUrl(double lat, double lon, double zoom, std::st
return urlSample;
}
std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name)
{
std::ostringstream oss;
oss << "geo:" << std::fixed << std::setprecision(7) << lat << ',' << lon << "?z=" << std::setprecision(1) << zoom;
if (!name.empty())
oss << '(' << url::UrlEncode(name) << ')';
return oss.str();
}
char Base64Char(int x)
{
CHECK_GREATER_OR_EQUAL(x, 0, ());

View file

@ -19,6 +19,23 @@ inline static int const kMaxCoordBits = kMaxPointBytes * 3;
// om://ZCoordba64/Name
std::string GenerateShortShowMapUrl(double lat, double lon, double zoomLevel, std::string const & name);
// Generates a geo: uri.
//
// - https://datatracker.ietf.org/doc/html/rfc5870
// - https://developer.android.com/guide/components/intents-common#Maps
// - https://developers.google.com/maps/documentation/urls/android-intents
//
// URL format:
//
// +-------------------------------- lat
// | +-------------------- lon
// | | +---- zoom
// | | | +-- url-encoded name
// | | | |
// | | | |
// geo:54.683486138,25.289361259&z=14(Forto%20dvaras)
std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name);
// Exposed for testing.
char Base64Char(int x);
int LatToInt(double lat, int maxValue);

View file

@ -242,6 +242,10 @@ void RoutingSession::Reset()
m_passedDistanceOnRouteMeters = 0.0;
m_isFollowing = false;
m_lastCompletionPercent = 0;
// reset announcement counters
m_routingRebuildCount = -1;
m_routingRebuildAnnounceCount = 0;
}
void RoutingSession::SetState(SessionState state)
@ -484,6 +488,14 @@ void RoutingSession::GenerateNotifications(std::vector<std::string> & notificati
ASSERT(m_route, ());
// Generate recalculating notification if needed and reset
if (m_routingRebuildCount > m_routingRebuildAnnounceCount)
{
m_routingRebuildAnnounceCount = m_routingRebuildCount;
notifications.emplace_back(m_turnNotificationsMgr.GenerateRecalculatingText());
return;
}
// Voice turn notifications.
if (!m_routingSettings.m_soundDirection)
return;

View file

@ -234,6 +234,7 @@ private:
double m_passedDistanceOnRouteMeters = 0.0;
// Rerouting count
int m_routingRebuildCount = -1; // -1 for the first rebuild called in BuildRoute().
int m_routingRebuildAnnounceCount = 0; // track TTS announcement state (ignore the first build)
mutable double m_lastCompletionPercent = 0.0;
DECLARE_THREAD_CHECKER(m_threadChecker);

View file

@ -84,6 +84,11 @@ std::string NotificationManager::GenerateTurnText(uint32_t distanceUnits, uint8_
return m_getTtsText.GetTurnNotification({distanceUnits, exitNum, useThenInsteadOfDistance, turn.m_pedestrianTurn, lengthUnits, nextStreetInfo});
}
std::string NotificationManager::GenerateRecalculatingText() const
{
return m_getTtsText.GetRecalculatingNotification();
}
std::string NotificationManager::GenerateSpeedCameraText() const
{
return m_getTtsText.GetSpeedCameraNotification();

View file

@ -49,6 +49,9 @@ public:
measurement_utils::Units GetLengthUnits() const { return m_settings.GetLengthUnits(); }
void SetLocaleWithJsonForTesting(std::string const & json, std::string const & locale);
/// \brief Generate text of route rebuild notification.
std::string GenerateRecalculatingText() const;
/// \brief Generate text of speed camera notification.
std::string GenerateSpeedCameraText() const;

View file

@ -250,6 +250,11 @@ std::string GetTtsText::GetTurnNotification(Notification const & notification) c
return out;
}
std::string GetTtsText::GetRecalculatingNotification() const
{
return GetTextById("route_recalculating");
}
std::string GetTtsText::GetSpeedCameraNotification() const
{
return GetTextById("unknown_camera");

View file

@ -25,6 +25,8 @@ public:
std::string GetTurnNotification(Notification const & notification) const;
std::string GetRecalculatingNotification() const;
std::string GetSpeedCameraNotification() const;
/// \brief Sets a locale.