Compare commits

..

9 Commits

Author SHA1 Message Date
x7z4w
b0d1abb0d1 Update .git-blame-ignore-revs
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
x7z4w
eb28c50161 [core] Implicit m2::Point hash
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
x7z4w
650f0ca120 [generator] Implement hash for OsmElement::Tag
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
x7z4w
882dccb87d [core] Replace SmallMap
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
x7z4w
969e1ef2da [core] Switch to ankerl::unordered_dense
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
x7z4w
a60efa9b43 [drape] nits
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-11 21:51:56 +07:00
map-per
f1cf844986 [editor] Check OSM max char length in value validation
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 09:09:47 +01:00
map-per
f20c3bf50c [Android] Add info about outdated maps and update button to place page
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:11:44 +01:00
map-per
e7cc602904 [editor] Remove unused code from the old OSM editor
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:03:31 +01:00
464 changed files with 6158 additions and 16297 deletions

View File

@@ -2,3 +2,5 @@
480fa6c2fcf53be296504ac6ba8e6b3d70f92b42
a6ede2b1466f0c9d8a443600ef337ba6b5832e58
1377b81bf1cac72bb6da192da7fed6696d5d5281
99900fad21bab3f97065d11562346c5525a31f57
a53828154fc7aa363dfe8926affaea56237832e5

1
.gitignore vendored
View File

@@ -21,7 +21,6 @@ data/symbols/**/symbols.sdf
data/bookmarks
data/edits.xml
data/traffic.xml
data/World.mwm
data/WorldCoasts.mwm
data/world_mwm/*

83
3party/ankerl/stl.h Normal file
View File

@@ -0,0 +1,83 @@
///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////
// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
// Version 4.8.1
// https://github.com/martinus/unordered_dense
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 Martin Leitner-Ankerl <martin.ankerl@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef ANKERL_STL_H
#define ANKERL_STL_H
#include <array> // for array
#include <cstdint> // for uint64_t, uint32_t, std::uint8_t, UINT64_C
#include <cstring> // for size_t, memcpy, memset
#include <functional> // for equal_to, hash
#include <initializer_list> // for initializer_list
#include <iterator> // for pair, distance
#include <limits> // for numeric_limits
#include <memory> // for allocator, allocator_traits, shared_ptr
#include <optional> // for optional
#include <stdexcept> // for out_of_range
#include <string> // for basic_string
#include <string_view> // for basic_string_view, hash
#include <tuple> // for forward_as_tuple
#include <type_traits> // for enable_if_t, declval, conditional_t, ena...
#include <utility> // for forward, exchange, pair, as_const, piece...
#include <vector> // for vector
// <memory_resource> includes <mutex>, which fails to compile if
// targeting GCC >= 13 with the (rewritten) win32 thread model, and
// targeting Windows earlier than Vista (0x600). GCC predefines
// _REENTRANT when using the 'posix' model, and doesn't when using the
// 'win32' model.
#if defined __MINGW64__ && defined __GNUC__ && __GNUC__ >= 13 && !defined _REENTRANT
// _WIN32_WINNT is guaranteed to be defined here because of the
// <cstdint> inclusion above.
# ifndef _WIN32_WINNT
# error "_WIN32_WINNT not defined"
# endif
# if _WIN32_WINNT < 0x600
# define ANKERL_MEMORY_RESOURCE_IS_BAD() 1 // NOLINT(cppcoreguidelines-macro-usage)
# endif
#endif
#ifndef ANKERL_MEMORY_RESOURCE_IS_BAD
# define ANKERL_MEMORY_RESOURCE_IS_BAD() 0 // NOLINT(cppcoreguidelines-macro-usage)
#endif
#if defined(__has_include) && !defined(ANKERL_UNORDERED_DENSE_DISABLE_PMR)
# if __has_include(<memory_resource>) && !ANKERL_MEMORY_RESOURCE_IS_BAD()
# define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage)
# include <memory_resource> // for polymorphic_allocator
# elif __has_include(<experimental/memory_resource>)
# define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage)
# include <experimental/memory_resource> // for polymorphic_allocator
# endif
#endif
#if defined(_MSC_VER) && defined(_M_X64)
# include <intrin.h>
# pragma intrinsic(_umul128)
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,3 @@
# R8 crypts the source line numbers in all log messages.
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
-dontoptimize
# Keep classes for Android TraFF support
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_7 { *; }
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_8 { *; }

View File

@@ -1 +1 @@
version: 2025.07.23-4-FDroid+25072304
version: 2025.03.02-7-FDroid+25030207

View File

@@ -62,21 +62,6 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="org.traffxml.traff.GET_CAPABILITIES"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.POLL"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.SUBSCRIBE"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.SUBSCRIPTION_CHANGE"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.UNSUBSCRIBE"/>
</intent>
</queries>
<supports-screens

View File

@@ -28,7 +28,6 @@ import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import com.google.android.material.textview.MaterialTextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

View File

@@ -253,7 +253,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
mProgress.setMax(bytes);
// Start progress at 1% according to M3 guidelines
mProgress.setProgressCompat(bytes/100, true);
mProgress.setProgressCompat(bytes / 100, true);
}
else
finishFilesDownload(bytes);
@@ -372,7 +372,7 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
mTvMessage.setText(getString(R.string.downloading_country_can_proceed, item.name, fileSizeString));
mProgress.setMax((int) item.totalSize);
// Start progress at 1% according to M3 guidelines
mProgress.setProgressCompat((int) (item.totalSize/100), true);
mProgress.setProgressCompat((int) (item.totalSize / 100), true);
mCountryDownloadListenerSlot = MapManager.nativeSubscribe(mCountryDownloadListener);
MapManagerHelper.startDownload(mCurrentCountry);
@@ -424,17 +424,17 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity
default -> throw new AssertionError("Unexpected result code = " + result);
};
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
.setTitle(titleId)
.setMessage(messageId)
.setCancelable(true)
.setOnCancelListener((dialog) -> setAction(PAUSE))
.setPositiveButton(R.string.try_again,
(dialog, which) -> {
setAction(TRY_AGAIN);
onTryAgainClicked();
})
.setOnDismissListener(dialog -> mAlertDialog = null)
.show();
mAlertDialog = new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog)
.setTitle(titleId)
.setMessage(messageId)
.setCancelable(true)
.setOnCancelListener((dialog) -> setAction(PAUSE))
.setPositiveButton(R.string.try_again,
(dialog, which) -> {
setAction(TRY_AGAIN);
onTryAgainClicked();
})
.setOnDismissListener(dialog -> mAlertDialog = null)
.show();
}
}

View File

@@ -38,8 +38,9 @@ public class OsmUploadWork extends Worker
{
final Constraints c = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(OsmUploadWork.class).setConstraints(c);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
{
builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST);
}
final OneTimeWorkRequest wr = builder.build();
WorkManager.getInstance(context).beginUniqueWork("UploadOsmChanges", ExistingWorkPolicy.KEEP, wr).enqueue();

View File

@@ -17,7 +17,6 @@ import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import app.organicmaps.R;
import app.organicmaps.sdk.util.log.Logger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;

View File

@@ -10,7 +10,6 @@ import androidx.fragment.app.DialogFragment;
public class BaseMwmDialogFragment extends DialogFragment
{
protected int getStyle()
{
return STYLE_NORMAL;

View File

@@ -282,11 +282,13 @@ public class BookmarksListFragment extends BaseMwmRecyclerFragment<ConcatAdapter
{
if (isEmptySearchResults())
{
requirePlaceholder().setContent(R.string.search_not_found, R.string.search_not_found_query, R.drawable.ic_search_fail);
requirePlaceholder().setContent(R.string.search_not_found, R.string.search_not_found_query,
R.drawable.ic_search_fail);
}
else if (isEmpty())
{
requirePlaceholder().setContent(R.string.bookmarks_empty_list_title, R.string.bookmarks_empty_list_message, R.drawable.ic_bookmarks);
requirePlaceholder().setContent(R.string.bookmarks_empty_list_title, R.string.bookmarks_empty_list_message,
R.drawable.ic_bookmarks);
}
boolean isEmptyRecycler = isEmpty() || isEmptySearchResults();

View File

@@ -23,7 +23,6 @@ import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.recycler.RecyclerClickListener;
import app.organicmaps.widget.recycler.RecyclerLongClickListener;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.imageview.ShapeableImageView;
@@ -458,10 +457,12 @@ public class Holders
String formattedDesc = desc.replace("\n", "<br>");
Spanned spannedDesc = Utils.fromHtml(formattedDesc);
if (!TextUtils.isEmpty(spannedDesc)) {
if (!TextUtils.isEmpty(spannedDesc))
{
mDescText.setText(spannedDesc);
}
else {
else
{
mDescText.setText(R.string.list_description_empty);
}
}

View File

@@ -50,8 +50,8 @@ public final class IntentUtils
}
// https://developer.android.com/reference/androidx/car/app/CarContext#startCarApp(android.content.Intent)
private static void processNavigationIntent(@NonNull CarContext carContext,
@NonNull Renderer surfaceRenderer, @NonNull Intent intent)
private static void processNavigationIntent(@NonNull CarContext carContext, @NonNull Renderer surfaceRenderer,
@NonNull Intent intent)
{
// TODO (AndrewShkrob): This logic will need to be revised when we introduce support for adding stops during
// navigation or route planning. Skip navigation intents during navigation

View File

@@ -31,7 +31,7 @@ public final class RoutingHelpers
default -> Distance.UNIT_METERS;
};
return Distance.create(distance.mDistance, displayUnit);
return Distance.create(distance.mDistance, displayUnit);
}
@NonNull
@@ -52,7 +52,7 @@ public final class RoutingHelpers
default -> LaneDirection.SHAPE_UNKNOWN;
};
return LaneDirection.create(shape, isRecommended);
return LaneDirection.create(shape, isRecommended);
}
@NonNull
@@ -77,7 +77,7 @@ public final class RoutingHelpers
case EXIT_HIGHWAY_TO_LEFT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT;
case EXIT_HIGHWAY_TO_RIGHT -> Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT;
};
final Maneuver.Builder builder = new Maneuver.Builder(maneuverType);
final Maneuver.Builder builder = new Maneuver.Builder(maneuverType);
if (maneuverType == Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW)
builder.setRoundaboutExitNumber(roundaboutExitNum > 0 ? roundaboutExitNum : 1);
builder.setIcon(new CarIcon.Builder(createManeuverIcon(context, carDirection, roundaboutExitNum)).build());
@@ -85,7 +85,8 @@ public final class RoutingHelpers
}
@NonNull
private static IconCompat createManeuverIcon(@NonNull final CarContext context, @NonNull CarDirection carDirection, int roundaboutExitNum)
private static IconCompat createManeuverIcon(@NonNull final CarContext context, @NonNull CarDirection carDirection,
int roundaboutExitNum)
{
if (!CarDirection.isRoundAbout(carDirection) || roundaboutExitNum == 0)
{

View File

@@ -39,8 +39,7 @@ public final class UiHelpers
}
@NonNull
public static ActionStrip createMapActionStrip(@NonNull CarContext context,
@NonNull Renderer surfaceRenderer)
public static ActionStrip createMapActionStrip(@NonNull CarContext context, @NonNull Renderer surfaceRenderer)
{
final CarIcon iconPlus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_plus)).build();
final CarIcon iconMinus = new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_minus)).build();
@@ -59,15 +58,13 @@ public final class UiHelpers
}
@NonNull
public static MapController createMapController(@NonNull CarContext context,
@NonNull Renderer surfaceRenderer)
public static MapController createMapController(@NonNull CarContext context, @NonNull Renderer surfaceRenderer)
{
return new MapController.Builder().setMapActionStrip(createMapActionStrip(context, surfaceRenderer)).build();
}
@NonNull
public static Action createSettingsAction(@NonNull BaseMapScreen mapScreen,
@NonNull Renderer surfaceRenderer)
public static Action createSettingsAction(@NonNull BaseMapScreen mapScreen, @NonNull Renderer surfaceRenderer)
{
return createSettingsAction(mapScreen, surfaceRenderer, null);
}
@@ -81,8 +78,7 @@ public final class UiHelpers
}
@NonNull
private static Action createSettingsAction(@NonNull BaseMapScreen mapScreen,
@NonNull Renderer surfaceRenderer,
private static Action createSettingsAction(@NonNull BaseMapScreen mapScreen, @NonNull Renderer surfaceRenderer,
@Nullable OnScreenResultListener onScreenResultListener)
{
final CarContext context = mapScreen.getCarContext();
@@ -123,8 +119,7 @@ public final class UiHelpers
return null;
final Row.Builder builder = new Row.Builder();
builder.setImage(
new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_opening_hours)).build());
builder.setImage(new CarIcon.Builder(IconCompat.createWithResource(context, R.drawable.ic_opening_hours)).build());
if (isEmptyTT)
builder.setTitle(ohStr);

View File

@@ -125,7 +125,8 @@ public class EditTextDialogFragment extends BaseMwmDialogFragment
positiveButton.setOnClickListener(view -> {
final String result = mEtInput.getText().toString();
if (validateInput(requireActivity(), result)) {
if (validateInput(requireActivity(), result))
{
processInput(result);
editTextDialog.dismiss();
}

View File

@@ -4,7 +4,6 @@ import android.location.Location;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import app.organicmaps.MwmActivity;
@@ -49,7 +48,8 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
@Override
public void onStatusChanged(List<MapManager.StorageCallbackData> data)
{
if (mCurrentCountry == null) {
if (mCurrentCountry == null)
{
updateOfflineExplanationVisibility();
return;
}
@@ -109,10 +109,13 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
return enqueued || progress || applying;
}
private void updateOfflineExplanationVisibility() {
if (mOfflineExplanation == null) return;
private void updateOfflineExplanationVisibility()
{
if (mOfflineExplanation == null)
return;
// hide once threshold reached; safe to call repeatedly.
app.organicmaps.util.UiUtils.showIf(MapManager.nativeGetDownloadedCount() < (DEFAULT_MAP_BASELINE + HIDE_THRESHOLD), mOfflineExplanation);
app.organicmaps.util.UiUtils.showIf(MapManager.nativeGetDownloadedCount() < (DEFAULT_MAP_BASELINE + HIDE_THRESHOLD),
mOfflineExplanation);
}
private void updateProgressState(boolean shouldAutoDownload)

View File

@@ -15,7 +15,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AutoCompleteTextView;
import android.widget.GridLayout;
import androidx.annotation.CallSuper;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
@@ -398,7 +397,7 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
List<String> SOCKET_TYPES = Arrays.stream(getResources().getStringArray(R.array.charge_socket_types)).toList();
for (String socketType : SOCKET_TYPES)
{
ChargeSocketDescriptor socket = new ChargeSocketDescriptor(socketType,0,0);
ChargeSocketDescriptor socket = new ChargeSocketDescriptor(socketType, 0, 0);
MaterialButton btn = (MaterialButton) inflater.inflate(R.layout.button_socket_type, typeBtns, false);
@@ -406,16 +405,16 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// load SVG icon converted into VectorDrawable in res/drawable
@SuppressLint("DiscouragedApi")
int resIconId =
getResources().getIdentifier("ic_charge_socket_" + socket.visualType(), "drawable", requireContext().getPackageName());
int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.visualType(), "drawable",
requireContext().getPackageName());
if (resIconId != 0)
{
btn.setIcon(getResources().getDrawable(resIconId));
}
@SuppressLint("DiscouragedApi")
int resTypeId =
getResources().getIdentifier("charge_socket_" + socket.visualType(), "string", requireContext().getPackageName());
int resTypeId = getResources().getIdentifier("charge_socket_" + socket.visualType(), "string",
requireContext().getPackageName());
if (resTypeId != 0)
{
btn.setText(getResources().getString(resTypeId));
@@ -463,13 +462,16 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// Add a TextWatcher to validate on text change
countView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void onTextChanged(CharSequence s, int start, int before, int count)
{}
@Override
public void afterTextChanged(Editable s) {
public void afterTextChanged(Editable s)
{
validatePositiveField(s.toString(), countInputLayout);
}
});
@@ -484,13 +486,16 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
// Add a TextWatcher to validate on text change
powerView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void onTextChanged(CharSequence s, int start, int before, int count)
{}
@Override
public void afterTextChanged(Editable s) {
public void afterTextChanged(Editable s)
{
validatePositiveField(s.toString(), powerInputLayout);
}
});
@@ -498,74 +503,82 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
return new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
.setTitle(R.string.editor_socket)
.setView(dialogView)
.setPositiveButton(R.string.save,
(dialog, which) -> {
String socketType = "";
for (MaterialButton b : buttonList)
{
if (b.isChecked())
{
socketType = b.getTag(R.id.socket_type).toString();
break;
}
}
.setPositiveButton(
R.string.save,
(dialog, which) -> {
String socketType = "";
for (MaterialButton b : buttonList)
{
if (b.isChecked())
{
socketType = b.getTag(R.id.socket_type).toString();
break;
}
}
int countValue = 0; // 0 means 'unknown count'
try
{
countValue = Integer.parseInt(countView.getText().toString());
}
catch (NumberFormatException ignored)
{
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
}
int countValue = 0; // 0 means 'unknown count'
try
{
countValue = Integer.parseInt(countView.getText().toString());
}
catch (NumberFormatException ignored)
{
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
}
if (countValue < 0)
{
countValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
}
if (countValue < 0)
{
countValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid count value for socket:" + countView.getText().toString());
}
double powerValue = 0; // 0 means 'unknown power'
try
{
powerValue = Double.parseDouble(powerView.getText().toString());
}
catch (NumberFormatException ignored)
{
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
}
double powerValue = 0; // 0 means 'unknown power'
try
{
powerValue = Double.parseDouble(powerView.getText().toString());
}
catch (NumberFormatException ignored)
{
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
}
if (powerValue < 0)
{
powerValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
}
if (powerValue < 0)
{
powerValue = 0;
Logger.w(CHARGE_SOCKETS_TAG, "Invalid power value for socket:" + powerView.getText().toString());
}
ChargeSocketDescriptor socket =
new ChargeSocketDescriptor(socketType, countValue, powerValue);
ChargeSocketDescriptor socket = new ChargeSocketDescriptor(socketType, countValue, powerValue);
updateChargeSockets(socketIndex, socket);
})
updateChargeSockets(socketIndex, socket);
})
.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
}
// Helper method for validation logic
private boolean validatePositiveField(String text, TextInputLayout layout) {
if (text.isEmpty()) {
private boolean validatePositiveField(String text, TextInputLayout layout)
{
if (text.isEmpty())
{
layout.setError(null); // No error if empty (assuming 0 is the default)
return true;
}
try {
try
{
double value = Double.parseDouble(text);
if (value < 0) {
if (value < 0)
{
layout.setError(getString(R.string.error_value_must_be_positive));
return false;
} else {
layout.setError(null);
return true;
}
} catch (NumberFormatException e) {
else
{
layout.setError(null);
return true;
}
}
catch (NumberFormatException e)
{
layout.setError(getString(R.string.error_invalid_number));
return false;
}
@@ -585,7 +598,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
{
sockets[socketIndex] = socket;
}
else {
else
{
List<ChargeSocketDescriptor> list = new ArrayList<>(Arrays.asList(sockets));
list.add(socket);
sockets = list.toArray(new ChargeSocketDescriptor[0]);
@@ -603,7 +617,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
GridLayout socketsGrid = mChargeSockets.findViewById(R.id.socket_grid_editor);
socketsGrid.removeAllViews();
for (int i = 0; i < sockets.length; i++) {
for (int i = 0; i < sockets.length; i++)
{
final int currentIndex = i;
ChargeSocketDescriptor socket = sockets[i];
@@ -614,27 +629,30 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
MaterialTextView power = itemView.findViewById(R.id.socket_power);
MaterialTextView count = itemView.findViewById(R.id.socket_count);
// load SVG icon converted into VectorDrawable in res/drawable
@SuppressLint("DiscouragedApi")
int resIconId = getResources().getIdentifier("ic_charge_socket_" + socket.visualType(), "drawable",
requireContext().getPackageName());
if (resIconId != 0) {
requireContext().getPackageName());
if (resIconId != 0)
{
icon.setImageResource(resIconId);
}
@SuppressLint("DiscouragedApi")
int resTypeId =
getResources().getIdentifier("charge_socket_" + socket.visualType(), "string", requireContext().getPackageName());
if (resTypeId != 0) {
int resTypeId = getResources().getIdentifier("charge_socket_" + socket.visualType(), "string",
requireContext().getPackageName());
if (resTypeId != 0)
{
type.setText(resTypeId);
}
if (socket.power() != 0) {
if (socket.power() != 0)
{
DecimalFormat df = new DecimalFormat("#.##");
power.setText(getString(R.string.kw_label, df.format(socket.power())));
}
else if (socket.ignorePower()) {
else if (socket.ignorePower())
{
power.setVisibility(INVISIBLE);
}
@@ -643,7 +661,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
count.setText(getString(R.string.count_label, socket.count()));
}
itemView.setOnClickListener(v -> buildChargeSocketDialog(currentIndex, socket.type(), socket.count(), socket.power()).show());
itemView.setOnClickListener(
v -> buildChargeSocketDialog(currentIndex, socket.type(), socket.count(), socket.power()).show());
socketsGrid.addView(itemView);
}
@@ -789,9 +808,8 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
View lineContactBlock =
initBlock(view, Metadata.MetadataType.FMD_CONTACT_LINE, R.id.block_line, R.drawable.ic_line_white,
R.string.editor_line_social_network, InputType.TYPE_TEXT_VARIATION_URI);
View blueskyContactBlock =
initBlock(view, Metadata.MetadataType.FMD_CONTACT_BLUESKY, R.id.block_bluesky, R.drawable.ic_bluesky,
R.string.bluesky, InputType.TYPE_TEXT_VARIATION_URI);
View blueskyContactBlock = initBlock(view, Metadata.MetadataType.FMD_CONTACT_BLUESKY, R.id.block_bluesky,
R.drawable.ic_bluesky, R.string.bluesky, InputType.TYPE_TEXT_VARIATION_URI);
View operatorBlock = initBlock(view, Metadata.MetadataType.FMD_OPERATOR, R.id.block_operator,
R.drawable.ic_operator, R.string.editor_operator, 0);
@@ -1023,14 +1041,15 @@ public class EditorFragment extends BaseMwmFragment implements View.OnClickListe
private void placeDisused()
{
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
.setTitle(R.string.editor_mark_business_vacant_title)
.setMessage(R.string.editor_mark_business_vacant_description)
.setPositiveButton(R.string.editor_submit, (dlg, which) -> {
Editor.nativeMarkPlaceAsDisused();
mParent.processEditedFeatures();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
.setTitle(R.string.editor_mark_business_vacant_title)
.setMessage(R.string.editor_mark_business_vacant_description)
.setPositiveButton(R.string.editor_submit,
(dlg, which) -> {
Editor.nativeMarkPlaceAsDisused();
mParent.processEditedFeatures();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void commitPlaceDoesntExists(@NonNull String text)

View File

@@ -13,7 +13,6 @@ import app.organicmaps.R;
import app.organicmaps.sdk.editor.data.FeatureCategory;
import app.organicmaps.sdk.util.StringUtils;
import app.organicmaps.util.UiUtils;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textview.MaterialTextView;
@@ -69,8 +68,7 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter<RecyclerView.Vi
}
case TYPE_FOOTER ->
{
return new FooterViewHolder(inflater.inflate(R.layout.item_feature_category_footer, parent, false),
mFragment);
return new FooterViewHolder(inflater.inflate(R.layout.item_feature_category_footer, parent, false), mFragment);
}
default -> throw new IllegalArgumentException("Unsupported viewType: " + viewType);
}
@@ -134,26 +132,21 @@ public class FeatureCategoryAdapter extends RecyclerView.Adapter<RecyclerView.Vi
mSendNoteButton = itemView.findViewById(R.id.send_note_button);
mSendNoteButton.setOnClickListener(v -> listener.onSendNoteClicked());
final ColorStateList bgButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
mSendNoteButton.getContext(), R.color.base_accent),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_disabled)
});
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mSendNoteButton.getContext(), R.color.base_accent),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_disabled)});
final ColorStateList textButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
mSendNoteButton.getContext(),
UiUtils.getStyledResourceId(mSendNoteButton.getContext(), android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_text_disabled)
});
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mSendNoteButton.getContext(),
UiUtils.getStyledResourceId(mSendNoteButton.getContext(),
android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mSendNoteButton.getContext(), R.color.button_accent_text_disabled)});
mSendNoteButton.setBackgroundTintList(bgButtonColor);
mSendNoteButton.setTextColor(textButtonColor);
mNoteEditText.addTextChangedListener(new StringUtils.SimpleTextWatcher() {

View File

@@ -2,19 +2,16 @@ package app.organicmaps.editor;
import android.content.res.Configuration;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import app.organicmaps.R;
import app.organicmaps.sdk.editor.data.HoursMinutes;
import app.organicmaps.sdk.util.DateUtils;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
public class FromToTimePicker
{
@@ -32,18 +29,11 @@ public class FromToTimePicker
private boolean mIsFromTimePicked;
private int mInputMode;
public static void pickTime(@NonNull Fragment fragment,
@NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime,
@NonNull HoursMinutes toTime,
int id,
public static void pickTime(@NonNull Fragment fragment, @NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime, @NonNull HoursMinutes toTime, int id,
boolean startWithToTime)
{
FromToTimePicker timePicker = new FromToTimePicker(fragment,
listener,
fromTime,
toTime,
id);
FromToTimePicker timePicker = new FromToTimePicker(fragment, listener, fromTime, toTime, id);
if (startWithToTime)
timePicker.showToTimePicker();
@@ -51,11 +41,8 @@ public class FromToTimePicker
timePicker.showFromTimePicker();
}
private FromToTimePicker(@NonNull Fragment fragment,
@NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime,
@NonNull HoursMinutes toTime,
int id)
private FromToTimePicker(@NonNull Fragment fragment, @NonNull FromToTimePicker.OnPickListener listener,
@NonNull HoursMinutes fromTime, @NonNull HoursMinutes toTime, int id)
{
mActivity = fragment.requireActivity();
mFragmentManager = fragment.getChildFragmentManager();
@@ -100,15 +87,12 @@ public class FromToTimePicker
private MaterialTimePicker buildFromTimePicker()
{
MaterialTimePicker timePicker = buildTimePicker(mFromTime,
mResources.getString(R.string.editor_time_from),
mResources.getString(R.string.next_button),
null);
MaterialTimePicker timePicker = buildTimePicker(mFromTime, mResources.getString(R.string.editor_time_from),
mResources.getString(R.string.next_button), null);
timePicker.addOnNegativeButtonClickListener(view -> finishTimePicking(false));
timePicker.addOnPositiveButtonClickListener(view ->
{
timePicker.addOnPositiveButtonClickListener(view -> {
mIsFromTimePicked = true;
saveState(timePicker, true);
mFromTimePicker = null;
@@ -122,13 +106,10 @@ public class FromToTimePicker
private MaterialTimePicker buildToTimePicker()
{
MaterialTimePicker timePicker = buildTimePicker(mToTime,
mResources.getString(R.string.editor_time_to),
null,
MaterialTimePicker timePicker = buildTimePicker(mToTime, mResources.getString(R.string.editor_time_to), null,
mResources.getString(R.string.back));
timePicker.addOnNegativeButtonClickListener(view ->
{
timePicker.addOnNegativeButtonClickListener(view -> {
saveState(timePicker, false);
mToTimePicker = null;
if (mIsFromTimePicked)
@@ -137,8 +118,7 @@ public class FromToTimePicker
finishTimePicking(false);
});
timePicker.addOnPositiveButtonClickListener(view ->
{
timePicker.addOnPositiveButtonClickListener(view -> {
saveState(timePicker, false);
finishTimePicking(true);
});
@@ -149,18 +129,18 @@ public class FromToTimePicker
}
@NonNull
private MaterialTimePicker buildTimePicker(@NonNull HoursMinutes time,
@NonNull String title,
private MaterialTimePicker buildTimePicker(@NonNull HoursMinutes time, @NonNull String title,
@Nullable String positiveButtonTextOverride,
@Nullable String negativeButtonTextOverride)
{
MaterialTimePicker.Builder builder = new MaterialTimePicker.Builder()
.setTitleText(title)
.setTimeFormat(mIs24HourFormat ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H)
.setInputMode(mInputMode)
.setTheme(R.style.MwmTheme_MaterialTimePicker)
.setHour((int) time.hours)
.setMinute((int) time.minutes);
MaterialTimePicker.Builder builder =
new MaterialTimePicker.Builder()
.setTitleText(title)
.setTimeFormat(mIs24HourFormat ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H)
.setInputMode(mInputMode)
.setTheme(R.style.MwmTheme_MaterialTimePicker)
.setHour((int) time.hours)
.setMinute((int) time.minutes);
if (positiveButtonTextOverride != null)
builder.setPositiveButtonText(positiveButtonTextOverride);

View File

@@ -7,7 +7,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import androidx.annotation.IdRes;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
@@ -122,20 +121,14 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter<SimpleTimetableAdapter
notifyItemChanged(getItemCount() - 1);
}
private void pickTime(int position,
@IntRange(from = ID_OPENING_TIME, to = ID_CLOSED_SPAN) int id,
private void pickTime(int position, @IntRange(from = ID_OPENING_TIME, to = ID_CLOSED_SPAN) int id,
boolean startWithToTime)
{
final Timetable data = mItems.get(position);
mPickingPosition = position;
FromToTimePicker.pickTime(mFragment,
this,
data.workingTimespan.start,
data.workingTimespan.end,
id,
startWithToTime);
FromToTimePicker.pickTime(mFragment, this, data.workingTimespan.start, data.workingTimespan.end, id,
startWithToTime);
}
@Override
@@ -384,26 +377,21 @@ class SimpleTimetableAdapter extends RecyclerView.Adapter<SimpleTimetableAdapter
final String text = mFragment.getString(R.string.editor_time_add);
mAdd.setEnabled(enable);
final ColorStateList bgButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
mAdd.getContext(), R.color.base_accent),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_disabled)
});
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {ContextCompat.getColor(mAdd.getContext(), R.color.base_accent),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_disabled)});
final ColorStateList textButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
mAdd.getContext(),
UiUtils.getStyledResourceId(mAdd.getContext(), android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_text_disabled)
});
new int[][] {
new int[] {android.R.attr.state_enabled}, // enabled
new int[] {-android.R.attr.state_enabled} // disabled
},
new int[] {
ContextCompat.getColor(mAdd.getContext(), UiUtils.getStyledResourceId(
mAdd.getContext(), android.R.attr.textColorPrimaryInverse)),
ContextCompat.getColor(mAdd.getContext(), R.color.button_accent_text_disabled)});
mAdd.setBackgroundTintList(bgButtonColor);
mAdd.setTextColor(textButtonColor);
mAdd.setText(enable ? text + " (" + TimeFormatUtils.formatWeekdays(mComplementItem) + ")" : text);

View File

@@ -9,8 +9,8 @@ import androidx.annotation.Nullable;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmRecyclerFragment;
public class SimpleTimetableFragment extends BaseMwmRecyclerFragment<SimpleTimetableAdapter>
implements TimetableProvider
public class SimpleTimetableFragment
extends BaseMwmRecyclerFragment<SimpleTimetableAdapter> implements TimetableProvider
{
private SimpleTimetableAdapter mAdapter;
@Nullable

View File

@@ -35,25 +35,30 @@ public class LayerBottomSheetItem
@DrawableRes
int drawableResId = 0;
@StringRes
int buttonTextResource = switch (mode) {
case OUTDOORS -> {
drawableResId = R.drawable.ic_layers_outdoors;
yield R.string.button_layer_outdoor;
}
case SUBWAY -> {
drawableResId = R.drawable.ic_layers_subway;
yield R.string.subway;
}
case ISOLINES -> {
drawableResId = R.drawable.ic_layers_isoline;
yield R.string.button_layer_isolines;
}
case TRAFFIC -> {
drawableResId = R.drawable.ic_layers_traffic;
yield R.string.button_layer_traffic;
}
int buttonTextResource = switch (mode)
{
case OUTDOORS ->
{
drawableResId = R.drawable.ic_layers_outdoors;
yield R.string.button_layer_outdoor;
}
case SUBWAY ->
{
drawableResId = R.drawable.ic_layers_subway;
yield R.string.subway;
}
case ISOLINES ->
{
drawableResId = R.drawable.ic_layers_isoline;
yield R.string.button_layer_isolines;
}
case TRAFFIC ->
{
drawableResId = R.drawable.ic_layers_traffic;
yield R.string.button_layer_traffic;
}
};
return new LayerBottomSheetItem(drawableResId, buttonTextResource, mode, layerItemClickListener);
return new LayerBottomSheetItem(drawableResId, buttonTextResource, mode, layerItemClickListener);
}
@NonNull

View File

@@ -5,11 +5,9 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.adapter.OnItemClickListener;
import com.google.android.material.textview.MaterialTextView;
class LayerHolder extends RecyclerView.ViewHolder
{

View File

@@ -12,7 +12,6 @@ public class LayersUtils
availableLayers.add(Mode.OUTDOORS);
availableLayers.add(Mode.ISOLINES);
availableLayers.add(Mode.SUBWAY);
availableLayers.add(Mode.TRAFFIC);
return availableLayers;
}
}

View File

@@ -395,7 +395,7 @@ public class MapButtonsController extends Fragment
0;
// Allow offset tolerance for zoom buttons
};
showButton(getViewTopOffset(translation, button) >= toleranceOffset, entry.getKey());
showButton(getViewTopOffset(translation, button) >= toleranceOffset, entry.getKey());
}
}
}

View File

@@ -43,7 +43,6 @@ import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.widget.recycler.DotDividerItemDecoration;
import app.organicmaps.widget.recycler.MultilineLayoutManager;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView;
@@ -123,9 +122,9 @@ final class RoutingBottomMenuController implements View.OnClickListener
@NonNull View timeElevationLine, @NonNull View transitFrame,
@NonNull MaterialTextView error, @NonNull MaterialButton start,
@NonNull ShapeableImageView altitudeChart, @NonNull MaterialTextView time,
@NonNull MaterialTextView altitudeDifference, @NonNull MaterialTextView timeVehicle,
@Nullable MaterialTextView arrival, @NonNull View actionFrame,
@Nullable RoutingBottomMenuListener listener)
@NonNull MaterialTextView altitudeDifference,
@NonNull MaterialTextView timeVehicle, @Nullable MaterialTextView arrival,
@NonNull View actionFrame, @Nullable RoutingBottomMenuListener listener)
{
mContext = context;
mAltitudeChartFrame = altitudeChartFrame;

View File

@@ -12,9 +12,6 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.MwmApplication;
import app.organicmaps.R;
import app.organicmaps.sdk.Framework;
@@ -29,6 +26,7 @@ import app.organicmaps.util.WindowInsetUtils.PaddingInsetsListener;
import app.organicmaps.widget.RoutingToolbarButton;
import app.organicmaps.widget.ToolbarController;
import app.organicmaps.widget.WheelProgressView;
import com.google.android.material.textview.MaterialTextView;
public class RoutingPlanController extends ToolbarController
{
@@ -264,7 +262,7 @@ public class RoutingPlanController extends ToolbarController
default -> throw new IllegalArgumentException("unknown router: " + router);
};
RoutingToolbarButton button = mRouterTypes.findViewById(mRouterTypes.getCheckedRadioButtonId());
RoutingToolbarButton button = mRouterTypes.findViewById(mRouterTypes.getCheckedRadioButtonId());
button.progress();
updateProgressLabels();

View File

@@ -14,12 +14,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.sdk.search.DisplayedCategories;
import app.organicmaps.sdk.util.Language;
import com.google.android.material.textview.MaterialTextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;

View File

@@ -10,14 +10,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.sdk.search.SearchResult;
import app.organicmaps.util.Graphics;
import app.organicmaps.util.ThemeUtils;
import app.organicmaps.util.UiUtils;
import com.google.android.material.textview.MaterialTextView;
class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHolder>
{
@@ -152,7 +150,8 @@ class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHol
{
final Resources resources = mSearchFragment.getResources();
if (result.description.openNow != SearchResult.OPEN_NOW_YES && result.description.openNow != SearchResult.OPEN_NOW_NO)
if (result.description.openNow != SearchResult.OPEN_NOW_YES
&& result.description.openNow != SearchResult.OPEN_NOW_NO)
{
// Hide if unknown opening hours state
UiUtils.hide(mOpen);
@@ -169,15 +168,18 @@ class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.SearchDataViewHol
{
final String minsToChangeStr = resources.getQuantityString(
R.plurals.minutes_short, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1));
final String nextChangeFormatted = resources.getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
final String nextChangeFormatted =
resources.getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
UiUtils.setTextAndShow(mOpen, nextChangeFormatted);
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), R.color.base_yellow));
}
else
{
UiUtils.setTextAndShow(mOpen, isOpen ? resources.getString(R.string.editor_time_open) : resources.getString(R.string.closed));
mOpen.setTextColor(ContextCompat.getColor(mSearchFragment.getContext(), isOpen ? R.color.base_green : R.color.base_red));
UiUtils.setTextAndShow(
mOpen, isOpen ? resources.getString(R.string.editor_time_open) : resources.getString(R.string.closed));
mOpen.setTextColor(
ContextCompat.getColor(mSearchFragment.getContext(), isOpen ? R.color.base_green : R.color.base_red));
}
}

View File

@@ -273,7 +273,8 @@ public class SearchFragment extends BaseMwmFragment implements SearchListener, C
RecyclerView mResults = mResultsFrame.findViewById(R.id.recycler);
setRecyclerScrollListener(mResults);
mResultsPlaceholder = mResultsFrame.findViewById(R.id.placeholder);
mResultsPlaceholder.setContent(R.string.search_not_found, R.string.search_not_found_query, R.drawable.ic_search_fail);
mResultsPlaceholder.setContent(R.string.search_not_found, R.string.search_not_found_query,
R.drawable.ic_search_fail);
mSearchAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver()
{

View File

@@ -5,15 +5,13 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.MwmApplication;
import app.organicmaps.R;
import app.organicmaps.sdk.routing.RoutingController;
import app.organicmaps.sdk.search.SearchRecents;
import app.organicmaps.util.Graphics;
import app.organicmaps.widget.SearchToolbarController;
import com.google.android.material.textview.MaterialTextView;
class SearchHistoryAdapter extends RecyclerView.Adapter<SearchHistoryAdapter.ViewHolder>
{

View File

@@ -8,14 +8,12 @@ import android.view.ViewGroup;
import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.materialswitch.MaterialSwitch;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmToolbarFragment;
import app.organicmaps.sdk.routing.RoutingController;
import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.settings.RoadType;
import com.google.android.material.materialswitch.MaterialSwitch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

View File

@@ -3,19 +3,12 @@ package app.organicmaps.settings;
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.TwoStatePreference;
@@ -35,7 +28,6 @@ import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.search.SearchRecents;
import app.organicmaps.sdk.settings.MapLanguageCode;
import app.organicmaps.sdk.settings.UnitLocale;
import app.organicmaps.sdk.traffxml.AndroidTransport;
import app.organicmaps.sdk.util.Config;
import app.organicmaps.sdk.util.NetworkPolicy;
import app.organicmaps.sdk.util.PowerManagment;
@@ -43,13 +35,11 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
import app.organicmaps.sdk.util.log.LogsManager;
import app.organicmaps.util.ThemeSwitcher;
import app.organicmaps.util.Utils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
{
@@ -71,10 +61,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
initAutoDownloadPrefsCallbacks();
initLargeFontSizePrefsCallbacks();
initTransliterationPrefsCallbacks();
initTrafficHttpEnabledPrefsCallbacks();
initTrafficHttpUrlPrefsCallbacks();
initTrafficAppsPrefs();
initTrafficLegacyEnabledPrefsCallbacks();
init3dModePrefsCallbacks();
initPerspectivePrefsCallbacks();
initAutoZoomPrefsCallbacks();
@@ -150,46 +136,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
pref.setSummary(locale.getDisplayLanguage());
}
private void updateTrafficHttpUrlSummary()
{
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
String summary = Config.getTrafficHttpUrl();
if (summary.length() == 0)
pref.setSummary(R.string.traffic_http_url_not_set);
else
pref.setSummary(summary);
}
private void updateTrafficAppsSummary()
{
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
/*
* If the preference is disabled, it has not been initialized. This is the case if no TraFF
* apps were found. The code below would crash when trying to access the entries, and there
* is no need to update the summary if the setting cannot be changed.
*/
if (!pref.isEnabled())
return;
String[] apps = Config.getTrafficApps();
if (apps.length == 0)
pref.setSummary(R.string.traffic_apps_none_selected);
else
{
String summary = "";
for (int i = 0; i < apps.length; i++)
{
if (i > 0)
summary = summary + ", ";
int index = pref.findIndexOfValue(apps[i]);
if (i >= 0)
summary = summary + pref.getEntries()[index];
else
summary = summary + apps[i];
}
pref.setSummary(summary);
}
}
private void updateRoutingSettingsPrefsSummary()
{
final Preference pref = getPreference(getString(R.string.prefs_routing));
@@ -217,8 +163,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
updateVoiceInstructionsPrefsSummary();
updateRoutingSettingsPrefsSummary();
updateMapLanguageCodeSummary();
updateTrafficHttpUrlSummary();
updateTrafficAppsSummary();
}
@Override
@@ -280,91 +224,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
});
}
private void initTrafficHttpEnabledPrefsCallbacks()
{
final Preference pref = getPreference(getString(R.string.pref_traffic_http_enabled));
((TwoStatePreference)pref).setChecked(Config.getTrafficHttpEnabled());
pref.setOnPreferenceChangeListener((preference, newValue) -> {
final boolean oldVal = Config.getTrafficHttpEnabled();
final boolean newVal = (Boolean) newValue;
if (oldVal != newVal)
Config.setTrafficHttpEnabled(newVal);
return true;
});
}
private void initTrafficHttpUrlPrefsCallbacks()
{
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
((EditTextPreference)pref).setText(Config.getTrafficHttpUrl());
pref.setOnPreferenceChangeListener((preference, newValue) -> {
final String oldVal = Config.getTrafficHttpUrl();
final String newVal = (String) newValue;
if (!oldVal.equals(newVal))
Config.setTrafficHttpUrl(newVal);
return true;
});
}
private void initTrafficAppsPrefs()
{
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0);
if (receivers == null || receivers.isEmpty())
{
pref.setSummary(R.string.traffic_apps_not_available);
pref.setEnabled(false);
return;
}
pref.setEnabled(true);
List<String> entryList = new ArrayList<>(receivers.size());
List<String> valueList = new ArrayList<>(receivers.size());
for (ResolveInfo receiver : receivers)
{
// friendly name
entryList.add(receiver.loadLabel(pm).toString());
// actual value (we just need the package name, broadcasts are sent to any receiver in the package)
valueList.add(receiver.activityInfo.applicationInfo.packageName);
}
pref.setEntries(entryList.toArray(new CharSequence[0]));
pref.setEntryValues(valueList.toArray(new CharSequence[0]));
pref.setOnPreferenceChangeListener((preference, newValue) -> {
// newValue is a Set<String>, each item is a package ID
String[] apps = ((Set<String>)newValue).toArray(new String[0]);
Config.setTrafficApps(apps);
updateTrafficAppsSummary();
return true;
});
}
private void initTrafficLegacyEnabledPrefsCallbacks()
{
final Preference pref = getPreference(getString(R.string.pref_traffic_legacy_enabled));
((TwoStatePreference)pref).setChecked(Config.getTrafficLegacyEnabled());
pref.setOnPreferenceChangeListener((preference, newValue) -> {
final boolean oldVal = Config.getTrafficLegacyEnabled();
final boolean newVal = (Boolean) newValue;
if (oldVal != newVal)
Config.setTrafficLegacyEnabled(newVal);
return true;
});
}
private void initUseMobileDataPrefsCallbacks()
{
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));

View File

@@ -8,165 +8,165 @@ import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public abstract class BaseSignView extends View
{
private float mBorderWidthRatio = 0.1f;
protected void setBorderWidthRatio(float ratio) {
mBorderWidthRatio = ratio;
}
private float mBorderWidthRatio = 0.1f;
protected void setBorderWidthRatio(float ratio)
{
mBorderWidthRatio = ratio;
}
private float mBorderInsetRatio = 0f;
protected void setBorderInsetRatio(float ratio) {
mBorderInsetRatio = ratio;
}
private float mBorderInsetRatio = 0f;
protected void setBorderInsetRatio(float ratio)
{
mBorderInsetRatio = ratio;
}
// colors
protected int mBackgroundColor;
protected int mBorderColor;
protected int mAlertColor;
protected int mTextColor;
protected int mTextAlertColor;
// colors
protected int mBackgroundColor;
protected int mBorderColor;
protected int mAlertColor;
protected int mTextColor;
protected int mTextAlertColor;
// paints
protected final Paint mBackgroundPaint;
protected final Paint mBorderPaint;
protected final Paint mTextPaint;
// paints
protected final Paint mBackgroundPaint;
protected final Paint mBorderPaint;
protected final Paint mTextPaint;
// geometry
protected float mWidth;
protected float mHeight;
protected float mRadius;
protected float mBorderWidth;
protected float mBorderRadius;
// geometry
protected float mWidth;
protected float mHeight;
protected float mRadius;
protected float mBorderWidth;
protected float mBorderRadius;
public BaseSignView(Context ctx, @Nullable AttributeSet attrs)
public BaseSignView(Context ctx, @Nullable AttributeSet attrs)
{
super(ctx, attrs);
mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
}
protected void setColors(int backgroundColor, int borderColor, int alertColor, int textColor, int textAlertColor)
{
mBackgroundColor = backgroundColor;
mBorderColor = borderColor;
mAlertColor = alertColor;
mTextColor = textColor;
mTextAlertColor = textAlertColor;
mBackgroundPaint.setColor(mBackgroundColor);
mBorderPaint.setColor(mBorderColor);
mTextPaint.setColor(mTextColor);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
super.onSizeChanged(width, height, oldWidth, oldHeight);
final float paddingX = getPaddingLeft() + getPaddingRight();
final float paddingY = getPaddingTop() + getPaddingBottom();
mWidth = width - paddingX;
mHeight = height - paddingY;
mRadius = Math.min(mWidth, mHeight) / 2f;
mBorderWidth = mRadius * mBorderWidthRatio;
// subtract half the stroke PLUS the extra inset
final float gap = mRadius * mBorderInsetRatio;
mBorderRadius = mRadius - (mBorderWidth / 2f) - gap;
configureTextSize();
}
@Override
protected void onDraw(@NonNull Canvas canvas)
{
super.onDraw(canvas);
final String str = getValueString();
if (str == null)
return;
final float cx = mWidth / 2f;
final float cy = mHeight / 2f;
// background & border
boolean alert = isAlert();
mBackgroundPaint.setColor(alert ? mAlertColor : mBackgroundColor);
canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint);
if (!alert)
{
super(ctx, attrs);
mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint.setStyle(Paint.Style.STROKE);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint);
}
protected void setColors(int backgroundColor,
int borderColor,
int alertColor,
int textColor,
int textAlertColor)
// text
mTextPaint.setColor(alert ? mTextAlertColor : mTextColor);
drawValueString(canvas, cx, cy, str);
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent e)
{
final float cx = mWidth / 2f, cy = mHeight / 2f;
final float dx = e.getX() - cx, dy = e.getY() - cy;
if ((dx * dx) + (dy * dy) <= (mRadius * mRadius))
{
mBackgroundColor = backgroundColor;
mBorderColor = borderColor;
mAlertColor = alertColor;
mTextColor = textColor;
mTextAlertColor = textAlertColor;
mBackgroundPaint.setColor(mBackgroundColor);
mBorderPaint.setColor(mBorderColor);
mTextPaint.setColor(mTextColor);
performClick();
return true;
}
return false;
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight);
final float paddingX = getPaddingLeft() + getPaddingRight();
final float paddingY = getPaddingTop() + getPaddingBottom();
mWidth = width - paddingX;
mHeight = height - paddingY;
mRadius = Math.min(mWidth, mHeight) / 2f;
mBorderWidth = mRadius * mBorderWidthRatio;
// subtract half the stroke PLUS the extra inset
final float gap = mRadius * mBorderInsetRatio;
mBorderRadius = mRadius - (mBorderWidth / 2f) - gap;
configureTextSize();
}
@Override
public boolean performClick()
{
super.performClick();
return false;
}
@Override
protected void onDraw(@NonNull Canvas canvas)
private void drawValueString(Canvas c, float cx, float cy, String str)
{
Rect b = new Rect();
mTextPaint.getTextBounds(str, 0, str.length(), b);
final float y = cy - b.exactCenterY();
c.drawText(str, cx, y, mTextPaint);
}
void configureTextSize()
{
String text = getValueString();
if (text == null)
return;
final float textRadius = mBorderRadius - mBorderWidth;
final float maxTextSize = 2f * textRadius;
final float maxTextSize2 = maxTextSize * maxTextSize;
float lo = 0f, hi = maxTextSize, sz = maxTextSize;
Rect b = new Rect();
while (lo <= hi)
{
super.onDraw(canvas);
final String str = getValueString();
if (str == null) return;
final float cx = mWidth / 2f;
final float cy = mHeight / 2f;
// background & border
boolean alert = isAlert();
mBackgroundPaint.setColor(alert ? mAlertColor : mBackgroundColor);
canvas.drawCircle(cx, cy, mRadius, mBackgroundPaint);
if (!alert)
{
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setColor(mBorderColor);
canvas.drawCircle(cx, cy, mBorderRadius, mBorderPaint);
}
// text
mTextPaint.setColor(alert ? mTextAlertColor : mTextColor);
drawValueString(canvas, cx, cy, str);
sz = (lo + hi) / 2f;
mTextPaint.setTextSize(sz);
mTextPaint.getTextBounds(text, 0, text.length(), b);
float area = b.width() * b.width() + b.height() * b.height();
if (area <= maxTextSize2)
lo = sz + 1f;
else
hi = sz - 1f;
}
mTextPaint.setTextSize(Math.max(1f, sz));
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent e)
{
final float cx = mWidth / 2f, cy = mHeight / 2f;
final float dx = e.getX() - cx, dy = e.getY() - cy;
if ((dx * dx) + (dy * dy) <= (mRadius * mRadius))
{
performClick();
return true;
}
return false;
}
/** child must return the string to draw, or null if nothing */
@Nullable
protected abstract String getValueString();
@Override
public boolean performClick()
{
super.performClick();
return false;
}
private void drawValueString(Canvas c, float cx, float cy, String str)
{
Rect b = new Rect();
mTextPaint.getTextBounds(str, 0, str.length(), b);
final float y = cy - b.exactCenterY();
c.drawText(str, cx, y, mTextPaint);
}
void configureTextSize()
{
String text = getValueString();
if (text == null) return;
final float textRadius = mBorderRadius - mBorderWidth;
final float maxTextSize = 2f * textRadius;
final float maxTextSize2 = maxTextSize * maxTextSize;
float lo = 0f, hi = maxTextSize, sz = maxTextSize;
Rect b = new Rect();
while (lo <= hi)
{
sz = (lo + hi) / 2f;
mTextPaint.setTextSize(sz);
mTextPaint.getTextBounds(text, 0, text.length(), b);
float area = b.width()*b.width() + b.height()*b.height();
if (area <= maxTextSize2)
lo = sz + 1f;
else
hi = sz - 1f;
}
mTextPaint.setTextSize(Math.max(1f, sz));
}
/** child must return the string to draw, or null if nothing */
@Nullable
protected abstract String getValueString();
/** child decides if this is in “alert” state */
protected abstract boolean isAlert();
/** child decides if this is in “alert” state */
protected abstract boolean isAlert();
}

View File

@@ -4,9 +4,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Pair;
import androidx.annotation.Nullable;
import app.organicmaps.R;
import app.organicmaps.sdk.util.StringUtils;
@@ -22,18 +20,18 @@ public class CurrentSpeedView extends BaseSignView
setBorderWidthRatio(0.1f);
setBorderInsetRatio(0.05f);
try (TypedArray a = ctx.getTheme()
.obtainStyledAttributes(attrs, R.styleable.CurrentSpeedView /* reuse same attrs or define new */ , 0, 0))
try (TypedArray a = ctx.getTheme().obtainStyledAttributes(
attrs, R.styleable.CurrentSpeedView /* reuse same attrs or define new */, 0, 0))
{
int bg = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBackgroundColor, DefaultValues.BACKGROUND_COLOR);
int bd = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBorderColor, DefaultValues.BORDER_COLOR);
int tc = a.getColor(R.styleable.CurrentSpeedView_currentSpeedTextColor, DefaultValues.TEXT_COLOR);
int bg = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBackgroundColor, DefaultValues.BACKGROUND_COLOR);
int bd = a.getColor(R.styleable.CurrentSpeedView_currentSpeedBorderColor, DefaultValues.BORDER_COLOR);
int tc = a.getColor(R.styleable.CurrentSpeedView_currentSpeedTextColor, DefaultValues.TEXT_COLOR);
setColors(bg, bd, 0, tc, 0);
if (isInEditMode())
{
mSpeedMps = a.getInt(R.styleable.CurrentSpeedView_currentSpeedEditModeCurrentSpeed, 50);
mSpeedStr = Integer.toString((int)mSpeedMps);
mSpeedMps = a.getInt(R.styleable.CurrentSpeedView_currentSpeedEditModeCurrentSpeed, 50);
mSpeedStr = Integer.toString((int) mSpeedMps);
}
}
}
@@ -47,7 +45,7 @@ public class CurrentSpeedView extends BaseSignView
}
else
{
Pair<String,String> su = StringUtils.nativeFormatSpeedAndUnits(mps);
Pair<String, String> su = StringUtils.nativeFormatSpeedAndUnits(mps);
mSpeedStr = su.first;
}
requestLayout();
@@ -70,8 +68,8 @@ public class CurrentSpeedView extends BaseSignView
private interface DefaultValues
{
int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFF000000;
int TEXT_COLOR = 0xFF000000;
int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFF000000;
int TEXT_COLOR = 0xFF000000;
}
}

View File

@@ -12,12 +12,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.util.UiUtils;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView;
public class PlaceholderView extends LinearLayout
{

View File

@@ -5,16 +5,14 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import app.organicmaps.R;
public class SpeedLimitView extends BaseSignView
{
private int mSpeedLimit = -1;
private boolean mAlert = false;
private String mSpeedStr = "-1";
private int mSpeedLimit = -1;
private boolean mAlert = false;
private String mSpeedStr = "-1";
private final int unlimitedBorderColor;
private final int unlimitedStripeColor;
@@ -27,15 +25,22 @@ public class SpeedLimitView extends BaseSignView
try (TypedArray styleAttrs = ctx.getTheme().obtainStyledAttributes(attrs, R.styleable.SpeedLimitView, 0, 0))
{
final int bgColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR);
final int borderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR);
final int alertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR);
final int textColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR);
final int txtAlertColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR);
final int bgColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBackgroundColor, DefaultValues.BACKGROUND_COLOR);
final int borderColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitBorderColor, DefaultValues.BORDER_COLOR);
final int alertColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitAlertColor, DefaultValues.ALERT_COLOR);
final int textColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextColor, DefaultValues.TEXT_COLOR);
final int txtAlertColor =
styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitTextAlertColor, DefaultValues.TEXT_ALERT_COLOR);
setColors(bgColor, borderColor, alertColor, textColor, txtAlertColor);
unlimitedBorderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedBorderColor, DefaultValues.UNLIMITED_BORDER_COLOR);
unlimitedStripeColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedStripeColor, DefaultValues.UNLIMITED_STRIPE_COLOR);
unlimitedBorderColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedBorderColor,
DefaultValues.UNLIMITED_BORDER_COLOR);
unlimitedStripeColor = styleAttrs.getColor(R.styleable.SpeedLimitView_speedLimitUnlimitedStripeColor,
DefaultValues.UNLIMITED_STRIPE_COLOR);
if (isInEditMode())
{
@@ -51,7 +56,7 @@ public class SpeedLimitView extends BaseSignView
if (mSpeedLimit != limit)
{
mSpeedLimit = limit;
mSpeedStr = Integer.toString(limit);
mSpeedStr = Integer.toString(limit);
requestLayout();
}
mAlert = alert;
@@ -75,7 +80,7 @@ public class SpeedLimitView extends BaseSignView
@Override
protected void onDraw(Canvas canvas)
{
final float cx = mWidth/2f, cy = mHeight/2f;
final float cx = mWidth / 2f, cy = mHeight / 2f;
if (mSpeedLimit == 0) // 0 means unlimited speed (maxspeed=none)
{
@@ -105,7 +110,7 @@ public class SpeedLimitView extends BaseSignView
stripe.setStrokeWidth(mBorderWidth * 0.4f);
final float radius = mRadius * 0.8f; // Shorten to 80% of full radius
final float diag = (float) (1/Math.sqrt(2)); // 45 degrees
final float diag = (float) (1 / Math.sqrt(2)); // 45 degrees
final float dx = -diag, dy = +diag;
final float px = -dy, py = +dx; // Perpendicular
final float step = radius * 0.15f; // Spacing
@@ -122,14 +127,13 @@ public class SpeedLimitView extends BaseSignView
}
}
private interface DefaultValues
{
int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFFFF0000;
int ALERT_COLOR = 0xFFFF0000;
int TEXT_COLOR = 0xFF000000;
int TEXT_ALERT_COLOR = 0xFFFFFFFF;
int BACKGROUND_COLOR = 0xFFFFFFFF;
int BORDER_COLOR = 0xFFFF0000;
int ALERT_COLOR = 0xFFFF0000;
int TEXT_COLOR = 0xFF000000;
int TEXT_ALERT_COLOR = 0xFFFFFFFF;
int UNLIMITED_BORDER_COLOR = 0xFF000000;
int UNLIMITED_STRIPE_COLOR = 0xFF000000;
}

View File

@@ -72,7 +72,7 @@ public class MyPositionButton
case LocationState.FOLLOW_AND_ROTATE -> R.drawable.ic_follow_and_rotate;
default -> throw new IllegalArgumentException("Invalid button mode: " + mode);
};
image = ResourcesCompat.getDrawable(resources, drawableRes, context.getTheme());
image = ResourcesCompat.getDrawable(resources, drawableRes, context.getTheme());
mIcons.put(mode, image);
}

View File

@@ -1,9 +1,9 @@
package app.organicmaps.widget.placepage;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.sdk.util.StringUtils;
import com.github.mikephil.charting.charts.BarLineChartBase;
import androidx.annotation.Nullable;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;

View File

@@ -105,7 +105,8 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View.
public EditBookmarkFragment() {}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
public void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.MwmTheme_FullScreenDialog);
}
@@ -184,10 +185,9 @@ public class EditBookmarkFragment extends BaseMwmDialogFragment implements View.
{
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
dialog.getWindow().setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
if (dialog != null)
{
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
// Focus name and show keyboard for "Unknown Place" bookmarks

View File

@@ -6,9 +6,6 @@ import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.ChartController;
import app.organicmaps.R;
import app.organicmaps.sdk.Framework;
@@ -17,6 +14,7 @@ import app.organicmaps.sdk.bookmarks.data.Track;
import app.organicmaps.sdk.bookmarks.data.TrackStatistics;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import com.google.android.material.textview.MaterialTextView;
import java.util.Objects;
public class ElevationProfileViewRenderer implements PlacePageStateListener

View File

@@ -14,7 +14,8 @@ public class OpenStateTextFormatter
return String.format(Locale.ROOT, "%02d:%02d", hour, minute);
int h = hour % 12;
if (h == 0) h = 12;
if (h == 0)
h = 12;
String ampm = (hour < 12) ? "AM" : "PM";
return String.format(Locale.ROOT, "%d:%02d %s", h, minute, ampm);
}
@@ -29,21 +30,13 @@ public class OpenStateTextFormatter
return t.getDayOfWeek().getDisplayName(TextStyle.SHORT, locale);
}
static String buildAtLabel(
boolean opens,
boolean isToday,
String dayShort,
String time,
String opensAtLocalized,
String closesAtLocalized,
String opensDayAtLocalized,
String closesDayAtLocalized
)
static String buildAtLabel(boolean opens, boolean isToday, String dayShort, String time, String opensAtLocalized,
String closesAtLocalized, String opensDayAtLocalized, String closesDayAtLocalized)
{
if (isToday)
return opens ? String.format(Locale.ROOT, opensAtLocalized, time) // Opens at %s
: String.format(Locale.ROOT, closesAtLocalized, time); // Closes at %s
: String.format(Locale.ROOT, closesAtLocalized, time); // Closes at %s
return opens ? String.format(Locale.ROOT, opensDayAtLocalized, dayShort, time) // Opens %s at %s
: String.format(Locale.ROOT, closesDayAtLocalized, dayShort, time); // Closes %s at %s
: String.format(Locale.ROOT, closesDayAtLocalized, dayShort, time); // Closes %s at %s
}
}

View File

@@ -80,6 +80,6 @@ public class PlacePageButtonFactory
yield R.drawable.ic_more;
}
};
return new PlacePageButton(titleId, iconId, buttonType);
return new PlacePageButton(titleId, iconId, buttonType);
}
}

View File

@@ -31,7 +31,6 @@ import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import app.organicmaps.MwmActivity;
import app.organicmaps.MwmApplication;
import app.organicmaps.R;
@@ -154,6 +153,7 @@ public class PlacePageView extends Fragment
private MaterialTextView mTvLastChecked;
private View mEditPlace;
private View mAddPlace;
private View mMapTooOld;
private View mEditTopSpace;
private ShapeableImageView mColorIcon;
private MaterialTextView mTvCategory;
@@ -318,6 +318,7 @@ public class PlacePageView extends Fragment
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
mAddPlace = mFrame.findViewById(R.id.ll__place_add);
mMapTooOld = mFrame.findViewById(R.id.cv__map_too_old);
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
latlon.setOnLongClickListener(this);
address.setOnLongClickListener(this);
@@ -684,7 +685,7 @@ public class PlacePageView extends Fragment
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
{
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld);
}
else
{
@@ -692,34 +693,58 @@ public class PlacePageView extends Fragment
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
mTvEditPlace.setOnClickListener(this);
mTvAddPlace.setOnClickListener(this);
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
final int editTextButtonColor =
Editor.nativeShouldEnableEditPlace()
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace();
if (shouldEnableEditPlace)
{
mTvEditPlace.setOnClickListener(this);
mTvAddPlace.setOnClickListener(this);
}
else
{
mTvEditPlace.setOnClickListener(
(v) -> { Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit); });
mTvAddPlace.setOnClickListener(
(v) -> { Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit); });
CountryItem map = CountryItem.fill(MapManager.nativeGetSelectedCountry());
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|| map.status == CountryItem.STATUS_FAILED)
{
mMapTooOld.setVisibility(VISIBLE);
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
if (canUpdateMap)
{
mTvUpdateTooOldMap.setOnClickListener((v) -> {
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
mMapTooOld.setVisibility(GONE);
});
}
else
{
mTvUpdateTooOldMap.setVisibility(GONE);
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
}
}
}
final int editButtonColor =
shouldEnableEditPlace
? ContextCompat.getColor(
getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
final ColorStateList editStrokeButtonColor = new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_enabled}, // enabled
new int[]{-android.R.attr.state_enabled} // disabled
},
new int[]{
ContextCompat.getColor(
getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)),
ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled)
});
mTvEditPlace.setTextColor(editTextButtonColor);
mTvAddPlace.setTextColor(editTextButtonColor);
mTvEditPlace.setStrokeColor(editStrokeButtonColor);
mTvAddPlace.setStrokeColor(editStrokeButtonColor);
UiUtils.showIf(
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
mEditTopSpace);
mTvEditPlace.setTextColor(editButtonColor);
mTvAddPlace.setTextColor(editButtonColor);
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
UiUtils.showIf(UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace), mEditTopSpace);
}
updateLinksView();
updateOpeningHoursView();
@@ -820,10 +845,9 @@ public class PlacePageView extends Fragment
}
// Get colours
final ForegroundColorSpan colorGreen =
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
final ForegroundColorSpan colorGreen = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_green));
final ForegroundColorSpan colorYellow =
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_yellow));
final ForegroundColorSpan colorRed = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.base_red));
// Get next state info
@@ -849,13 +873,12 @@ public class PlacePageView extends Fragment
if (nextStateTime > 0 && nextStateTime < Long.MAX_VALUE / 2)
{
// NOTE: Timezone is currently device timezone. TODO: use feature-specific timezone.
nextChangeLocal = ZonedDateTime.ofInstant(
Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault()
);
nextChangeLocal = ZonedDateTime.ofInstant(Instant.ofEpochSecond(nextStateTime), ZoneId.systemDefault());
hasFiniteNextChange = true;
}
}
catch (Throwable ignored) {}
catch (Throwable ignored)
{}
}
if (!hasFiniteNextChange) // No valid next change
@@ -870,7 +893,7 @@ public class PlacePageView extends Fragment
}
String localizedTimeString = OpenStateTextFormatter.formatHoursMinutes(
nextChangeLocal.getHour(), nextChangeLocal.getMinute(), DateUtils.is24HourFormat(context));
nextChangeLocal.getHour(), nextChangeLocal.getMinute(), DateUtils.is24HourFormat(context));
final boolean shortHorizonClosing = isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_CLOSE_MIN;
final boolean shortHorizonOpening = !isOpen && minsToNextState >= 0 && minsToNextState <= SHORT_HORIZON_OPEN_MIN;
@@ -878,12 +901,12 @@ public class PlacePageView extends Fragment
if (shortHorizonClosing || shortHorizonOpening) // POI Opens/Closes in 60 mins • at 18:00
{
final String minsToChangeStr = getResources().getQuantityString(
R.plurals.minutes_short, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1));
R.plurals.minutes_short, Math.max(minsToNextState, 1), Math.max(minsToNextState, 1));
final String nextChangeFormatted = getString(isOpen ? R.string.closes_in : R.string.opens_in, minsToChangeStr);
openStateString.append(nextChangeFormatted, colorYellow, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
.append("") // Add spacer
.append(getString(R.string.at, localizedTimeString));
.append("") // Add spacer
.append(getString(R.string.at, localizedTimeString));
}
else
{
@@ -893,18 +916,16 @@ public class PlacePageView extends Fragment
final String closesDayAtStr = getString(R.string.closes_day_at); // "Closes %1$s at %2$s"
final boolean isToday =
OpenStateTextFormatter.isSameLocalDate(nextChangeLocal, ZonedDateTime.now(nextChangeLocal.getZone()));
OpenStateTextFormatter.isSameLocalDate(nextChangeLocal, ZonedDateTime.now(nextChangeLocal.getZone()));
// Full weekday name per design feedback.
final String dayName =
nextChangeLocal.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault());
final String dayName = nextChangeLocal.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault());
if (isOpen) // > 60 minutes OR negative (safety). Show “Open now • Closes at 18:00”
{
openStateString.append(getString(R.string.open_now), colorGreen, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String atLabel =
OpenStateTextFormatter.buildAtLabel(false, isToday, dayName, localizedTimeString,
opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
final String atLabel = OpenStateTextFormatter.buildAtLabel(
false, isToday, dayName, localizedTimeString, opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
if (!TextUtils.isEmpty(atLabel))
openStateString.append("").append(atLabel);
@@ -913,9 +934,8 @@ public class PlacePageView extends Fragment
{
openStateString.append(getString(R.string.closed_now), colorRed, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final String atLabel =
OpenStateTextFormatter.buildAtLabel(true, isToday, dayName, localizedTimeString,
opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
final String atLabel = OpenStateTextFormatter.buildAtLabel(
true, isToday, dayName, localizedTimeString, opensAtStr, closesAtStr, opensDayAtStr, closesDayAtStr);
if (!TextUtils.isEmpty(atLabel))
openStateString.append("").append(atLabel);

View File

@@ -3,9 +3,7 @@ package app.organicmaps.widget.placepage;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import app.organicmaps.sdk.bookmarks.data.MapObject;
import java.util.List;
public class PlacePageViewModel extends ViewModel

View File

@@ -31,7 +31,8 @@ import app.organicmaps.widget.placepage.PlacePageViewModel;
import com.google.android.material.textview.MaterialTextView;
public class PlacePageBookmarkFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener,
Observer<MapObject>, EditBookmarkFragment.EditBookmarkListener
Observer<MapObject>,
EditBookmarkFragment.EditBookmarkListener
{
private View mFrame;
private MaterialTextView mTvBookmarkNote;

View File

@@ -13,15 +13,13 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView;
import app.organicmaps.R;
import app.organicmaps.sdk.Framework;
import app.organicmaps.sdk.bookmarks.data.ChargeSocketDescriptor;
import app.organicmaps.sdk.bookmarks.data.MapObject;
import app.organicmaps.widget.placepage.PlacePageViewModel;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.textview.MaterialTextView;
import java.text.DecimalFormat;
public class PlacePageChargeSocketsFragment extends Fragment implements Observer<MapObject>
@@ -96,8 +94,8 @@ public class PlacePageChargeSocketsFragment extends Fragment implements Observer
}
@SuppressLint("DiscouragedApi")
int resTypeId =
getResources().getIdentifier("charge_socket_" + socket.visualType(), "string", requireContext().getPackageName());
int resTypeId = getResources().getIdentifier("charge_socket_" + socket.visualType(), "string",
requireContext().getPackageName());
if (resTypeId != 0)
{
type.setText(resTypeId);
@@ -108,7 +106,8 @@ public class PlacePageChargeSocketsFragment extends Fragment implements Observer
DecimalFormat df = new DecimalFormat("#.##");
power.setText(getString(R.string.kw_label, df.format(socket.power())));
}
else if (socket.ignorePower()) {
else if (socket.ignorePower())
{
power.setVisibility(INVISIBLE);
}

View File

@@ -191,8 +191,9 @@ public class PlacePageLinksFragment extends Fragment implements Observer<MapObje
case FMD_PANORAMAX -> null; // Don't add raw ID to list, as it's useless for users.
default -> mMapObject.getMetadata(type);
};
// Add user names for social media if available
if (!TextUtils.isEmpty(title) && !title.equals(url) && !title.contains("/")) items.add(title);
// Add user names for social media if available
if (!TextUtils.isEmpty(title) && !title.equals(url) && !title.contains("/"))
items.add(title);
if (items.size() == 1)
PlacePageUtils.copyToClipboard(requireContext(), mFrame, items.get(0));

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M453,680L513,680L513,440L453,440L453,680ZM479.98,366Q494,366 503.5,356.8Q513,347.6 513,334Q513,319.55 503.52,309.78Q494.04,300 480.02,300Q466,300 456.5,309.78Q447,319.55 447,334Q447,347.6 456.48,356.8Q465.96,366 479.98,366ZM480.27,880Q397.53,880 324.77,848.5Q252,817 197.5,762.5Q143,708 111.5,635.16Q80,562.32 80,479.5Q80,396.68 111.5,323.84Q143,251 197.5,197Q252,143 324.84,111.5Q397.68,80 480.5,80Q563.32,80 636.16,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,479.73Q880,562.47 848.5,635.23Q817,708 763,762.32Q709,816.63 636,848.32Q563,880 480.27,880ZM480.5,820Q622,820 721,720.5Q820,621 820,479.5Q820,338 721.19,239Q622.38,140 480,140Q339,140 239.5,238.81Q140,337.62 140,480Q140,621 239.5,720.5Q339,820 480.5,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -89,6 +89,8 @@
android:paddingHorizontal="@dimen/margin_base"
tools:text="Existence confirmed 1 month ago"/>
<include android:visibility="gone" layout="@layout/place_page_map_too_old"/>
<include android:visibility="gone" layout="@layout/place_page_editor"/>
<include android:visibility="gone" layout="@layout/place_page_add"/>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cv__map_too_old"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_half"
app:strokeWidth="1dp"
app:strokeColor="@color/base_accent"
app:cardBackgroundColor="@color/bg_cards">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/margin_base"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_base"
app:srcCompat="@drawable/info_icon"
app:tint="@color/base_accent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_title"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textStyle="bold"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv__map_too_old_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_description"
android:fontFamily="@string/robotoRegular"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mb__update_too_old_map"
style="@style/MwmWidget.M3.Button.Primary"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="end"
android:text="@string/place_page_update_too_old_map"/>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -35,11 +35,6 @@
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
<string name="pref_navigation" translatable="false">Navigation</string>
<string name="pref_information" translatable="false">Information</string>
<string name="pref_traffic" translatable="false">Traffic</string>
<string name="pref_traffic_http_enabled" translatable="false">TrafficHttpEnabled</string>
<string name="pref_traffic_http_url" translatable="false">TrafficHttpUrl</string>
<string name="pref_traffic_apps" translatable="false">TrafficApps</string>
<string name="pref_traffic_legacy_enabled" translatable="false">TrafficLegacyEnabled</string>
<string name="pref_transliteration" translatable="false">Transliteration</string>
<string name="pref_power_management" translatable="false">PowerManagment</string>
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>

View File

@@ -215,7 +215,6 @@
<!-- Settings information group in settings screen -->
<string name="prefs_group_information">Information</string>
<string name="prefs_group_route">Navigation</string>
<string name="prefs_group_traffic">Traffic information</string>
<string name="pref_zoom_title">Zoom buttons</string>
<string name="pref_zoom_summary">Display on the map</string>
<!-- Settings «Map» category: «Night style» title -->
@@ -574,6 +573,16 @@
<string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string>
<string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string>
<string name="placepage_add_place_button">Add Place to OpenStreetMap</string>
<!-- Title of info shown when the map is older than 3 to 6 months -->
<string name="place_page_map_too_old_title">Map data outdated</string>
<!-- Description of info shown when the map is older than 3 months -->
<string name="place_page_map_too_old_description"> Your current map data is very old, please update the map.</string>
<!-- Description of info shown when the app and the map are older than 6 months -->
<string name="place_page_app_too_old_description"> Your current map data is very old, please update the CoMaps app.</string>
<!-- Button to update map region, part of the map too old info -->
<string name="place_page_update_too_old_map">Update map region</string>
<!-- Toast shown after pressing add place / edit OpenStreetMap when editing is disabled -->
<string name="place_page_too_old_to_edit">OpenStreetMap editing is disabled because the map data is too old.</string>
<string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string>
<string name="osm_note_toast">Note will be sent to OpenStreetMap</string>
<!-- Displayed when saving some edits to the map to warn against publishing personal data -->
@@ -790,24 +799,6 @@
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
<!-- Current language of the map! -->
<string name="change_map_locale">Map language</string>
<!-- Enable live traffic data via HTTP (title) -->
<string name="traffic_http_enabled">Enable live traffic data</string>
<!-- Enable live traffic data via HTTP (description) -->
<string name="traffic_http_enabled_description">When enabled, the app will periodically retrieve traffic information from the configured URL.</string>
<!-- URL for live traffic data -->
<string name="traffic_http_url">Traffic service URL</string>
<!-- Status message indicating that user did not set a traffic URL yet. -->
<string name="traffic_http_url_not_set">Not set</string>
<!-- TraFF 0.8 apps from which to receive data (title) -->
<string name="traffic_apps">Use data from TraFF applications</string>
<!-- Status message indicating that no TraFF 0.8 apps are installed -->
<string name="traffic_apps_not_available">No apps installed</string>
<!-- Status message indicating that no TraFF 0.8 apps are currently selected -->
<string name="traffic_apps_none_selected">No apps salected</string>
<!-- Enable traffic data from TraFF 0.7 apps (title) -->
<string name="traffic_legacy_enabled">Use data from legacy TraFF applications</string>
<!-- Enable traffic data from TraFF 0.7 apps (description) -->
<string name="traffic_legacy_enabled_description">When enabled, the app will receive and process traffic data from legacy TraFF applications.</string>
<!-- OpenStreetMap text on splash screen -->
<string name="splash_subtitle">Map data from OpenStreetMap</string>
<!-- Telegram group url for the "?" About page -->

View File

@@ -191,36 +191,6 @@
android:widgetLayout="@layout/preference_switch"
android:order="5"/>
</PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_traffic"
android:title="@string/prefs_group_traffic"
android:order="4">
<SwitchPreferenceCompat
android:key="@string/pref_traffic_http_enabled"
android:title="@string/traffic_http_enabled"
app:singleLineTitle="false"
android:summary="@string/traffic_http_enabled_description"
android:defaultValue="true"
android:order="1"/>
<EditTextPreference
android:key="@string/pref_traffic_http_url"
android:title="@string/traffic_http_url"
app:singleLineTitle="false"
android:order="2"/>
<MultiSelectListPreference
android:key="@string/pref_traffic_apps"
android:title="@string/traffic_apps"
app:singleLineTitle="false"
android:order="3"/>
<SwitchPreferenceCompat
android:key="@string/pref_traffic_legacy_enabled"
android:title="@string/traffic_legacy_enabled"
app:singleLineTitle="false"
android:summary="@string/traffic_legacy_enabled_description"
android:defaultValue="true"
android:order="4"/>
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_privacy"
android:title="@string/privacy"

View File

@@ -4,11 +4,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Locale;
import org.junit.Test;
public class OpenStateTextFormatterTest
{
@@ -36,10 +35,10 @@ public class OpenStateTextFormatterTest
@Test
public void buildAtLabel_today_open_close()
{
String open = OpenStateTextFormatter.buildAtLabel(true, true, "Sat", "09:00",
OPENS_AT, CLOSES_AT, OPENS_DAY_AT, CLOSES_DAY_AT);
String close = OpenStateTextFormatter.buildAtLabel(false, true, "Sat", "18:00",
OPENS_AT, CLOSES_AT, OPENS_DAY_AT, CLOSES_DAY_AT);
String open = OpenStateTextFormatter.buildAtLabel(true, true, "Sat", "09:00", OPENS_AT, CLOSES_AT, OPENS_DAY_AT,
CLOSES_DAY_AT);
String close = OpenStateTextFormatter.buildAtLabel(false, true, "Sat", "18:00", OPENS_AT, CLOSES_AT, OPENS_DAY_AT,
CLOSES_DAY_AT);
assertEquals("Opens at 09:00", open);
assertEquals("Closes at 18:00", close);
}
@@ -47,10 +46,10 @@ public class OpenStateTextFormatterTest
@Test
public void buildAtLabel_other_day()
{
String open = OpenStateTextFormatter.buildAtLabel(true, false, "Sat", "09:00",
OPENS_AT, CLOSES_AT, OPENS_DAY_AT, CLOSES_DAY_AT);
String close = OpenStateTextFormatter.buildAtLabel(false, false, "Tue", "18:00",
OPENS_AT, CLOSES_AT, OPENS_DAY_AT, CLOSES_DAY_AT);
String open = OpenStateTextFormatter.buildAtLabel(true, false, "Sat", "09:00", OPENS_AT, CLOSES_AT, OPENS_DAY_AT,
CLOSES_DAY_AT);
String close = OpenStateTextFormatter.buildAtLabel(false, false, "Tue", "18:00", OPENS_AT, CLOSES_AT, OPENS_DAY_AT,
CLOSES_DAY_AT);
assertEquals("Opens Sat at 09:00", open);
assertEquals("Closes Tue at 18:00", close);
}

View File

@@ -17,7 +17,6 @@ set(SRC
app/organicmaps/sdk/opengl/gl3stub.h
app/organicmaps/sdk/platform/GuiThread.hpp
app/organicmaps/sdk/platform/AndroidPlatform.hpp
app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp
app/organicmaps/sdk/util/Distance.hpp
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
@@ -77,8 +76,6 @@ set(SRC
app/organicmaps/sdk/platform/PThreadImpl.cpp
app/organicmaps/sdk/platform/SecureStorage.cpp
app/organicmaps/sdk/platform/SocketImpl.cpp
app/organicmaps/sdk/traffxml/AndroidTraffSource.cpp
app/organicmaps/sdk/traffxml/SourceImpl.cpp
app/organicmaps/sdk/util/Config.cpp
app/organicmaps/sdk/util/GeoUtils.cpp
app/organicmaps/sdk/util/HttpClient.cpp
@@ -130,7 +127,6 @@ target_link_libraries(${PROJECT_NAME}
# icu
# agg
# vulkan_wrapper
traffxml
# Android libs
log

View File

@@ -182,8 +182,6 @@ public:
void Set3dMode(bool allow3d, bool allow3dBuildings);
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
TrafficManager & GetTrafficManager() { return m_work.GetTrafficManager(); }
void SetMapLanguageCode(std::string const & languageCode);
std::string GetMapLanguageCode();

View File

@@ -18,7 +18,6 @@
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>
namespace
@@ -53,7 +52,7 @@ struct TBatchedData
jobject g_countryChangedListener = nullptr;
DECLARE_THREAD_CHECKER(g_batchingThreadChecker);
std::unordered_map<jobject, std::vector<TBatchedData>> g_batchedCallbackData;
ankerl::unordered_dense::map<jobject, std::vector<TBatchedData>> g_batchedCallbackData;
bool g_isBatched;
storage::Storage & GetStorage()

View File

@@ -1,7 +1,6 @@
#include "app/organicmaps/sdk/Framework.hpp"
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
#include "app/organicmaps/sdk/core/jni_helper.hpp"
@@ -35,26 +34,6 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_OrganicMaps_nativeInitFramework(
JNIEnv * env = jni::GetEnv();
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
env->CallVoidMethod(*onComplete, methodId);
ASSERT(g_framework, ("g_framework must be non-null"));
/*
* Add traffic sources for Android.
*/
jclass configClass = env->FindClass("app/organicmaps/sdk/util/Config");
jmethodID const getTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
"getTrafficLegacyEnabled", "()Z");
jmethodID const applyTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
"applyTrafficLegacyEnabled", "(Z)V");
jmethodID const getTrafficAppsId = jni::GetStaticMethodID(env, configClass,
"getTrafficApps", "()[Ljava/lang/String;");
jmethodID const applyTrafficAppsId = jni::GetStaticMethodID(env, configClass,
"applyTrafficApps", "([Ljava/lang/String;)V");
env->CallStaticVoidMethod(configClass, applyTrafficLegacyEnabledId,
env->CallStaticBooleanMethod(configClass, getTrafficLegacyEnabledId));
env->CallStaticVoidMethod(configClass, applyTrafficAppsId,
(jobjectArray)env->CallStaticObjectMethod(configClass, getTrafficAppsId));
});
}
}

View File

@@ -320,7 +320,7 @@ JNIEXPORT jobject JNICALL Java_app_organicmaps_sdk_editor_OpeningHours_nativeCur
jclass ohStateClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/editor/OhState");
jclass ruleStateClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/editor/OhState$State");
static std::unordered_map<RuleState, char const *> const ruleState = {
static ankerl::unordered_dense::map<RuleState, char const *> const ruleState = {
{RuleState::Open, "Open"}, {RuleState::Closed, "Closed"}, {RuleState::Unknown, "Unknown"}};
jfieldID stateField =

View File

@@ -1,115 +0,0 @@
#include "AndroidTraffSource.hpp"
#include "app/organicmaps/sdk/core/jni_helper.hpp"
namespace traffxml {
void AndroidTraffSourceV0_7::Create(TraffSourceManager & manager)
{
std::unique_ptr<AndroidTraffSourceV0_7> source = std::unique_ptr<AndroidTraffSourceV0_7>(new AndroidTraffSourceV0_7(manager));
manager.RegisterSource(std::move(source));
}
AndroidTraffSourceV0_7::AndroidTraffSourceV0_7(TraffSourceManager & manager)
: TraffSource(manager)
{
JNIEnv * env = jni::GetEnv();
static jclass const implClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/traffxml/SourceImplV0_7");
static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;J)V");
jlong nativeManager = reinterpret_cast<jlong>(&manager);
jobject implObject = env->NewObject(
implClass, implConstructor, android::Platform::Instance().GetContext(), nativeManager);
m_implObject = env->NewGlobalRef(implObject);
m_subscribeImpl = jni::GetMethodID(env, m_implObject, "subscribe", "(Ljava/lang/String;)V");
m_unsubscribeImpl = jni::GetMethodID(env, m_implObject, "unsubscribe", "()V");
}
AndroidTraffSourceV0_7::~AndroidTraffSourceV0_7()
{
jni::GetEnv()->DeleteGlobalRef(m_implObject);
}
void AndroidTraffSourceV0_7::Close()
{
Unsubscribe();
}
void AndroidTraffSourceV0_7::Subscribe(std::set<MwmSet::MwmId> & mwms)
{
jni::GetEnv()->CallVoidMethod(m_implObject, m_subscribeImpl, nullptr);
}
void AndroidTraffSourceV0_7::Unsubscribe()
{
jni::GetEnv()->CallVoidMethod(m_implObject, m_unsubscribeImpl);
}
void AndroidTraffSourceV0_8::Create(TraffSourceManager & manager, std::string const & packageId)
{
std::unique_ptr<AndroidTraffSourceV0_8> source = std::unique_ptr<AndroidTraffSourceV0_8>(new AndroidTraffSourceV0_8(manager, packageId));
manager.RegisterSource(std::move(source));
}
AndroidTraffSourceV0_8::AndroidTraffSourceV0_8(TraffSourceManager & manager, std::string const & packageId)
: TraffSource(manager)
{
JNIEnv * env = jni::GetEnv();
static jclass const implClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/traffxml/SourceImplV0_8");
static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;JLjava/lang/String;)V");
jlong nativeManager = reinterpret_cast<jlong>(&manager);
jobject implObject = env->NewObject(
implClass, implConstructor, android::Platform::Instance().GetContext(), nativeManager, jni::ToJavaString(env, packageId));
m_implObject = env->NewGlobalRef(implObject);
m_subscribeImpl = jni::GetMethodID(env, m_implObject, "subscribe", "(Ljava/lang/String;)V");
m_changeSubscriptionImpl = jni::GetMethodID(env, m_implObject, "changeSubscription", "(Ljava/lang/String;)V");
m_unsubscribeImpl = jni::GetMethodID(env, m_implObject, "unsubscribe", "()V");
// TODO packageId (if we need that at all here)
}
AndroidTraffSourceV0_8::~AndroidTraffSourceV0_8()
{
jni::GetEnv()->DeleteGlobalRef(m_implObject);
}
void AndroidTraffSourceV0_8::Close()
{
Unsubscribe();
}
void AndroidTraffSourceV0_8::Subscribe(std::set<MwmSet::MwmId> & mwms)
{
JNIEnv * env = jni::GetEnv();
std::string data = "<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>";
env->CallVoidMethod(m_implObject, m_subscribeImpl, jni::ToJavaString(env, data));
}
void AndroidTraffSourceV0_8::ChangeSubscription(std::set<MwmSet::MwmId> & mwms)
{
JNIEnv * env = jni::GetEnv();
std::string data = "<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>";
env->CallVoidMethod(m_implObject, m_changeSubscriptionImpl, jni::ToJavaString(env, data));
}
void AndroidTraffSourceV0_8::Unsubscribe()
{
jni::GetEnv()->CallVoidMethod(m_implObject, m_unsubscribeImpl);
}
} // namespace traffxml

View File

@@ -1,199 +0,0 @@
#pragma once
#include "traffxml/traff_source.hpp"
namespace traffxml
{
/**
* @brief A TraFF source which relies on Android Binder for message delivery, using version 0.7 of the TraFF protocol.
*
* TraFF 0.7 does not support subscriptions. Messages are broadcast as the payload to a `FEED` intent.
*/
class AndroidTraffSourceV0_7 : public TraffSource
{
public:
/**
* @brief Creates a new `AndroidTraffSourceV0_7` instance and registers it with the traffic manager.
*
* @param manager The traffic manager to register the new instance with
*/
static void Create(TraffSourceManager & manager);
virtual ~AndroidTraffSourceV0_7() override;
/**
* @brief Prepares the traffic source for unloading.
*/
// TODO do we need a close operation here?
// TODO move this to the parent class and override it here?
void Close();
/**
* @brief Subscribes to a traffic service.
*
* TraFF 0.7 does not support subscriptions. This implementation registers a broadcast receiver.
*
* @param mwms The MWMs for which data is needed (not used by this implementation).
*/
virtual void Subscribe(std::set<MwmSet::MwmId> & mwms) override;
/**
* @brief Changes an existing traffic subscription.
*
* This implementation does nothing, as TraFF 0.7 does not support subscriptions.
*
* @param mwms The new set of MWMs for which data is needed.
*/
virtual void ChangeSubscription(std::set<MwmSet::MwmId> & mwms) override {};
/**
* @brief Unsubscribes from a traffic service we are subscribed to.
*
* TraFF 0.7 does not support subscriptions. This implementation unregisters the broadcast
* receiver which was registered by `Subscribe()`.
*/
virtual void Unsubscribe() override;
/**
* @brief Whether this source should be polled.
*
* Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and
* poll the source only if the result is true.
*
* This implementation always returns false, as message delivery on Android uses `FEED` (push).
*
* @return true if the source should be polled, false if not.
*/
virtual bool IsPollNeeded() override { return false; };
/**
* @brief Polls the traffic service for updates.
*
* This implementation does nothing, as message delivery on Android uses `FEED` (push).
*/
virtual void Poll() override {};
protected:
/**
* @brief Constructs a new `AndroidTraffSourceV0_7`.
* @param manager The `TrafficSourceManager` instance to register the source with.
*/
AndroidTraffSourceV0_7(TraffSourceManager & manager);
private:
// TODO “subscription” (i.e. broadcast receiver) state
/**
* @brief The Java implementation class instance.
*/
jobject m_implObject;
/**
* @brief The Java subscribe method.
*/
jmethodID m_subscribeImpl;
/**
* @brief The Java unsubscribe method.
*/
jmethodID m_unsubscribeImpl;
};
/**
* @brief A TraFF source which relies on Android Binder for message delivery, using version 0.8 of the TraFF protocol.
*
* TraFF 0.8 supports subscriptions. Messages are announced through a `FEED` intent, whereupon the
* consumer can retrieve them from a content provider.
*/
class AndroidTraffSourceV0_8 : public TraffSource
{
public:
/**
* @brief Creates a new `AndroidTraffSourceV0_8` instance and registers it with the traffic manager.
*
* @param manager The traffic manager to register the new instance with
* @param packageId The package ID of the app providing the TraFF source.
*/
static void Create(TraffSourceManager & manager, std::string const & packageId);
virtual ~AndroidTraffSourceV0_8() override;
/**
* @brief Prepares the traffic source for unloading.
*
* If there is still an active subscription, it unsubscribes, but without processing the result
* received from the service. Otherwise, teardown is a no-op.
*/
// TODO move this to the parent class and override it here?
void Close();
/**
* @brief Subscribes to a traffic service.
*
* @param mwms The MWMs for which data is needed.
*/
virtual void Subscribe(std::set<MwmSet::MwmId> & mwms) override;
/**
* @brief Changes an existing traffic subscription.
*
* @param mwms The new set of MWMs for which data is needed.
*/
virtual void ChangeSubscription(std::set<MwmSet::MwmId> & mwms) override;
/**
* @brief Unsubscribes from a traffic service we are subscribed to.
*/
virtual void Unsubscribe() override;
/**
* @brief Whether this source should be polled.
*
* Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and
* poll the source only if the result is true.
*
* This implementation always returns false, as message delivery on Android uses `FEED` (push).
*
* @return true if the source should be polled, false if not.
*/
virtual bool IsPollNeeded() override { return false; };
/**
* @brief Polls the traffic service for updates.
*
* This implementation does nothing, as message delivery on Android uses `FEED` (push).
*/
virtual void Poll() override {};
protected:
/**
* @brief Constructs a new `AndroidTraffSourceV0_8`.
* @param manager The `TrafficSourceManager` instance to register the source with.
* @param packageId The package ID of the app providing the TraFF source.
*/
AndroidTraffSourceV0_8(TraffSourceManager & manager, std::string const & packageId);
private:
// TODO subscription state
/**
* @brief The Java implementation class instance.
*/
jobject m_implObject;
/**
* @brief The Java subscribe method.
*/
jmethodID m_subscribeImpl;
/**
* @brief The Java changeSubscription method.
*/
jmethodID m_changeSubscriptionImpl;
/**
* @brief The Java unsubscribe method.
*/
jmethodID m_unsubscribeImpl;
};
} // namespace traffxml

View File

@@ -1,34 +0,0 @@
// TODO which of the two do we need? (jni_helper includes jni)
//#include <jni>
#include "app/organicmaps/sdk/core/jni_helper.hpp"
#include "traffxml/traff_source.hpp"
#include "traffxml/traff_model_xml.hpp"
#include <optional>
extern "C"
{
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_traffxml_SourceImpl_onFeedReceivedImpl(JNIEnv * env, jclass thiz, jlong nativeManager, jstring feed)
{
std::string feedStd = jni::ToNativeString(env, feed);
pugi::xml_document document;
traffxml::TraffFeed parsedFeed;
if (!document.load_string(feedStd.c_str()))
{
LOG(LWARNING, ("Feed is not a well-formed XML document"));
return;
}
if (!traffxml::ParseTraff(document, std::nullopt, parsedFeed))
{
LOG(LWARNING, ("Feed is not a valid TraFF feed"));
return;
}
traffxml::TraffSourceManager & manager = *reinterpret_cast<traffxml::TraffSourceManager*>(nativeManager);
manager.ReceiveFeed(parsedFeed);
}
}

View File

@@ -119,74 +119,4 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_util_Config_nativeSetTranslitera
frm()->SaveTransliteration(value);
frm()->AllowTransliteration(value);
}
JNIEXPORT jboolean JNICALL
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz)
{
return frm()->LoadTrafficHttpEnabled();
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz,
jboolean value)
{
frm()->SaveTrafficHttpEnabled(value);
frm()->SetTrafficHttpEnabled(value);
}
JNIEXPORT jstring JNICALL
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz)
{
std::string value = frm()->LoadTrafficHttpUrl();
return jni::ToJavaString(env, value);
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz,
jstring value)
{
frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value));
frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value));
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_applyTrafficLegacyEnabled(JNIEnv * env, jclass thiz,
jboolean value)
{
TrafficManager & tm = g_framework->GetTrafficManager();
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
if (traffxml::AndroidTraffSourceV0_7* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_7*>(source))
{
traffSource->Close();
return true;
}
else
return false;
});
if (value)
traffxml::AndroidTraffSourceV0_7::Create(tm);
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_applyTrafficApps(JNIEnv * env, jclass thiz, jobjectArray value)
{
jsize valueLen = env->GetArrayLength(value);
TrafficManager & tm = g_framework->GetTrafficManager();
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
if (traffxml::AndroidTraffSourceV0_8* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_8*>(source))
{
traffSource->Close();
return true;
}
else
return false;
});
for (jsize i = 0; i < valueLen; i++)
{
jstring jAppId = (jstring)env->GetObjectArrayElement(value, i);
std::string appId = jni::ToNativeString(env, jAppId);
traffxml::AndroidTraffSourceV0_8::Create(tm, appId);
env->DeleteLocalRef(jAppId);
}
}
} // extern "C"

View File

@@ -35,7 +35,8 @@ SOFTWARE.
#include <iterator>
#include <string>
#include <unordered_map>
#include "3party/ankerl/unordered_dense.h"
DECLARE_EXCEPTION(JniException, RootException);
@@ -151,7 +152,7 @@ public:
}
private:
std::unordered_map<std::string, jfieldID> m_fieldIds;
ankerl::unordered_dense::map<std::string, jfieldID> m_fieldIds;
};
} // namespace

View File

@@ -4,28 +4,30 @@ package app.organicmaps.sdk.bookmarks.data;
* represents the details of the socket available on a particular charging station
*
*/
public record ChargeSocketDescriptor(String type, int count, double power) {
/**
* Some charge sockets have the same visuals as other sockets, even though they are different and are tagged
* differently in OSM. This method returns the 'visual' type that should be used for the socket.
*
* @return the 'equivalent' visual style that should be used for this socket
*/
public String visualType() {
if (type.equals("typee")) {
return "schuko";
}
return type;
}
/**
* For some sockets (eg, domestic sockets), the power is usually not provided, as it is 'implicit'
*
* @return true if this socket type does not require displaying the power
*/
public Boolean ignorePower() {
return type.equals("typee") || type.equals("schuko");
public record ChargeSocketDescriptor(String type, int count, double power)
{
/**
* Some charge sockets have the same visuals as other sockets, even though they are different and are tagged
* differently in OSM. This method returns the 'visual' type that should be used for the socket.
*
* @return the 'equivalent' visual style that should be used for this socket
*/
public String visualType()
{
if (type.equals("typee"))
{
return "schuko";
}
return type;
}
/**
* For some sockets (eg, domestic sockets), the power is usually not provided, as it is 'implicit'
*
* @return true if this socket type does not require displaying the power
*/
public Boolean ignorePower()
{
return type.equals("typee") || type.equals("schuko");
}
}

View File

@@ -3,7 +3,6 @@ package app.organicmaps.sdk.editor;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.sdk.editor.data.Timespan;
import app.organicmaps.sdk.editor.data.Timetable;

View File

@@ -1,111 +0,0 @@
/*
* Copyright © 20172020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
import java.util.List;
import app.organicmaps.sdk.traffxml.Version;
import app.organicmaps.sdk.traffxml.AndroidTransport;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
public class AndroidConsumer {
/**
* Creates an Intent filter which matches the Intents a TraFF consumer needs to receive.
*
* <p>Different filters are available for consumers implementing different versions of the TraFF
* specification.
*
* @param version The version of the TraFF specification (one of the constants in {@link org.traffxml.traff.Version})
*
* @return An intent filter matching the necessary Intents
*/
public static IntentFilter createIntentFilter(int version) {
IntentFilter res = new IntentFilter();
switch (version) {
case Version.V0_7:
res.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
break;
case Version.V0_8:
res.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
res.addDataScheme(AndroidTransport.CONTENT_SCHEMA);
try {
res.addDataType(AndroidTransport.MIME_TYPE_TRAFF);
} catch (MalformedMimeTypeException e) {
// as long as the constant is a well-formed MIME type, this exception never gets thrown
e.printStackTrace();
}
break;
default:
throw new IllegalArgumentException("Invalid version code: " + version);
}
return res;
}
/**
* Sends a TraFF intent to a source.
*
* <p>This encapsulates most of the low-level Android handling.
*
* <p>If the recipient specified in {@code packageName} declares multiple receivers for the intent in its
* manifest, a separate intent will be delivered to each of them. The intent will not be delivered to
* receivers registered at runtime.
*
* <p>All intents are sent as explicit ordered broadcasts. This means two things:
*
* <p>Any app which declares a matching receiver in its manifest will be woken up to process the intent.
* This works even with certain Android 7 builds which restrict intent delivery to apps which are not
* currently running.
*
* <p>It is safe for the recipient to unconditionally set result data. If the recipient does not set
* result data, the result will have a result code of
* {@link org.traffxml.transport.android.AndroidTransport#RESULT_INTERNAL_ERROR}, no data and no extras.
*
* @param context The context
* @param action The intent action.
* @param data The intent data (for TraFF, this is the content provider URI), or null
* @param extras The extras for the intent
* @param packageName The package name for the intent recipient, or null to deliver the intent to all matching receivers
* @param receiverPermission A permission which the recipient must hold, or null if not required
* @param resultReceiver A BroadcastReceiver which will receive the result for the intent
*/
public static void sendTraffIntent(Context context, String action, Uri data, Bundle extras, String packageName,
String receiverPermission, BroadcastReceiver resultReceiver) {
Intent outIntent = new Intent(action);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
if (receivers != null)
for (ResolveInfo receiver : receivers) {
if ((packageName != null) && !packageName.equals(receiver.activityInfo.applicationInfo.packageName))
continue;
ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
receiver.activityInfo.name);
outIntent = new Intent(action);
if (data != null)
outIntent.setData(data);
if (extras != null)
outIntent.putExtras(extras);
outIntent.setComponent(cn);
context.sendOrderedBroadcast (outIntent,
receiverPermission,
resultReceiver,
null, // scheduler,
AndroidTransport.RESULT_INTERNAL_ERROR, // initialCode,
null, // initialData,
null);
}
}
}

View File

@@ -1,222 +0,0 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
public class AndroidTransport {
/**
* Intent to poll a peer for its capabilities.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*/
public static final String ACTION_TRAFF_GET_CAPABILITIES = "org.traffxml.traff.GET_CAPABILITIES";
/**
* Intent to send a heartbeat to a peer.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*/
public static final String ACTION_TRAFF_HEARTBEAT = "org.traffxml.traff.GET_HEARTBEAT";
/**
* Intent to poll a source for information.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>Polling is a legacy feature on Android and deprecated in TraFF 0.8 (rather than polling, TraFF 0.8
* applications query the content provider). Therefore, poll operations are subscriptionless, and the
* source should either reply with all messages it currently holds, or ignore the request.
*/
@Deprecated
public static final String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL";
/**
* Intent for a push feed.
*
* <p>This is a broadcast intent. It can be used in different forms:
*
* <p>As of TraFF 0.8, it must be sent as an explicit broadcast and include the
* {@link #EXTRA_SUBSCRIPTION_ID} extra. The intent data must be a URI to the content provider from which
* the messages can be retrieved. The {@link #EXTRA_FEED} extra is not supported. The feed is part of a
* subscription and will contain only changes over feeds sent previously as part of the same
* subscription.
*
* <p>Legacy applications omit the {@link #EXTRA_SUBSCRIPTION_ID} extra and may send it as an implicit
* broadcast. If an application supports both legacy transport and TraFF 0.8 or later, it must include
* the {@link #EXTRA_PACKAGE} extra. The feed is sent in the {@link #EXTRA_FEED} extra, as legacy
* applications may not support content providers. If sent as a response to a subscriptionless poll, the
* source should include all messages it holds, else the set of messages included is at the discretion of
* the source.
*
* <p>Future applications may reintroduce unsolicited push operations for certain scenarios.
*/
public static final String ACTION_TRAFF_PUSH = "org.traffxml.traff.FEED";
/**
* Intent for a subscription request.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>The filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra.
*
* <p>The sender must indicate its package name in the {@link #EXTRA_PACKAGE} extra.
*/
public static final String ACTION_TRAFF_SUBSCRIBE = "org.traffxml.traff.SUBSCRIBE";
/**
* Intent for a subscription change request,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between
* the calling consumer and the source which receives the broadcast.
*
* <p>The new filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra.
*/
public static final String ACTION_TRAFF_SUBSCRIPTION_CHANGE = "org.traffxml.traff.SUBSCRIPTION_CHANGE";
/**
* Intent for an unsubscribe request,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between
* the calling consumer and the source which receives the broadcast. It signals that the consumer is no
* longer interested in receiving messages related to that subscription, and that the source should stop
* sending updates. Unsubscribing from a nonexistent subscription is a no-op.
*/
public static final String ACTION_TRAFF_UNSUBSCRIBE = "org.traffxml.traff.UNSUBSCRIBE";
/**
* Name for the column which holds the message data.
*/
public static final String COLUMN_DATA = "data";
/**
* Schema for TraFF content URIs.
*/
public static final String CONTENT_SCHEMA = "content";
/**
* String representations of TraFF result codes
*/
public static final String[] ERROR_STRINGS = {
"unknown (0)",
"invalid request (1)",
"subscription rejected by the source (2)",
"requested area not covered (3)",
"requested area partially covered (4)",
"subscription ID not recognized by the source (5)",
"unknown (6)",
"source reported an internal error (7)"
};
/**
* Extra which contains the capabilities of the peer.
*
* <p>This is a String extra. It contains a {@code capabilities} XML element.
*/
public static final String EXTRA_CAPABILITIES = "capabilities";
/**
* Extra which contains a TraFF feed.
*
* <p>This is a String extra. It contains a {@code feed} XML element.
*
* <p>The sender should be careful to keep the size of this extra low, as Android has a 1 MByte limit on all
* pending Binder transactions. However, there is no feedback to the sender about the capacity still
* available, or whether a request exceeds that limit. Therefore, senders should keep the size if each
* feed significantly below that limit. If necessary, they should split up a feed into multiple smaller
* ones and send them with a delay in between.
*
* <p>This mechanism is deprecated since TraFF 0.8 and peers are no longer required to support it. Peers
* which support TraFF 0.8 must rely on content providers for message transport.
*/
@Deprecated
public static final String EXTRA_FEED = "feed";
/**
* Extra which contains a filter list.
*
* <p>This is a String extra. It contains a {@code filter_list} XML element.
*/
public static final String EXTRA_FILTER_LIST = "filter_list";
/**
* Extra which contains the package name of the app sending it.
*
* <p>This is a String extra.
*/
public static final String EXTRA_PACKAGE = "package";
/**
* Extra which contains a subscription ID.
*
* <p>This is a String extra.
*/
public static final String EXTRA_SUBSCRIPTION_ID = "subscription_id";
/**
* Extra which contains the timeout duration for a subscription.
*
* <p>This is an integer extra.
*/
public static final String EXTRA_TIMEOUT = "timeout";
/**
* The MIME type for TraFF content providers.
*/
public static final String MIME_TYPE_TRAFF = "vnd.android.cursor.dir/org.traffxml.message";
/**
* The operation completed successfully.
*/
public static final int RESULT_OK = -1;
/**
* An internal error prevented the recipient from fulfilling the request.
*/
public static final int RESULT_INTERNAL_ERROR = 7;
/**
* A nonexistent operation was attempted, or an operation was attempted with incomplete or otherwise
* invalid data.
*/
public static final int RESULT_INVALID = 1;
/**
* The subscription was rejected, and no messages will be sent.
*/
public static final int RESULT_SUBSCRIPTION_REJECTED = 2;
/**
* The subscription was rejected because the source will never provide messages matching the selection.
*/
public static final int RESULT_NOT_COVERED = 3;
/**
* The subscription was accepted but the source can only provide messages for parts of the selection.
*/
public static final int RESULT_PARTIALLY_COVERED = 4;
/**
* The request failed because it refers to a subscription which does not exist between the source and
* consumer involved.
*/
public static final int RESULT_SUBSCRIPTION_UNKNOWN = 5;
/**
* The request failed because the aggregator does not accept unsolicited push requests from the sensor.
*/
public static final int RESULT_PUSH_REJECTED = 6;
public static String formatTraffError(int code) {
if ((code < 0) || (code >= ERROR_STRINGS.length))
return String.format("unknown (%d)", code);
else
return ERROR_STRINGS[code];
}
}

View File

@@ -1,70 +0,0 @@
package app.organicmaps.sdk.traffxml;
import android.content.BroadcastReceiver;
import android.content.Context;
/**
* Abstract superclass for TraFF source implementations.
*/
public abstract class SourceImpl extends BroadcastReceiver
{
/**
* Creates a new instance.
*
* @param context The application context
*/
public SourceImpl(Context context, long nativeManager)
{
super();
this.context = context;
this.nativeManager = nativeManager;
}
protected Context context;
/**
* The native `TraffSourceManager` instance.
*/
protected long nativeManager;
/**
* Subscribes to a traffic source.
*
* @param filterList The filter list in XML format
*/
public abstract void subscribe(String filterList);
/**
* Changes an existing traffic subscription.
*
* @param filterList The filter list in XML format
*/
public abstract void changeSubscription(String filterList);
/**
* Unsubscribes from a traffic source we are subscribed to.
*/
public abstract void unsubscribe();
/**
* Forwards a newly received TraFF feed to the traffic module for processing.
*
* Called when a TraFF feed is received. This is a wrapper around {@link #onFeedReceivedImpl(long, String)}.
*
* @param feed The TraFF feed
*/
protected void onFeedReceived(String feed)
{
onFeedReceivedImpl(nativeManager, feed);
}
/**
* Forwards a newly received TraFF feed to the traffic module for processing.
*
* Called when a TraFF feed is received.
*
* @param nativeManager The native `TraffSourceManager` instance
* @param feed The TraFF feed
*/
protected static native void onFeedReceivedImpl(long nativeManager, String feed);
}

View File

@@ -1,127 +0,0 @@
package app.organicmaps.sdk.traffxml;
import java.util.ArrayList;
import java.util.List;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import app.organicmaps.sdk.util.log.Logger;
/**
* Implementation for a TraFF 0.7 source.
*/
public class SourceImplV0_7 extends SourceImpl
{
private PackageManager pm;
/**
* Creates a new instance.
*
* @param context The application context
*/
public SourceImplV0_7(Context context, long nativeManager)
{
super(context, nativeManager);
// TODO Auto-generated constructor stub
}
/**
* Subscribes to a traffic source.
*
* @param filterList The filter list in XML format
*/
@Override
public void subscribe(String filterList)
{
IntentFilter traffFilter07 = new IntentFilter();
traffFilter07.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
this.context.registerReceiver(this, traffFilter07);
// Broadcast a poll intent to all TraFF 0.7-only receivers
Intent outIntent = new Intent(AndroidTransport.ACTION_TRAFF_POLL);
pm = this.context.getPackageManager();
List<ResolveInfo> receivers07 = pm.queryBroadcastReceivers(outIntent, 0);
List<ResolveInfo> receivers08 = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0);
if (receivers07 != null)
{
/*
* Get receivers which support only TraFF 0.7 and poll them.
* If there are no TraFF 0.7 sources at the moment, we register the receiver nonetheless.
* That way, if any new sources are added during the session, we get any messages they send.
*/
if (receivers08 != null)
receivers07.removeAll(receivers08);
for (ResolveInfo receiver : receivers07)
{
ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
receiver.activityInfo.name);
outIntent = new Intent(AndroidTransport.ACTION_TRAFF_POLL);
outIntent.setComponent(cn);
this.context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION);
}
}
}
/**
* Changes an existing traffic subscription.
*
* This implementation does nothing, as TraFF 0.7 does not support subscriptions.
*
* @param filterList The filter list in XML format
*/
@Override
public void changeSubscription(String filterList)
{
// NOP
}
/**
* Unsubscribes from a traffic source we are subscribed to.
*/
@Override
public void unsubscribe()
{
this.context.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent)
{
if (intent == null)
return;
if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_PUSH))
{
/* 0.7 feed */
String packageName = intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE);
/*
* If the feed comes from a TraFF 0.8+ source, skip it (this may happen with “bilingual”
* TraFF 0.7/0.8 sources). That ensures the only way to get information from such sources is
* through a TraFF 0.8 subscription. Fetching the list from scratch each time ensures that
* apps installed during runtime get considered.)
*/
if (packageName != null)
{
for (ResolveInfo info : pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0))
if (packageName.equals(info.resolvePackageName))
return;
}
String feed = intent.getStringExtra(AndroidTransport.EXTRA_FEED);
if (feed == null)
{
Logger.w(this.getClass().getSimpleName(), "empty feed, ignoring");
}
else
{
onFeedReceived(feed);
}
}
}
}

View File

@@ -1,240 +0,0 @@
package app.organicmaps.sdk.traffxml;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import app.organicmaps.sdk.util.log.Logger;
/**
* Implementation for a TraFF 0.8 source.
*/
public class SourceImplV0_8 extends SourceImpl
{
private String packageName;
private String subscriptionId = null;
/**
* Creates a new instance.
*
* @param context The application context
* @param packageName The package name for the source
*/
public SourceImplV0_8(Context context, long nativeManager, String packageName)
{
super(context, nativeManager);
this.packageName = packageName;
}
/**
* Subscribes to a traffic source.
*
* @param filterList The filter list in XML format
*/
@Override
public void subscribe(String filterList)
{
IntentFilter filter = new IntentFilter();
filter.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
filter.addDataScheme(AndroidTransport.CONTENT_SCHEMA);
try
{
filter.addDataType(AndroidTransport.MIME_TYPE_TRAFF);
}
catch (MalformedMimeTypeException e)
{
// as long as the constant is a well-formed MIME type, this exception never gets thrown
// TODO revisit logging
e.printStackTrace();
}
context.registerReceiver(this, filter);
Bundle extras = new Bundle();
extras.putString(AndroidTransport.EXTRA_PACKAGE, context.getPackageName());
extras.putString(AndroidTransport.EXTRA_FILTER_LIST, filterList);
AndroidConsumer.sendTraffIntent(context, AndroidTransport.ACTION_TRAFF_SUBSCRIBE, null,
extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this);
}
/**
* Changes an existing traffic subscription.
*
* @param filterList The filter list in XML format
*/
@Override
public void changeSubscription(String filterList)
{
Bundle extras = new Bundle();
extras.putString(AndroidTransport.EXTRA_SUBSCRIPTION_ID, subscriptionId);
extras.putString(AndroidTransport.EXTRA_FILTER_LIST, filterList);
AndroidConsumer.sendTraffIntent(context, AndroidTransport.ACTION_TRAFF_SUBSCRIPTION_CHANGE, null,
extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this);
}
/**
* Unsubscribes from a traffic source we are subscribed to.
*/
@Override
public void unsubscribe()
{
Bundle extras = new Bundle();
extras.putString(AndroidTransport.EXTRA_SUBSCRIPTION_ID, subscriptionId);
AndroidConsumer.sendTraffIntent(this.context, AndroidTransport.ACTION_TRAFF_UNSUBSCRIBE, null,
extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this);
this.context.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent)
{
if (intent == null)
return;
if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_PUSH))
{
Uri uri = intent.getData();
if (uri != null)
{
/* 0.8 feed */
String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId.equals(this.subscriptionId))
fetchMessages(context, uri);
}
else
{
Logger.w(this.getClass().getSimpleName(), "no URI in feed, ignoring");
} // uri != null
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_SUBSCRIBE)) {
if (this.getResultCode() != AndroidTransport.RESULT_OK) {
Bundle extras = this.getResultExtras(true);
if (extras != null)
Logger.e(this.getClass().getSimpleName(), String.format("subscription to %s failed, %s",
extras.getString(AndroidTransport.EXTRA_PACKAGE), AndroidTransport.formatTraffError(this.getResultCode())));
else
Logger.e(this.getClass().getSimpleName(), String.format("subscription failed, %s",
AndroidTransport.formatTraffError(this.getResultCode())));
if (this.getResultCode() == AndroidTransport.RESULT_INTERNAL_ERROR)
Logger.e(this.getClass().getSimpleName(), "Make sure the TraFF source app has at least coarse location permission, even when running in background");
return;
}
Bundle extras = this.getResultExtras(true);
String data = this.getResultData();
String packageName = extras.getString(AndroidTransport.EXTRA_PACKAGE);
if (!this.packageName.equals(packageName))
return;
String subscriptionId = extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId == null) {
Logger.e(this.getClass().getSimpleName(),
String.format("subscription to %s failed: no subscription ID returned", packageName));
return;
} else if (packageName == null) {
Logger.e(this.getClass().getSimpleName(), "subscription failed: no package name");
return;
} else if (data == null) {
Logger.w(this.getClass().getSimpleName(),
String.format("subscription to %s successful (ID: %s) but no content URI was supplied. "
+ "This is an issue with the source and may result in delayed message retrieval.",
packageName, subscriptionId));
this.subscriptionId = subscriptionId;
return;
}
Logger.d(this.getClass().getSimpleName(),
"subscription to " + packageName + " successful, ID: " + subscriptionId);
this.subscriptionId = subscriptionId;
fetchMessages(context, Uri.parse(data));
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_SUBSCRIPTION_CHANGE)) {
if (this.getResultCode() != AndroidTransport.RESULT_OK) {
Bundle extras = this.getResultExtras(true);
if (extras != null)
Logger.e(this.getClass().getSimpleName(),
String.format("subscription change for %s failed: %s",
extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID),
AndroidTransport.formatTraffError(this.getResultCode())));
else
Logger.e(this.getClass().getSimpleName(),
String.format("subscription change failed: %s",
AndroidTransport.formatTraffError(this.getResultCode())));
return;
}
Bundle extras = intent.getExtras();
String data = this.getResultData();
String subscriptionId = extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId == null) {
Logger.w(this.getClass().getSimpleName(),
"subscription change successful but the source did not specify the subscription ID. "
+ "This is an issue with the source and may result in delayed message retrieval. "
+ "URI: " + data);
return;
} else if (!subscriptionId.equals(this.subscriptionId)) {
return;
} else if (data == null) {
Logger.w(this.getClass().getSimpleName(),
String.format("subscription change for %s successful but no content URI was supplied. "
+ "This is an issue with the source and may result in delayed message retrieval.",
subscriptionId));
return;
}
Logger.d(this.getClass().getSimpleName(),
"subscription change for " + subscriptionId + " successful");
fetchMessages(context, Uri.parse(data));
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_UNSUBSCRIBE)) {
String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId.equals(this.subscriptionId))
this.subscriptionId = null;
// TODO is there anything to do here? (Comment below is from Navit)
/*
* If we ever unsubscribe for reasons other than that we are shutting down or got a feed for
* a subscription we dont recognize, or if we start keeping a persistent list of
* subscriptions, we need to delete the subscription from our list. Until then, there is
* nothing to do here: either the subscription isnt in the list, or we are about to shut
* down and the whole list is about to get discarded.
*/
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_HEARTBEAT)) {
String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId.equals(this.subscriptionId)) {
Logger.d(this.getClass().getSimpleName(),
String.format("got a heartbeat from %s for subscription %s; sending result",
intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE), subscriptionId));
this.setResult(AndroidTransport.RESULT_OK, null, null);
}
} // intent.getAction()
// TODO Auto-generated method stub
}
/**
* Fetches TraFF messages from a content provider.
*
* @param context The context to use for the content resolver
* @param uri The content provider URI
*/
private void fetchMessages(Context context, Uri uri) {
try {
Cursor cursor = context.getContentResolver().query(uri, new String[] {AndroidTransport.COLUMN_DATA}, null, null, null);
if (cursor == null)
return;
if (cursor.getCount() < 1) {
cursor.close();
return;
}
StringBuilder builder = new StringBuilder("<feed>\n");
while (cursor.moveToNext())
builder.append(cursor.getString(cursor.getColumnIndex(AndroidTransport.COLUMN_DATA))).append("\n");
builder.append("</feed>");
cursor.close();
onFeedReceived(builder.toString());
} catch (Exception e) {
Logger.w(this.getClass().getSimpleName(),
String.format("Unable to fetch messages from %s", uri.toString()), e);
e.printStackTrace();
}
}
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
/**
* Constants for versions.
*/
public class Version {
/** Version 0.7: introduced transport on Android. */
public static final int V0_7 = 7;
/** Version 0.8: introduced subscriptions and HTTP transport. */
public static final int V0_8 = 8;
}

View File

@@ -70,16 +70,6 @@ public final class Config
* True if the first start animation has been seen.
*/
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen";
/**
* Whether feeds from legacy TraFF applications (TraFF 0.7, Android transport) are enabled.
*/
private static final String KEY_TRAFFIC_LEGACY_ENABLED = "TrafficLegacyEnabled";
/**
* TraFF (0.8+) applications from which to request traffic data.
*/
private static final String KEY_TRAFFIC_APPS = "TrafficApps";
private Config() {}
@@ -403,63 +393,6 @@ public final class Config
nativeSetTransliteration(value);
}
public static boolean getTrafficHttpEnabled()
{
return nativeGetTrafficHttpEnabled();
}
public static void setTrafficHttpEnabled(boolean value)
{
nativeSetTrafficHttpEnabled(value);
}
public static String getTrafficHttpUrl()
{
return nativeGetTrafficHttpUrl();
}
public static void setTrafficHttpUrl(String value)
{
nativeSetTrafficHttpUrl(value);
}
public static String[] getTrafficApps()
{
String appString = getString(KEY_TRAFFIC_APPS, "");
if (appString.length() == 0)
return new String[0];
return appString.split(",");
}
public static void setTrafficApps(String[] value)
{
String valueString = "";
for (int i = 0; i < value.length; i++)
{
valueString = valueString + value[i];
if ((i + 1) < value.length)
valueString = valueString + ",";
}
setString(KEY_TRAFFIC_APPS, valueString);
applyTrafficApps(value);
}
public static boolean getTrafficLegacyEnabled()
{
return getBool(KEY_TRAFFIC_LEGACY_ENABLED, false);
}
public static void setTrafficLegacyEnabled(boolean value)
{
setBool(KEY_TRAFFIC_LEGACY_ENABLED, value);
applyTrafficLegacyEnabled(value);
}
public static boolean isNY()
{
return getBool("NY");
}
@NonNull
public static String getDonateUrl()
{
@@ -603,10 +536,4 @@ public final class Config
private static native void nativeSetLargeFontsSize(boolean value);
private static native boolean nativeGetTransliteration();
private static native void nativeSetTransliteration(boolean value);
private static native boolean nativeGetTrafficHttpEnabled();
private static native void nativeSetTrafficHttpEnabled(boolean value);
private static native String nativeGetTrafficHttpUrl();
private static native void nativeSetTrafficHttpUrl(String value);
private static native void applyTrafficApps(String[] value);
private static native void applyTrafficLegacyEnabled(boolean value);
}

View File

@@ -2,16 +2,14 @@ package app.organicmaps.sdk.util;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import app.organicmaps.sdk.R;
import java.text.DateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import app.organicmaps.sdk.R;
public final class DateUtils
{
private DateUtils() {}
@@ -44,7 +42,7 @@ public final class DateUtils
if (days == 1)
return resources.getString(R.string.yesterday).toLowerCase();
if (days < 7)
return resources.getString(R.string.days_ago, Integer.toString(days));
return resources.getString(R.string.days_ago, Integer.toString(days));
if (days < 30)
return resources.getString(days < 14 ? R.string.week_ago : R.string.weeks_ago, Integer.toString(days / 7));
if (days < 365)

View File

@@ -148,11 +148,11 @@
<ul class="license-list">
<li><a href="https://agg.sourceforge.net/antigrain.com/index.html">Anti-Grain Geometry</a><br>&copy; 2002&ndash;2005 Maxim Shemanarev; <a href="#agg-license" class="license">License</a></li>
<li><a href="https://www.boost.org/">Boost</a>; <a href="#boost-license" class="license">Boost License</a></li>
<li><a href="https://www.boost.org">Boost</a>; <a href="#boost-license" class="license">Boost License</a></li>
<li><a href="https://www.daemonology.net/bsdiff/">bsdiff</a>; <a href="#bsd3-license" class="license">BSD License</a></li>
<li><a href="https://www.daemonology.net/bsdiff">bsdiff</a>; <a href="#bsd3-license" class="license">BSD License</a></li>
<li><a href="https://chromium.googlesource.com/chromium/src/courgette/">Chromium's Courgette</a>;
<li><a href="https://chromium.googlesource.com/chromium/src/courgette">Chromium's Courgette</a>;
<a href="#bsd3-license" class="license">BSD License</a></li>
<li><a href="https://github.com/dpogue/CMake-MetalShaderSupport">CMake Metal support files</a><br>
@@ -170,22 +170,22 @@
<li><a href="https://www.freetype.org">FreeType</a><br>
&copy; 2013 The FreeType Project; <a href="#freetype-license" class="license">FTL</a></li>
<li><a href="https://stephenberry.github.io/glaze/">glaze</a><br>
<li><a href="https://stephenberry.github.io/glaze">glaze</a><br>
&copy; 2019 - present, Stephen Berry; <a href="#mit-license" class="license">MIT License</a></li>
<li><a href="https://www.glfw.org/">GLFW</a><br>
<li><a href="https://www.glfw.org">GLFW</a><br>
&copy; 2002-2006 Marcus Geelnard;2006-2019 Camilla Löwy; <a href="#zlib-license" class="license">Zlib License</a></li>
<li><a href="https://www.g-truc.net/project-0016.html">GLM</a><br>
&copy; 2005&ndash;2014 G-Truc Creation; <a href="#mit-license" class="license">MIT License</a></li>
<li><a href="https://site.icu-project.org/">ICU</a><br>
<li><a href="https://site.icu-project.org">ICU</a><br>
&copy; 1995&ndash;2016 IBM Corporation and others; <a href="#icu-license" class="license">ICU License</a></li>
<li><a href="https://www.digip.org/jansson/">Jansson</a><br>
<li><a href="https://www.digip.org/jansson">Jansson</a><br>
&copy; 2009-2013 Petri Lehtinen; <a href="#mit-license" class="license">MIT License</a></li>
<li><a href="https://libkdtree.alioth.debian.org/">libkdtree++</a><br>
<li><a href="https://libkdtree.alioth.debian.org">libkdtree++</a><br>
&copy; 2004-2007 Martin F. Krafft, parts are &copy; 2004-2008 Paul Harris and &copy; 2007-2008 Sylvain Bougerel; <a href="#artistic-license" class="license">Artistic License</a></li>
<li><a href="https://github.com/mapsme/omim">MAPS.ME</a><br>
@@ -215,7 +215,7 @@
<li><a href="https://github.com/googlesamples/android-vulkan-tutorials">Vulkan Wrapper</a><br>
&copy; 2016 Google Inc.; <a href="#apache2-license" class="license">Apache License</a></li>
<li class="android"><a href="https://cocosw.com/BottomSheet/">BottomSheet</a><br>
<li class="android"><a href="https://cocosw.com/BottomSheet">BottomSheet</a><br>
&copy; 2011, 2015 Kai Liao; <a href="#apache2-license" class="license">Apache License</a></li>
<li><a href="https://github.com/google/open-location-code">Open Location Code</a><br>
@@ -226,6 +226,9 @@
<li><a href="https://github.com/skarupke/flat_hash_map">Skarupke Hash Tables</a><br>
&copy; Malte Skarupke 2017; <a href="#boost-license" class="license">Boost License</a></li>
<li><a href="https://github.com/martinus/unordered_dense"></a><br>
&copy; 2022 Martin Leitner-Ankerl; <a href="#mit-license" class="license">MIT License</a></li>
</ul>
<p lang="en">Beyond OpenStreetMap, we also use a few other open data sources to improve our map data:</p>

View File

@@ -1,17 +0,0 @@
<!-- From DE-RealWorldLargeFeed. -->
<!--
The route starts at a large junction, goes down the wrong carriageway of the motorway,
changes direction at the next junction, then back.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.7:d.1.10209.n.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_class="MOTORWAY" road_name="Westlicher Berliner Ring" road_ref="A10">
<from junction_name="Werder" junction_ref="21">+52.334801 +12.814650</from>
<to junction_name="Groß Kreutz" junction_ref="22">+52.393700 +12.835000</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
</events>
</message>
</feed>

View File

@@ -1,17 +0,0 @@
<!-- From DE-RealWorldLargeFeed. -->
<!--
The route ends at a large junction, proceeds past the reference point, then
goes back down the wrong carriageway of the motorway.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.7:d.1.10292.p.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_class="MOTORWAY" road_name="Autobahnzubringer Magdeburg" road_ref="A115">
<from junction_name="Potsdam-Drewitz" junction_ref="5a">+52.352650 +13.140700</from>
<to junction_name="Nuthetal" junction_ref="7">+52.300201 +13.083500</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
</events>
</message>
</feed>

View File

@@ -1,16 +0,0 @@
<!--
From DE-realworld5.
Test case for turn penalty (eastern end).
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.29829.n.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Mittlerer Ring" road_ref="B2R">
<from junction_name="Lerchenauer Straße">+48.176102 +11.558100</from>
<to junction_name="Petueltunnel">+48.178001 +11.572800</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_QUEUE">
</event>
</events>
</message>
</feed>

View File

@@ -1,19 +0,0 @@
<!-- From DE-realworld5. -->
<!--
A combination of reference points on the opposite carriageway, high offroad cost,
allowing any road to be used and a low penalty results in a completely incorrect
location (through a residential area rather than along the opposite carriageway).
Test case for turn penalty (eastern end) if the above is resolved.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.22689.p.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Mittlerer Ring" road_ref="B2R">
<from junction_name="München-Sendling-Süd">+48.110901 +11.518500</from>
<to junction_name="Passauerstraße">+48.110649 +11.534150</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_QUEUE">
</event>
</events>
</message>
</feed>

View File

@@ -1,22 +0,0 @@
<!--
Subset of realworld5.
Test case for turn penalty (both ends).
Junction names are the names of the crossing roads.
No segments of the crossing roads should be among the matched segments.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.46572.p.1,14" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Leopoldstraße" road_ref="GM4">
<from junction_name="Potsdamer Straße">+48.167301 +11.586200</from>
<to junction_name="Ungererstraße">+48.164200 +11.586500</to>
</location>
<events>
<event class="INCIDENT" type="INCIDENT_ACCIDENT">
</event>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
<event class="HAZARD" type="HAZARD_PASSABLE_WITH_CARE_BELOW_ELEVATION">
</event>
</events>
</message>
</feed>

View File

@@ -1,58 +0,0 @@
<!--
Test cases for reference points inside a roundabout.
If the roundabout itself is included in the decoded location, the message would
affect all roads which connect to it, resulting in incorrect routing, especially
in the case of closure events. For this reason, we must truncate roundabouts if
they occur at the start or end of the decoded location.
-->
<feed>
<message id="tmc:d.1.12:d.1.13962.p.5,11" receive_time="2018-07-27T10:22:22+02:00" update_time="2018-07-27T08:43:15Z" expiration_time="2018-07-27T09:43:15Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="SECONDARY" road_ref="L87">
<from junction_name="Rheinau-Freistett/B36">+48.661098 +7.936800</from>
<to junction_name="Gambsheim (F)">+48.683701 +7.916600</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_CONSTRUCTION_WORK">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.26212.p.5,11" receive_time="2018-07-27T10:21:49+02:00" update_time="2018-07-27T08:42:41Z" expiration_time="2018-07-27T09:42:41Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="PRIMARY" road_ref="B428">
<from junction_name="Hackenheim">+49.822948 +7.906900</from>
<to junction_name="Frei-Laubersheim">+49.803650 +7.901450</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_LONGTERM_ROADWORKS">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.48638.n.9,11" receive_time="2018-07-27T10:11:06+02:00" update_time="2018-07-27T08:31:43Z" expiration_time="2018-07-27T09:31:43Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="PRIMARY" road_ref="B272">
<from junction_name="Hochstadt">+49.239750 +8.222300</from>
<to junction_name="Weingarten">+49.253448 +8.268100</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_ROADWORKS">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED_AHEAD">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.56576.n.9,11" receive_time="2018-07-27T10:22:53+02:00" update_time="2018-07-27T08:43:45Z" expiration_time="2018-07-27T09:43:45Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="ONE_DIRECTION" road_class="PRIMARY" road_ref="B417">
<from junction_name="Diez">+50.372398 +8.038500</from>
<to junction_name="Diez">+50.370850 +8.004050</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_RESURFACING_WORK">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
<supplementary_info class="VEHICLE" type="S_VEHICLE_THROUGH_TRAFFIC"/>
</event>
</events>
</message>
</feed>

View File

@@ -1,23 +0,0 @@
<!--
This is a 140 m location. Reference points are almost exactly on the opposite
carriageway; since this is inside a junction, there is a wider gap between
the two carriageways than there would be on a normal stretch of expressway.
Without truncation, the decoded location starts approximately in the right
spot but overshoots the end point, going to the nearest junction, then back
in the opposite direction and to the end point on the opposite carriageway,
ending within 5 m of the end point.
-->
<feed>
<message id="lt.eismoinfo.restrictions:4249b6510b73750684ca94de5fe8cf32,eastbound" receive_time="2025-01-21T12:33:06Z" update_time="2025-01-21T12:33:06Z" expiration_time="2025-12-31T21:00:00Z" cancellation="false" forecast="false" urgency="NORMAL">
<merge/>
<location country="LT" origin="Suvalkai*" directionality="ONE_DIRECTION" destination="Kaunas" road_class="TRUNK" road_ref="A5">
<from distance="0.14">54.939945 23.879789</from>
<to distance="0.0" junction_name="Kaunas">54.940094 23.881950</to>
</location>
<events>
<event class="RESTRICTION" type="RESTRICTION_MAX_WIDTH" q_dimension="3.5"/>
<event class="CONSTRUCTION" type="CONSTRUCTION_ROADWORKS"/>
<event class="RESTRICTION" type="RESTRICTION_SPEED_LIMIT" speed="50"/>
</events>
</message>
</feed>

View File

@@ -108,6 +108,8 @@ auto constexpr TMP_OFFSETS_EXT = OFFSET_EXT EXTENSION_TMP;
#define CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways"
#define TEMP_ADDR_EXTENSION ".tempaddr"
#define TRAFFIC_FILE_EXTENSION ".traffic"
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"

View File

@@ -18,6 +18,8 @@ Below are our specific (but not all!) exceptions to the Google's coding standard
- We ARE using C++ exceptions.
- We are using all features of C++17 and C++23 except `std::filesystem`, `std::to_chars`, `std::from_chars` and `std::format` which are not fully supported on all platforms.
- We try to limit the usage of boost libraries which require linking (and prefer C++23 types over their boost counterparts).
- Do not use `std::unordered_map` for hashmaps. Use `ankerl::unordered_dense::map` (from `3party/ankerl/unordered_dense.h`). When dealing with integers and not iterating, use `ska::flat_hash_map`.
- Use `ankerl::unordered_dense::set` instead of `std::unordered_set`.
Naming and formatting

View File

@@ -207,6 +207,8 @@ set(SRC
tesselator.hpp
towns_dumper.cpp
towns_dumper.hpp
traffic_generator.cpp
traffic_generator.hpp
transit_generator.cpp
transit_generator.hpp
transit_generator_experimental.cpp

View File

@@ -1,7 +1,7 @@
#pragma once
#include "generator/collector_interface.hpp"
#include <unordered_map>
#include "3party/ankerl/unordered_dense.h"
namespace generator
{
@@ -18,7 +18,7 @@ public:
};
private:
std::unordered_map<uint64_t, AddressInfo> m_addresses;
ankerl::unordered_dense::map<uint64_t, AddressInfo> m_addresses;
public:
void Add(feature::FeatureBuilder const & fb);
@@ -44,7 +44,7 @@ class AddressesCollector : public CollectorInterface
uint64_t m_beg, m_end;
};
// OSM Way ID is a key here.
std::unordered_map<uint64_t, WayInfo> m_interpolWays;
ankerl::unordered_dense::map<uint64_t, WayInfo> m_interpolWays;
public:
explicit AddressesCollector(std::string const & filename);

View File

@@ -133,7 +133,7 @@ template <typename T>
std::vector<std::string> GetHonestAffiliations(T && t, IndexSharedPtr const & index)
{
std::vector<std::string> affiliations;
std::unordered_set<borders::CountryPolygons const *> countires;
ankerl::unordered_dense::set<borders::CountryPolygons const *> countires;
ForEachPoint(t, [&](auto const & point)
{
std::vector<CountriesFilesIndexAffiliation::Value> values;
@@ -196,7 +196,7 @@ CountriesFilesIndexAffiliation::CountriesFilesIndexAffiliation(std::string const
: CountriesFilesAffiliation(borderPath, haveBordersForWholeWorld)
{
static std::mutex cacheMutex;
static std::unordered_map<std::string, std::shared_ptr<Tree>> cache;
static ankerl::unordered_dense::map<std::string, std::shared_ptr<Tree>> cache;
auto const key = borderPath + std::to_string(haveBordersForWholeWorld);
std::lock_guard<std::mutex> lock(cacheMutex);
@@ -228,7 +228,7 @@ std::vector<std::string> CountriesFilesIndexAffiliation::GetAffiliations(m2::Poi
std::shared_ptr<CountriesFilesIndexAffiliation::Tree> CountriesFilesIndexAffiliation::BuildIndex(
std::vector<m2::RectD> const & net)
{
std::unordered_map<borders::CountryPolygons const *, std::vector<m2::RectD>> countriesRects;
ankerl::unordered_dense::map<borders::CountryPolygons const *, std::vector<m2::RectD>> countriesRects;
std::mutex countriesRectsMutex;
std::vector<Value> treeCells;
std::mutex treeCellsMutex;

View File

@@ -262,7 +262,7 @@ CountryPolygonsCollection const & GetOrCreateCountryPolygonsTree(std::string con
{
/// @todo Are there many different paths with polygons, that we have to store map?
static std::mutex mutex;
static std::unordered_map<std::string, CountryPolygonsCollection> countriesMap;
static ankerl::unordered_dense::map<std::string, CountryPolygonsCollection> countriesMap;
std::lock_guard<std::mutex> lock(mutex);
auto const it = countriesMap.find(baseDir);

View File

@@ -10,10 +10,10 @@
#include "geometry/tree4d.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "3party/ankerl/unordered_dense.h"
#define BORDERS_DIR "borders/"
#define BORDERS_EXTENSION ".poly"
@@ -112,7 +112,7 @@ public:
template <typename ToDo>
void ForEachCountryInRect(m2::RectD const & rect, ToDo && toDo) const
{
std::unordered_set<CountryPolygons const *> uniq;
ankerl::unordered_dense::set<CountryPolygons const *> uniq;
m_regionsTree.ForEachInRect(rect, [&](CountryPolygons const & cp)
{
if (uniq.insert(&cp).second)
@@ -131,7 +131,7 @@ public:
private:
m4::Tree<std::reference_wrapper<CountryPolygons const>> m_regionsTree;
std::unordered_map<std::string, CountryPolygons> m_countryPolygonsMap;
ankerl::unordered_dense::map<std::string, CountryPolygons> m_countryPolygonsMap;
};
using PolygonsList = std::vector<Polygon>;

View File

@@ -15,10 +15,12 @@
#include <utility>
#include <vector>
#include "3party/ankerl/unordered_dense.h"
namespace generator
{
using base::GeoObjectId;
using std::pair, std::string, std::unordered_map, std::vector;
using std::pair, std::string, ankerl::unordered_dense::map, std::vector;
DECLARE_EXCEPTION(ParsingError, RootException);
@@ -42,7 +44,7 @@ static void ParseFeatureToBrand(json_t * root, string const & field, GeoObjectId
}
}
void ParseTranslations(json_t * root, std::set<string> const & keys, unordered_map<uint32_t, string> & idToKey)
void ParseTranslations(json_t * root, std::set<string> const & keys, map<uint32_t, string> & idToKey)
{
string const empty;
auto getKey = [&](string & translation) -> string const &
@@ -81,8 +83,7 @@ void ParseTranslations(json_t * root, std::set<string> const & keys, unordered_m
}
}
bool LoadBrands(string const & brandsFilename, string const & translationsFilename,
unordered_map<GeoObjectId, string> & brands)
bool LoadBrands(string const & brandsFilename, string const & translationsFilename, map<GeoObjectId, string> & brands)
{
string jsonBuffer;
try
@@ -126,7 +127,7 @@ bool LoadBrands(string const & brandsFilename, string const & translationsFilena
return false;
}
unordered_map<uint32_t, string> idToKey;
map<uint32_t, string> idToKey;
try
{
base::Json root(jsonBuffer.c_str());

View File

@@ -3,7 +3,8 @@
#include "base/geo_object_id.hpp"
#include <string>
#include <unordered_map>
#include "3party/ankerl/unordered_dense.h"
namespace generator
{
@@ -52,5 +53,5 @@ namespace generator
// name is "Сабвей", then "Сабвэй".
bool LoadBrands(std::string const & brandsFilename, std::string const & translationsFilename,
std::unordered_map<base::GeoObjectId, std::string> & brands);
ankerl::unordered_dense::map<base::GeoObjectId, std::string> & brands);
} // namespace generator

View File

@@ -5,11 +5,12 @@
#include <cstddef>
#include <cstdint>
#include <map>
#include <optional>
#include <utility>
#include <vector>
#include "3party/skarupke/flat_hash_map.hpp"
namespace generator
{
namespace cells_merger
@@ -67,7 +68,7 @@ private:
m2::RectD Union(m2::PointI const & startXy);
void Remove(m2::PointI const & minXy, m2::PointI const & maxXy);
std::map<m2::PointI, CellWrapper> m_matrix;
ska::flat_hash_map<m2::PointI, CellWrapper> m_matrix;
int32_t m_maxX = 0;
int32_t m_maxY = 0;
};

View File

@@ -16,9 +16,10 @@
#include "base/logging.hpp"
#include <memory>
#include <unordered_map>
#include <vector>
#include "3party/ankerl/unordered_dense.h"
#include "defines.hpp"
namespace generator
@@ -60,7 +61,7 @@ bool BuildCitiesBoundaries(string const & dataPath, BoundariesTable & table, Map
bool BuildCitiesBoundaries(string const & dataPath, OsmIdToBoundariesTable & table)
{
std::unordered_map<uint32_t, base::GeoObjectId> mapping;
ankerl::unordered_dense::map<uint32_t, base::GeoObjectId> mapping;
if (!ParseFeatureIdToOsmIdMapping(dataPath + OSM2FEATURE_FILE_EXTENSION, mapping))
{
LOG(LERROR, ("Can't parse feature id to osm id mapping."));
@@ -71,7 +72,7 @@ bool BuildCitiesBoundaries(string const & dataPath, OsmIdToBoundariesTable & tab
bool BuildCitiesBoundariesForTesting(string const & dataPath, TestIdToBoundariesTable & table)
{
std::unordered_map<uint32_t, uint64_t> mapping;
ankerl::unordered_dense::map<uint32_t, uint64_t> mapping;
if (!ParseFeatureIdToTestIdMapping(dataPath, mapping))
{
LOG(LERROR, ("Can't parse feature id to test id mapping."));

View File

@@ -6,21 +6,18 @@
#include "indexer/data_header.hpp"
#include "indexer/feature_to_osm.hpp"
#include "search/categories_cache.hpp"
#include "search/cbv.hpp"
#include "search/localities_source.hpp"
#include "search/mwm_context.hpp"
#include "coding/file_writer.hpp"
#include "coding/files_container.hpp"
#include "base/cancellable.hpp"
#include "base/checked_cast.hpp"
#include "base/geo_object_id.hpp"
#include "base/logging.hpp"
#include <cstdint>
#include <unordered_map>
#include "3party/ankerl/unordered_dense.h"
#include "defines.hpp"
@@ -40,7 +37,7 @@ bool IsWorldMwm(std::string const & path)
}
void WriteCitiesIdsSectionToFile(std::string const & dataPath,
std::unordered_map<uint32_t, base::GeoObjectId> const & mapping)
ankerl::unordered_dense::map<uint32_t, base::GeoObjectId> const & mapping)
{
indexer::FeatureIdToGeoObjectIdBimapMem map;
auto const localities = generator::GetLocalities(dataPath);
@@ -89,7 +86,7 @@ bool BuildCitiesIds(std::string const & dataPath, std::string const & osmToFeatu
classificator::Load();
std::unordered_map<uint32_t, base::GeoObjectId> mapping;
ankerl::unordered_dense::map<uint32_t, base::GeoObjectId> mapping;
if (!ParseFeatureIdToOsmIdMapping(osmToFeaturePath, mapping))
{
LOG(LERROR, ("Can't parse feature id to osm id mapping."));
@@ -104,11 +101,11 @@ bool BuildCitiesIdsForTesting(std::string const & dataPath)
{
CHECK(IsWorldMwm(dataPath), ());
std::unordered_map<uint32_t, uint64_t> mapping;
ankerl::unordered_dense::map<uint32_t, uint64_t> mapping;
if (!ParseFeatureIdToTestIdMapping(dataPath, mapping))
return false;
std::unordered_map<uint32_t, base::GeoObjectId> mappingToGeoObjects;
ankerl::unordered_dense::map<uint32_t, base::GeoObjectId> mappingToGeoObjects;
for (auto const & entry : mapping)
{
// todo(@m) Make test ids a new source in base::GeoObjectId?

View File

@@ -19,9 +19,10 @@
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <unordered_map>
#include <utility>
#include "3party/ankerl/unordered_dense.h"
namespace
{
using IdRelationVec = std::vector<std::pair<uint64_t, RelationElement>>;

View File

@@ -6,9 +6,10 @@
#include <optional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include "3party/ankerl/unordered_dense.h"
template <typename T>
class ReaderSource;
@@ -82,6 +83,6 @@ private:
IDRInterfacePtr m_cache;
std::vector<uint64_t> m_roadOsmIDs;
std::unordered_map<uint64_t, CameraInfo> m_speedCameras;
ankerl::unordered_dense::map<uint64_t, CameraInfo> m_speedCameras;
};
} // namespace routing_builder

Some files were not shown because too many files have changed in this diff Show More